uart: begin bringing up COBS

This commit is contained in:
Fischer Moseley 2024-10-06 09:20:27 -06:00
parent c2666a7295
commit 416d537cec
5 changed files with 281 additions and 0 deletions

View File

@ -0,0 +1,2 @@
from manta.cobs.hw_decoder import COBSDecoder
from manta.cobs.hw_encoder import COBSEncoder

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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()