From dd6a3ac467fd9f9ee6e6a9e48ef686090ee2cc93 Mon Sep 17 00:00:00 2001 From: Fischer Moseley <42497969+fischermoseley@users.noreply.github.com> Date: Sun, 8 Mar 2026 13:36:08 -0600 Subject: [PATCH] uart: rewrite bridge to support backpressure on output --- src/manta/ethernet/bridge.py | 229 ++++++++++++++--------------------- 1 file changed, 92 insertions(+), 137 deletions(-) diff --git a/src/manta/ethernet/bridge.py b/src/manta/ethernet/bridge.py index e2f2995..4d3a21c 100644 --- a/src/manta/ethernet/bridge.py +++ b/src/manta/ethernet/bridge.py @@ -15,183 +15,138 @@ class EthernetBridge(wiring.Component): def elaborate(self, platform): m = Module() + msg_type = Signal(MessageTypes) seq_num_expected = Signal(13) - read_len = Signal(7) - with m.FSM(init="IDLE"): + seen_last = Signal() + + count = Signal(7) + + with m.FSM() as fsm: with m.State("IDLE"): - m.d.sync += self.sink.ready.eq(1) - m.d.sync += self.source.valid.eq(0) - - # TODO: not necessary, but makes debugging way easier - m.d.sync += self.source.last.eq(0) - m.d.sync += self.source.data.eq(0) - with m.If(self.sink.valid & self.sink.ready): - # First 32 bits was presented, which contains message type (first 3 bits) - # as well as sequence number (next 13 bits). The remaining 16 bits are unused. - # Send NACK if message type or sequence number is incorrect with m.If( - (self.sink.data[:3] > max(MessageTypes)) + ( + (self.sink.data[:3] != MessageTypes.READ_REQUEST) + & (self.sink.data[:3] != MessageTypes.WRITE_REQUEST) + ) | (self.sink.data[3:16] != seq_num_expected) ): - # Wait to NACK if this isn't the last beat in message - with m.If(~self.sink.last): - m.next = "NACK_WAIT_FOR_LAST" + m.d.sync += seen_last.eq(self.sink.last) + m.next = "NACK" - # Otherwise, NACK immediately - with m.Else(): - m.d.sync += self.source.data.eq( - EthernetMessageHeader.concat_signals( - MessageTypes.NACK, - seq_num_expected, - ) - ) - m.d.sync += self.source.valid.eq(1) - m.d.sync += self.source.last.eq(1) - m.d.sync += self.sink.ready.eq(0) - m.next = "NACK_WAIT_FOR_READY" + with m.Else(): + m.d.sync += msg_type.eq(self.sink.data[:3]) + m.d.sync += count.eq(self.sink.data[16:23]) + m.next = "WAIT_FOR_ADDR" - with m.Elif(self.sink.data[:3] == MessageTypes.READ_REQUEST): - m.d.sync += seq_num_expected.eq(seq_num_expected + 1) - m.d.sync += read_len.eq(self.sink.data[16:23] - 1) + with m.State("WAIT_FOR_ADDR"): + with m.If(self.sink.valid & self.sink.ready): + m.d.sync += self.bus_source.p.addr.eq(self.sink.data) + m.d.sync += seq_num_expected.eq(seq_num_expected + 1) + with m.If(msg_type == MessageTypes.READ_REQUEST): + # Send read response header + m.d.sync += self.source.valid.eq(1) + m.d.sync += self.source.last.eq(0) m.d.sync += self.source.data.eq( EthernetMessageHeader.concat_signals( MessageTypes.READ_RESPONSE, seq_num_expected, ) ) - m.d.sync += self.source.valid.eq(1) - m.next = "READ_WAIT_FOR_ADDR" - with m.Elif(self.sink.data[:3] == MessageTypes.WRITE_REQUEST): - m.next = "WRITE_WAIT_FOR_ADDR" + m.next = "READ" - with m.State("READ_WAIT_FOR_ADDR"): - m.d.sync += self.source.valid.eq(0) - m.d.sync += self.source.data.eq(0) - - with m.If(self.sink.valid): - # we have the length and the address to read from, let's go! - m.d.sync += self.bus_source.p.addr.eq(self.sink.data) - m.d.sync += self.bus_source.p.data.eq(0) - m.d.sync += self.bus_source.p.rw.eq(0) - m.d.sync += self.bus_source.p.valid.eq(1) - - with m.If(read_len == 0): - # we've sent the last read request in this batch to the bus - m.d.sync += self.bus_source.p.last.eq(1) - m.d.sync += read_len.eq(0) - - m.next = "READ" - - with m.State("READ"): - m.d.sync += self.sink.ready.eq(0) - - # Clock out read requests to the bus - with m.If(read_len > 0): - m.d.sync += self.bus_source.p.addr.eq(self.bus_source.p.addr + 1) - m.d.sync += read_len.eq(read_len - 1) - - with m.If(read_len == 1): - m.d.sync += self.bus_source.p.last.eq(1) - - with m.Else(): - m.d.sync += self.bus_source.p.eq( - 0 - ) # TODO: it's probably overzealous to set the whole bus to zero, but it makes debugging easy so we're doing it xD - - # Clock out any read data from the bus - with m.If(self.bus_sink.p.valid): - m.d.sync += self.source.data.eq(self.bus_sink.p.data) - m.d.sync += self.source.valid.eq(1) - m.d.sync += self.source.last.eq(self.bus_sink.p.last) - - with m.If(self.source.last): - m.d.sync += self.source.data.eq(0) - m.d.sync += self.source.valid.eq(0) - m.d.sync += self.source.last.eq(0) - m.next = "IDLE" # TODO: could save a cycle by checking valid_i to see if there's more work to do - - with m.State("WRITE_WAIT_FOR_ADDR"): - with m.If(self.sink.valid): - m.d.sync += self.bus_source.p.addr.eq(self.sink.data) - m.next = "WRITE_FIRST" - - # Don't want to increment address on the first write, - # and I'm lazy so I'm making a new state to keep track of that - with m.State("WRITE_FIRST"): - with m.If(self.sink.valid): - m.d.sync += self.bus_source.p.data.eq(self.sink.data) - m.d.sync += self.bus_source.p.rw.eq(1) - m.d.sync += self.bus_source.p.valid.eq(1) - m.d.sync += self.bus_source.p.last.eq(self.sink.last) - - with m.If(self.sink.last): - m.d.sync += self.sink.ready.eq(0) - m.next = "WRITE_WAIT_FOR_LAST" - - with m.Else(): + with m.Elif(msg_type == MessageTypes.WRITE_REQUEST): m.next = "WRITE" with m.State("WRITE"): - with m.If(self.sink.valid): + # Keep a running count of the number of requests inflight on the bus + # Once that hits zero and seen_last is high, we're done! + # Send write response and wait for it to clock out + + m.d.sync += seen_last.eq( + seen_last | (self.sink.last & self.sink.valid & self.sink.ready) + ) + m.d.sync += count.eq( + count + (self.sink.valid & self.sink.ready) - self.bus_sink.p.valid + ) + + with m.If(self.sink.valid & self.sink.ready): m.d.sync += self.bus_source.p.addr.eq(self.bus_source.p.addr + 1) m.d.sync += self.bus_source.p.data.eq(self.sink.data) m.d.sync += self.bus_source.p.rw.eq(1) m.d.sync += self.bus_source.p.valid.eq(1) - m.d.sync += self.bus_source.p.last.eq(self.sink.last) - - with m.If(self.sink.last): - m.d.sync += self.sink.ready.eq(0) - m.next = "WRITE_WAIT_FOR_LAST" - - with m.Else(): - m.next = "WRITE" with m.Else(): - m.next = "WRITE" + m.d.sync += self.bus_source.p.data.eq(0) # just for clarity of debugging + m.d.sync += self.bus_source.p.rw.eq(0) # just for clarity of debugging + m.d.sync += self.bus_source.p.valid.eq(0) - with m.State("WRITE_WAIT_FOR_LAST"): - m.d.sync += self.bus_source.p.eq(0) + with m.If(seen_last & (count == 0)): + with m.If(self.source.valid & self.source.ready): + m.d.sync += self.source.valid.eq(0) + m.d.sync += self.source.last.eq(0) # just for clarity of debugging + m.d.sync += self.source.data.eq(0) # just for clarity of debugging + m.next = "IDLE" - with m.If(self.bus_sink.p.last): - m.d.sync += seq_num_expected.eq(seq_num_expected + 1) - m.d.sync += self.source.data.eq( - EthernetMessageHeader.concat_signals( - MessageTypes.WRITE_RESPONSE, - seq_num_expected, + with m.Else(): + m.d.sync += self.source.valid.eq(1) + m.d.sync += self.source.last.eq(1) + m.d.sync += self.source.data.eq( + EthernetMessageHeader.concat_signals( + MessageTypes.WRITE_RESPONSE, + seq_num_expected, + ) ) - ) + + with m.State("READ"): + # Wait for downstream to accept data. Put next read request on the bus after it's accepted, if more is needed + m.d.sync += self.bus_source.p.valid.eq(0) + with m.If(self.source.valid): + with m.If(self.source.ready): + m.d.sync += self.source.valid.eq(0) + m.d.sync += self.source.data.eq(0) # for debugging + m.d.sync += self.source.last.eq(0) # for debugging + + with m.If(self.source.last): + m.next = "IDLE" + + with m.Else(): + m.d.sync += self.bus_source.p.rw.eq(0) + m.d.sync += self.bus_source.p.valid.eq(1) + m.d.sync += count.eq(count - 1) + + # Not waiting on downstream to accept data. No need to issue bus request. + # Instead check if data's available on bus_sink, and put it on source if so + with m.Else(): + with m.If(self.bus_sink.p.valid): + m.d.sync += self.source.data.eq(self.bus_sink.p.data) + m.d.sync += self.source.valid.eq(1) + m.d.sync += self.source.last.eq(count == 0) + m.d.sync += self.bus_source.p.addr.eq(self.bus_source.p.addr + 1) + + with m.State("NACK"): + # Only send NACK after full packet has been received + with m.If(seen_last | (self.sink.valid & self.sink.ready & self.sink.last)): m.d.sync += self.source.valid.eq(1) m.d.sync += self.source.last.eq(1) - m.next = "IDLE" # TODO: could save a cycle by checking valid_i to see if there's more work to do - - with m.State("NACK_WAIT_FOR_LAST"): - with m.If(self.sink.last): m.d.sync += self.source.data.eq( EthernetMessageHeader.concat_signals( MessageTypes.NACK, seq_num_expected, ) ) - m.d.sync += self.source.valid.eq(1) - m.d.sync += self.source.last.eq(1) - m.d.sync += self.sink.ready.eq(0) - m.next = "NACK_WAIT_FOR_READY" - - with m.State("NACK_WAIT_FOR_READY"): - with m.If(self.source.ready): - m.d.sync += self.source.valid.eq(0) - - # TODO: remove these next two lines, they're not necessary - # although they are nice for debug... - m.d.sync += self.source.data.eq(0) - m.d.sync += self.source.last.eq(0) - m.d.sync += self.sink.ready.eq(1) + with m.If(self.source.valid & self.source.ready): m.next = "IDLE" + m.d.sync += self.source.valid.eq(0) + m.d.sync += self.source.last.eq(0) + m.d.sync += self.source.data.eq(0) + + m.d.comb += self.sink.ready.eq(~fsm.ongoing("READ")) + return m