mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 00:02:16 +00:00
channelf: initial verison, not working, no BIOS
This commit is contained in:
1 parent
2d65e63943
commit
ebcae078c7
7 files changed
+1269
No files matched your search
@@ -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<F8State>, Interruptable<number>, 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;
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -9,6 +9,7 @@ export function importPlatform(name: string) : Promise<any> {
|
||||
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");
|
||||
|
||||
@@ -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<ChannelF> 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;
|
||||
@@ -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";
|
||||
|
||||
Reference in new issue
Block a user