From 416d537cec191822c05d921c2bbdf784b7c809ca Mon Sep 17 00:00:00 2001 From: Fischer Moseley <42497969+fischermoseley@users.noreply.github.com> Date: Sun, 6 Oct 2024 09:20:27 -0600 Subject: [PATCH] uart: begin bringing up COBS --- src/manta/cobs/__init__.py | 2 + src/manta/cobs/hw_decoder.py | 57 ++++++++++++++++ src/manta/cobs/hw_encoder.py | 113 ++++++++++++++++++++++++++++++++ src/manta/cobs/sw_encoder.py | 28 ++++++++ test/test_interfaces_e2e_sim.py | 81 +++++++++++++++++++++++ 5 files changed, 281 insertions(+) create mode 100644 src/manta/cobs/__init__.py create mode 100644 src/manta/cobs/hw_decoder.py create mode 100644 src/manta/cobs/hw_encoder.py create mode 100644 src/manta/cobs/sw_encoder.py create mode 100644 test/test_interfaces_e2e_sim.py diff --git a/src/manta/cobs/__init__.py b/src/manta/cobs/__init__.py new file mode 100644 index 0000000..1ff564b --- /dev/null +++ b/src/manta/cobs/__init__.py @@ -0,0 +1,2 @@ +from manta.cobs.hw_decoder import COBSDecoder +from manta.cobs.hw_encoder import COBSEncoder \ No newline at end of file diff --git a/src/manta/cobs/hw_decoder.py b/src/manta/cobs/hw_decoder.py new file mode 100644 index 0000000..4a30709 --- /dev/null +++ b/src/manta/cobs/hw_decoder.py @@ -0,0 +1,57 @@ +from amaranth import * +from amaranth.lib.memory import Memory + +class COBSDecoder(Elaboratable): + def __init__(self): + + # Stream-like data input + self.data_in = Signal(8) + self.data_in_valid = Signal(1) + + # Stream-like data output + self.data_out = Signal(8) + self.data_out_valid = Signal(1) + self.end_of_packet = Signal(1) + + def elaborate(self, platform): + m = Module() + + counter = Signal(8) + + m.d.sync += self.data_out.eq(0) + m.d.sync += self.data_out_valid.eq(0) + m.d.sync += self.end_of_packet.eq(0) + + # State Machine: + with m.FSM() as fsm: + with m.State("WAIT_FOR_PACKET_START"): + with m.If( (self.data_in == 0) & (self.data_in_valid) ): + m.next = "START_OF_PACKET" + + with m.State("START_OF_PACKET"): + with m.If(self.data_in_valid): + m.d.sync += counter.eq(self.data_in - 1) + m.next = "DECODING" + + with m.Else(): + m.next = "START_OF_PACKET" + + with m.State("DECODING"): + with m.If(self.data_in_valid): + with m.If(counter > 0): + m.d.sync += counter.eq(counter - 1) + m.d.sync += self.data_out.eq(self.data_in) + m.d.sync += self.data_out_valid.eq(1) + m.next = "DECODING" + + with m.Else(): + with m.If(self.data_in == 0): + m.d.sync += self.end_of_packet.eq(1) + m.next = "START_OF_PACKET" + + with m.Else(): + m.d.sync += counter.eq(self.data_in - 1) + m.d.sync += self.data_out_valid.eq(1) + m.next = "DECODING" + + return m \ No newline at end of file diff --git a/src/manta/cobs/hw_encoder.py b/src/manta/cobs/hw_encoder.py new file mode 100644 index 0000000..a64ecc1 --- /dev/null +++ b/src/manta/cobs/hw_encoder.py @@ -0,0 +1,113 @@ +from amaranth import * +from amaranth.lib.memory import Memory + +class COBSEncoder(Elaboratable): + def __init__(self): + + # Top-Level IO + self.start = Signal(1) + self.done = Signal(1) + + # Stream-like data input + self.data_in = Signal(8) + self.data_in_valid = Signal(1) + + # Stream-like data output + self.data_out = Signal(8) + self.data_out_valid = Signal(1) + + # Define memory + self.memory = Memory(shape=8, depth=256, init = [0]*256) + + def elaborate(self, platform): + m = Module() + + # Internal Signals + head_pointer = Signal(range(256)) + tail_pointer = Signal(range(256)) + + # Add memory and read/write ports + m.submodules.memory = self.memory + rd_port = self.memory.read_port() + wr_port = self.memory.write_port() + + # Reset top-level IO + m.d.sync += self.data_out.eq(0) + m.d.sync += self.data_out_valid.eq(0) + + # Generate rd_port_addr_prev + rd_port_addr_prev = Signal().like(rd_port.addr) + m.d.sync += rd_port_addr_prev.eq(rd_port.addr) + + # State Machine: + with m.FSM() as fsm: + with m.State("IDLE"): + with m.If(self.start): + m.d.sync += head_pointer.eq(0) + m.d.sync += tail_pointer.eq(0) + m.d.sync += rd_port.addr.eq(0) + m.next = "SFZ" + + with m.State("SFZ"): + # Drive read addr until length is reached + with m.If(rd_port.addr < wr_port.addr): + m.d.sync += rd_port.addr.eq(rd_port.addr + 1) + + # Watch prev_addr and data + with m.If((rd_port_addr_prev == wr_port.addr) | (rd_port.data == 0)): + # Either reached the end of the input buffer or found a zero + + m.d.sync += head_pointer.eq(rd_port_addr_prev) + m.d.sync += rd_port.addr.eq(tail_pointer) + m.d.sync += self.data_out.eq(rd_port_addr_prev - tail_pointer + 1) + m.d.sync += self.data_out_valid.eq(1) + + m.next = "COB_STALL" + + with m.Else(): + m.next = "SFZ" + + with m.State("COB_STALL"): + m.d.sync += rd_port.addr.eq(rd_port.addr + 1) + m.next = "COB" + + with m.State("COB"): + # Drive rd_port.addr + with m.If(rd_port.addr < head_pointer): + m.d.sync += rd_port.addr.eq(rd_port.addr + 1) + + # Watch prev_addr + with m.If(rd_port_addr_prev <= head_pointer): + m.d.sync += self.data_out.eq(rd_port.data) + m.d.sync += self.data_out_valid.eq(1) + m.next = "COB" + + with m.If(rd_port_addr_prev == head_pointer): + # Reached end of message + with m.If(head_pointer == wr_port.addr): + m.d.sync += self.data_out.eq(0) + m.d.sync += self.data_out_valid.eq(1) + + m.next = "IDLE" + + with m.Else(): # this section is a beautiful! + m.d.sync += tail_pointer.eq(head_pointer + 1) + m.d.sync += head_pointer.eq(head_pointer + 1) + m.d.sync += rd_port.addr.eq(head_pointer + 1) + m.d.sync += self.data_out_valid.eq(0) # i have no idea why this works + + m.next = "SFZ_STALL" + + with m.State("SFZ_STALL"): + m.next = "SFZ" + + + # Fill memory from input stream + m.d.comb += wr_port.en.eq((fsm.ongoing("IDLE")) & (self.data_in_valid)) + m.d.comb += wr_port.data.eq(self.data_in) + m.d.sync += wr_port.addr.eq(wr_port.addr + wr_port.en) + + return m + + + diff --git a/src/manta/cobs/sw_encoder.py b/src/manta/cobs/sw_encoder.py new file mode 100644 index 0000000..1e0a058 --- /dev/null +++ b/src/manta/cobs/sw_encoder.py @@ -0,0 +1,28 @@ + +def cobs_encode(data): + final_zero = True + out = [] + idx = 0 + search_start_idx = 0 + for d in data: + if d == 0: + final_zero = True + out += [idx - search_start_idx + 1] + out += data[search_start_idx:idx] + search_start_idx = idx + 1 + + else: + if idx - search_start_idx == 0xFD: + final_zero = False + out += [0xFF] + out += data[search_start_idx:idx+1] + search_start_idx = idx + 1 + + idx += 1 + + if idx != search_start_idx or final_zero: + out += [idx - search_start_idx + 1] + out += data[search_start_idx:idx] + + return out + [0] + diff --git a/test/test_interfaces_e2e_sim.py b/test/test_interfaces_e2e_sim.py new file mode 100644 index 0000000..29f5e46 --- /dev/null +++ b/test/test_interfaces_e2e_sim.py @@ -0,0 +1,81 @@ +# the purpose of this test is to vet the + +# uart_rx -> cobs decode -> bridge -> core chain -> cobs encode -> uart_tx pipeline + +from amaranth import * +from manta.uart import UARTReceiver, UARTTransmitter, ReceiveBridge, TransmitBridge +from manta import IOCore +from manta.cobs import COBSEncoder, COBSDecoder +from manta.utils import * + + +class EndToEndInterfaceTest(Elaboratable): + def __init__(self): + self.uart_rx = UARTReceiver(clocks_per_baud=2) + self.cobs_decoder = COBSDecoder() + self.bridge_rx = ReceiveBridge() + + # wow this is so ugly + dummy_signal = Signal() + self.io_core = IOCore(inputs = [dummy_signal]) + self.io_core.base_addr = 0 + _ = self.io_core.max_addr + + self.bridge_tx = TransmitBridge() + + def elaborate(self, platform): + + m = Module() + + m.submodules.uart_rx = uart_rx = self.uart_rx + m.submodules.cobs_decoder = cobs_decoder = self.cobs_decoder + m.submodules.bridge_rx = bridge_rx = self.bridge_rx + + m.submodules.io_core = io_core = self.io_core + + m.submodules.bridge_tx = bridge_tx = self.bridge_tx + + m.d.comb += [ + cobs_decoder.data_in.eq(uart_rx.data_o), + cobs_decoder.data_in_valid.eq(uart_rx.valid_o), + + bridge_rx.data_i.eq(cobs_decoder.data_out), + bridge_rx.valid_i.eq(cobs_decoder.data_out_valid), + + io_core.bus_i.addr.eq(bridge_rx.addr_o), + io_core.bus_i.data.eq(bridge_rx.data_o), + io_core.bus_i.rw.eq(bridge_rx.rw_o), + io_core.bus_i.valid.eq(bridge_rx.valid_o), + + bridge_tx.data_i.eq(io_core.bus_o.data), + bridge_tx.rw_i.eq(io_core.bus_o.rw), + bridge_tx.valid_i.eq(io_core.bus_o.valid), + + ] + + return m + +e2e_interface_test = EndToEndInterfaceTest() + + +@simulate(e2e_interface_test) +async def test_send_some_bytes(ctx): + ctx.set(e2e_interface_test.uart_rx.rx, 1) + await ctx.tick() + + datas = [0x00, 0x08, 0x52, 0x31, 0x32, 0x33, 0x34, 0x0d, 0x0a, 0x00] + + for data in datas: + await send_byte_uart(ctx, data) + + +async def send_byte_uart(ctx, data): + # 8N1 serial, LSB sent first + data_bits = "0" + f"{data:08b}"[::-1] + "1" + data_bits = [int(bit) for bit in data_bits] + + for i in range(10 * e2e_interface_test.uart_rx._clocks_per_baud): + bit_index = i // e2e_interface_test.uart_rx._clocks_per_baud + + ctx.set(e2e_interface_test.uart_rx.rx, data_bits[bit_index]) + await ctx.tick() \ No newline at end of file