- USB CDC 경로 복원: #define Serial Serial1 우회 제거 - rp2040js/avr8js/codemirror/xterm/monaco-editor vendor화 - /vendor 정적 라우팅 추가 (Express 5 catch-all 우회) - 부팅 완료 타임아웃 감지 (Serial 출력 없어도 8초 후 RAF 전환) - 리셋 시 Simulator 재생성으로 주변 장치 상태 초기화 - 시리얼 패널의 내부 메시지를 console.log로 이동
1091 lines
37 KiB
JavaScript
1091 lines
37 KiB
JavaScript
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;
|
|
}
|
|
}
|
|
}
|