diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 50f730f..4e00ec2 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -1,2 +1,5 @@ steps: - - command: zig build test + - name: Build Examples + command: zig build + - name: Test + command: zig build test diff --git a/build.zig b/build.zig index 21a91ff..f9d3729 100644 --- a/build.zig +++ b/build.zig @@ -39,8 +39,8 @@ pub fn addPiPicoExecutable( // project requires multiple HALs, it accepts microzig as a param pub fn build(b: *Builder) !void { const optimize = b.standardOptimizeOption(.{}); - //var examples = Examples.init(b, optimize); - //examples.install(); + var examples = Examples.init(b, optimize); + examples.install(b); const pio_tests = b.addTest(.{ .root_source_file = .{ @@ -51,7 +51,7 @@ pub fn build(b: *Builder) !void { pio_tests.addIncludePath("src/hal/pio/assembler"); const test_step = b.step("test", "run unit tests"); - test_step.dependOn(&pio_tests.run().step); + test_step.dependOn(&b.addRunArtifact(pio_tests).step); } fn root() []const u8 { @@ -85,8 +85,9 @@ pub const Examples = struct { return ret; } - pub fn install(examples: *Examples) void { - inline for (@typeInfo(Examples).Struct.fields) |field| - @field(examples, field.name).install(); + pub fn install(examples: *Examples, b: *Builder) void { + inline for (@typeInfo(Examples).Struct.fields) |field| { + b.installArtifact(@field(examples, field.name).inner); + } } }; diff --git a/examples/squarewave.zig b/examples/squarewave.zig index b320681..7437000 100644 --- a/examples/squarewave.zig +++ b/examples/squarewave.zig @@ -13,11 +13,12 @@ const squarewave_program = (rp2040.pio.assemble( \\ set pins, 1 [1] ; Drive pin high and then delay for one cycle \\ set pins, 0 ; Drive pin low \\ jmp again ; Set PC to label `again` -) catch +, .{}) catch @panic("failed to assemble program")) .get_program_by_name("squarewave"); pub fn main() void { + gpio.reset(); // Pick one PIO instance arbitrarily. We're also arbitrarily picking state // machine 0 on this PIO instance (the state machines are numbered 0 to 3 // inclusive). @@ -35,19 +36,27 @@ pub fn main() void { // speed down uniformly to meet some precise frequency target, e.g. for a // UART baud rate. This register has 16 integer divisor bits and 8 // fractional divisor bits. - pio.set_clkdiv_int_frac(sm, 2, 0x80); + pio.sm_set_clkdiv(sm, .{ + .int = 2, + .frac = 0x80, + }); // There are five pin mapping groups (out, in, set, side-set, jmp pin) // which are used by different instructions or in different circumstances. // Here we're just using SET instructions. Configure state machine 0 SETs // to affect GPIO 0 only; then configure GPIO0 to be controlled by PIO0, // as opposed to e.g. the processors. - pio.set_out_pins(sm, 0, 1); - gpio.set_function(0, .pio0); + pio.gpio_init(0); + pio.sm_set_pin_mappings(sm, .{ + .out = .{ + .base = 0, + .count = 1, + }, + }); // Set the state machine running. The PIO CTRL register is global within a // PIO instance, so you can start/stop multiple state machines // simultaneously. We're using the register's hardware atomic set alias to // make one bit high without doing a read-modify-write on the register. - pio.set_enabled(sm, true); + pio.sm_set_enabled(sm, true); } diff --git a/src/hal/pio.zig b/src/hal/pio.zig index e37d713..22dc5a3 100644 --- a/src/hal/pio.zig +++ b/src/hal/pio.zig @@ -1,5 +1,6 @@ //! A PIO instance can load a single `Bytecode`, it has to be loaded into memory const std = @import("std"); +const assert = std.debug.assert; const microzig = @import("microzig"); const PIO = microzig.chip.types.peripherals.PIO0; @@ -8,26 +9,19 @@ const PIO1 = microzig.chip.peripherals.PIO1; const gpio = @import("gpio.zig"); const assembler = @import("pio/assembler.zig"); -pub const Bytecode = Bytecode; -pub const Program = assembler.Program; -pub const assemble = assembler.assemble; +const encoder = @import("pio/assembler/encoder.zig"); +// global state for keeping track of used things var used_instruction_space: [2]u32 = [_]u32{ 0, 0 }; var claimed_state_machines: [2]u4 = [_]u4{ 0, 0 }; -pub const Join = enum { - none, - rx, - tx, -}; - -pub const Status = enum { - rx_lessthan, - tx_lessthan, -}; +pub const Instruction = encoder.Instruction; +pub const Program = assembler.Program; +pub const assemble = assembler.assemble; -pub const Configuration = struct { - pin: u32, +pub const Fifo = enum { + tx, + rx, }; pub const StateMachine = enum(u2) { @@ -37,6 +31,97 @@ pub const StateMachine = enum(u2) { sm3, }; +pub const Irq = enum { + irq0, + irq1, + + pub const Regs = extern struct { + enable: @TypeOf(PIO0.IRQ0_INTE), + force: @TypeOf(PIO0.IRQ0_INTF), + status: @TypeOf(PIO0.IRQ0_INTS), + }; + + pub const Source = enum { + rx_not_empty, + tx_not_full, + // TODO: determine what this does, is it just a combination of the + // first two, or is it other things? + statemachine, + }; +}; + +pub const StateMachineRegs = extern struct { + clkdiv: @TypeOf(PIO0.SM0_CLKDIV), + execctrl: @TypeOf(PIO0.SM0_EXECCTRL), + shiftctrl: @TypeOf(PIO0.SM0_SHIFTCTRL), + addr: @TypeOf(PIO0.SM0_ADDR), + instr: @TypeOf(PIO0.SM0_INSTR), + pinctrl: @TypeOf(PIO0.SM0_PINCTRL), +}; + +pub const ClkDivOptions = struct { + int: u16, + frac: u8, + + pub fn from_float(div: f32) ClkDivOptions { + const fixed = @floatToInt(u24, div * 256); + return ClkDivOptions{ + .int = @truncate(u16, fixed >> 8), + .frac = @truncate(u8, fixed), + }; + } +}; + +pub const ExecOptions = struct { + wrap: u5, + wrap_target: u5, + side_pindir: bool, + side_set_optional: bool, +}; + +pub const ShiftOptions = struct { + autopush: bool = false, + autopull: bool = false, + in_shiftdir: Direction = .left, + out_shiftdir: Direction = .left, + push_threshold: u5 = 0, + pull_threshold: u5 = 0, + join_tx: bool = false, + join_rx: bool = false, + + pub const Direction = enum(u1) { + left, + right, + }; +}; + +pub fn PinMapping(comptime Count: type) type { + return struct { + base: u5 = 0, + count: Count = 0, + }; +} + +pub const PinMappingOptions = struct { + out: PinMapping(u6) = .{}, + set: PinMapping(u3) = .{}, + side_set: PinMapping(u3) = .{}, + in_base: u5 = 0, +}; + +pub const StateMachineInitOptions = struct { + clkdiv: ClkDivOptions, + exec: ExecOptions, + shift: ShiftOptions = .{}, + pin_mappings: PinMappingOptions = .{}, +}; + +pub const LoadAndStartProgramOptions = struct { + clkdiv: ClkDivOptions, + shift: ShiftOptions = .{}, + pin_mappings: PinMappingOptions = .{}, +}; + pub const Pio = enum(u1) { pio0 = 0, pio1 = 1, @@ -60,11 +145,6 @@ pub const Pio = enum(u1) { }); } - pub fn load(self: Pio, bytecode: Bytecode) !void { - _ = self; - _ = bytecode; - } - fn can_add_program_at_offset(self: Pio, program: Program, offset: u5) bool { if (program.origin) |origin| if (origin != offset) @@ -84,9 +164,10 @@ pub const Pio = enum(u1) { origin else error.NoSpace - else for (0..32 - program.isntruction.len) |i| { - if (self.can_add_program_at_offset(program, i)) - break i; + else for (0..(32 - program.instructions.len)) |i| { + const offset = @intCast(u5, i); + if (self.can_add_program_at_offset(program, offset)) + break offset; } else error.NoSpace; } @@ -103,12 +184,13 @@ pub const Pio = enum(u1) { } /// Public functions will need to lock independently, so only exposing this function for now - pub fn add_program(self: Pio, program: Program) !void { - // TODO: const lock = hw.Lock.claim() - // defer lock.unlock(); + pub fn add_program(self: Pio, program: Program) !u5 { + //const lock = hw.Lock.claim(); + //defer lock.unlock(); - const offset = try self.find_offset_for_program_unlocked(); - try self.add_program_at_offset(program, offset); + const offset = try self.find_offset_for_program(program); + try self.add_program_at_offset_unlocked(program, offset); + return offset; } pub fn claim_unused_state_machine(self: Pio) !StateMachine { @@ -125,15 +207,6 @@ pub const Pio = enum(u1) { } else error.NoSpace; } - pub const StateMachineRegs = extern struct { - clkdiv: @TypeOf(PIO0.SM0_CLKDIV), - execctrl: @TypeOf(PIO0.SM0_EXECCTRL), - shiftctrl: @TypeOf(PIO0.SM0_SHIFTCTRL), - addr: @TypeOf(PIO0.SM0_ADDR), - instr: @TypeOf(PIO0.SM0_INSTR), - pinctrl: @TypeOf(PIO0.SM0_PINCTRL), - }; - fn get_sm_regs(self: Pio, sm: StateMachine) *volatile StateMachineRegs { const pio_regs = self.get_regs(); return switch (sm) { @@ -144,71 +217,88 @@ pub const Pio = enum(u1) { }; } - pub fn join_fifo(self: Pio, sm: StateMachine, join: Join) void { - const tx: u1 = switch (join) { - .tx => 1, - .rx => 0, - .none => 0, - }; - const rx: u1 = switch (join) { - .tx => 0, - .rx => 1, - .none => 0, + fn get_irq_regs(self: Pio, irq: Irq) *volatile Irq.Regs { + const pio_regs = self.get_regs(); + return switch (irq) { + .irq0 => @ptrCast(*volatile Irq.Regs, &pio_regs.IRQ0_INTE), + .irq1 => @ptrCast(*volatile Irq.Regs, &pio_regs.IRQ1_INTE), }; - - const sm_regs = self.get_sm_regs(sm); - sm_regs.shiftctrl.modify(.{ - .FJOIN_TX = tx, - .FJOIN_RX = rx, - }); } - pub fn set_clkdiv_int_frac(self: Pio, sm: StateMachine, div_int: u16, div_frac: u8) void { - if (div_int == 0 and div_frac != 0) + pub inline fn sm_set_clkdiv(self: Pio, sm: StateMachine, options: ClkDivOptions) void { + if (options.int == 0 and options.frac != 0) @panic("invalid params"); const sm_regs = self.get_sm_regs(sm); sm_regs.clkdiv.write(.{ - .INT = div_int, - .FRAC = div_frac, + .INT = options.int, + .FRAC = options.frac, .reserved8 = 0, }); } - pub fn set_out_shift(self: Pio, sm: StateMachine, args: struct { - shift_right: bool, - autopull: bool, - pull_threshold: u5, - }) void { + pub inline fn sm_set_exec_options(self: Pio, sm: StateMachine, options: ExecOptions) void { const sm_regs = self.get_sm_regs(sm); - sm_regs.shiftctrl.modify(.{ - .OUT_SHIFTDIR = @boolToInt(args.shift_right), - .AUTOPULL = @boolToInt(args.autopull), - .PULL_THRESH = args.pull_threshold, + sm_regs.execctrl.modify(.{ + .WRAP_BOTTOM = options.wrap_target, + .WRAP_TOP = options.wrap, + .SIDE_PINDIR = @boolToInt(options.side_pindir), + .SIDE_EN = @boolToInt(options.side_set_optional), + + // TODO: plug in rest of the options + // STATUS_N + // STATUS_SEL + // OUT_STICKY + // INLINE_OUT_EN + // OUT_EN_SEL + // JMP_PIN + // EXEC_STALLED }); } - pub fn set_out_pins(self: Pio, sm: StateMachine, base: u5, count: u5) void { + pub inline fn sm_set_shift_options(self: Pio, sm: StateMachine, options: ShiftOptions) void { const sm_regs = self.get_sm_regs(sm); - sm_regs.pinctrl.modify(.{ - .OUT_BASE = base, - .OUT_COUNT = count, + sm_regs.shiftctrl.write(.{ + .AUTOPUSH = @boolToInt(options.autopush), + .AUTOPULL = @boolToInt(options.autopush), + + .IN_SHIFTDIR = @enumToInt(options.in_shiftdir), + .OUT_SHIFTDIR = @enumToInt(options.out_shiftdir), + + .PUSH_THRESH = options.push_threshold, + .PULL_THRESH = options.pull_threshold, + + .FJOIN_TX = @boolToInt(options.join_tx), + .FJOIN_RX = @boolToInt(options.join_rx), + + .reserved16 = 0, }); } - pub fn set_sideset_pins(self: Pio, sm: StateMachine, base: u5) void { + pub inline fn sm_set_pin_mappings(self: Pio, sm: StateMachine, options: PinMappingOptions) void { const sm_regs = self.get_sm_regs(sm); - sm_regs.pinctrl.modify(.{ .SIDESET_BASE = base }); + sm_regs.pinctrl.modify(.{ + .OUT_BASE = options.out.base, + .OUT_COUNT = options.out.count, + + .SET_BASE = options.set.base, + .SET_COUNT = options.set.count, + + .SIDESET_BASE = options.side_set.base, + .SIDESET_COUNT = options.side_set.count, + + .IN_BASE = options.in_base, + }); } - pub fn is_tx_fifo_full(self: Pio, sm: StateMachine) bool { + pub inline fn sm_is_tx_fifo_full(self: Pio, sm: StateMachine) bool { const regs = self.get_regs(); const txfull = regs.FSTAT.read().TXFULL; return (txfull & (@as(u4, 1) << @enumToInt(sm))) != 0; } - pub fn get_tx_fifo(self: Pio, sm: StateMachine) *volatile u32 { + pub inline fn sm_get_tx_fifo(self: Pio, sm: StateMachine) *volatile u32 { const regs = self.get_regs(); return switch (sm) { .sm0 => ®s.TXF0, @@ -218,33 +308,14 @@ pub const Pio = enum(u1) { }; } - pub fn blocking_write(self: Pio, sm: StateMachine, value: u32) void { - while (self.is_tx_fifo_full(sm)) {} + pub inline fn sm_blocking_write(self: Pio, sm: StateMachine, value: u32) void { + while (self.sm_is_tx_fifo_full(sm)) {} - const fifo_ptr = self.get_tx_fifo(sm); + const fifo_ptr = self.sm_get_tx_fifo(sm); fifo_ptr.* = value; } - pub fn encode_jmp() void {} - - //static inline uint _pio_encode_instr_and_args(enum pio_instr_bits instr_bits, uint arg1, uint arg2) { - // valid_params_if(PIO_INSTRUCTIONS, arg1 <= 0x7); - //#if PARAM_ASSERTIONS_ENABLED(PIO_INSTRUCTIONS) - // uint32_t major = _pio_major_instr_bits(instr_bits); - // if (major == pio_instr_bits_in || major == pio_instr_bits_out) { - // assert(arg2 && arg2 <= 32); - // } else { - // assert(arg2 <= 31); - // } - //#endif - // return instr_bits | (arg1 << 5u) | (arg2 & 0x1fu); - //} - // - //static inline uint pio_encode_jmp(uint addr) { - // return _pio_encode_instr_and_args(pio_instr_bits_jmp, 0, addr); - //} - - pub fn set_enabled(self: Pio, sm: StateMachine, enabled: bool) void { + pub inline fn sm_set_enabled(self: Pio, sm: StateMachine, enabled: bool) void { const regs = self.get_regs(); var value = regs.CTRL.read(); @@ -256,103 +327,172 @@ pub const Pio = enum(u1) { regs.CTRL.write(value); } - pub fn sm_init(self: Pio, sm: StateMachine, initial_pc: u5, config: StateMachineRegs) void { - // Halt the machine, set some sensible defaults - self.set_enabled(sm, false); + inline fn sm_clear_debug(self: Pio, sm: StateMachine) void { + const regs = self.get_regs(); + const mask: u4 = (@as(u4, 1) << @enumToInt(sm)); + + // write 1 to clear this register + regs.FDEBUG.modify(.{ + .RXSTALL = mask, + .RXUNDER = mask, + .TXOVER = mask, + .TXSTALL = mask, + }); + } + + /// changing the state of fifos will clear them + pub fn sm_clear_fifos(self: Pio, sm: StateMachine) void { + const sm_regs = self.get_sm_regs(sm); + var shiftctrl = sm_regs.shiftctrl.read(); + shiftctrl.FJOIN_TX ^= 1; + shiftctrl.FJOIN_RX ^= 1; + sm_regs.shiftctrl.write(shiftctrl); + } + + pub fn sm_fifo_level(self: Pio, sm: StateMachine, fifo: Fifo) u4 { + const num = @enumToInt(sm); + const offset: u5 = switch (fifo) { + .tx => 0, + .rx => 4, + }; + + const regs = self.get_regs(); + const levels = regs.FLEVEL.raw; - self.set_config(sm, config); - self.clear_fifos(sm); + return @truncate(u4, levels >> (@as(u5, 4) * num) + offset); + } - // Clear FIFO debug flags - //const uint32_t fdebug_sm_mask = - // (1u << PIO_FDEBUG_TXOVER_LSB) | - // (1u << PIO_FDEBUG_RXUNDER_LSB) | - // (1u << PIO_FDEBUG_TXSTALL_LSB) | - // (1u << PIO_FDEBUG_RXSTALL_LSB); - //pio->fdebug = fdebug_sm_mask << sm; + inline fn interrupt_bit_pos( + sm: StateMachine, + source: Irq.Source, + ) u5 { + return (@as(u5, 4) * @enumToInt(source)) + @enumToInt(sm); + } + + pub inline fn sm_clear_interrupt( + self: Pio, + sm: StateMachine, + irq: Irq, + source: Irq.Source, + ) void { + // TODO: why does the raw interrupt register no have irq1/0? + _ = irq; + const regs = self.get_regs(); + regs.INTR.raw &= ~(@as(u32, 1) << interrupt_bit_pos(sm, source)); + } + + // TODO: be able to disable an interrupt + pub inline fn sm_enable_interrupt( + self: Pio, + sm: StateMachine, + irq: Irq, + source: Irq.Source, + ) void { + const irq_regs = self.get_irq_regs(irq); + irq_regs.enable.raw |= @as(u32, 1) << interrupt_bit_pos(sm, source); + } + + pub inline fn sm_restart(self: Pio, sm: StateMachine) void { + const mask: u4 = (@as(u4, 1) << @enumToInt(sm)); + const regs = self.get_regs(); + regs.CTRL.modify(.{ + .SM_RESTART = mask, + }); + } + + pub inline fn sm_clkdiv_restart(self: Pio, sm: StateMachine) void { + const mask: u4 = (@as(u4, 1) << @enumToInt(sm)); + const regs = self.get_regs(); + regs.CTRL.modify(.{ + .CLKDIV_RESTART = mask, + }); + } + + pub fn sm_init( + self: Pio, + sm: StateMachine, + initial_pc: u5, + options: StateMachineInitOptions, + ) void { + // Halt the machine, set some sensible defaults + self.sm_set_enabled(sm, false); + self.sm_set_pin_mappings(sm, options.pin_mappings); + self.sm_set_clkdiv(sm, options.clkdiv); + self.sm_set_exec_options(sm, options.exec); + self.sm_set_shift_options(sm, options.shift); + + //self.set_config(sm, config); + self.sm_clear_fifos(sm); + self.sm_clear_debug(sm); // Finally, clear some internal SM state - self.restart(sm); - self.clkdiv_restart(sm); - self.exec(sm, encode_jmp(initial_pc)); + self.sm_restart(sm); + self.sm_clkdiv_restart(sm); + self.sm_exec(sm, Instruction{ + .tag = .jmp, + + .delay_side_set = 0, + .payload = .{ + .jmp = .{ + .address = initial_pc, + .condition = .always, + }, + }, + }); + } + + pub fn sm_exec(self: Pio, sm: StateMachine, instruction: Instruction) void { + const sm_regs = self.get_sm_regs(sm); + sm_regs.instr.raw = @bitCast(u16, instruction); } - // state machine configuration helpers: - // - // - set_out_pins - // - set_set_pins - // - set_in_pins - // - set_sideset_pins - // - set_sideset - // - calculate_clkdiv_from_float - // - set_clkdiv - // - set_wrap - // - set_jmp_pin - // - set_in_shift - // - set_out_shift - // - set_fifo_join - // - set_out_special - // - set_mov_status - // - // PIO: - // - // - can_add_program - // - add_program_at_offset - // - add_program - // - remove_program - // - clear_instruction_memory - // - sm_init - // - sm_set_enabled - // - sm_mask_enabled - // - sm_restart - // - restart_sm_mask - // - sm_clkdiv_restart - // - clkdiv_restart_sm_mask - // - enable_sm_mask_in_sync - // - set_irq0_source_enabled - // - set_irq1_source_enabled - // - set_irq0_source_mask_enabled - // - set_irq1_source_mask_enabled - // - set_irqn_source_enabled - // - set_irqn_source_mask_enabled - // - interrupt_get - // - interrupt_clear - // - sm_get_pc - // - sm_exec - // - sm_is_exec_stalled - // - sm_exec_wait_blocking - // - sm_set_wrap - // - sm_set_out_pins - // - sm_set_set_pins - // - sm_set_in_pins - // - sm_set_sideset_pins - // - sm_put - // - sm_get - // - sm_is_rx_fifo_full - // - sm_is_rx_fifo_empty - // - sm_is_rx_fifo_level - // - sm_is_tx_fifo_full - // - sm_is_tx_fifo_empty - // - sm_is_tx_fifo_level - // - sm_put_blocking - // - sm_get_blocking - // - sm_drain_tx_fifo - // - sm_set_clkdiv_int_frac - // - sm_set_clkdiv - // - sm_clear_fifos - // - sm_set_pins - // - sm_set_pins_with_mask - // - sm_set_pindirs_with_mask - // - sm_set_consecutive_pindirs - // - sm_claim - // - claim_sm_mask - // - sm_unclaim - // - claim_unused_sm - // - sm_is_claimed - // + pub fn sm_load_and_start_program( + self: Pio, + sm: StateMachine, + program: Program, + options: LoadAndStartProgramOptions, + ) !void { + const expected_side_set_pins = if (program.side_set) |side_set| + if (side_set.optional) + side_set.count - 1 + else + side_set.count + else + 0; + + assert(expected_side_set_pins == options.pin_mappings.side_set.count); + + // TODO: check program settings vs pin mapping + const offset = try self.add_program(program); + self.sm_init(sm, offset, .{ + .clkdiv = options.clkdiv, + .shift = options.shift, + .pin_mappings = options.pin_mappings, + .exec = .{ + .wrap = if (program.wrap) |wrap| + wrap + else + offset + @intCast(u5, program.instructions.len), + + .wrap_target = if (program.wrap_target) |wrap_target| + wrap_target + else + offset, + + .side_pindir = if (program.side_set) |side_set| + side_set.pindirs + else + false, + + .side_set_optional = if (program.side_set) |side_set| + side_set.optional + else + false, + }, + }); + } }; test "pio" { std.testing.refAllDecls(assembler); - //std.testing.refAllDecls(@import("pio/test.zig")); } diff --git a/src/hal/pio/assembler.zig b/src/hal/pio/assembler.zig index d3596fd..af4153a 100644 --- a/src/hal/pio/assembler.zig +++ b/src/hal/pio/assembler.zig @@ -22,7 +22,7 @@ pub const Program = struct { wrap: ?u5, pub fn get_mask(program: Program) u32 { - return (1 << program.instructions.len) - 1; + return (@as(u32, 1) << @intCast(u5, program.instructions.len)) - 1; } }; @@ -35,7 +35,7 @@ pub const Output = struct { comptime name: []const u8, ) Program { return for (output.programs) |program| { - if (std.mem.eql(u8, program.name, program)) + if (std.mem.eql(u8, name, program.name)) break program; } else @panic(std.fmt.comptimePrint("program '{s}' not found", .{name})); }