Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rp2-pio/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func DefaultStateMachineConfig() StateMachineConfig {

// DefaultStateMachineConfig is a facilitator method to produce a config for a PIO state machine given the program produced by the same assembler.
// It is equivalent to the code produced by pioasm in _pio.go files.
func (asm Assembler) DefaultStateMachineConfig(progOffset uint8, program []uint16) StateMachineConfig {
func (asm AssemblerV0) DefaultStateMachineConfig(progOffset uint8, program []uint16) StateMachineConfig {
cfg := DefaultStateMachineConfig()
cfg.SetWrap(progOffset, progOffset+uint8(len(program))-1)
if asm.SidesetBits > 0 {
Expand Down
280 changes: 151 additions & 129 deletions rp2-pio/instr.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,84 +8,190 @@ import (
// 5 bits of delay/sideset.
const delaySidesetbits = 0b1_1111 << 8

// Assembler provides a fluent API for programming PIO
// within the Go language. Here's an example Parallel API
// AssemblerV0 provides a fluent API for programming PIO
// within the Go language for PIO version 0 (RP2040).
//
// Here's an example Parallel API
// with a variable bus width:
//
// // Translation of below program:
// // 0: out pins, <npins> side 0
// // 1: nop side 1
// // 2: nop side 0
// var program = [3]uint16{
// asm.Out(pio.SrcDestPins, cfg.BusWidth).Side(0).Encode(),
// asm.Out(pio.SrcDestPins, numberOfPins).Side(0).Encode(),
// asm.Nop().Side(1).Encode(),
// asm.Nop().Side(0).Encode(),
// }
type Assembler struct {
type AssemblerV0 struct {
SidesetBits uint8
}

type instruction struct {
type instructionV0 struct {
instr uint16
asm Assembler
asm AssemblerV0
}

// EncodeInstr encodes an arbitrary PIO instruction with the given arguments.
func (asm AssemblerV0) EncodeInstr(instr InstrKind, delaySideset, arg1_3b, arg2_5b uint8) uint16 {
return uint16(instr&0b111)<<13 | uint16(delaySideset&0x1f)<<8 | uint16(arg1_3b&0b111)<<5 | uint16(arg2_5b&0x1f)
}

func (asm AssemblerV0) encodeIRQ(relative bool, irq uint8) uint8 {
return boolAsU8(relative)<<4 | irq&0b111
}

func (instr instructionV0) majorbits() uint16 {
return instr.instr & _INSTR_BITS_Msk
}

func (instr instruction) Encode() uint16 {
func (asm AssemblerV0) instrArgs(instr uint16, arg1 uint8, arg2 uint8) instructionV0 {
return asm.instr(instr | (uint16(arg1) << 5) | uint16(arg2&0x1f))
}

func (asm AssemblerV0) instrSrcDest(instr uint16, srcDest SrcDest, value uint8) instructionV0 {
return asm.instrArgs(instr, uint8(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.
func (instr instructionV0) Encode() uint16 {
return instr.instr
}

func (instr instruction) Side(value uint8) instruction {
// Side sets the sideset functionality of an instruction.
//
// value (see Section 3.3.2) is applied to the side_set pins at the start of the instruction. Note that
// the rules for a side-set value via side <side_set_value> are dependent on the .side_set (see
// pioasm_side_set) directive for the program. If no .side_set is specified then the side <side_set_value>
// is invalid, if an optional number of sideset pins is specified then side <side_set_value> may be
// present, and if a non-optional number of sideset pins is specified, then side <side_set_value> is
// required. The <side_set_value> must fit within the number of side-set bits specified in the .side_set
// directive.
func (instr instructionV0) Side(value uint8) instructionV0 {
instr.instr &^= instr.asm.sidesetbits()
instr.instr |= EncodeSideSet(instr.asm.SidesetBits, value) // TODO: panic on bit overflow.
instr.instr |= uint16(value) << (13 - instr.asm.SidesetBits) // TODO: panic on bit overflow.
return instr
}

func (instr instruction) Delay(cycles uint8) instruction {
// Delay sets the delay functionality of an instruction.
//
// cycles specifies amount of cycles to delay after the instruction completes. The delay_value is
// specified as a value (see Section 3.3.2), and in general is between 0 and 31 inclusive (a 5-bit
// value), however the number of bits is reduced when sideset is enabled via the .side_set (see
// pioasm_side_set) directive. If the <delay_value> is not present, then the instruction has no delay
func (instr instructionV0) Delay(cycles uint8) instructionV0 {
instr.instr &^= instr.asm.delaybits()
instr.instr |= EncodeDelay(cycles) // TODO: panic on bit overflow due to sideset bits excess.
instr.instr |= uint16(0b11111&cycles) << 8 // TODO: panic on bit overflow due to sideset bits excess.
return instr
}

func (asm Assembler) sidesetbits() uint16 {
func (asm AssemblerV0) sidesetbits() uint16 {
return delaySidesetbits & (uint16(0b111) << (13 - asm.SidesetBits))
}

func (asm Assembler) delaybits() uint16 {
func (asm AssemblerV0) delaybits() uint16 {
return delaySidesetbits & (0b11111 << (8 - asm.SidesetBits))
}

func (asm Assembler) instr(instr uint16) instruction {
return instruction{instr: instr, asm: asm}
func (asm AssemblerV0) instr(instr uint16) instructionV0 {
return instructionV0{instr: instr, asm: asm}
}

// Set program counter to Address if Condition is true, otherwise no operation.
// Delay cycles on a JMP always take effect, whether Condition is true or false, and they take place after Condition is
// evaluated and the program counter is updated.
func (asm AssemblerV0) Jmp(addr uint8, cond JmpCond) instructionV0 {
return asm.instrArgs(_INSTR_BITS_JMP, uint8(cond&0b111), addr)
}

// WaitPin stalls until Input pin selected by Index. This state machine’s input IO mapping is applied first, and then Index
// selects which of the mapped bits to wait on. In other words, the pin is selected by adding Index to the
// PINCTRL_IN_BASE configuration, modulo 32.
func (asm AssemblerV0) WaitPin(polarity bool, pin uint8) instructionV0 {
flag := boolAsU8(polarity) << 2
return asm.instrArgs(_INSTR_BITS_WAIT, 1|flag, pin)
}

// WaitIRQ stalls until PIO IRQ flag selected by irqindex. This IRQ behaves differently to other WAIT sources.
// - If Polarity is 1, the selected IRQ flag is cleared by the state machine upon the wait condition being met.
// - The flag index is decoded in the same way as the IRQ index field: if the MSB is set, the state machine ID (0…3) is
// added to the IRQ 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.
func (asm AssemblerV0) WaitIRQ(polarity, relative bool, irqindex uint8) instructionV0 {
flag := boolAsU8(polarity) << 2
return asm.instrArgs(_INSTR_BITS_WAIT, 2|flag, asm.encodeIRQ(relative, irqindex))
}

// WaitGPIO stalls until System GPIO input selected by Index. This is an absolute GPIO index, and is not affected by the state machine’s input IO mapping.
func (asm AssemblerV0) WaitGPIO(polarity bool, pin uint8) instructionV0 {
flag := boolAsU8(polarity) << 2
return asm.instrArgs(_INSTR_BITS_WAIT, 0|flag, pin)
}

// 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)
}

// 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)
}

// Push the contents of the ISR into the RX FIFO, as a single 32-bit word. Clear ISR to all-zeroes.
// - IfFull: If 1, do nothing unless the total input shift count has reached its threshold, SHIFTCTRL_PUSH_THRESH (the same
// as for autopush; see Section 3.5.4).
// - Block: If 1, stall execution if RX FIFO is full.
func (asm AssemblerV0) Push(ifFull, block bool) instructionV0 {
arg := boolAsU8(ifFull)<<1 | boolAsU8(block)
return asm.instrArgs(_INSTR_BITS_PUSH, arg, 0)
}

func (asm Assembler) Out(dest SrcDest, value uint8) instruction {
return asm.instr(EncodeOut(dest, value))
// Load a 32-bit word from the TX FIFO into the OSR.
// - ifEmpty: If 1, do nothing unless the total output shift count has reached its threshold, SHIFTCTRL_PULL_THRESH (the
// same as for autopull; see Section 3.5.4).
// - Block: If 1, stall if TX FIFO is empty. If 0, pulling from an empty FIFO copies scratch X to OSR.
func (asm AssemblerV0) Pull(ifEmpty, block bool) instructionV0 {
arg := boolAsU8(ifEmpty)<<1 | boolAsU8(block)
return asm.instrArgs(_INSTR_BITS_PULL, arg, 0)
}

func (asm Assembler) Nop() instruction {
return asm.instr(EncodeNOP())
// 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 Assembler) Jmp(addr uint8, cond JmpCond) instruction {
return asm.instr(EncodeJmp(addr, cond))
// 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 Assembler) In(src SrcDest, value uint8) instruction {
return asm.instr(EncodeIn(src, value))
// 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 Assembler) Pull(ifEmpty, block bool) instruction {
return asm.instr(EncodePull(ifEmpty, block))
// IRQSet sets the IRQ flag selected by irqIndex argument.
func (asm AssemblerV0) IRQSet(relative bool, irqIndex uint8) instructionV0 {
return asm.instrArgs(_INSTR_BITS_IRQ, 0, asm.encodeIRQ(relative, irqIndex))
}

func (asm Assembler) Push(ifFull, block bool) instruction {
return asm.instr(EncodePush(ifFull, block))
// IRQClear clears the IRQ flag selected by irqIndex argument. See [AssemblerV0.IRQSet].
func (asm AssemblerV0) IRQClear(relative bool, irqIndex uint8) instructionV0 {
return asm.instrArgs(_INSTR_BITS_IRQ, 2, asm.encodeIRQ(relative, irqIndex))
}

func (asm Assembler) Mov(dest, src SrcDest) instruction {
return asm.instr(EncodeMov(dest, src))
// 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)
}

// Nop is pseudo instruction that lasts a single PIO cycle. Usually used for timings.
func (asm AssemblerV0) Nop() instructionV0 { return asm.Mov(SrcDestY, SrcDestY) }

// InstrKind is a enum for the PIO instruction type. It only represents the kind of
// instruction. It cannot store the arguments.
type InstrKind uint8
Expand Down Expand Up @@ -156,111 +262,27 @@ const (
JmpOSRNotEmpty
)

// IRQIndexMode modifies the behaviour if the Index field, either modifying the index, or indexing IRQ flags from a different PIO block.
type IRQIndexMode uint8

const (
// 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
// 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
// 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
)

// EncodeInstr encodes an arbitrary PIO instruction with the given arguments.
func EncodeInstr(instr InstrKind, delaySideset, arg1_3b, arg2_5b uint8) uint16 {
return uint16(instr&0b111)<<13 | uint16(delaySideset&0x1f)<<8 | uint16(arg1_3b&0b111)<<5 | uint16(arg2_5b&0x1f)
}

func majorInstrBits(instr uint16) uint16 {
return instr & _INSTR_BITS_Msk
}

func encodeInstrAndArgs(instr uint16, arg1 uint8, arg2 uint8) uint16 {
return instr | (uint16(arg1) << 5) | uint16(arg2&0x1f)
}

func encodeInstrAndSrcDest(instr uint16, dest SrcDest, value uint8) uint16 {
return encodeInstrAndArgs(instr, uint8(dest)&7, value)
}

func EncodeDelay(cycles uint8) uint16 {
return uint16(0b11111&cycles) << 8
}

func EncodeSideSet(bitCount, value uint8) uint16 {
return uint16(value) << (13 - bitCount)
}

func EncodeSetSetOpt(bitCount uint8, value uint8) uint16 {
return 0x1000 | uint16(value)<<(12-bitCount)
}

func EncodeJmp(addr uint8, condition JmpCond) uint16 {
return encodeInstrAndArgs(_INSTR_BITS_JMP, uint8(condition&0b111), addr)
}

func encodeIRQ(relative bool, irq uint8) uint8 {
return boolAsU8(relative) << 4
}

func EncodeWaitGPIO(polarity bool, pin uint8) uint16 {
flag := boolAsU8(polarity) << 2
return encodeInstrAndArgs(_INSTR_BITS_WAIT, 0|flag, pin)
}

func EncodeWaitPin(polarity bool, pin uint8) uint16 {
flag := boolAsU8(polarity) << 2

return encodeInstrAndArgs(_INSTR_BITS_WAIT, 1|flag, pin)
}

func EncodeWaitIRQ(polarity bool, relative bool, irq uint8) uint16 {
flag := boolAsU8(polarity) << 2

return encodeInstrAndArgs(_INSTR_BITS_WAIT, 2|flag, encodeIRQ(relative, irq))
}

func EncodeIn(src SrcDest, value uint8) uint16 {
return encodeInstrAndSrcDest(_INSTR_BITS_IN, src, value)
}

func EncodeOut(dest SrcDest, value uint8) uint16 {
return encodeInstrAndSrcDest(_INSTR_BITS_OUT, dest, value)
}

func EncodePush(ifFull bool, block bool) uint16 {
arg := boolAsU8(ifFull)<<1 | boolAsU8(block)
return encodeInstrAndArgs(_INSTR_BITS_PUSH, arg, 0)
}

func EncodePull(ifEmpty bool, block bool) uint16 {
arg := boolAsU8(ifEmpty)<<1 | boolAsU8(block)
return encodeInstrAndArgs(_INSTR_BITS_PULL, arg, 0)
}

func EncodeMov(dest SrcDest, src SrcDest) uint16 {
return encodeInstrAndSrcDest(_INSTR_BITS_MOV, dest, uint8(src)&7)
}

func EncodeMovNot(dest SrcDest, src SrcDest) uint16 {
return encodeInstrAndSrcDest(_INSTR_BITS_MOV, dest, (1<<3)|(uint8(src)&7))
}

func EncodeMovReverse(dest SrcDest, src SrcDest) uint16 {
return encodeInstrAndSrcDest(_INSTR_BITS_MOV, dest, (2<<3)|(uint8(src)&7))
}

func EncodeIRQSet(relative bool, irq uint8) uint16 {
return encodeInstrAndArgs(_INSTR_BITS_IRQ, 0, encodeIRQ(relative, irq))
}

func EncodeIRQClear(relative bool, irq uint8) uint16 {
return encodeInstrAndArgs(_INSTR_BITS_IRQ, 2, encodeIRQ(relative, irq))
}

func EncodeSet(dest SrcDest, value uint8) uint16 {
return encodeInstrAndSrcDest(_INSTR_BITS_SET, dest, value)
}

func EncodeNOP() uint16 {
return EncodeMov(SrcDestY, SrcDestY)
}

// encodeTRAP encodes a trap instruction. It must be stored at the argument offset.
func encodeTRAP(trapOffset uint8) uint16 {
return EncodeJmp(trapOffset, JmpAlways)
}

// ClkDivFromPeriod calculates the CLKDIV register values
// to reach a given StateMachine cycle period given the RP2040 CPU frequency.
// period is expected to be in nanoseconds. freq is expected to be in Hz.
Expand Down
2 changes: 1 addition & 1 deletion rp2-pio/pio.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func (pio *PIO) ClearProgramSection(offset, len uint8) {
for i := offset; i < offset+len; i++ {
// We encode trap instructions to prevent undefined behaviour if
// a state machine is currently using the program memory.
hw.INSTR_MEM[i].Set(uint32(encodeTRAP(offset)))
hw.INSTR_MEM[i].Set(uint32(AssemblerV0{}.Jmp(offset, JmpAlways).Encode()))
}
pio.usedSpaceMask &^= uint32((1<<len)-1) << offset
}
Expand Down
3 changes: 1 addition & 2 deletions rp2-pio/piolib/i2s.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ func NewI2S(sm pio.StateMachine, data, clockAndNext machine.Pin) (*I2S, error) {
pinMask := uint32(1<<data) | uint32(0b11<<clockAndNext)
sm.SetPindirsMasked(pinMask, pinMask)
sm.SetPinsMasked(0, pinMask)

sm.Exec(pio.EncodeJmp(offset+i2soffset_entry_point, pio.JmpAlways))
sm.Jmp(offset+i2soffset_entry_point, pio.JmpAlways)

i2s := &I2S{
sm: sm,
Expand Down
2 changes: 1 addition & 1 deletion rp2-pio/piolib/parallel.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type ParallelConfig struct {
func NewParallel(sm pio.StateMachine, cfg ParallelConfig) (*Parallel, error) {
const sideSetBitCount = 1
const programOrigin = -1
asm := pio.Assembler{
asm := pio.AssemblerV0{
SidesetBits: sideSetBitCount,
}
var program = [3]uint16{
Expand Down
2 changes: 1 addition & 1 deletion rp2-pio/piolib/pulsar.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (p *Pulsar) Stop() {
p.sm.ClearFIFOs()
p.sm.Restart()
p.sm.ClkDivRestart()
p.sm.Exec(pio.EncodeJmp(p.offsetPlusOne-1, pio.JmpAlways))
p.sm.Jmp(p.offsetPlusOne-1, pio.JmpAlways)
p.sm.SetEnabled(true)
}

Expand Down
Loading