Newer
Older
class GearboxBusLayout(Layout):
def __init__(self, *, in_width, out_width):
super().__init__([
# DATA
("data_in", unsigned(in_width)), # FROM SOURCE
("data_out", unsigned(out_width)), # TO DEST
# CONTROL
("valid_in", 1), # FROM SOURCE
("ready_out", 1), # TO SOURCE
("ready_in", 1), # FROM DEST
("valid_out", 1), # TO DEST
])
class GearboxBus(Record):
def __init__(self, *, in_width, out_width):
super().__init__(GearboxBusLayout(in_width=in_width, out_width=out_width))
class ArbitraryGearbox(Elaboratable):
def __init__(self, *, in_width, out_width):
self.in_width = in_width
self.out_width = out_width
loop = Signal(1)
len_storage = self.in_width + self.out_width
#storage = Signal(len_storage, reset=0b001_010_011_100_101_110_111)
#storage = Signal(len_storage, reset= 0b111_110_101_100_011_010_001)
# read index logic here
with m.If(read_ptr + self.out_width >= len_storage):
m.d.sync += read_ptr.eq(read_ptr + self.out_width - len_storage)
with m.Else():
m.d.sync += read_ptr.eq(read_ptr + self.out_width)
# read-out case analysis here:
with m.If(read_ptr + self.out_width <= len_storage):
m.d.comb += self.bus.data_out.eq(storage.bit_select(read_ptr, self.out_width))
with m.Else():
with m.Switch(read_ptr):
for cand_rptr in range(len_storage - self.out_width + 1, len_storage):
with m.Case(cand_rptr):
m.d.comb += loop.eq(1)
non_wrapping_bitwidth = len_storage - cand_rptr
wrapping_bitwidth = self.out_width + cand_rptr - len_storage
m.d.comb += self.bus.data_out.eq(Cat(
storage.bit_select(cand_rptr, non_wrapping_bitwidth), # the non-wrapping bits are less-significant
storage.bit_select(0, wrapping_bitwidth) # ...than the wrapping bit
))
# The buffer is composed of two different flavor of bits:
# Invalid bits CANNOT BE READ OUT CAN BE WRITTEN TO
# Valid bits CAN BE READ OUT CANNOT BE WRITTEN TO
# and the location of those bits are given by the read_ptr and the write_ptr
# We only allow a read operation (which is not idempotent as the read_ptr is advanced) if:
# 1) the downstream interface is signaling ready
# 2) there are out_width valid bits in front of the read_ptr
# Likewise, we only allow a write operation (which is heavily non-idempotent as the write_ptr is advanced
# and the buffer is modified) if:
# 1) the upstream interface is signaling valid (we cannot allow ourselves to ingest invalid data!)
# 2) there are in_width invalid bits in front of the write_ptr
# read_ptr < write_ptr. Here, the valid bits do not wrap, the invalid bits wrap.
# The valid bits are: inclusive [read_ptr, write_ptr) exclusive
# the invalid bits are inclusive [write_ptr, K) exclusive, union with inclusive [0, read_ptr) exclusive
# write_ptr < read_ptr. Here, the valid bits wrap, and the invalid bits do not wrap.
# The valid bits are: inclusive [read_ptr, K) exclusive, union with inclusive [0, write_ptr) exclusive
# the invalid bits are inclusive [write_ptr, read_ptr) exclusive
# Naturally, there is a tricky edge case which requires an extra bit (literally) of disambiguation.
# If write_ptr == read_ptr we don't know if the buffer is entirely full or entirely empty.
# If the last operation was a read, and we find write_ptr == read_ptr, we know it's empty
# If the last operation was a write, and we find write_ptr == read_ptr, we know it's full
return m
class DummyPlug(Elaboratable):
m.submodules.table = table = ArbitraryGearbox(in_width=(9-3), out_width=3)
counter = Signal(8)
m.d.sync += counter.eq(counter+1)
with m.If(counter == 3):
m.d.comb += table.bus.data_in.eq(1)