uart: begin bringing up COBS
This commit is contained in:
parent
c2666a7295
commit
416d537cec
|
|
@ -0,0 +1,2 @@
|
|||
from manta.cobs.hw_decoder import COBSDecoder
|
||||
from manta.cobs.hw_encoder import COBSEncoder
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
@ -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]
|
||||
|
||||
|
|
@ -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()
|
||||
Loading…
Reference in New Issue