Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions docs/tec1g-emulation-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,11 +372,11 @@ Disco LEDs (Fullisik under mechanical keys) — **N/A**

### 17. CONFIG DIP switch

| Feature | Rating | Notes |
| ----------------------------------------- | ----------- | ------------------------- |
| Switch 1: Keyboard mode (74C923 / Matrix) | **Missing** | Always in 74C923 mode |
| Switch 2: Protect on reset (OFF / ON) | **Missing** | Protect always starts OFF |
| Switch 3: Expansion bank (LO / HI) | **Missing** | No bank select |
| Feature | Rating | Notes |
| ----------------------------------------- | ------------ | ------------------------------------------------- |
| Switch 1: Keyboard mode (74C923 / Matrix) | **Complete** | Configurable via `matrixMode` |
| Switch 2: Protect on reset (OFF / ON) | **Complete** | Configurable via `protectOnReset` |
| Switch 3: Expansion bank (LO / HI) | **Complete** | Configurable via `expansionBankHi` |

### 18. Joystick (J9)

Expand All @@ -402,8 +402,8 @@ Disco LEDs (Fullisik under mechanical keys) — **N/A**
| Expansion window | Med | **Partial** | 60% |
| SYS_CTRL latch (0xFF) | Med | **Partial** | 37% |
| SYS_INPUT register (0x03) | Med | **Partial** | 50% |
| Matrix keyboard | Med | **Missing** | 0% |
| CONFIG DIP switch | Low | **Missing** | 0% |
| Matrix keyboard | Med | **Complete** | 100% |
| CONFIG DIP switch | Low | **Complete** | 100% |
| RTC (DS1302) | Low | **Complete** | 100% |
| SD card SPI | Low | **Missing** | 0% |
| Cartridge | Low | **Partial** | 40% |
Expand Down
9 changes: 7 additions & 2 deletions src/platforms/tec1g/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ workflow and hardware contract. For full MON-3 behavior notes, see
- RTC (DS1302) and SD SPI (0xFC/0xFD) when enabled in config.

## Not yet emulated
- Matrix keyboard input on `IN 0xFE` (and keypad-disable behavior when matrix mode is active).
- Cartridge boot entry uses CART flag (MON-3 style) and maps payload into expansion banks.
- SYS_CTRL bits 3-7: latched and decoded but bank switching not yet wired to memory.
- SYS_INPUT bits 0 (SKEY), 4 (RKEY), 5 (GIMP): state exposed but no hardware trigger wired.
Expand Down Expand Up @@ -107,10 +106,16 @@ config (and optionally ROM listings via `extraListings`).
"tec1g": {
"romHex": "../roms/tec1g/mon-3/mon-3.hex",
"appStart": 16384,
"entry": 0
"entry": 0,
"matrixMode": false,
"protectOnReset": false,
"expansionBankHi": false
}
}
```

`matrixMode`, `protectOnReset`, and `expansionBankHi` correspond to the CONFIG DIP
switches (keyboard mode, protect on reset, expansion bank select).

## Examples
- `examples/Tec1g` includes a 4800-baud serial echo program for MON-3.
2 changes: 2 additions & 0 deletions src/platforms/tec1g/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export const TEC1G_ENTRY_DEFAULT = 0x0000;
export const TEC1G_ADDR_MAX = 0xffff;

// ===== System Control Bits =====
/** Write protection enabled (sysctrl bit 1). */
export const TEC1G_SYSCTRL_PROTECT = 0x02;
/** Expansion bank A14 select (sysctrl bit 3). */
export const TEC1G_SYSCTRL_BANK_A14 = 0x08;

Expand Down
32 changes: 30 additions & 2 deletions src/platforms/tec1g/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import {
TEC1G_PORT_STATUS,
TEC1G_PORT_SYSCTRL,
TEC1G_ADDR_MAX,
TEC1G_SYSCTRL_PROTECT,
TEC1G_SYSCTRL_BANK_A14,
TEC1G_KEY_SHIFT_MASK,
TEC1G_LCD_ARROW_LEFT,
Expand Down Expand Up @@ -168,6 +169,8 @@ export interface Tec1gState {
glcdExpectColumn: boolean;
glcdRe: boolean;
glcdGraphics: boolean;
glcdReadPrimed: boolean;
glcdReadLatch: number;
glcdDisplayOn: boolean;
glcdCursorOn: boolean;
glcdCursorBlink: boolean;
Expand Down Expand Up @@ -299,6 +302,7 @@ export function normalizeTec1gConfig(cfg?: Tec1gPlatformConfig): Tec1gPlatformCo
const gimpSignal = config.gimpSignal === true;
const expansionBankHi = config.expansionBankHi === true;
const matrixMode = config.matrixMode === true;
const protectOnReset = config.protectOnReset === true;
const rtcEnabled = config.rtcEnabled === true;
const sdEnabled = config.sdEnabled === true;
const sdImagePath =
Expand All @@ -317,6 +321,7 @@ export function normalizeTec1gConfig(cfg?: Tec1gPlatformConfig): Tec1gPlatformCo
gimpSignal,
expansionBankHi,
matrixMode,
protectOnReset,
rtcEnabled,
sdEnabled,
...(sdImagePath !== undefined ? { sdImagePath } : {}),
Expand All @@ -339,7 +344,9 @@ export function createTec1gRuntime(
onSerialByte?: (byte: number) => void,
onPortWrite?: (payload: { port: number; value: number }) => void
): Tec1gRuntime {
const initialSysCtrl = config.expansionBankHi ? TEC1G_SYSCTRL_BANK_A14 : 0;
const initialSysCtrl =
(config.expansionBankHi ? TEC1G_SYSCTRL_BANK_A14 : 0) |
(config.protectOnReset ? TEC1G_SYSCTRL_PROTECT : 0);
const matrixMode = config.matrixMode;
const rtcEnabled = config.rtcEnabled;
const rtc = rtcEnabled ? new Ds1302() : null;
Expand Down Expand Up @@ -370,6 +377,8 @@ export function createTec1gRuntime(
glcdExpectColumn: false,
glcdRe: false,
glcdGraphics: false,
glcdReadPrimed: false,
glcdReadLatch: 0,
glcdDisplayOn: true,
glcdCursorOn: false,
glcdCursorBlink: false,
Expand Down Expand Up @@ -486,6 +495,7 @@ export function createTec1gRuntime(
state.glcdRowAddr = value & TEC1G_MASK_LOW5;
state.glcdExpectColumn = true;
state.glcdGdramPhase = 0;
state.glcdReadPrimed = false;
};

const glcdSetColumn = (value: number): void => {
Expand All @@ -494,6 +504,7 @@ export function createTec1gRuntime(
state.glcdCol = value & TEC1G_GLCD_COL_MASK;
state.glcdExpectColumn = false;
state.glcdGdramPhase = 0;
state.glcdReadPrimed = false;
};

// ST7920 DDRAM row address to linear index mapping.
Expand Down Expand Up @@ -633,13 +644,26 @@ export function createTec1gRuntime(
const col = state.glcdCol & TEC1G_GLCD_COL_MASK;
const index = row * TEC1G_GLCD_ROW_STRIDE + col * TEC1G_GLCD_COL_STRIDE + state.glcdGdramPhase;
const value = index >= 0 && index < state.glcd.length ? (state.glcd[index] ?? 0) : 0;
if (!state.glcdReadPrimed) {
state.glcdReadPrimed = true;
state.glcdReadLatch = value & TEC1G_MASK_BYTE;
return 0;
}
const out = state.glcdReadLatch & TEC1G_MASK_BYTE;
if (state.glcdGdramPhase === 0) {
state.glcdGdramPhase = 1;
} else {
state.glcdGdramPhase = 0;
state.glcdCol = (state.glcdCol + 1) & TEC1G_GLCD_COL_MASK;
}
return value & TEC1G_MASK_BYTE;
const nextIndex =
((state.glcdRowBase + state.glcdRowAddr) & TEC1G_GLCD_ROW_MASK) *
TEC1G_GLCD_ROW_STRIDE +
(state.glcdCol & TEC1G_GLCD_COL_MASK) * TEC1G_GLCD_COL_STRIDE +
state.glcdGdramPhase;
state.glcdReadLatch =
nextIndex >= 0 && nextIndex < state.glcd.length ? (state.glcd[nextIndex] ?? 0) : 0;
return out;
};

const glcdReadStatus = (): number => {
Expand Down Expand Up @@ -1053,6 +1077,8 @@ export function createTec1gRuntime(
state.glcdGraphics = g;
state.glcdExpectColumn = false;
state.glcdGdramPhase = 0;
state.glcdReadPrimed = false;
state.glcdReadLatch = 0;
glcdSetBusy(TEC1G_GLCD_BUSY_US);
queueUpdate();
return;
Expand Down Expand Up @@ -1356,6 +1382,8 @@ export function createTec1gRuntime(
state.glcdExpectColumn = false;
state.glcdRe = false;
state.glcdGraphics = false;
state.glcdReadPrimed = false;
state.glcdReadLatch = 0;
state.glcdDisplayOn = true;
state.glcdCursorOn = false;
state.glcdCursorBlink = false;
Expand Down
2 changes: 2 additions & 0 deletions src/platforms/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export interface Tec1gPlatformConfig {
gimpSignal?: boolean;
expansionBankHi?: boolean;
matrixMode?: boolean;
protectOnReset?: boolean;
rtcEnabled?: boolean;
sdEnabled?: boolean;
sdImagePath?: string;
Expand Down Expand Up @@ -91,6 +92,7 @@ export interface Tec1gPlatformConfigNormalized {
gimpSignal: boolean;
expansionBankHi: boolean;
matrixMode: boolean;
protectOnReset: boolean;
rtcEnabled: boolean;
sdEnabled: boolean;
sdImagePath?: string;
Expand Down
13 changes: 13 additions & 0 deletions tests/platforms/tec1g/glcd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function makeRuntime() {
gimpSignal: false,
expansionBankHi: false,
matrixMode: false,
protectOnReset: false,
rtcEnabled: false,
sdEnabled: false,
};
Expand Down Expand Up @@ -44,10 +45,22 @@ describe('TEC-1G GLCD instruction handling', () => {
rt.state.glcdRowBase = 0;
rt.state.glcdCol = 0;
rt.state.glcdGdramPhase = 0;
const dummy = rt.ioHandlers.read(0x87);
const value = rt.ioHandlers.read(0x87);
expect(dummy).toBe(0x00);
expect(value).toBe(0xaa);
});

it('toggles reverse line mask in extended mode', () => {
const rt = makeRuntime();
rt.ioHandlers.write(0x07, 0x24); // function set: RE=1, G=0
rt.ioHandlers.write(0x07, 0x04); // reverse line 0
expect(rt.state.glcdReverseMask & 0x01).toBe(0x01);
rt.ioHandlers.write(0x07, 0x04); // toggle off
expect(rt.state.glcdReverseMask & 0x01).toBe(0x00);
rt.ioHandlers.write(0x07, 0x20); // back to basic
});

it('busy flag clears after cycles', () => {
const rt = makeRuntime();
rt.ioHandlers.write(0x07, 0x80);
Expand Down
1 change: 1 addition & 0 deletions tests/platforms/tec1g/lcd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function makeRuntime() {
gimpSignal: false,
expansionBankHi: false,
matrixMode: false,
protectOnReset: false,
rtcEnabled: false,
sdEnabled: false,
};
Expand Down
1 change: 1 addition & 0 deletions tests/platforms/tec1g/matrix.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function makeRuntime(matrixMode = true) {
gimpSignal: false,
expansionBankHi: false,
matrixMode,
protectOnReset: false,
rtcEnabled: false,
sdEnabled: false,
};
Expand Down
7 changes: 7 additions & 0 deletions tests/platforms/tec1g/port03.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function makeRuntime(overrides: Partial<Tec1gPlatformConfigNormalized> = {}) {
gimpSignal: false,
expansionBankHi: false,
matrixMode: false,
protectOnReset: false,
rtcEnabled: false,
sdEnabled: false,
...overrides,
Expand Down Expand Up @@ -122,4 +123,10 @@ describe('port 0x03 (SYS_INPUT)', () => {
}
expect(highSeen).toBe(true);
});

it('sets protect on reset when configured', () => {
const rt = makeRuntime({ protectOnReset: true });
expect(rt.state.sysCtrl & 0x02).toBe(0x02);
expect(rt.ioHandlers.read(0x03) & 0x02).toBe(0x02);
});
});
1 change: 1 addition & 0 deletions tests/platforms/tec1g/portfc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function makeRuntime(rtcEnabled: boolean) {
gimpSignal: false,
expansionBankHi: false,
matrixMode: false,
protectOnReset: false,
rtcEnabled,
sdEnabled: false,
};
Expand Down
1 change: 1 addition & 0 deletions tests/platforms/tec1g/portfd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function makeRuntime(sdEnabled: boolean) {
gimpSignal: false,
expansionBankHi: false,
matrixMode: false,
protectOnReset: false,
rtcEnabled: false,
sdEnabled,
};
Expand Down
1 change: 1 addition & 0 deletions tests/platforms/tec1g/serial.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function makeRuntime(onByte: (byte: number) => void) {
gimpSignal: false,
expansionBankHi: false,
matrixMode: false,
protectOnReset: false,
rtcEnabled: false,
sdEnabled: false,
};
Expand Down
14 changes: 11 additions & 3 deletions webview/tec1g/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,9 +465,17 @@ function drawGlcd() {
const py = (py0 + dy - scroll + GLCD_HEIGHT) & 0x3f;
if (px < GLCD_WIDTH && py < GLCD_HEIGHT) {
const idx = (py * GLCD_WIDTH + px) * 4;
data[idx] = onR;
data[idx + 1] = onG;
data[idx + 2] = onB;
if (glcdGraphicsOn) {
const isOn =
data[idx] === onR && data[idx + 1] === onG && data[idx + 2] === onB;
data[idx] = isOn ? offR : onR;
data[idx + 1] = isOn ? offG : onG;
data[idx + 2] = isOn ? offB : onB;
} else {
data[idx] = onR;
data[idx + 1] = onG;
data[idx + 2] = onB;
}
}
}
}
Expand Down
Loading