import { FIFO } from '../utils/fifo.js'; import { DREQChannel } from './dma.js'; import { BasePeripheral } from './peripheral.js'; // Generic registers const CTRL = 0x000; const FSTAT = 0x004; const FDEBUG = 0x008; const FLEVEL = 0x00c; const IRQ = 0x030; const IRQ_FORCE = 0x034; const INPUT_SYNC_BYPASS = 0x038; const DBG_PADOUT = 0x03c; const DBG_PADOE = 0x040; const DBG_CFGINFO = 0x044; const INSTR_MEM0 = 0x48; const INSTR_MEM31 = 0x0c4; const INTR = 0x128; // Raw Interrupts const IRQ0_INTE = 0x12c; // Interrupt Enable for irq0 const IRQ0_INTF = 0x130; // Interrupt Force for irq0 const IRQ0_INTS = 0x134; // Interrupt status after masking & forcing for irq0 const IRQ1_INTE = 0x138; // Interrupt Enable for irq1 const IRQ1_INTF = 0x13c; // Interrupt Force for irq1 const IRQ1_INTS = 0x140; // Interrupt status after masking & forcing for irq1 // State-machine specific registers const TXF0 = 0x010; const TXF1 = 0x014; const TXF2 = 0x018; const TXF3 = 0x01c; const RXF0 = 0x020; const RXF1 = 0x024; const RXF2 = 0x028; const RXF3 = 0x02c; const SM0_CLKDIV = 0x0c8; // Clock divisor register for state machine 0 const SM0_EXECCTRL = 0x0cc; // Execution/behavioural settings for state machine 0 const SM0_SHIFTCTRL = 0x0d0; // Control behaviour of the input/output shift registers for state machine 0 const SM0_ADDR = 0x0d4; // Current instruction address of state machine 0 const SM0_INSTR = 0x0d8; // Write to execute an instruction immediately (including jumps) and then resume execution. const SM0_PINCTRL = 0x0dc; //State machine pin control const SM1_CLKDIV = 0x0e0; const SM1_PINCTRL = 0x0f4; const SM2_CLKDIV = 0x0f8; const SM2_PINCTRL = 0x10c; const SM3_CLKDIV = 0x110; const SM3_PINCTRL = 0x124; // FSTAT bits const FSTAT_TXEMPTY = 1 << 24; const FSTAT_TXFULL = 1 << 16; const FSTAT_RXEMPTY = 1 << 8; const FSTAT_RXFULL = 1 << 0; // FDEBUG bits const FDEBUG_TXSTALL = 1 << 24; const FDEBUG_TXOVER = 1 << 16; const FDEBUG_RXUNDER = 1 << 8; const FDEBUG_RXSTALL = 1 << 0; // SHIFTCTRL bits const SHIFTCTRL_AUTOPUSH = 1 << 16; const SHIFTCTRL_AUTOPULL = 1 << 17; const SHIFTCTRL_IN_SHIFTDIR = 1 << 18; // 1 = shift input shift register to right (data enters from left). 0 = to left const SHIFTCTRL_OUT_SHIFTDIR = 1 << 19; // 1 = shift out of output shift register to right. 0 = to left // EXECCTRL bits const EXECCTRL_STATUS_SEL = 1 << 4; const EXECCTRL_SIDE_PINDIR = 1 << 29; const EXECCTRL_SIDE_EN = 1 << 30; const EXECCTRL_EXEC_STALLED = 1 << 31; export var WaitType; (function (WaitType) { WaitType[WaitType["None"] = 0] = "None"; WaitType[WaitType["Pin"] = 1] = "Pin"; WaitType[WaitType["rxFIFO"] = 2] = "rxFIFO"; WaitType[WaitType["txFIFO"] = 3] = "txFIFO"; WaitType[WaitType["IRQ"] = 4] = "IRQ"; WaitType[WaitType["Out"] = 5] = "Out"; })(WaitType || (WaitType = {})); function bitReverse(x) { x = ((x & 0x55555555) << 1) | ((x & 0xaaaaaaaa) >>> 1); x = ((x & 0x33333333) << 2) | ((x & 0xcccccccc) >>> 2); x = ((x & 0x0f0f0f0f) << 4) | ((x & 0xf0f0f0f0) >>> 4); x = ((x & 0x00ff00ff) << 8) | ((x & 0xff00ff00) >>> 8); x = ((x & 0x0000ffff) << 16) | ((x & 0xffff0000) >>> 16); return x >>> 0; } function irqIndex(irq, machineIndex) { const rel = !!(irq & 0x10); return rel ? (irq & 0x4) | (((irq & 0x3) + machineIndex) & 0x3) : irq & 0x7; } const dreqRx0 = [ DREQChannel.DREQ_PIO0_RX0, DREQChannel.DREQ_PIO0_RX1, DREQChannel.DREQ_PIO0_RX2, DREQChannel.DREQ_PIO0_RX3, ]; const dreqTx0 = [ DREQChannel.DREQ_PIO0_TX0, DREQChannel.DREQ_PIO0_TX1, DREQChannel.DREQ_PIO0_TX2, DREQChannel.DREQ_PIO0_TX3, ]; const dreqRx1 = [ DREQChannel.DREQ_PIO1_RX0, DREQChannel.DREQ_PIO1_RX1, DREQChannel.DREQ_PIO1_RX2, DREQChannel.DREQ_PIO1_RX3, ]; const dreqTx1 = [ DREQChannel.DREQ_PIO1_TX0, DREQChannel.DREQ_PIO1_TX1, DREQChannel.DREQ_PIO1_TX2, DREQChannel.DREQ_PIO1_TX3, ]; export class StateMachine { constructor(rp2040, pio, index) { this.rp2040 = rp2040; this.pio = pio; this.index = index; this.enabled = false; // State machine registers this.x = 0; this.y = 0; this.pc = 0; this.inputShiftReg = 0; this.inputShiftCount = 0; this.outputShiftReg = 0; this.outputShiftCount = 0; this.cycles = 0; this.execOpcode = 0; this.execValid = false; this.updatePC = true; this.clockDivInt = 1; this.clockDivFrac = 0; this.execCtrl = 0x1f << 12; this.shiftCtrl = 0b11 << 18; this.pinCtrl = 0x5 << 26; this.rxFIFO = new FIFO(4); this.txFIFO = new FIFO(4); this.outPinValues = 0; this.outPinDirection = 0; this.waiting = false; this.waitType = WaitType.None; this.waitIndex = 0; this.waitPolarity = false; this.waitDelay = -1; this.dreqRx = this.pio.dreqRx[this.index]; this.dreqTx = this.pio.dreqTx[this.index]; this.updateDMARx(); this.updateDMATx(); } updateDMATx() { if (this.txFIFO.full) { this.rp2040.dma.clearDREQ(this.dreqTx); } else { this.rp2040.dma.setDREQ(this.dreqTx); } } updateDMARx() { if (this.rxFIFO.empty) { this.rp2040.dma.clearDREQ(this.dreqRx); } else { this.rp2040.dma.setDREQ(this.dreqRx); } } writeFIFO(value) { if (this.txFIFO.full) { this.pio.fdebug |= FDEBUG_TXOVER << this.index; return; } this.txFIFO.push(value); this.pio.txStall &= ~(FDEBUG_TXSTALL << this.index); this.updateDMATx(); this.checkWait(); if (this.txFIFO.full) { this.pio.checkInterrupts(); } } readFIFO() { if (this.rxFIFO.empty) { this.pio.fdebug |= FDEBUG_RXUNDER << this.index; return 0; } const result = this.rxFIFO.pull(); this.pio.rxStall &= ~(FDEBUG_RXSTALL << this.index); this.updateDMARx(); this.checkWait(); if (this.rxFIFO.empty) { this.pio.checkInterrupts(); } return result; } get status() { const statusN = this.execCtrl & 0xf; if (this.execCtrl & EXECCTRL_STATUS_SEL) { return this.rxFIFO.itemCount < statusN ? 0xffffffff : 0; } else { return this.txFIFO.itemCount < statusN ? 0xffffffff : 0; } } jmpCondition(condition) { switch (condition) { // (no condition): Always case 0b000: return true; // !X: scratch X zero case 0b001: return this.x === 0; // X--: scratch X non-zero, post-decrement case 0b010: { const oldX = this.x; this.x = (this.x - 1) >>> 0; return oldX !== 0; } // !Y: scratch Y zero case 0b011: return this.y === 0; // Y--: scratch Y non-zero, post-decrement case 0b100: { const oldY = this.y; this.y = (this.y - 1) >>> 0; return oldY !== 0; } // X!=Y: scratch X not equal scratch Y case 0b101: return this.x >>> 0 !== this.y >>> 0; // PIN: branch on input pin case 0b110: { const { gpio } = this.rp2040; const { jmpPin } = this; return jmpPin < gpio.length ? gpio[jmpPin].inputValue : false; } // !OSRE: output shift register not empty case 0b111: return this.outputShiftCount < this.pullThreshold; } this.pio.error(`jmpCondition with unsupported condition: ${condition}`); return false; } get inPins() { const { gpioValues } = this.rp2040; const { inBase } = this; return inBase ? (gpioValues << (32 - inBase)) | (gpioValues >>> inBase) : gpioValues; } inSourceValue(source) { switch (source) { // PINS case 0b000: return this.inPins; // X (scratch register X) case 0b001: return this.x; // Y (scratch register Y) case 0b010: return this.y; // NULL (all zeroes) case 0b011: return 0; // Reserved case 0b100: return 0; // Reserved for IN, STATUS for MOV case 0b101: return this.status; // ISR case 0b110: return this.inputShiftReg; // OSR case 0b111: return this.outputShiftReg; } this.pio.error(`inSourceValue with unsupported source: ${source}`); return 0; } writeOutValue(destination, value, bitCount) { switch (destination) { // PINS case 0b000: this.setOutPins(value); break; // X (scratch register X) case 0b001: this.x = value; break; // Y (scratch register Y) case 0b010: this.y = value; break; // NULL (discard data) case 0b011: break; // PINDIRS case 0b100: this.setOutPinDirs(value); break; // PC case 0b101: this.pc = value & 0x1f; this.updatePC = false; break; // ISR (also sets ISR shift counter to Bit count) case 0b110: this.inputShiftReg = value; this.inputShiftCount = bitCount; break; // EXEC (Execute OSR shift data as instruction) case 0b111: this.execOpcode = value; this.execValid = true; break; } } get pushThreshold() { const value = (this.shiftCtrl >> 20) & 0x1f; return value ? value : 32; } get pullThreshold() { const value = (this.shiftCtrl >> 25) & 0x1f; return value ? value : 32; } get sidesetCount() { return (this.pinCtrl >> 29) & 0x7; } get setCount() { return (this.pinCtrl >> 26) & 0x7; } get outCount() { return (this.pinCtrl >> 20) & 0x3f; } get inBase() { return (this.pinCtrl >> 15) & 0x1f; } get sidesetBase() { return (this.pinCtrl >> 10) & 0x1f; } get setBase() { return (this.pinCtrl >> 5) & 0x1f; } get outBase() { return (this.pinCtrl >> 0) & 0x1f; } get jmpPin() { return (this.execCtrl >> 24) & 0x1f; } get wrapTop() { return (this.execCtrl >> 12) & 0x1f; } get wrapBottom() { return (this.execCtrl >> 7) & 0x1f; } setOutPinDirs(value) { this.outPinDirection = value; this.pio.pinDirectionsChanged(value, this.outBase, this.outCount); } setOutPins(value) { this.outPinValues = value; this.pio.pinValuesChanged(value, this.outBase, this.outCount); } outInstruction(arg) { const bitCount = arg & 0x1f; const destination = arg >> 5; if (bitCount === 0) { this.writeOutValue(destination, this.outputShiftReg, 32); this.outputShiftCount = 32; } else { if (this.shiftCtrl & SHIFTCTRL_OUT_SHIFTDIR) { const value = this.outputShiftReg & ((1 << bitCount) - 1); this.outputShiftReg >>>= bitCount; this.writeOutValue(destination, value, bitCount); } else { const value = this.outputShiftReg >>> (32 - bitCount); this.outputShiftReg <<= bitCount; this.writeOutValue(destination, value, bitCount); } this.outputShiftCount += bitCount; if (this.outputShiftCount > 32) { this.outputShiftCount = 32; } } } executeInstruction(opcode) { const arg = opcode & 0xff; switch (opcode >>> 13) { /* JMP */ case 0b000: if (this.jmpCondition(arg >> 5)) { this.pc = arg & 0x1f; this.updatePC = false; } break; /* WAIT */ case 0b001: { const polarity = !!(arg & 0x80); const source = (arg >> 5) & 0x3; const index = arg & 0x1f; switch (source) { // GPIO: case 0b00: this.wait(WaitType.Pin, polarity, index); break; // PIN: case 0b01: this.wait(WaitType.Pin, polarity, (index + this.inBase) % 32); break; // IRQ: case 0b10: this.wait(WaitType.IRQ, polarity, irqIndex(index, this.index)); break; } break; } /* IN */ case 0b010: { const bitCount = arg & 0x1f; let sourceValue = this.inSourceValue(arg >> 5); if (bitCount == 0) { this.inputShiftReg = sourceValue; this.inputShiftCount = 32; } else { sourceValue &= (1 << bitCount) - 1; if (this.shiftCtrl & SHIFTCTRL_IN_SHIFTDIR) { this.inputShiftReg >>>= bitCount; this.inputShiftReg |= sourceValue << (32 - bitCount); } else { this.inputShiftReg <<= bitCount; this.inputShiftReg |= sourceValue; } this.inputShiftCount += bitCount; if (this.inputShiftCount > 32) { this.inputShiftCount = 32; } } if (this.shiftCtrl & SHIFTCTRL_AUTOPUSH && this.inputShiftCount >= this.pushThreshold) { if (!this.rxFIFO.full) { this.rxFIFO.push(this.inputShiftReg); this.updateDMARx(); this.pio.checkInterrupts(); } else { this.pio.rxStall |= FDEBUG_RXSTALL << this.index; this.pio.fdebug |= this.pio.rxStall; this.wait(WaitType.rxFIFO, false, this.inputShiftReg); } this.inputShiftCount = 0; this.inputShiftReg = 0; } break; } /* OUT */ case 0b011: { if (this.shiftCtrl & SHIFTCTRL_AUTOPULL && this.outputShiftCount >= this.pullThreshold) { this.outputShiftCount = 0; if (!this.txFIFO.empty) { this.outputShiftReg = this.txFIFO.pull(); this.updateDMATx(); this.pio.checkInterrupts(); } else { this.pio.txStall |= FDEBUG_TXSTALL << this.index; this.pio.fdebug |= this.pio.txStall; this.wait(WaitType.Out, false, arg); } } if (!this.waiting) { this.outInstruction(arg); } break; } /* PUSH/PULL */ case 0b100: { const block = !!(arg & (1 << 5)); const ifFullOrEmpty = !!(arg & (1 << 6)); if (arg & 0x1f) { // Unknown instruction break; } if (arg & 0x80) { // PULL if (ifFullOrEmpty && this.shiftCtrl & SHIFTCTRL_AUTOPULL && this.outputShiftCount < this.pullThreshold) { break; } if (!this.txFIFO.empty) { this.outputShiftReg = this.txFIFO.pull(); this.updateDMATx(); this.pio.checkInterrupts(); } else { this.pio.txStall |= FDEBUG_TXSTALL << this.index; this.pio.fdebug |= this.pio.txStall; if (block) { this.wait(WaitType.txFIFO, false, 0); } else { this.outputShiftReg = this.x; } } this.outputShiftCount = 0; } else { // PUSH if (ifFullOrEmpty && this.shiftCtrl & SHIFTCTRL_AUTOPUSH && this.inputShiftCount < this.pushThreshold) { break; } if (!this.rxFIFO.full) { this.rxFIFO.push(this.inputShiftReg); this.updateDMARx(); this.pio.checkInterrupts(); } else { this.pio.rxStall |= FDEBUG_RXSTALL << this.index; this.pio.fdebug |= this.pio.rxStall; if (block) { this.wait(WaitType.rxFIFO, false, this.inputShiftReg); } } this.inputShiftReg = 0; this.inputShiftCount = 0; } break; } /* MOV */ case 0b101: { const source = arg & 0x7; const op = (arg >> 3) & 0x3; const destination = (arg >> 5) & 0x7; const value = this.inSourceValue(source); const transformedValue = this.transformMovValue(value, op) >>> 0; this.setMovDestination(destination, transformedValue); break; } /* IRQ */ case 0b110: { if (arg & 0x80) { // Unknown instruction break; } const clear = !!(arg & 0x40); const wait = !!(arg & 0x20); const irq = irqIndex(arg & 0x1f, this.index); if (clear) { this.pio.irq &= ~(1 << irq); this.pio.irqUpdated(); } else { this.pio.irq |= 1 << irq; this.pio.irqUpdated(); if (wait) { this.wait(WaitType.IRQ, false, irq); } } break; } /* SET */ case 0b111: { const data = arg & 0x1f; const destination = arg >> 5; switch (destination) { case 0b000: this.setSetPins(data); break; case 0b001: this.x = data; break; case 0b010: this.y = data; break; case 0b100: this.setSetPinDirs(data); break; } break; } } this.cycles++; const { sidesetCount, execCtrl } = this; const delaySideset = (opcode >> 8) & 0x1f; const sideEn = !!(execCtrl & EXECCTRL_SIDE_EN); const delay = delaySideset & ((1 << (5 - sidesetCount)) - 1); if (sidesetCount && (!sideEn || delaySideset & 0x10)) { const sideset = delaySideset >> (5 - sidesetCount); this.setSideset(sideset, sideEn ? sidesetCount - 1 : sidesetCount); } if (this.execValid) { this.execValid = false; this.executeInstruction(this.execOpcode); } else if (this.waiting) { if (this.waitDelay < 0) { this.waitDelay = delay; } this.checkWait(); } else { this.cycles += delay; } } wait(type, polarity, index) { this.waiting = true; this.waitType = type; this.waitPolarity = polarity; this.waitIndex = index; this.waitDelay = -1; this.updatePC = false; } nextPC() { if (this.pc === this.wrapTop) { this.pc = this.wrapBottom; } else { this.pc = (this.pc + 1) & 0x1f; } } step() { if (this.waiting) { this.checkWait(); if (this.waiting) { return; } } this.updatePC = true; this.executeInstruction(this.pio.instructions[this.pc]); if (this.updatePC) { this.nextPC(); } } setSetPinDirs(value) { this.pio.pinDirectionsChanged(value, this.setBase, this.setCount); } setSetPins(value) { this.pio.pinValuesChanged(value, this.setBase, this.setCount); } setSideset(value, count) { if (this.execCtrl & EXECCTRL_SIDE_PINDIR) { this.pio.pinDirectionsChanged(value, this.sidesetBase, count); } else { this.pio.pinValuesChanged(value, this.sidesetBase, count); } } transformMovValue(value, op) { switch (op) { case 0b00: return value; case 0b01: return ~value; case 0b10: return bitReverse(value); case 0b11: default: return value; // reserved } } setMovDestination(destination, value) { switch (destination) { // PINS case 0b000: this.setOutPins(value); break; // X (scratch register X) case 0b001: this.x = value; break; // Y (scratch register Y) case 0b010: this.y = value; break; // reserved (discard data) case 0b011: break; // EXEC case 0b100: this.execOpcode = value; this.execValid = true; break; // PC case 0b101: this.pc = value & 0x1f; this.updatePC = false; break; // ISR (Input shift counter is reset to 0 by this operation, i.e. empty) case 0b110: this.inputShiftReg = value; this.inputShiftCount = 0; break; // OSR (Output shift counter is reset to 0 by this operation, i.e. full) case 0b111: this.outputShiftReg = value; this.outputShiftCount = 0; break; } } readUint32(offset) { switch (offset + SM0_CLKDIV) { case SM0_CLKDIV: return (this.clockDivInt << 16) | (this.clockDivFrac << 8); case SM0_EXECCTRL: return this.execCtrl; case SM0_SHIFTCTRL: return this.shiftCtrl; case SM0_ADDR: return this.pc; case SM0_INSTR: return this.pio.instructions[this.pc]; case SM0_PINCTRL: return this.pinCtrl; } this.pio.error(`Read from invalid state machine register: ${offset}`); return 0; } writeUint32(offset, value) { switch (offset + SM0_CLKDIV) { case SM0_CLKDIV: this.clockDivFrac = (value >>> 8) & 0xff; this.clockDivInt = value >>> 16; break; case SM0_EXECCTRL: this.execCtrl = ((value & 0x7fffffff) | (this.execCtrl & 0x80000000)) >>> 0; break; case SM0_SHIFTCTRL: this.shiftCtrl = value; break; case SM0_ADDR: /* read-only */ break; case SM0_INSTR: this.executeInstruction(value & 0xffff); if (this.waiting) { this.execCtrl |= EXECCTRL_EXEC_STALLED; } break; case SM0_PINCTRL: this.pinCtrl = value; break; default: this.pio.error(`Write to invalid state machine register: ${offset}`); } } get fifoStat() { const result = (this.txFIFO.empty ? FSTAT_TXEMPTY : 0) | (this.txFIFO.full ? FSTAT_TXFULL : 0) | (this.rxFIFO.empty ? FSTAT_RXEMPTY : 0) | (this.rxFIFO.full ? FSTAT_RXFULL : 0); return result << this.index; } restart() { this.cycles = 0; this.inputShiftCount = 0; this.outputShiftCount = 32; this.inputShiftReg = 0; this.waiting = false; // TODO any pin write left asserted due to OUT_STICKY. } clkDivRestart() { this.pio.warn('clkDivRestart not implemented'); } checkWait() { if (!this.waiting) { return; } switch (this.waitType) { case WaitType.IRQ: { const irqValue = !!(this.pio.irq & (1 << this.waitIndex)); if (irqValue === this.waitPolarity) { this.waiting = false; if (irqValue) { this.pio.irq &= ~(1 << this.waitIndex); } } break; } case WaitType.Pin: { if (this.waitIndex < this.rp2040.gpio.length && this.rp2040.gpio[this.waitIndex].inputValue === this.waitPolarity) { this.waiting = false; } break; } case WaitType.rxFIFO: { if (!this.rxFIFO.full) { this.rxFIFO.push(this.waitIndex); this.waiting = false; this.updateDMARx(); this.pio.checkInterrupts(); } break; } case WaitType.txFIFO: { if (!this.txFIFO.empty) { this.outputShiftReg = this.txFIFO.pull(); this.waiting = false; this.updateDMATx(); this.pio.checkInterrupts(); } break; } case WaitType.Out: { if (!this.txFIFO.empty) { this.outputShiftReg = this.txFIFO.pull(); this.outInstruction(this.waitIndex); this.waiting = false; this.updateDMATx(); this.pio.checkInterrupts(); } break; } } if (!this.waiting) { this.nextPC(); this.cycles += this.waitDelay; this.execCtrl &= ~EXECCTRL_EXEC_STALLED; } } } export class RPPIO extends BasePeripheral { constructor(rp2040, name, firstIrq, index) { super(rp2040, name); this.firstIrq = firstIrq; this.index = index; this.instructions = new Uint32Array(32); this.dreqRx = this.index ? dreqRx1 : dreqRx0; this.dreqTx = this.index ? dreqTx1 : dreqTx0; this.machines = [ new StateMachine(this.rp2040, this, 0), new StateMachine(this.rp2040, this, 1), new StateMachine(this.rp2040, this, 2), new StateMachine(this.rp2040, this, 3), ]; this.stopped = true; this.fdebug = 0; this.txStall = 0; this.rxStall = 0; this.inputSyncBypass = 0; this.irq = 0; this.pinValues = 0; this.pinDirections = 0; this.oldPinValues = 0; this.oldPinDirections = 0; this.runTimer = null; this.irq0IntEnable = 0; this.irq0IntForce = 0; this.irq1IntEnable = 0; this.irq1IntForce = 0; } get intRaw() { return (((this.irq & 0xf) << 8) | (!this.machines[3].txFIFO.full ? 0x80 : 0) | (!this.machines[2].txFIFO.full ? 0x40 : 0) | (!this.machines[1].txFIFO.full ? 0x20 : 0) | (!this.machines[0].txFIFO.full ? 0x10 : 0) | (!this.machines[3].rxFIFO.empty ? 0x08 : 0) | (!this.machines[2].rxFIFO.empty ? 0x04 : 0) | (!this.machines[1].rxFIFO.empty ? 0x02 : 0) | (!this.machines[0].rxFIFO.empty ? 0x01 : 0)); } get irq0IntStatus() { return (this.intRaw & this.irq0IntEnable) | this.irq0IntForce; } get irq1IntStatus() { return (this.intRaw & this.irq1IntEnable) | this.irq1IntForce; } readUint32(offset) { if (offset >= SM0_CLKDIV && offset <= SM0_PINCTRL) { return this.machines[0].readUint32(offset - SM0_CLKDIV); } if (offset >= SM1_CLKDIV && offset <= SM1_PINCTRL) { return this.machines[1].readUint32(offset - SM1_CLKDIV); } if (offset >= SM2_CLKDIV && offset <= SM2_PINCTRL) { return this.machines[2].readUint32(offset - SM2_CLKDIV); } if (offset >= SM3_CLKDIV && offset <= SM3_PINCTRL) { return this.machines[3].readUint32(offset - SM3_CLKDIV); } switch (offset) { case CTRL: return ((this.machines[0].enabled ? 1 << 0 : 0) | (this.machines[1].enabled ? 1 << 1 : 0) | (this.machines[2].enabled ? 1 << 2 : 0) | (this.machines[3].enabled ? 1 << 3 : 0)); case FSTAT: return (this.machines[0].fifoStat | this.machines[1].fifoStat | this.machines[2].fifoStat | this.machines[3].fifoStat); case FDEBUG: return this.fdebug; case FLEVEL: return ((this.machines[0].txFIFO.itemCount & 0xf) | ((this.machines[0].rxFIFO.itemCount & 0xf) << 4) | ((this.machines[1].txFIFO.itemCount & 0xf) << 8) | ((this.machines[1].rxFIFO.itemCount & 0xf) << 12) | ((this.machines[2].txFIFO.itemCount & 0xf) << 16) | ((this.machines[2].rxFIFO.itemCount & 0xf) << 20) | ((this.machines[3].txFIFO.itemCount & 0xf) << 24) | ((this.machines[3].rxFIFO.itemCount & 0xf) << 28)); case RXF0: return this.machines[0].readFIFO(); case RXF1: return this.machines[1].readFIFO(); case RXF2: return this.machines[2].readFIFO(); case RXF3: return this.machines[3].readFIFO(); case IRQ: return this.irq; case IRQ_FORCE: return 0; case INPUT_SYNC_BYPASS: return this.inputSyncBypass; case DBG_PADOUT: return this.pinValues; case DBG_PADOE: return this.pinDirections; case DBG_CFGINFO: return 0x200404; case INTR: return this.intRaw; case IRQ0_INTE: return this.irq0IntEnable; case IRQ0_INTF: return this.irq0IntForce; case IRQ0_INTS: return this.irq0IntStatus; case IRQ1_INTE: return this.irq1IntEnable; case IRQ1_INTF: return this.irq1IntForce; case IRQ1_INTS: return this.irq1IntStatus; } return super.readUint32(offset); } writeUint32(offset, value) { if (offset >= INSTR_MEM0 && offset <= INSTR_MEM31) { const index = (offset - INSTR_MEM0) >> 2; this.instructions[index] = value & 0xffff; return; } if (offset >= SM0_CLKDIV && offset <= SM0_PINCTRL) { this.machines[0].writeUint32(offset - SM0_CLKDIV, value); return; } if (offset >= SM1_CLKDIV && offset <= SM1_PINCTRL) { this.machines[1].writeUint32(offset - SM1_CLKDIV, value); return; } if (offset >= SM2_CLKDIV && offset <= SM2_PINCTRL) { this.machines[2].writeUint32(offset - SM2_CLKDIV, value); return; } if (offset >= SM3_CLKDIV && offset <= SM3_PINCTRL) { this.machines[3].writeUint32(offset - SM3_CLKDIV, value); return; } switch (offset) { case CTRL: { for (let index = 0; index < 4; index++) { this.machines[index].enabled = value & (1 << index) ? true : false; if (value & (1 << (4 + index))) { this.machines[index].restart(); } if (value & (1 << (8 + index))) { this.machines[index].clkDivRestart(); } } const shouldRun = value & 0xf; if (this.stopped && shouldRun) { this.stopped = false; this.run(); } if (!shouldRun) { this.stopped = true; } break; } case FDEBUG: this.fdebug &= ~this.rawWriteValue; this.fdebug |= this.txStall | this.rxStall; break; case TXF0: this.machines[0].writeFIFO(value); break; case TXF1: this.machines[1].writeFIFO(value); break; case TXF2: this.machines[2].writeFIFO(value); break; case TXF3: this.machines[3].writeFIFO(value); break; case IRQ: this.irq &= ~this.rawWriteValue; this.irqUpdated(); break; case INPUT_SYNC_BYPASS: this.inputSyncBypass = value; break; case IRQ_FORCE: this.irq |= value; this.irqUpdated(); break; case IRQ0_INTE: this.irq0IntEnable = value & 0xfff; this.checkInterrupts(); break; case IRQ0_INTF: this.irq0IntForce = value & 0xfff; this.checkInterrupts(); break; case IRQ1_INTE: this.irq1IntEnable = value & 0xfff; this.checkInterrupts(); break; case IRQ1_INTF: this.irq1IntForce = value & 0xfff; this.checkInterrupts(); break; default: super.writeUint32(offset, value); } } pinValuesChanged(value, firstPin, count) { // TODO: wrapping after pin 31 const mask = count > 31 ? 0xffffffff : ((1 << count) - 1) << firstPin; const newValue = ((this.pinValues & ~mask) | ((value << firstPin) & mask)) & 0x3fffffff; this.pinValues = newValue; } pinDirectionsChanged(value, firstPin, count) { // TODO: wrapping after pin 31 const mask = count > 31 ? 0xffffffff : ((1 << count) - 1) << firstPin; const newValue = ((this.pinDirections & ~mask) | ((value << firstPin) & mask)) & 0x3fffffff; this.pinDirections = newValue; } checkInterrupts() { const { firstIrq } = this; this.rp2040.setInterrupt(firstIrq, !!this.irq0IntStatus); this.rp2040.setInterrupt(firstIrq + 1, !!this.irq1IntStatus); } irqUpdated() { for (const machine of this.machines) { machine.checkWait(); } this.checkInterrupts(); } checkChangedPins() { const changedPins = (this.oldPinDirections ^ this.pinDirections) | (this.oldPinValues ^ this.pinValues); if (changedPins) { this.oldPinDirections = this.pinDirections; this.oldPinValues = this.pinValues; // Notify GPIO about the changed pins const { gpio } = this.rp2040; for (let gpioIndex = 0; gpioIndex < gpio.length; gpioIndex++) { if (changedPins & (1 << gpioIndex)) { gpio[gpioIndex].checkForUpdates(); } } } } step() { for (const machine of this.machines) { machine.step(); } this.checkChangedPins(); } run() { for (let i = 0; i < 1000 && !this.stopped; i++) { this.step(); } if (!this.stopped) { this.runTimer = setTimeout(() => this.run(), 0); } } stop() { for (const machine of this.machines) { machine.enabled = false; } this.stopped = true; if (this.runTimer) { clearTimeout(this.runTimer); this.runTimer = null; } } }