summaryrefslogtreecommitdiff
path: root/src/peripherals/avrdx-port-org.ts
diff options
context:
space:
mode:
authorApexo2026-03-28 23:40:53 +0100
committerApexo2026-03-28 23:40:53 +0100
commit1b194ac4578dea8e71b0d61d1cb4d875f435ba71 (patch)
tree786019a0c6f34b458f3272bf2ecbde0de1976e0a /src/peripherals/avrdx-port-org.ts
downloadanduril-sim-1b194ac4578dea8e71b0d61d1cb4d875f435ba71.tar.gz
anduril-sim-1b194ac4578dea8e71b0d61d1cb4d875f435ba71.tar.bz2
anduril-sim-1b194ac4578dea8e71b0d61d1cb4d875f435ba71.zip
D3AA simulator
Diffstat (limited to 'src/peripherals/avrdx-port-org.ts')
-rw-r--r--src/peripherals/avrdx-port-org.ts228
1 files changed, 228 insertions, 0 deletions
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);
+ }
+ }
+}