uart: rewrite bridge to support backpressure on output

This commit is contained in:
Fischer Moseley 2026-03-08 13:36:08 -06:00
parent 776d465ef2
commit dd6a3ac467
1 changed files with 92 additions and 137 deletions

View File

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