From 1b194ac4578dea8e71b0d61d1cb4d875f435ba71 Mon Sep 17 00:00:00 2001 From: Apexo Date: Sat, 28 Mar 2026 23:40:53 +0100 Subject: D3AA simulator --- src/peripherals/avrdx-port-org.ts | 228 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 src/peripherals/avrdx-port-org.ts (limited to 'src/peripherals/avrdx-port-org.ts') diff --git a/src/peripherals/avrdx-port-org.ts b/src/peripherals/avrdx-port-org.ts new file mode 100644 index 0000000..538384e --- /dev/null +++ b/src/peripherals/avrdx-port-org.ts @@ -0,0 +1,228 @@ +// AVR-Dx VPORT + PORT GPIO peripheral +// Implements the dual VPORT (fast, 4 regs) + PORT (full, ~24 regs) model. + +import { CPU, AVRInterruptConfig } from 'avr8js/dist/esm/cpu/cpu'; +import { + VPORT_DIR, VPORT_OUT, VPORT_IN, VPORT_INTFLAGS, + PORT_DIR, PORT_DIRSET, PORT_DIRCLR, PORT_DIRTGL, + PORT_OUT, PORT_OUTSET, PORT_OUTCLR, PORT_OUTTGL, + PORT_IN, PORT_INTFLAGS, PORT_PIN0CTRL, + PORT_ISC_gm, PORT_ISC_INTDISABLE_gc, PORT_ISC_BOTHEDGES_gc, + PORT_ISC_RISING_gc, PORT_ISC_FALLING_gc, PORT_ISC_LEVEL_gc, +} from '../d3aa-config'; + +export interface AVRDxPortConfig { + vportBase: number; + portBase: number; + interrupt: AVRInterruptConfig; +} + +export type PortListener = (dir: number, out: number) => void; + +export class AVRDxPort { + private pinState = 0x00; // external input state + private listeners: PortListener[] = []; + + constructor( + private cpu: CPU, + private config: AVRDxPortConfig, + ) { + const { vportBase: vb, portBase: pb } = config; + + cpu.writeHooks[vb + VPORT_DIR] = (value) => { + cpu.data[vb + VPORT_DIR] = value; + cpu.data[pb + PORT_DIR] = value; + return true; + }; + + cpu.writeHooks[vb + VPORT_OUT] = (value) => { + this.setOutput(value); + return true; + }; + + cpu.readHooks[vb + VPORT_IN] = () => { + return this.computeInputValue(); + }; + + // VPORT.INTFLAGS - write 1 to clear + cpu.writeHooks[vb + VPORT_INTFLAGS] = (value) => { + cpu.data[vb + VPORT_INTFLAGS] &= ~value; + cpu.data[pb + PORT_INTFLAGS] &= ~value; + // If all flags cleared, clear the interrupt + if (cpu.data[vb + VPORT_INTFLAGS] === 0) { + cpu.clearInterrupt(config.interrupt); + } + return true; + }; + + cpu.writeHooks[pb + PORT_DIR] = (value) => { + cpu.data[pb + PORT_DIR] = value; + cpu.data[vb + VPORT_DIR] = value; + return true; + }; + + // PORT.DIRSET - write 1 to set bits in DIR + cpu.writeHooks[pb + PORT_DIRSET] = (value) => { + const newDir = cpu.data[pb + PORT_DIR] | value; + cpu.data[pb + PORT_DIR] = newDir; + cpu.data[vb + VPORT_DIR] = newDir; + return true; + }; + + // PORT.DIRCLR - write 1 to clear bits in DIR + cpu.writeHooks[pb + PORT_DIRCLR] = (value) => { + const newDir = cpu.data[pb + PORT_DIR] & ~value; + cpu.data[pb + PORT_DIR] = newDir; + cpu.data[vb + VPORT_DIR] = newDir; + return true; + }; + + // PORT.DIRTGL - write 1 to toggle bits in DIR + cpu.writeHooks[pb + PORT_DIRTGL] = (value) => { + const newDir = cpu.data[pb + PORT_DIR] ^ value; + cpu.data[pb + PORT_DIR] = newDir; + cpu.data[vb + VPORT_DIR] = newDir; + return true; + }; + + // PORT.OUT + cpu.writeHooks[pb + PORT_OUT] = (value) => { + this.setOutput(value); + return true; + }; + + // PORT.OUTSET + cpu.writeHooks[pb + PORT_OUTSET] = (value) => { + this.setOutput(cpu.data[pb + PORT_OUT] | value); + return true; + }; + + // PORT.OUTCLR + cpu.writeHooks[pb + PORT_OUTCLR] = (value) => { + this.setOutput(cpu.data[pb + PORT_OUT] & ~value); + return true; + }; + + // PORT.OUTTGL + cpu.writeHooks[pb + PORT_OUTTGL] = (value) => { + this.setOutput(cpu.data[pb + PORT_OUT] ^ value); + return true; + }; + + // PORT.IN - read returns pin state + cpu.readHooks[pb + PORT_IN] = () => { + return this.computeInputValue(); + }; + + // PORT.INTFLAGS - write 1 to clear (same as VPORT) + cpu.writeHooks[pb + PORT_INTFLAGS] = (value) => { + cpu.data[pb + PORT_INTFLAGS] &= ~value; + cpu.data[vb + VPORT_INTFLAGS] &= ~value; + if (cpu.data[vb + VPORT_INTFLAGS] === 0) { + cpu.clearInterrupt(config.interrupt); + } + return true; + }; + + // PINnCTRL registers (0x10-0x17) + for (let pin = 0; pin < 8; pin++) { + cpu.writeHooks[pb + PORT_PIN0CTRL + pin] = (value) => { + cpu.data[pb + PORT_PIN0CTRL + pin] = value; + return true; + }; + } + } + + /** Set an external pin input value */ + setPin(pin: number, high: boolean) { + const oldInput = this.computeInputValue(); + if (high) { + this.pinState |= (1 << pin); + } else { + this.pinState &= ~(1 << pin); + } + const newInput = this.computeInputValue(); + this.checkInterrupts(oldInput, newInput); + } + + /** Register a listener for output changes */ + addListener(listener: PortListener) { + this.listeners.push(listener); + } + + /** Get the current output register value */ + get outputValue(): number { + return this.cpu.data[this.config.portBase + PORT_OUT]; + } + + /** Get the current direction register value */ + get dirValue(): number { + return this.cpu.data[this.config.portBase + PORT_DIR]; + } + + private setOutput(value: number) { + const { vportBase: vb, portBase: pb } = this.config; + const oldOut = this.cpu.data[pb + PORT_OUT]; + this.cpu.data[pb + PORT_OUT] = value; + this.cpu.data[vb + VPORT_OUT] = value; + if (oldOut !== value) { + const dir = this.cpu.data[pb + PORT_DIR]; + for (const listener of this.listeners) { + listener(dir, value); + } + } + } + + /** Compute the IN register value: output pins reflect OUT, input pins reflect external state */ + private computeInputValue(): number { + const { portBase: pb } = this.config; + const dir = this.cpu.data[pb + PORT_DIR]; + const out = this.cpu.data[pb + PORT_OUT]; + // Output pins read back OUT value; input pins read external pin state + return (dir & out) | (~dir & this.pinState); + } + + private checkInterrupts(oldIn: number, newIn: number) { + const { portBase: pb, vportBase: vb } = this.config; + const changed = oldIn ^ newIn; + if (!changed) return; + + let intFlags = 0; + for (let pin = 0; pin < 8; pin++) { + if (!(changed & (1 << pin))) continue; + const isc = this.cpu.data[pb + PORT_PIN0CTRL + pin] & PORT_ISC_gm; + const wasHigh = !!(oldIn & (1 << pin)); + const isHigh = !!(newIn & (1 << pin)); + + let fire = false; + switch (isc) { + case PORT_ISC_INTDISABLE_gc: + break; + case PORT_ISC_BOTHEDGES_gc: + fire = true; + break; + case PORT_ISC_RISING_gc: + fire = !wasHigh && isHigh; + break; + case PORT_ISC_FALLING_gc: + fire = wasHigh && !isHigh; + break; + case PORT_ISC_LEVEL_gc: + fire = !isHigh; // low level + break; + } + if (fire) { + intFlags |= (1 << pin); + } + } + + if (intFlags) { + this.cpu.data[vb + VPORT_INTFLAGS] |= intFlags; + this.cpu.data[pb + PORT_INTFLAGS] |= intFlags; + // Directly queue the interrupt (bypassing the enable check, since + // AVR-Dx port interrupts are enabled per-pin via PINnCTRL ISC bits, + // not via a centralized enable register) + this.cpu.queueInterrupt(this.config.interrupt); + } + } +} -- cgit v1.2.3