From 81c3c82b2160630b7c3ece5405866b63052324ce Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Sat, 8 Nov 2025 19:39:13 -0300 Subject: [PATCH 1/2] add AssemblerV1 and prepare API for V0/V1 usage --- rp2-pio/instr.go | 124 +++++++++++++++++++++++++------------ rp2-pio/instrv1.go | 110 ++++++++++++++++++++++++++++++++ rp2-pio/piolib/parallel.go | 2 +- rp2-pio/piolib/spi3w.go | 2 +- rp2-pio/statemachine.go | 18 +++--- 5 files changed, 207 insertions(+), 49 deletions(-) create mode 100644 rp2-pio/instrv1.go diff --git a/rp2-pio/instr.go b/rp2-pio/instr.go index 6d4bd23..da57168 100644 --- a/rp2-pio/instr.go +++ b/rp2-pio/instr.go @@ -45,12 +45,12 @@ func (instr instructionV0) majorbits() uint16 { return instr.instr & _INSTR_BITS_Msk } -func (asm AssemblerV0) instrArgs(instr uint16, arg1 uint8, arg2 uint8) instructionV0 { - return asm.instr(instr | (uint16(arg1) << 5) | uint16(arg2&0x1f)) +func (asm AssemblerV0) instrArgs(instr uint16, arg1_5b uint8, arg2 uint8) instructionV0 { + return asm.instr(instr | (uint16(arg1_5b) << 5) | uint16(arg2&0x1f)) } -func (asm AssemblerV0) instrSrcDest(instr uint16, srcDest SrcDest, value uint8) instructionV0 { - return asm.instrArgs(instr, uint8(srcDest)&7, value) +func (asm AssemblerV0) instrSrcDest(instr uint16, srcDest uint8, value uint8) instructionV0 { + return asm.instrArgs(instr, srcDest&7, value) } // Encode returns the finalized assembled instruction ready to be stored to the PIO program memory and used by a PIO state machine. @@ -131,14 +131,14 @@ func (asm AssemblerV0) WaitGPIO(polarity bool, pin uint8) instructionV0 { // Shift Bit count bits from Source into the Input Shift Register (ISR). Shift direction is configured for each state machine by // SHIFTCTRL_IN_SHIFTDIR. Additionally, increase the input shift count by Bit count, saturating at 32. -func (asm AssemblerV0) In(src SrcDest, value uint8) instructionV0 { - return asm.instrSrcDest(_INSTR_BITS_IN, src, value) +func (asm AssemblerV0) In(src InSrc, value uint8) instructionV0 { + return asm.instrSrcDest(_INSTR_BITS_IN, uint8(src), value) } // Shift Bit count bits out of the Output Shift Register (OSR), and write those bits to Destination. Additionally, increase the // output shift count by Bit count, saturating at 32. -func (asm AssemblerV0) Out(dest SrcDest, value uint8) instructionV0 { - return asm.instrSrcDest(_INSTR_BITS_OUT, dest, value) +func (asm AssemblerV0) Out(dest OutDest, value uint8) instructionV0 { + return asm.instrSrcDest(_INSTR_BITS_OUT, uint8(dest), value) } // Push the contents of the ISR into the RX FIFO, as a single 32-bit word. Clear ISR to all-zeroes. @@ -160,18 +160,18 @@ func (asm AssemblerV0) Pull(ifEmpty, block bool) instructionV0 { } // Mov copies data from src to dest. -func (asm AssemblerV0) Mov(dest, src SrcDest) instructionV0 { - return asm.instrSrcDest(_INSTR_BITS_MOV, dest, uint8(src)&7) +func (asm AssemblerV0) Mov(dest MovDest, src MovSrc) instructionV0 { + return asm.instrSrcDest(_INSTR_BITS_MOV, uint8(dest), uint8(src)&7) } // MovInvertBits does a Mov but inverting the resulting bits. -func (asm AssemblerV0) MovInvert(dest, src SrcDest) instructionV0 { - return asm.instrSrcDest(_INSTR_BITS_MOV, dest, (1<<3)|uint8(src&7)) +func (asm AssemblerV0) MovInvert(dest MovDest, src MovSrc) instructionV0 { + return asm.instrSrcDest(_INSTR_BITS_MOV, uint8(dest), (1<<3)|uint8(src&7)) } // MovReverse does a Mov but reversing the order of the resulting bits. -func (asm AssemblerV0) MovReverse(dest, src SrcDest) instructionV0 { - return asm.instrSrcDest(_INSTR_BITS_MOV, dest, (2<<3)|uint8(src&7)) +func (asm AssemblerV0) MovReverse(dest MovDest, src MovSrc) instructionV0 { + return asm.instrSrcDest(_INSTR_BITS_MOV, uint8(dest), (2<<3)|uint8(src&7)) } // IRQSet sets the IRQ flag selected by irqIndex argument. @@ -185,27 +185,27 @@ func (asm AssemblerV0) IRQClear(relative bool, irqIndex uint8) instructionV0 { } // Set writes an immediate value Data in range 0..31 to Destination. -func (asm AssemblerV0) Set(dest SrcDest, value uint8) instructionV0 { - return asm.instrSrcDest(_INSTR_BITS_SET, dest, value) +func (asm AssemblerV0) Set(dest SetDest, value uint8) instructionV0 { + return asm.instrSrcDest(_INSTR_BITS_SET, uint8(dest), value) } // Nop is pseudo instruction that lasts a single PIO cycle. Usually used for timings. -func (asm AssemblerV0) Nop() instructionV0 { return asm.Mov(SrcDestY, SrcDestY) } +func (asm AssemblerV0) Nop() instructionV0 { return asm.Mov(MovDestY, MovSrcY) } // InstrKind is a enum for the PIO instruction type. It only represents the kind of // instruction. It cannot store the arguments. type InstrKind uint8 const ( - InstrJMP InstrKind = iota - InstrWAIT - InstrIN - InstrOUT - InstrPUSH - InstrPULL - InstrMOV - InstrIRQ - InstrSET + InstrJMP InstrKind = iota // jmp + InstrWAIT // wait + InstrIN // in + InstrOUT // out + InstrPUSH // push + InstrPULL // pull + InstrMOV // mov + InstrIRQ // irq + InstrSET // set ) // This file contains the primitives for creating instructions dynamically @@ -224,20 +224,68 @@ const ( _INSTR_BITS_Msk = 0xe000 ) -type SrcDest uint8 +// OutDest encodes Out instruction data destination. +type OutDest uint8 const ( - SrcDestPins SrcDest = 0 - SrcDestX SrcDest = 1 - SrcDestY SrcDest = 2 - SrcDestNull SrcDest = 3 - SrcDestPinDirs SrcDest = 4 - SrcDestExecMov SrcDest = 4 - SrcDestStatus SrcDest = 5 - SrcDestPC SrcDest = 5 - SrcDestISR SrcDest = 6 - SrcDestOSR SrcDest = 7 - SrcExecOut SrcDest = 7 + OutDestPins OutDest = 0b000 // pins + OutDestX OutDest = 0b001 // x + OutDestY OutDest = 0b010 // y + OutDestNull OutDest = 0b011 // null + OutDestPindirs OutDest = 0b100 // pindirs + OutDestPC OutDest = 0b101 // pc + OutDestISR OutDest = 0b110 // isr + OutDestExec OutDest = 0b111 // exec +) + +// InSrc encodes In instruction data source. +type InSrc uint8 + +const ( + InSrcPins InSrc = 0b000 // pins + InSrcX InSrc = 0b001 // x + InSrcY InSrc = 0b010 // y + InSrcNull InSrc = 0b011 // null + InSrcISR InSrc = 0b110 // isr + InSrcOSR InSrc = 0b111 // osr +) + +// SetDest encodes Set instruction data destination. +type SetDest uint8 + +const ( + SetDestPins SetDest = 0b000 // pins + SetDestX SetDest = 0b001 // x + SetDestY SetDest = 0b010 // y + SetDestPindirs SetDest = 0b100 // pindirs +) + +// MovSrc encodes Mov instruction data source. +type MovSrc uint8 + +const ( + MovSrcPins MovSrc = 0b000 // pins + MovSrcX MovSrc = 0b001 // x + MovSrcY MovSrc = 0b010 // y + MovSrcNull MovSrc = 0b011 // null + MovSrcStatus MovSrc = 0b101 // status + MovSrcISR MovSrc = 0b110 // isr + MovSrcOSR MovSrc = 0b111 // osr +) + +// MovDest encodes Mov instruction data destination. +type MovDest uint8 + +const ( + MovDestPins MovDest = 0b000 // pins + MovDestX MovDest = 0b001 // x + MovDestY MovDest = 0b010 // y + // MovDestPindirs was introduced in PIO version 1. Not available on RP2040 + MovDestPindirs MovDest = 0b011 // pindirs + MovDestExec MovDest = 0b100 // exec + MovDestPC MovDest = 0b101 // pc + MovDestISR MovDest = 0b110 // isr + MovDestOSR MovDest = 0b111 // osr ) type JmpCond uint8 diff --git a/rp2-pio/instrv1.go b/rp2-pio/instrv1.go new file mode 100644 index 0000000..cc42cd5 --- /dev/null +++ b/rp2-pio/instrv1.go @@ -0,0 +1,110 @@ +package pio + +// AssemblerV1 provides a fluent API for programming PIO +// within the Go language for PIO version 1 (RP2350). +// Most logic is shared with [AssemblerV0]. +type AssemblerV1 struct { + SidesetBits uint8 +} + +func (asm AssemblerV1) v0() AssemblerV0 { + return AssemblerV0{ + SidesetBits: asm.SidesetBits, + } +} + +// Jmp instruction unchanged from [AssemblerV0.Jmp]. +func (asm AssemblerV1) Jmp(addr uint8, cond JmpCond) instructionV0 { return asm.v0().Jmp(addr, cond) } + +// WaitGPIO instruction unchanged from [AssemblerV0.WaitGPIO]. +func (asm AssemblerV1) WaitGPIO(polarity bool, pin uint8) instructionV0 { + return asm.v0().WaitGPIO(polarity, pin) +} + +// WaitIRQ instruction unchanged from [AssemblerV0.WaitIRQ]. +func (asm AssemblerV1) WaitIRQ(polarity bool, relative bool, irqindex uint8) instructionV0 { + return asm.v0().WaitIRQ(polarity, relative, irqindex) +} + +// WaitPin instruction unchanged from [AssemblerV0.WaitPin]. +func (asm AssemblerV1) WaitPin(polarity bool, pin uint8) instructionV0 { + return asm.v0().WaitPin(polarity, pin) +} + +// WaitJmpPin waits on the pin indexed by the PINCTRL_JMP_PIN configuration, plus an Index in the range 0-3, all +// modulo 32. Other values of Index are reserved. +func (asm AssemblerV1) WaitJmpPin(polarity bool, pin uint8) instructionV0 { + flag := boolAsU8(polarity) << 2 + return asm.v0().instrArgs(_INSTR_BITS_WAIT, 0b11|flag, pin) +} + +// In instruction unchanged from [AssemblerV0.In]. +func (asm AssemblerV1) In(src InSrc, value uint8) instructionV0 { + return asm.v0().In(src, value) +} + +// Out instruction unchanged from [AssemblerV0.Out]. +func (asm AssemblerV1) Out(dest OutDest, value uint8) instructionV0 { + return asm.v0().Out(dest, value) +} + +// Push instruction unchanged from [AssemblerV0.Push]. +func (asm AssemblerV1) Push(ifFull bool, block bool) instructionV0 { + return asm.v0().Push(ifFull, block) +} + +// Pull instruction unchanged from [AssemblerV0.Pull]. +func (asm AssemblerV1) Pull(ifEmpty bool, block bool) instructionV0 { + return asm.v0().Pull(ifEmpty, block) +} + +// Mov in version 1 of PIO works identically to version 0 but adding following new functionality. +// - Added Pindirs as destination for MOV: This allows changing the direction of all OUT-mapped pins with a single instruction: MOV PINDIRS, NULL or MOV +// PINDIRS, ~NULL +// - Adds SM IRQ flags as a source for MOV x, STATUS. This allows branching (as well as blocking) on the assertion of SM IRQ flags. +// - Adds the FJOIN_RX_GET FIFO mode. A new MOV encoding reads any of the four RX FIFO storage registers into OSR. +// - New FJOIN_RX_PUT FIFO mode. A new MOV encoding writes the ISR into any of the four RX FIFO storage registers. +func (asm AssemblerV1) Mov(dest MovDest, src MovSrc) instructionV0 { + return asm.v0().Mov(dest, src) +} + +// MovInvert is [AssemblerV0.MovInvert] unchanged but with available [AssemblerV1.Mov] functionality. +func (asm AssemblerV1) MovInvert(dest MovDest, src MovSrc) instructionV0 { + return asm.v0().MovInvert(dest, src) +} + +// MovReverse is [AssemblerV0.MovReverse] unchanged but with available [AssemblerV1.Mov] functionality. +func (asm AssemblerV1) MovReverse(dest MovDest, src MovSrc) instructionV0 { + return asm.v0().MovReverse(dest, src) +} + +// MovOSRFromRx reads the selected RX FIFO entry into the OSR. The PIO state machine can read the FIFO entries in any order, indexed +// either by the Y register, or an immediate Index in the instruction. Requires the SHIFTCTRL_FJOIN_RX_GET configuration field +// to be set, otherwise its operation is undefined. +// - If IdxI (index by immediate) is set, the RX FIFO’s registers are indexed by the two least-significant bits of the Index +// operand. Otherwise, they are indexed by the two least-significant bits of the Y register. When IdxI is clear, all non-zero +// values of Index are reserved encodings, and their operation is undefined. +func (asm AssemblerV1) MovOSRFromRx(idxByImmediate bool, RxFifoIndex uint8) instructionV0 { + instr := _INSTR_BITS_MOV | (0b1001 << 4) | (uint16(boolAsU8(idxByImmediate) << 3)) | uint16(RxFifoIndex)&0b111 + return asm.v0().instr(instr) +} + +// MovISRToRx writes the ISR to a selected RX FIFO entry. The state machine can write the RX FIFO entries in any order, indexed either +// by the Y register, or an immediate Index in the instruction. Requires the SHIFTCTRL_FJOIN_RX_PUT configuration field to be +// set, otherwise its operation is undefined. The FIFO configuration can be specified for the program via the .fifo directive +// (see pioasm_fifo). +// - If idxByImmediate (index by immediate) is set, the RX FIFO’s registers are indexed by the two least-significant bits of the Index +// operand. Otherwise, they are indexed by the two least-significant bits of the Y register. When IdxI is clear, all non-zero +// values of Index are reserved encodings, and their operation is undefined. +func (asm AssemblerV1) MovISRToRx(idxByImmediate bool, RxFifoIndex uint8) instructionV0 { + instr := _INSTR_BITS_MOV | (0b1000 << 4) | (uint16(boolAsU8(idxByImmediate) << 3)) | uint16(RxFifoIndex)&0b111 + return asm.v0().instr(instr) +} + +// Set instruction unchanged from [AssemblerV0.Set]. +func (asm AssemblerV1) Set(dest SetDest, value uint8) instructionV0 { + return asm.v0().Set(dest, value) +} + +// Nop instruction unchanged from [AssemblerV0.Nop]. +func (asm AssemblerV1) Nop() instructionV0 { return asm.v0().Nop() } diff --git a/rp2-pio/piolib/parallel.go b/rp2-pio/piolib/parallel.go index 0956d6a..d9be301 100644 --- a/rp2-pio/piolib/parallel.go +++ b/rp2-pio/piolib/parallel.go @@ -38,7 +38,7 @@ func NewParallel(sm pio.StateMachine, cfg ParallelConfig) (*Parallel, error) { SidesetBits: sideSetBitCount, } var program = [3]uint16{ - asm.Out(pio.SrcDestPins, cfg.BusWidth).Side(0).Encode(), // 0: out pins, side 0 + asm.Out(pio.OutDestPins, cfg.BusWidth).Side(0).Encode(), // 0: out pins, side 0 asm.Nop().Side(1).Encode(), // 1: nop side 1 asm.Nop().Side(0).Encode(), // 2: nop side 0 } diff --git a/rp2-pio/piolib/spi3w.go b/rp2-pio/piolib/spi3w.go index 2548d20..be09e9c 100644 --- a/rp2-pio/piolib/spi3w.go +++ b/rp2-pio/piolib/spi3w.go @@ -258,7 +258,7 @@ func (spi *SPI3w) prepTx(readbits, writebits uint32) { spi.sm.SetX(writebits) spi.sm.SetY(readbits) var asm pio.AssemblerV0 - spi.sm.Exec(asm.Set(pio.SrcDestPinDirs, 1).Encode()) // Set Pindir out. + spi.sm.Exec(asm.Set(pio.SetDestPindirs, 1).Encode()) // Set Pindir out. spi.sm.Jmp(spi.offset+spi3wWrapTarget, pio.JmpAlways) spi.sm.SetEnabled(true) diff --git a/rp2-pio/statemachine.go b/rp2-pio/statemachine.go index f49ad6a..8122e50 100644 --- a/rp2-pio/statemachine.go +++ b/rp2-pio/statemachine.go @@ -254,17 +254,17 @@ func makePinmask(base, count, bit uint8) (valMask, pinMask uint32) { // This method repeatedly reconfigures the state machines pins. // Use this method as convenience to set initial pin states BEFORE running state machine. func (sm StateMachine) SetPinsMasked(valueMask, pinMask uint32) { - sm.setPinExec(SrcDestPins, valueMask, pinMask) + sm.setPinExec(SetDestPins, valueMask, pinMask) } // SetPindirsMasked sets the pin directions (input/output) on multiple pins for // the PIO instance. This method repeatedly reconfigures the state machines pins. // Use this method as convenience to set initial pin states BEFORE running state machine. func (sm StateMachine) SetPindirsMasked(dirMask, pinMask uint32) { - sm.setPinExec(SrcDestPinDirs, dirMask, pinMask) + sm.setPinExec(SetDestPindirs, dirMask, pinMask) } -func (sm StateMachine) setPinExec(dest SrcDest, valueMask, pinMask uint32) { +func (sm StateMachine) setPinExec(dest SetDest, valueMask, pinMask uint32) { hw := sm.HW() pinctrlSaved := hw.PINCTRL.Get() execctrlSaved := hw.EXECCTRL.Get() @@ -318,27 +318,27 @@ func (sm StateMachine) SetWrap(target, wrap uint8) { // SetX sets the X register of a state machine. The state machine should be halted beforehand. func (sm StateMachine) SetX(value uint32) { - sm.setDst(SrcDestX, value) + sm.setDst(OutDestX, value) } // SetY sets the Y register of a state machine. The state machine should be halted beforehand. func (sm StateMachine) SetY(value uint32) { - sm.setDst(SrcDestY, value) + sm.setDst(OutDestY, value) } // GetX gets the X register of a state machine. The state machine should be halted beforehand. // Calling GetX during execution may desync the state machine. func (sm StateMachine) GetX() uint32 { - return sm.getDst(SrcDestX) + return sm.getDst(InSrcX) } // GetY gets the Y register of a state machine. The state machine should be halted beforehand. // Calling GetY during execution may desync the state machine. func (sm StateMachine) GetY() uint32 { - return sm.getDst(SrcDestY) + return sm.getDst(InSrcY) } -func (sm StateMachine) setDst(dst SrcDest, value uint32) { +func (sm StateMachine) setDst(dst OutDest, value uint32) { const bitCount = 32 instr := assm.Out(dst, bitCount).Encode() @@ -346,7 +346,7 @@ func (sm StateMachine) setDst(dst SrcDest, value uint32) { sm.Exec(instr) } -func (sm StateMachine) getDst(dst SrcDest) uint32 { +func (sm StateMachine) getDst(dst InSrc) uint32 { const bitCount = 32 instr := assm.In(dst, bitCount).Encode() sm.Exec(instr) From 6917f980a84b2c5ea213a782499fd4a0e0c2af9a Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Sat, 8 Nov 2025 20:26:11 -0300 Subject: [PATCH 2/2] add IRQ instructions to AssemblerV1 --- rp2-pio/instr.go | 8 +++++--- rp2-pio/instrv1.go | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/rp2-pio/instr.go b/rp2-pio/instr.go index da57168..74dc5be 100644 --- a/rp2-pio/instr.go +++ b/rp2-pio/instr.go @@ -314,16 +314,18 @@ const ( type IRQIndexMode uint8 const ( + // Direct: the three LSBs are used directly to index the IRQ flags in this PIO block. + IRQDirect IRQIndexMode = 0b00 // direct // Prev: the instruction references an IRQ flag from the next-lower-numbered PIO in the system, wrapping to // the highest-numbered PIO if this is PIO0. Available on RP2350 only. - IRQPrev IRQIndexMode = 0b01 + IRQPrev IRQIndexMode = 0b01 // prev // Rel: the state machine ID (0…3) is added to the IRQ flag index, by way of modulo-4 addition on the two // LSBs. For example, state machine 2 with a flag value of '0x11' will wait on flag 3, and a flag value of '0x13' will // wait on flag 1. This allows multiple state machines running the same program to synchronise with each other. - IRQRel IRQIndexMode = 0b10 + IRQRel IRQIndexMode = 0b10 // rel // Next: the instruction references an IRQ flag from the next-higher-numbered PIO in the system, wrapping to // PIO0 if this is the highest-numbered PIO. Available on RP2350 only. - IRQNext IRQIndexMode = 0b11 + IRQNext IRQIndexMode = 0b11 // next ) // EncodeInstr encodes an arbitrary PIO instruction with the given arguments. diff --git a/rp2-pio/instrv1.go b/rp2-pio/instrv1.go index cc42cd5..d533053 100644 --- a/rp2-pio/instrv1.go +++ b/rp2-pio/instrv1.go @@ -81,7 +81,7 @@ func (asm AssemblerV1) MovReverse(dest MovDest, src MovSrc) instructionV0 { // MovOSRFromRx reads the selected RX FIFO entry into the OSR. The PIO state machine can read the FIFO entries in any order, indexed // either by the Y register, or an immediate Index in the instruction. Requires the SHIFTCTRL_FJOIN_RX_GET configuration field // to be set, otherwise its operation is undefined. -// - If IdxI (index by immediate) is set, the RX FIFO’s registers are indexed by the two least-significant bits of the Index +// - If idxByImmediate (index by immediate) is set, the RX FIFO’s registers are indexed by the two least-significant bits of the Index // operand. Otherwise, they are indexed by the two least-significant bits of the Y register. When IdxI is clear, all non-zero // values of Index are reserved encodings, and their operation is undefined. func (asm AssemblerV1) MovOSRFromRx(idxByImmediate bool, RxFifoIndex uint8) instructionV0 { @@ -106,5 +106,26 @@ func (asm AssemblerV1) Set(dest SetDest, value uint8) instructionV0 { return asm.v0().Set(dest, value) } +// IRQSet sets the IRQ flag selected by irqIndex. +func (asm AssemblerV1) IRQSet(irqIndex uint8, idxMode IRQIndexMode) instructionV0 { + return asm.irq(false, false, irqIndex, idxMode) +} + +// IRQClear clears the IRQ flag selected by irqIndex argument. See [AssemblerV1.IRQSet]. +func (asm AssemblerV1) IRQClear(irqIndex uint8, idxMode IRQIndexMode) instructionV0 { + return asm.irq(true, false, irqIndex, idxMode) +} + +// IRQWait sets the IRQ flag selected by irqIndex and waits for it to be cleared before proceeding. +// If Wait is set, Delay cycles do not begin until after the wait period elapses. +func (asm AssemblerV1) IRQWait(irqIndex uint8, idxMode IRQIndexMode) instructionV0 { + return asm.irq(false, true, irqIndex, idxMode) +} + +func (asm AssemblerV1) irq(clear, wait bool, irqIndex uint8, idxMode IRQIndexMode) instructionV0 { + instr := _INSTR_BITS_IRQ | uint16(boolAsU8(clear))<<6 | uint16(boolAsU8(wait))<<6 | uint16(idxMode)<<3 | uint16(irqIndex&0b111) + return asm.v0().instr(instr) +} + // Nop instruction unchanged from [AssemblerV0.Nop]. func (asm AssemblerV1) Nop() instructionV0 { return asm.v0().Nop() }