From ebcae078c706aae2889bc95ccdc51be093dfa613 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Thu, 12 Mar 2026 14:32:53 +0100 Subject: [PATCH] channelf: initial verison, not working, no BIOS --- src/common/cpu/F8.ts | 691 +++++++++++++++++++++++++++++++++++++ src/common/cpu/disasmF8.ts | 139 ++++++++ src/machine/channelf.ts | 357 +++++++++++++++++++ src/machine/mcr.ts | 3 + src/platform/_index.ts | 1 + src/platform/channelf.ts | 75 ++++ src/platform/mcr.ts | 3 + 7 files changed, 1269 insertions(+) create mode 100644 src/common/cpu/F8.ts create mode 100644 src/common/cpu/disasmF8.ts create mode 100644 src/machine/channelf.ts create mode 100644 src/platform/channelf.ts diff --git a/src/common/cpu/F8.ts b/src/common/cpu/F8.ts new file mode 100644 index 00000000..19069497 --- /dev/null +++ b/src/common/cpu/F8.ts @@ -0,0 +1,691 @@ +// based on https://github.com/mamedev/mame/blob/master/src/devices/cpu/f8/f8.cpp +// license:BSD-3-Clause +// copyright-holders:Juergen Buchmueller +// and https://raw.githubusercontent.com/mamedev/mame/master/src/mame/fairchild/channelf.cpp +// license:GPL-2.0+ +// copyright-holders:Juergen Buchmueller, Frank Palazzolo, Sean Riddle + +import { Bus, CPU, InstructionBased, SavesState, Interruptable, MemoryBusConnected } from "../devices"; + +export interface F8State { + PC: number; // alias for PC0 (required by CpuState) + PC0: number; // program counter + PC1: number; // saved program counter (stack) + DC0: number; // data counter + DC1: number; // alternate data counter + A: number; // accumulator + W: number; // status register (flags) + IS: number; // indirect scratchpad address register (6-bit) + R: number[]; // 64-byte scratchpad RAM + o: number; // last opcode + irq: boolean; +} + +// Status register flag bits +const FLAG_S = 0x01; // Sign (complementary: set when positive) +const FLAG_C = 0x02; // Carry +const FLAG_Z = 0x04; // Zero +const FLAG_O = 0x08; // Overflow +const FLAG_I = 0x10; // Interrupt Control Bit + +export class F8CPU implements CPU, InstructionBased, SavesState, Interruptable, MemoryBusConnected { + + // Registers + PC0: number = 0; // active program counter + PC1: number = 0; // saved program counter + DC0: number = 0; // active data counter + DC1: number = 0; // alternate data counter + A: number = 0; // accumulator + W: number = 0; // status/flags + IS: number = 0; // indirect scratchpad address (6-bit) + R: Uint8Array = new Uint8Array(64); // scratchpad RAM + + bus: Bus; + iobus: Bus; + o: number = 0; // last opcode + stable: boolean = true; + irqRequest: boolean = false; + + connectMemoryBus(bus: Bus): void { + this.bus = bus; + } + connectIOBus(bus: Bus): void { + this.iobus = bus; + } + + reset(): void { + this.PC0 = 0; + this.PC1 = 0; + this.DC0 = 0; + this.DC1 = 0; + this.A = 0; + this.W = 0; + this.IS = 0; + this.R.fill(0); + this.irqRequest = false; + } + + interrupt(type: number): void { + this.irqRequest = true; + } + + getPC(): number { return this.PC0; } + getSP(): number { return this.PC1; } + isStable(): boolean { return this.stable; } + + saveState(): F8State { + return { + PC: this.PC0, + PC0: this.PC0, + PC1: this.PC1, + DC0: this.DC0, + DC1: this.DC1, + A: this.A, + W: this.W, + IS: this.IS, + R: Array.from(this.R), + o: this.o, + irq: this.irqRequest, + }; + } + + loadState(state: F8State): void { + this.PC0 = state.PC0 ?? state.PC; + this.PC1 = state.PC1; + this.DC0 = state.DC0; + this.DC1 = state.DC1; + this.A = state.A; + this.W = state.W; + this.IS = state.IS; + for (let i = 0; i < 64; i++) this.R[i] = state.R[i]; + this.o = state.o; + this.irqRequest = state.irq; + } + + // Read byte at PC0, increment PC0 + private fetchPC0(): number { + const val = this.bus.read(this.PC0); + this.PC0 = (this.PC0 + 1) & 0xFFFF; + return val; + } + + // Read byte at DC0, increment DC0 + private fetchDC0(): number { + const val = this.bus.read(this.DC0); + this.DC0 = (this.DC0 + 1) & 0xFFFF; + return val; + } + + // Write byte at DC0, increment DC0 + private storeDC0(val: number): void { + this.bus.write(this.DC0, val); + this.DC0 = (this.DC0 + 1) & 0xFFFF; + } + + // Clear O, Z, C, S flags + private clrOZCS(): void { + this.W &= ~(FLAG_O | FLAG_Z | FLAG_C | FLAG_S); + } + + // Set Sign and Zero flags from value + // Note: F8 sign flag is COMPLEMENTARY - set when bit 7 is CLEAR (positive) + private setSZ(val: number): void { + if ((val & 0xFF) === 0) this.W |= FLAG_Z; + if ((val & 0x80) === 0) this.W |= FLAG_S; // complementary sign + } + + // Addition with carry/overflow detection + private doAdd(a: number, b: number, carry: number = 0): number { + const result = (a & 0xFF) + (b & 0xFF) + (carry & 1); + // Set carry if overflow past 8 bits + if (result & 0x100) this.W |= FLAG_C; + // Set overflow using XOR method + if (((a ^ result) & (b ^ result) & 0x80) !== 0) this.W |= FLAG_O; + return result & 0xFF; + } + + // BCD addition + private doAddDecimal(augend: number, addend: number): number { + let tmp = (augend & 0x0F) + (addend & 0x0F); + let c1 = (tmp > 9) ? 1 : 0; + tmp = (augend >> 4) + (addend >> 4) + c1; + let c2 = (tmp > 9) ? 1 : 0; + + // Do binary add for flags + this.clrOZCS(); + let result = this.doAdd(augend, addend); + this.setSZ(result); + + // Apply BCD correction + let correction = 0; + if (c2) correction |= 0xA0; + if (c1) correction |= 0x0A; + if (correction) { + this.clrOZCS(); + result = this.doAdd(result, correction); + this.setSZ(result); + } + return result; + } + + // Get scratchpad register for operand (0-11 direct, 12=ISAR, 13=ISAR++, 14=ISAR--) + private getScratchpadIndex(r: number): number { + if (r < 12) return r; + if (r === 12) return this.IS & 0x3F; + if (r === 13) { + const idx = this.IS & 0x3F; + // Increment lower 3 bits of IS + this.IS = (this.IS & 0x38) | ((this.IS + 1) & 0x07); + return idx; + } + if (r === 14) { + const idx = this.IS & 0x3F; + // Decrement lower 3 bits of IS + this.IS = (this.IS & 0x38) | ((this.IS - 1) & 0x07); + return idx; + } + return 0; // illegal + } + + advanceInsn(): number { + // Check for interrupts + if (this.irqRequest && (this.W & FLAG_I)) { + this.irqRequest = false; + // Save PC0 to PC1 + this.PC1 = this.PC0; + // Load interrupt vector (at address 0x0020 typically) + this.PC0 = (this.bus.read(0x0FFE) << 8) | this.bus.read(0x0FFF); + // Clear interrupt enable + this.W &= ~FLAG_I; + return 6; + } + + const opcode = this.fetchPC0(); + this.o = opcode; + this.stable = true; + let cycles = 4; // short cycle default + let tmp: number; + let r: number; + + switch (opcode) { + // 0x00: LR A,KU + case 0x00: this.A = this.R[12]; break; + // 0x01: LR A,KL + case 0x01: this.A = this.R[13]; break; + // 0x02: LR A,QU + case 0x02: this.A = this.R[14]; break; + // 0x03: LR A,QL + case 0x03: this.A = this.R[15]; break; + // 0x04: LR KU,A + case 0x04: this.R[12] = this.A; break; + // 0x05: LR KL,A + case 0x05: this.R[13] = this.A; break; + // 0x06: LR QU,A + case 0x06: this.R[14] = this.A; break; + // 0x07: LR QL,A + case 0x07: this.R[15] = this.A; break; + + // 0x08: LR K,P (K <- PC1) + case 0x08: + this.R[12] = (this.PC1 >> 8) & 0xFF; + this.R[13] = this.PC1 & 0xFF; + cycles = 8; + break; + // 0x09: LR P,K (PC1 <- K) + case 0x09: + this.PC1 = (this.R[12] << 8) | this.R[13]; + cycles = 8; + break; + + // 0x0A: LR A,IS + case 0x0A: this.A = this.IS & 0x3F; break; + // 0x0B: LR IS,A + case 0x0B: this.IS = this.A & 0x3F; break; + + // 0x0C: PK (Push K to PC: PC1<-PC0+1, PC0<-K) + case 0x0C: + this.PC1 = this.PC0; + this.PC0 = (this.R[12] << 8) | this.R[13]; + cycles = 8; + break; + // 0x0D: LR P0,Q (PC0 <- Q) + case 0x0D: + this.PC0 = (this.R[14] << 8) | this.R[15]; + cycles = 8; + break; + // 0x0E: LR Q,DC (Q <- DC0) + case 0x0E: + this.R[14] = (this.DC0 >> 8) & 0xFF; + this.R[15] = this.DC0 & 0xFF; + cycles = 8; + break; + // 0x0F: LR DC,Q (DC0 <- Q) + case 0x0F: + this.DC0 = (this.R[14] << 8) | this.R[15]; + cycles = 8; + break; + + // 0x10: LR DC,H (DC0 <- H) + case 0x10: + this.DC0 = (this.R[10] << 8) | this.R[11]; + cycles = 8; + break; + // 0x11: LR H,DC (H <- DC0) + case 0x11: + this.R[10] = (this.DC0 >> 8) & 0xFF; + this.R[11] = this.DC0 & 0xFF; + cycles = 8; + break; + + // 0x12: SR 1 (shift right 1) + case 0x12: + this.A = (this.A >> 1) & 0xFF; + this.clrOZCS(); + this.setSZ(this.A); + break; + // 0x13: SL 1 (shift left 1) + case 0x13: + this.A = (this.A << 1) & 0xFF; + this.clrOZCS(); + this.setSZ(this.A); + break; + // 0x14: SR 4 (shift right 4) + case 0x14: + this.A = (this.A >> 4) & 0xFF; + this.clrOZCS(); + this.setSZ(this.A); + break; + // 0x15: SL 4 (shift left 4) + case 0x15: + this.A = (this.A << 4) & 0xFF; + this.clrOZCS(); + this.setSZ(this.A); + break; + + // 0x16: LM (load A from memory at DC0, increment DC0) + case 0x16: + this.A = this.fetchDC0(); + cycles = 6; + break; + // 0x17: ST (store A to memory at DC0, increment DC0) + case 0x17: + this.storeDC0(this.A); + cycles = 6; + break; + + // 0x18: COM (complement A) + case 0x18: + this.A = (~this.A) & 0xFF; + this.clrOZCS(); + this.setSZ(this.A); + break; + + // 0x19: LNK (add carry to A) + case 0x19: + tmp = (this.W & FLAG_C) ? 1 : 0; + this.clrOZCS(); + this.A = this.doAdd(this.A, tmp); + this.setSZ(this.A); + break; + + // 0x1A: DI (disable interrupts) + case 0x1A: + this.W &= ~FLAG_I; + break; + // 0x1B: EI (enable interrupts) + case 0x1B: + this.W |= FLAG_I; + break; + + // 0x1C: POP (PC0 <- PC1) + case 0x1C: + this.PC0 = this.PC1; + break; + + // 0x1D: LR W,J (W <- scratchpad[9]) + case 0x1D: + this.W = this.R[9] & 0x1F; + break; + // 0x1E: LR J,W (scratchpad[9] <- W) + case 0x1E: + this.R[9] = this.W & 0x1F; + break; + + // 0x1F: INC (increment A) + case 0x1F: + this.clrOZCS(); + this.A = this.doAdd(this.A, 1); + this.setSZ(this.A); + break; + + // 0x20: LI immediate + case 0x20: + this.A = this.fetchPC0(); + cycles = 6; + break; + // 0x21: NI immediate (AND) + case 0x21: + tmp = this.fetchPC0(); + this.A = this.A & tmp; + this.clrOZCS(); + this.setSZ(this.A); + cycles = 6; + break; + // 0x22: OI immediate (OR) + case 0x22: + tmp = this.fetchPC0(); + this.A = this.A | tmp; + this.clrOZCS(); + this.setSZ(this.A); + cycles = 6; + break; + // 0x23: XI immediate (XOR) + case 0x23: + tmp = this.fetchPC0(); + this.A = this.A ^ tmp; + this.clrOZCS(); + this.setSZ(this.A); + cycles = 6; + break; + // 0x24: AI immediate (ADD) + case 0x24: + tmp = this.fetchPC0(); + this.clrOZCS(); + this.A = this.doAdd(this.A, tmp); + this.setSZ(this.A); + cycles = 6; + break; + // 0x25: CI immediate (compare: sets flags from ~A + immediate + 1) + case 0x25: + tmp = this.fetchPC0(); + this.clrOZCS(); + this.doAdd((~this.A) & 0xFF, tmp, 1); + this.setSZ(this.doAdd((~this.A) & 0xFF, tmp, 1)); + cycles = 6; + break; + // 0x26: IN immediate (input from port) + case 0x26: + tmp = this.fetchPC0(); + this.A = this.iobus.read(tmp) & 0xFF; + this.clrOZCS(); + this.setSZ(this.A); + cycles = 8; + break; + // 0x27: OUT immediate (output to port) + case 0x27: + tmp = this.fetchPC0(); + this.iobus.write(tmp, this.A); + cycles = 8; + break; + + // 0x28: PI (push and jump immediate: PC1<-PC0, PC0<-address) + case 0x28: { + const hi = this.fetchPC0(); + const lo = this.fetchPC0(); + this.PC1 = this.PC0; + this.PC0 = (hi << 8) | lo; + cycles = 12; + break; + } + // 0x29: JMP (jump immediate) + case 0x29: { + const hi = this.fetchPC0(); + const lo = this.fetchPC0(); + this.PC0 = (hi << 8) | lo; + cycles = 10; + break; + } + // 0x2A: DCI (load DC0 immediate) + case 0x2A: { + const hi = this.fetchPC0(); + const lo = this.fetchPC0(); + this.DC0 = (hi << 8) | lo; + cycles = 10; + break; + } + // 0x2B: NOP + case 0x2B: break; + + // 0x2C: XDC (exchange DC0 and DC1) + case 0x2C: + tmp = this.DC0; + this.DC0 = this.DC1; + this.DC1 = tmp; + break; + + // 0x2D-0x2F: illegal + case 0x2D: case 0x2E: case 0x2F: break; + + // 0x30-0x3E: DS (decrement scratchpad register, skip if zero) + case 0x30: case 0x31: case 0x32: case 0x33: + case 0x34: case 0x35: case 0x36: case 0x37: + case 0x38: case 0x39: case 0x3A: case 0x3B: + case 0x3C: case 0x3D: case 0x3E: + r = this.getScratchpadIndex(opcode & 0x0F); + this.clrOZCS(); + this.R[r] = this.doAdd(this.R[r], 0xFF); + this.setSZ(this.R[r]); + cycles = 6; + break; + case 0x3F: break; // illegal + + // 0x40-0x4E: LR A,r (load A from scratchpad) + case 0x40: case 0x41: case 0x42: case 0x43: + case 0x44: case 0x45: case 0x46: case 0x47: + case 0x48: case 0x49: case 0x4A: case 0x4B: + case 0x4C: case 0x4D: case 0x4E: + r = this.getScratchpadIndex(opcode & 0x0F); + this.A = this.R[r]; + break; + case 0x4F: break; // illegal + + // 0x50-0x5E: LR r,A (store A to scratchpad) + case 0x50: case 0x51: case 0x52: case 0x53: + case 0x54: case 0x55: case 0x56: case 0x57: + case 0x58: case 0x59: case 0x5A: case 0x5B: + case 0x5C: case 0x5D: case 0x5E: + r = this.getScratchpadIndex(opcode & 0x0F); + this.R[r] = this.A; + break; + case 0x5F: break; // illegal + + // 0x60-0x67: LISU (load IS upper 3 bits) + case 0x60: case 0x61: case 0x62: case 0x63: + case 0x64: case 0x65: case 0x66: case 0x67: + this.IS = (this.IS & 0x07) | ((opcode & 0x07) << 3); + break; + + // 0x68-0x6F: LISL (load IS lower 3 bits) + case 0x68: case 0x69: case 0x6A: case 0x6B: + case 0x6C: case 0x6D: case 0x6E: case 0x6F: + this.IS = (this.IS & 0x38) | (opcode & 0x07); + break; + + // 0x70-0x7F: LIS (load A with 4-bit immediate) + case 0x70: case 0x71: case 0x72: case 0x73: + case 0x74: case 0x75: case 0x76: case 0x77: + case 0x78: case 0x79: case 0x7A: case 0x7B: + case 0x7C: case 0x7D: case 0x7E: case 0x7F: + this.A = opcode & 0x0F; + break; + + // 0x80-0x87: BT (branch if flag test true) + case 0x80: case 0x81: case 0x82: case 0x83: + case 0x84: case 0x85: case 0x86: case 0x87: { + const mask = opcode & 0x07; + const offset = this.fetchPC0(); + if (this.W & mask) { + // Sign-extend offset and add to PC0 + this.PC0 = (this.PC0 + ((offset & 0x80) ? (offset - 256) : offset)) & 0xFFFF; + cycles = 6; + } else { + cycles = 6; + } + break; + } + + // 0x88: AM (add memory at DC0 to A) + case 0x88: + tmp = this.fetchDC0(); + this.clrOZCS(); + this.A = this.doAdd(this.A, tmp); + this.setSZ(this.A); + cycles = 6; + break; + + // 0x89: AMD (decimal add memory at DC0 to A) + case 0x89: + tmp = this.fetchDC0(); + this.A = this.doAddDecimal(this.A, tmp); + cycles = 6; + break; + + // 0x8A: NM (AND memory at DC0) + case 0x8A: + tmp = this.fetchDC0(); + this.A = this.A & tmp; + this.clrOZCS(); + this.setSZ(this.A); + cycles = 6; + break; + + // 0x8B: OM (OR memory at DC0) + case 0x8B: + tmp = this.fetchDC0(); + this.A = this.A | tmp; + this.clrOZCS(); + this.setSZ(this.A); + cycles = 6; + break; + + // 0x8C: XM (XOR memory at DC0) + case 0x8C: + tmp = this.fetchDC0(); + this.A = this.A ^ tmp; + this.clrOZCS(); + this.setSZ(this.A); + cycles = 6; + break; + + // 0x8D: CM (compare memory at DC0) + case 0x8D: + tmp = this.fetchDC0(); + this.clrOZCS(); + { const cmp = this.doAdd((~this.A) & 0xFF, tmp, 1); this.setSZ(cmp); } + cycles = 6; + break; + + // 0x8E: ADC (add accumulator to DC0) + case 0x8E: + this.DC0 = (this.DC0 + ((this.A & 0x80) ? (this.A - 256) : this.A)) & 0xFFFF; + cycles = 6; + break; + + // 0x8F: BR7 (branch if IS lower 3 bits != 7) + case 0x8F: { + const offset = this.fetchPC0(); + if ((this.IS & 0x07) !== 7) { + this.PC0 = (this.PC0 + ((offset & 0x80) ? (offset - 256) : offset)) & 0xFFFF; + } + cycles = 6; + break; + } + + // 0x90-0x9F: BF (branch if flag false) + case 0x90: case 0x91: case 0x92: case 0x93: + case 0x94: case 0x95: case 0x96: case 0x97: + case 0x98: case 0x99: case 0x9A: case 0x9B: + case 0x9C: case 0x9D: case 0x9E: case 0x9F: { + const mask = opcode & 0x0F; + const offset = this.fetchPC0(); + if (mask === 0 || !(this.W & mask)) { + // BF 0 is unconditional branch (BR) + this.PC0 = (this.PC0 + ((offset & 0x80) ? (offset - 256) : offset)) & 0xFFFF; + } + cycles = 6; + break; + } + + // 0xA0-0xA1: INS 0-1 (input from port 0/1, direct) + case 0xA0: case 0xA1: + this.A = this.iobus.read(opcode & 0x01) & 0xFF; + this.clrOZCS(); + this.setSZ(this.A); + break; + // 0xA2-0xA3: illegal + case 0xA2: case 0xA3: break; + // 0xA4-0xAF: INS 4-F (input from addressed port) + case 0xA4: case 0xA5: case 0xA6: case 0xA7: + case 0xA8: case 0xA9: case 0xAA: case 0xAB: + case 0xAC: case 0xAD: case 0xAE: case 0xAF: + this.A = this.iobus.read(opcode & 0x0F) & 0xFF; + this.clrOZCS(); + this.setSZ(this.A); + cycles = 8; + break; + + // 0xB0-0xB1: OUTS 0-1 (output to port 0/1, direct) + case 0xB0: case 0xB1: + this.iobus.write(opcode & 0x01, this.A); + break; + // 0xB2-0xB3: illegal + case 0xB2: case 0xB3: break; + // 0xB4-0xBF: OUTS 4-F (output to addressed port) + case 0xB4: case 0xB5: case 0xB6: case 0xB7: + case 0xB8: case 0xB9: case 0xBA: case 0xBB: + case 0xBC: case 0xBD: case 0xBE: case 0xBF: + this.iobus.write(opcode & 0x0F, this.A); + cycles = 8; + break; + + // 0xC0-0xCE: AS (add scratchpad to A) + case 0xC0: case 0xC1: case 0xC2: case 0xC3: + case 0xC4: case 0xC5: case 0xC6: case 0xC7: + case 0xC8: case 0xC9: case 0xCA: case 0xCB: + case 0xCC: case 0xCD: case 0xCE: + r = this.getScratchpadIndex(opcode & 0x0F); + this.clrOZCS(); + this.A = this.doAdd(this.A, this.R[r]); + this.setSZ(this.A); + break; + case 0xCF: break; // illegal + + // 0xD0-0xDE: ASD (decimal add scratchpad to A) + case 0xD0: case 0xD1: case 0xD2: case 0xD3: + case 0xD4: case 0xD5: case 0xD6: case 0xD7: + case 0xD8: case 0xD9: case 0xDA: case 0xDB: + case 0xDC: case 0xDD: case 0xDE: + r = this.getScratchpadIndex(opcode & 0x0F); + this.A = this.doAddDecimal(this.A, this.R[r]); + cycles = 8; + break; + case 0xDF: break; // illegal + + // 0xE0-0xEE: XS (XOR scratchpad with A) + case 0xE0: case 0xE1: case 0xE2: case 0xE3: + case 0xE4: case 0xE5: case 0xE6: case 0xE7: + case 0xE8: case 0xE9: case 0xEA: case 0xEB: + case 0xEC: case 0xED: case 0xEE: + r = this.getScratchpadIndex(opcode & 0x0F); + this.A = (this.A ^ this.R[r]) & 0xFF; + this.clrOZCS(); + this.setSZ(this.A); + break; + case 0xEF: break; // illegal + + // 0xF0-0xFE: NS (AND scratchpad with A) + case 0xF0: case 0xF1: case 0xF2: case 0xF3: + case 0xF4: case 0xF5: case 0xF6: case 0xF7: + case 0xF8: case 0xF9: case 0xFA: case 0xFB: + case 0xFC: case 0xFD: case 0xFE: + r = this.getScratchpadIndex(opcode & 0x0F); + this.A = (this.A & this.R[r]) & 0xFF; + this.clrOZCS(); + this.setSZ(this.A); + break; + case 0xFF: break; // illegal + } + + return cycles; + } +} diff --git a/src/common/cpu/disasmF8.ts b/src/common/cpu/disasmF8.ts new file mode 100644 index 00000000..14f52393 --- /dev/null +++ b/src/common/cpu/disasmF8.ts @@ -0,0 +1,139 @@ +// based on https://github.com/mamedev/mame/blob/master/src/devices/cpu/f8/f8.cpp +// license:BSD-3-Clause +// copyright-holders:Juergen Buchmueller +// and https://raw.githubusercontent.com/mamedev/mame/master/src/mame/fairchild/channelf.cpp +// license:GPL-2.0+ +// copyright-holders:Juergen Buchmueller, Frank Palazzolo, Sean Riddle + +import { hex } from "../util"; + +// Scratchpad register names for operands 0-14 +function regName(r: number): string { + if (r < 9) return 'r' + r; + if (r === 9) return 'J'; + if (r === 10) return 'HU'; + if (r === 11) return 'HL'; + if (r === 12) return 'S'; // ISAR + if (r === 13) return 'I'; // ISAR++ + if (r === 14) return 'D'; // ISAR-- + return '?'; +} + +export function disassembleF8(pc: number, b0: number, b1: number, b2: number): { line: string, nbytes: number, isaddr: boolean } { + let mn = ''; + let args = ''; + let nb = 1; + let isaddr = false; + + if (b0 <= 0x07) { + const regs = ['KU', 'KL', 'QU', 'QL']; + if (b0 < 4) { mn = 'LR'; args = 'A,' + regs[b0]; } + else { mn = 'LR'; args = regs[b0 - 4] + ',A'; } + } else if (b0 === 0x08) { mn = 'LR'; args = 'K,P'; } + else if (b0 === 0x09) { mn = 'LR'; args = 'P,K'; } + else if (b0 === 0x0A) { mn = 'LR'; args = 'A,IS'; } + else if (b0 === 0x0B) { mn = 'LR'; args = 'IS,A'; } + else if (b0 === 0x0C) { mn = 'PK'; } + else if (b0 === 0x0D) { mn = 'LR'; args = 'P0,Q'; } + else if (b0 === 0x0E) { mn = 'LR'; args = 'Q,DC'; } + else if (b0 === 0x0F) { mn = 'LR'; args = 'DC,Q'; } + else if (b0 === 0x10) { mn = 'LR'; args = 'DC,H'; } + else if (b0 === 0x11) { mn = 'LR'; args = 'H,DC'; } + else if (b0 === 0x12) { mn = 'SR'; args = '1'; } + else if (b0 === 0x13) { mn = 'SL'; args = '1'; } + else if (b0 === 0x14) { mn = 'SR'; args = '4'; } + else if (b0 === 0x15) { mn = 'SL'; args = '4'; } + else if (b0 === 0x16) { mn = 'LM'; } + else if (b0 === 0x17) { mn = 'ST'; } + else if (b0 === 0x18) { mn = 'COM'; } + else if (b0 === 0x19) { mn = 'LNK'; } + else if (b0 === 0x1A) { mn = 'DI'; } + else if (b0 === 0x1B) { mn = 'EI'; } + else if (b0 === 0x1C) { mn = 'POP'; } + else if (b0 === 0x1D) { mn = 'LR'; args = 'W,J'; } + else if (b0 === 0x1E) { mn = 'LR'; args = 'J,W'; } + else if (b0 === 0x1F) { mn = 'INC'; } + else if (b0 === 0x20) { mn = 'LI'; args = '$' + hex(b1, 2); nb = 2; } + else if (b0 === 0x21) { mn = 'NI'; args = '$' + hex(b1, 2); nb = 2; } + else if (b0 === 0x22) { mn = 'OI'; args = '$' + hex(b1, 2); nb = 2; } + else if (b0 === 0x23) { mn = 'XI'; args = '$' + hex(b1, 2); nb = 2; } + else if (b0 === 0x24) { mn = 'AI'; args = '$' + hex(b1, 2); nb = 2; } + else if (b0 === 0x25) { mn = 'CI'; args = '$' + hex(b1, 2); nb = 2; } + else if (b0 === 0x26) { mn = 'IN'; args = '$' + hex(b1, 2); nb = 2; } + else if (b0 === 0x27) { mn = 'OUT'; args = '$' + hex(b1, 2); nb = 2; } + else if (b0 === 0x28) { + mn = 'PI'; args = '$' + hex((b1 << 8) | b2, 4); nb = 3; isaddr = true; + } + else if (b0 === 0x29) { + mn = 'JMP'; args = '$' + hex((b1 << 8) | b2, 4); nb = 3; isaddr = true; + } + else if (b0 === 0x2A) { + mn = 'DCI'; args = '$' + hex((b1 << 8) | b2, 4); nb = 3; isaddr = true; + } + else if (b0 === 0x2B) { mn = 'NOP'; } + else if (b0 === 0x2C) { mn = 'XDC'; } + else if (b0 <= 0x2F) { mn = '???'; } + else if (b0 >= 0x30 && b0 <= 0x3E) { + mn = 'DS'; args = regName(b0 & 0x0F); nb = 1; + } + else if (b0 === 0x3F) { mn = '???'; } + else if (b0 >= 0x40 && b0 <= 0x4E) { + mn = 'LR'; args = 'A,' + regName(b0 & 0x0F); + } + else if (b0 === 0x4F) { mn = '???'; } + else if (b0 >= 0x50 && b0 <= 0x5E) { + mn = 'LR'; args = regName(b0 & 0x0F) + ',A'; + } + else if (b0 === 0x5F) { mn = '???'; } + else if (b0 >= 0x60 && b0 <= 0x67) { + mn = 'LISU'; args = '' + (b0 & 0x07); + } + else if (b0 >= 0x68 && b0 <= 0x6F) { + mn = 'LISL'; args = '' + (b0 & 0x07); + } + else if (b0 >= 0x70 && b0 <= 0x7F) { + mn = 'LIS'; args = '' + (b0 & 0x0F); + } + else if (b0 >= 0x80 && b0 <= 0x87) { + const offset = (b1 & 0x80) ? (pc + 2 + b1 - 256) : (pc + 2 + b1); + mn = 'BT'; args = '' + (b0 & 0x07) + ',$' + hex(offset & 0xFFFF, 4); + nb = 2; isaddr = true; + } + else if (b0 === 0x88) { mn = 'AM'; } + else if (b0 === 0x89) { mn = 'AMD'; } + else if (b0 === 0x8A) { mn = 'NM'; } + else if (b0 === 0x8B) { mn = 'OM'; } + else if (b0 === 0x8C) { mn = 'XM'; } + else if (b0 === 0x8D) { mn = 'CM'; } + else if (b0 === 0x8E) { mn = 'ADC'; } + else if (b0 === 0x8F) { + const offset = (b1 & 0x80) ? (pc + 2 + b1 - 256) : (pc + 2 + b1); + mn = 'BR7'; args = '$' + hex(offset & 0xFFFF, 4); + nb = 2; isaddr = true; + } + else if (b0 >= 0x90 && b0 <= 0x9F) { + const offset = (b1 & 0x80) ? (pc + 2 + b1 - 256) : (pc + 2 + b1); + const mask = b0 & 0x0F; + if (mask === 0) mn = 'BR'; + else mn = 'BF'; + if (mask !== 0) args = '' + mask + ','; + args += '$' + hex(offset & 0xFFFF, 4); + nb = 2; isaddr = true; + } + else if (b0 >= 0xA0 && b0 <= 0xA1) { mn = 'INS'; args = '' + (b0 & 0x0F); } + else if (b0 >= 0xA2 && b0 <= 0xA3) { mn = '???'; } + else if (b0 >= 0xA4 && b0 <= 0xAF) { mn = 'INS'; args = '' + (b0 & 0x0F); } + else if (b0 >= 0xB0 && b0 <= 0xB1) { mn = 'OUTS'; args = '' + (b0 & 0x0F); } + else if (b0 >= 0xB2 && b0 <= 0xB3) { mn = '???'; } + else if (b0 >= 0xB4 && b0 <= 0xBF) { mn = 'OUTS'; args = '' + (b0 & 0x0F); } + else if (b0 >= 0xC0 && b0 <= 0xCE) { mn = 'AS'; args = regName(b0 & 0x0F); } + else if (b0 === 0xCF) { mn = '???'; } + else if (b0 >= 0xD0 && b0 <= 0xDE) { mn = 'ASD'; args = regName(b0 & 0x0F); } + else if (b0 === 0xDF) { mn = '???'; } + else if (b0 >= 0xE0 && b0 <= 0xEE) { mn = 'XS'; args = regName(b0 & 0x0F); } + else if (b0 === 0xEF) { mn = '???'; } + else if (b0 >= 0xF0 && b0 <= 0xFE) { mn = 'NS'; args = regName(b0 & 0x0F); } + else { mn = '???'; } + + return { line: mn + (args ? ' ' + args : ''), nbytes: nb, isaddr: isaddr }; +} diff --git a/src/machine/channelf.ts b/src/machine/channelf.ts new file mode 100644 index 00000000..d4474e7e --- /dev/null +++ b/src/machine/channelf.ts @@ -0,0 +1,357 @@ +// based on https://github.com/mamedev/mame/blob/master/src/devices/cpu/f8/f8.cpp +// license:BSD-3-Clause +// copyright-holders:Juergen Buchmueller +// and https://raw.githubusercontent.com/mamedev/mame/master/src/mame/fairchild/channelf.cpp +// license:GPL-2.0+ +// copyright-holders:Juergen Buchmueller, Frank Palazzolo, Sean Riddle + +import { F8CPU, F8State } from "../common/cpu/F8"; +import { BasicMachine, Bus, SampledAudioSink } from "../common/devices"; +import { KeyFlags, Keys, newAddressDecoder, padBytes } from "../common/emu"; +import { hex } from "../common/util"; + +// Channel F color palette (RGBA) +const CHANNELF_COLORS = [ + 0xFF484848, // 0: dark gray/background (approximation for "field" color) + 0xFF5050FF, // 1: blue + 0xFF50FF50, // 2: green -> actually the red + 0xFF808080, // 3: gray + 0xFFFFFFFF, // 4: white (BG palette) + 0xFF50FF50, // 5: green + 0xFFFF5050, // 6: red + 0xFFC8C8C8, // 7: light gray +]; + +// Full 16-entry color map (4 palette banks x 4 colors) +const CHANNELF_COLORMAP = [ + // Palette 0 (default) + 0xFF484848, // 0: background + 0xFF50FF50, // 1: green + 0xFF5050FF, // 2: blue + 0xFF808080, // 3: gray + // Palette 1 + 0xFFFF5050, // 0: red (background) + 0xFF50FF50, // 1: green + 0xFF5050FF, // 2: blue + 0xFF808080, // 3: gray + // Palette 2 + 0xFF484848, // 0: background + 0xFFFF5050, // 1: red + 0xFF5050FF, // 2: blue + 0xFF808080, // 3: gray + // Palette 3 + 0xFF484848, // 0: background + 0xFF50FF50, // 1: green + 0xFFFF5050, // 2: red + 0xFF808080, // 3: gray +]; + +// Input port bit definitions +const INPUT_RIGHT = 0x01; +const INPUT_LEFT = 0x02; +const INPUT_BACK = 0x04; +const INPUT_FORWARD = 0x08; +const INPUT_CCW = 0x10; // counter-clockwise +const INPUT_CW = 0x20; // clockwise +const INPUT_PULL = 0x40; +const INPUT_PUSH = 0x80; + +export class ChannelF extends BasicMachine { + + cpuFrequency = 1789773; // NTSC: 3.579545 MHz / 2 + defaultROMSize = 0x10000; + + // Video params + canvasWidth = 128; + numVisibleScanlines = 64; + numTotalScanlines = 64; + cpuCyclesPerLine = this.cpuFrequency / 60 / 64; // ~466 cycles per line + + sampleRate = this.cpuFrequency / 2; // audio sample rate + + cpu = new F8CPU(); + ram = new Uint8Array(0x10000); // address space (ROM + VRAM) + vram = new Uint8Array(0x2000); // 8KB video RAM (128x64, 2 bits per pixel) + + bios: Uint8Array; + + // I/O port latches + port0: number = 0; // console switches + port1: number = 0; // right controller + port4: number = 0; // left controller + port5: number = 0; // row/column/sound + + // Video registers + row_reg: number = 0; + col_reg: number = 0; + color: number = 0; + + // Audio state + soundMode: number = 0; + soundCounter: number = 0; + soundEnvelope: number = 0; + soundOnTime: number = 0; + + constructor() { + super(); + this.bios = new Uint8Array(0x800); // 2KB BIOS + this.connectCPUMemoryBus(this); + this.cpu.connectIOBus(this.newIOBus()); + } + + newIOBus(): Bus { + return this.probeIOBus({ + read: (port: number): number => { + return this.readPort(port); + }, + write: (port: number, val: number): void => { + this.writePort(port, val); + } + }); + } + + readPort(port: number): number { + switch (port & 0xFF) { + case 0: + // Console switches: TIME(0), MODE(1), HOLD(2), START(3) + return (~this.inputs[0]) & 0xFF; + case 1: + // Right controller + return (~this.inputs[1]) & 0xFF; + case 4: + // Left controller + return (~this.inputs[2]) & 0xFF; + default: + return 0xFF; + } + } + + writePort(port: number, val: number): void { + switch (port & 0xFF) { + case 0: + this.port0 = val; + break; + case 1: + this.port1 = val; + // The right controller port is also used for video column + this.col_reg = val; + break; + case 4: + this.port4 = val; + break; + case 5: + this.port5 = val; + // Upper 2 bits control sound + this.soundMode = (val >> 6) & 3; + if (this.soundMode > 0) { + this.soundEnvelope = 0x3FF; // reset envelope on new sound + this.soundOnTime = 60; // minimum on time (~2ms) + } + // Lower 6 bits control video row + this.row_reg = val & 0x3F; + // Write pixel to VRAM when both row and column are set + this.writePixel(); + break; + } + } + + writePixel(): void { + const row = this.row_reg; + const col = this.col_reg & 0x7F; + if (row < 64 && col < 128) { + // Color comes from port 1 upper bits + const color = (this.port1 >> 6) & 0x03; + const addr = row * 128 + col; + this.vram[addr] = color; + } + } + + read = newAddressDecoder([ + [0x0000, 0x07FF, 0x07FF, (a) => { return this.bios[a]; }], + [0x0800, 0xFFFF, 0xFFFF, (a) => { return this.rom ? (this.rom[a - 0x0800] || 0) : 0; }], + ], { gmask: 0xFFFF }); + + write = newAddressDecoder([ + // Channel F has no writable main memory in the base config + // Some cartridges have RAM at 0x2800-0x2FFF + [0x2800, 0x2FFF, 0x07FF, (a, v) => { if (this.rom) this.rom[a - 0x0800] = v; }], + ], { gmask: 0xFFFF }); + + readConst(a: number): number { + if (a < 0x0800) return this.bios[a]; + return this.rom ? (this.rom[a - 0x0800] || 0) : 0; + } + + loadROM(data: Uint8Array): void { + if (!this.rom) this.rom = new Uint8Array(this.defaultROMSize); + this.rom.set(data); + this.reset(); + } + + loadBIOS(data: Uint8Array): void { + this.bios = padBytes(data, 0x800); + this.reset(); + } + + reset(): void { + super.reset(); + this.vram.fill(0); + this.port0 = 0; + this.port1 = 0; + this.port4 = 0; + this.port5 = 0; + this.row_reg = 0; + this.col_reg = 0; + this.soundMode = 0; + this.soundCounter = 0; + this.soundEnvelope = 0; + } + + advanceFrame(trap): number { + var clock = 0; + var targetClocks = this.cpuFrequency / 60; + this.probe.logNewFrame(); + while (clock < targetClocks) { + if (trap && trap()) break; + clock += this.advanceCPU(); + } + this.renderFrame(); + this.generateAudio(targetClocks); + return clock; + } + + renderFrame(): void { + if (!this.pixels) return; + // Determine palette offset from VRAM locations 125 and 126 of row 0 + // (simplified - uses palette 0 by default) + for (let y = 0; y < 64; y++) { + // Palette selection per row from vram[y*128 + 125] and [y*128 + 126] + let palOffset = 0; + const p125 = this.vram[y * 128 + 125]; + const p126 = this.vram[y * 128 + 126]; + palOffset = ((p125 & 2) | ((p126 & 2) >> 1)) * 4; + + for (let x = 0; x < 128; x++) { + const color = this.vram[y * 128 + x] & 0x03; + const rgba = CHANNELF_COLORMAP[palOffset + color]; + // Double vertical: each Channel F row maps to 4 display rows (NTSC) + this.pixels[y * 128 + x] = rgba; + } + } + } + + generateAudio(cycles: number): void { + if (!this.audio) return; + // Simple tone generation based on sound mode + const samplesPerFrame = Math.floor(this.sampleRate / 60); + for (let i = 0; i < samplesPerFrame; i++) { + let sample = 0; + if (this.soundMode > 0 && this.soundEnvelope > 0) { + // Generate square wave at appropriate frequency + let freq: number; + switch (this.soundMode) { + case 1: freq = 1000; break; // ~1000 Hz + case 2: freq = 500; break; // ~500 Hz + case 3: freq = 120; break; // ~120 Hz + default: freq = 0; + } + if (freq > 0) { + const period = Math.floor(this.sampleRate / freq); + this.soundCounter = (this.soundCounter + 1) % period; + sample = this.soundCounter < period / 2 ? 0.3 : -0.3; + sample *= this.soundEnvelope / 0x3FF; + } + // Decay envelope + if (this.soundOnTime > 0) { + this.soundOnTime--; + } else { + this.soundEnvelope -= 2; + if (this.soundEnvelope < 0) this.soundEnvelope = 0; + } + } + this.audio.feedSample(sample, 1); + } + } + + setKeyInput(key: number, code: number, flags: number): void { + // inputs[0] = console switches + // inputs[1] = right controller + // inputs[2] = left controller + const pressed = !!(flags & KeyFlags.KeyDown); + const released = !!(flags & KeyFlags.KeyUp); + + // Console buttons (mapped to number keys) + if (key === Keys.VK_1.c) { // TIME + if (pressed) this.inputs[0] |= 0x01; else if (released) this.inputs[0] &= ~0x01; + } + if (key === Keys.VK_2.c) { // MODE + if (pressed) this.inputs[0] |= 0x02; else if (released) this.inputs[0] &= ~0x02; + } + if (key === Keys.VK_3.c) { // HOLD + if (pressed) this.inputs[0] |= 0x04; else if (released) this.inputs[0] &= ~0x04; + } + if (key === Keys.VK_4.c) { // START + if (pressed) this.inputs[0] |= 0x08; else if (released) this.inputs[0] &= ~0x08; + } + + // Right controller (WASD + QE for twist + RF for push/pull) + if (key === Keys.VK_UP.c || key === Keys.VK_W.c) { + if (pressed) this.inputs[1] |= INPUT_FORWARD; else if (released) this.inputs[1] &= ~INPUT_FORWARD; + } + if (key === Keys.VK_DOWN.c || key === Keys.VK_S.c) { + if (pressed) this.inputs[1] |= INPUT_BACK; else if (released) this.inputs[1] &= ~INPUT_BACK; + } + if (key === Keys.VK_LEFT.c || key === Keys.VK_A.c) { + if (pressed) this.inputs[1] |= INPUT_LEFT; else if (released) this.inputs[1] &= ~INPUT_LEFT; + } + if (key === Keys.VK_RIGHT.c || key === Keys.VK_D.c) { + if (pressed) this.inputs[1] |= INPUT_RIGHT; else if (released) this.inputs[1] &= ~INPUT_RIGHT; + } + if (key === Keys.VK_Q.c) { // CCW twist + if (pressed) this.inputs[1] |= INPUT_CCW; else if (released) this.inputs[1] &= ~INPUT_CCW; + } + if (key === Keys.VK_E.c) { // CW twist + if (pressed) this.inputs[1] |= INPUT_CW; else if (released) this.inputs[1] &= ~INPUT_CW; + } + if (key === Keys.VK_R.c) { // Push + if (pressed) this.inputs[1] |= INPUT_PUSH; else if (released) this.inputs[1] &= ~INPUT_PUSH; + } + if (key === Keys.VK_F.c) { // Pull + if (pressed) this.inputs[1] |= INPUT_PULL; else if (released) this.inputs[1] &= ~INPUT_PULL; + } + } + + saveState() { + return { + c: this.cpu.saveState(), + ram: this.ram.slice(0), + inputs: this.inputs.slice(0), + vram: this.vram.slice(0), + port0: this.port0, + port1: this.port1, + port4: this.port4, + port5: this.port5, + row_reg: this.row_reg, + col_reg: this.col_reg, + soundMode: this.soundMode, + soundCounter: this.soundCounter, + soundEnvelope: this.soundEnvelope, + }; + } + + loadState(state) { + this.cpu.loadState(state.c); + this.ram.set(state.ram); + this.inputs.set(state.inputs); + this.vram.set(state.vram); + this.port0 = state.port0; + this.port1 = state.port1; + this.port4 = state.port4; + this.port5 = state.port5; + this.row_reg = state.row_reg; + this.col_reg = state.col_reg; + this.soundMode = state.soundMode; + this.soundCounter = state.soundCounter; + this.soundEnvelope = state.soundEnvelope; + } +} diff --git a/src/machine/mcr.ts b/src/machine/mcr.ts index 61ff8764..68259aa8 100644 --- a/src/machine/mcr.ts +++ b/src/machine/mcr.ts @@ -1,3 +1,6 @@ +// based on https://raw.githubusercontent.com/mamedev/mame/refs/heads/master/src/mame/midway/mcr.cpp +// license:BSD-3-Clause +// copyright-holders:Aaron Giles import { Z80, Z80State } from "../common/cpu/ZilogZ80"; import { BasicScanlineMachine } from "../common/devices"; diff --git a/src/platform/_index.ts b/src/platform/_index.ts index 2dde4f03..a992c951 100644 --- a/src/platform/_index.ts +++ b/src/platform/_index.ts @@ -9,6 +9,7 @@ export function importPlatform(name: string) : Promise { case "atari8": return import("../platform/atari8"); case "basic": return import("../platform/basic"); case "c64": return import("../platform/c64"); + case "channelf": return import("../platform/channelf"); case "coleco": return import("../platform/coleco"); case "cpc": return import("../platform/cpc"); case "devel": return import("../platform/devel"); diff --git a/src/platform/channelf.ts b/src/platform/channelf.ts new file mode 100644 index 00000000..dfb74c95 --- /dev/null +++ b/src/platform/channelf.ts @@ -0,0 +1,75 @@ +// based on https://github.com/mamedev/mame/blob/master/src/devices/cpu/f8/f8.cpp +// license:BSD-3-Clause +// copyright-holders:Juergen Buchmueller +// and https://raw.githubusercontent.com/mamedev/mame/master/src/mame/fairchild/channelf.cpp +// license:GPL-2.0+ +// copyright-holders:Juergen Buchmueller, Frank Palazzolo, Sean Riddle + +import { Platform, BaseMachinePlatform, DisasmLine } from "../common/baseplatform"; +import { PLATFORMS } from "../common/emu"; +import { ChannelF } from "../machine/channelf"; +import { disassembleF8 } from "../common/cpu/disasmF8"; +import { hex, printFlags } from "../common/util"; + +var CHANNELF_PRESETS = [ + { id: 'hello.dasm', name: 'Hello World (ASM)' }, +]; + +function cpuStateToLongString_F8(c): string { + return "PC0 = " + hex(c.PC0, 4) + " PC1 = " + hex(c.PC1, 4) + "\n" + + "DC0 = " + hex(c.DC0, 4) + " DC1 = " + hex(c.DC1, 4) + "\n" + + " A = " + hex(c.A, 2) + " W = " + hex(c.W, 2) + "\n" + + " IS = " + hex(c.IS, 2) + "\n" + + printFlags(c.W, ['S', 'C', 'Z', 'O', 'I'], true) + "\n" + + " R0-R3: " + c.R.slice(0, 4).map(v => hex(v, 2)).join(' ') + "\n" + + " R4-R7: " + c.R.slice(4, 8).map(v => hex(v, 2)).join(' ') + "\n" + + " R8/J/H: " + c.R.slice(8, 12).map(v => hex(v, 2)).join(' ') + "\n" + + " K/Q: " + c.R.slice(12, 16).map(v => hex(v, 2)).join(' ') + "\n"; +} + +class ChannelFPlatform extends BaseMachinePlatform implements Platform { + + newMachine() { return new ChannelF(); } + getPresets() { return CHANNELF_PRESETS; } + getDefaultExtensions() { return [".dasm"]; } + getToolForFilename(fn: string): string { + if (fn.endsWith(".c")) return "cc65"; + if (fn.endsWith(".s") || fn.endsWith(".ca65")) return "ca65"; + return "dasm"; + } + readAddress(a) { return this.machine.readConst(a); } + + disassemble(pc: number, read: (addr: number) => number): DisasmLine { + return disassembleF8(pc, read(pc), read(pc + 1), read(pc + 2)); + } + + getDebugCategories() { + return ['CPU', 'I/O', 'VRAM']; + } + + getDebugInfo(category: string, state): string { + switch (category) { + case 'CPU': return cpuStateToLongString_F8(state.c); + case 'I/O': + return "Port 0: " + hex(state.port0 || 0, 2) + "\n" + + "Port 1: " + hex(state.port1 || 0, 2) + "\n" + + "Port 4: " + hex(state.port4 || 0, 2) + "\n" + + "Port 5: " + hex(state.port5 || 0, 2) + "\n" + + "Row: " + hex(state.row_reg || 0, 2) + "\n" + + "Col: " + hex(state.col_reg || 0, 2) + "\n"; + case 'VRAM': return ''; + default: return ''; + } + } + + getMemoryMap() { + return { + main: [ + { name: 'BIOS', start: 0x0000, size: 0x0800, type: 'rom' }, + { name: 'Cart ROM', start: 0x0800, size: 0x7800, type: 'rom' }, + ] + }; + } +} + +PLATFORMS['channelf'] = ChannelFPlatform; diff --git a/src/platform/mcr.ts b/src/platform/mcr.ts index 274b8667..cef86e8a 100644 --- a/src/platform/mcr.ts +++ b/src/platform/mcr.ts @@ -1,3 +1,6 @@ +// based on https://raw.githubusercontent.com/mamedev/mame/refs/heads/master/src/mame/midway/mcr.cpp +// license:BSD-3-Clause +// copyright-holders:Aaron Giles import { MCR2Machine } from "../machine/mcr"; import { BaseZ80MachinePlatform } from "../common/baseplatform";