From 84b18a74c585a2c338261f66fe526b28f1f30af6 Mon Sep 17 00:00:00 2001 From: Kia <kia@special-circumstanc.es> Date: Wed, 3 Mar 2021 17:41:26 -0700 Subject: [PATCH] more work on gearbox, start splitting off submodules so we can write test suites to exercise all the edge cases --- gearbox.py | 164 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 131 insertions(+), 33 deletions(-) diff --git a/gearbox.py b/gearbox.py index a82bc01..1e679ca 100755 --- a/gearbox.py +++ b/gearbox.py @@ -1,8 +1,12 @@ +from enum import Enum from nmigen import * from nmigen.hdl.rec import * - from nmigen.cli import main +class IndexDisambiguator(Enum): + LAST_OP_UNKNOWN = 0 + LAST_OP_WAS_WRITE = 1 + LAST_OP_WAS_READ = 2 class GearboxBusLayout(Layout): def __init__(self, *, in_width, out_width): @@ -17,6 +21,7 @@ class GearboxBusLayout(Layout): ("ready_in", 1), # FROM DEST ("valid_out", 1), # TO DEST + ("fault", 1) ]) @@ -24,12 +29,75 @@ class GearboxBus(Record): def __init__(self, *, in_width, out_width): super().__init__(GearboxBusLayout(in_width=in_width, out_width=out_width)) + +class GearboxFlow(Elaboratable): + def __init__(self): + + def elaborate(self, platform): + # The top-level flow control logic works as follows. + # First, we determine which operations are *possible* based on the read/write indices + # and the index disambiguator bit: + + # 1) we determine if we have enough invalid bits in the buffer to write-in + # If so, we set ready_out to signal to upstream we're ready to ingest + can_write_this_cycle = Signal(1) + # 2) we determine if we have enough valid bits in the buffer to read-out + # If so, we set valid_out to signal to downstream we're ready to produce + can_read_this_cycle = Signal(1) + # Then, we look at the flow-control signals from upstream/downstream to see which + # transactions will happen, and set the following two internal signals, which are + # used to gate the read/write index updates: + + write_happens_this_cycle = Signal(1) + read_happens_this_cycle = Signal(1) + + + with m.If(read_ptr == write_ptr): # the special case first + with m.Switch(disambiguator): + with m.Case(IndexDisambiguator.LAST_OP_UNKNOWN): # fault + m.d.comb += can_read_this_cycle.eq(0) + m.d.comb += can_write_this_cycle.eq(0) + m.d.comb += self.bus.fault.eq(1) + with m.Case(IndexDisambiguator.LAST_OP_WAS_WRITE): # completely full + m.d.comb += can_read_this_cycle.eq(1) + m.d.comb += can_write_this_cycle.eq(0) + with m.Case(IndexDisambiguator.LAST_OP_WAS_READ): # completely empty + m.d.comb += can_read_this_cycle.eq(0) + m.d.comb += can_write_this_cycle.eq(1) + + with m.Elif(read_ptr < 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] inclusive, union with inclusive [0, read_ptr) exclusive + + # We first calculate the number of valid bits: + numvalid = Signal(range(len_storage)) + + m.d.comb += numvalid.eq(write_ptr - read_ptr) + + with m.If(numvalid >= self.out_width): + m.d.comb += can_read_this_cycle.eq(1) + + # We calculate the number of invalid bits: + numinvalid = Signal(range(len_storage)) + + m.d.comb += numinvalid.eq(len_storage - write_ptr + read_ptr) + + with m.If(numinvalid >= self.in_width): + m.d.comb += can_write_this_cycle.eq(1) + + + + + + + + class ArbitraryGearbox(Elaboratable): def __init__(self, *, in_width, out_width): self.in_width = in_width self.out_width = out_width - self.bus = GearboxBus(in_width=in_width, out_width=out_width) def elaborate(self, platform): @@ -45,28 +113,7 @@ class ArbitraryGearbox(Elaboratable): write_ptr = Signal(range(len_storage)) read_ptr = Signal(range(len_storage)) - # 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 - )) - + disambiguator = Signal(IndexDisambiguator, reset=IndexDisambiguator.LAST_OP_WAS_READ) # The buffer is composed of two different flavor of bits: @@ -88,13 +135,8 @@ class ArbitraryGearbox(Elaboratable): # There are multiple cases for read_ptr and 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 valid bits are: inclusive [read_ptr, K] inclusive, 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. @@ -102,7 +144,63 @@ class ArbitraryGearbox(Elaboratable): # 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 - + + # We keep a bit of state -- which is only relevant if write_ptr == read_ptr, to keep track of + # this: IndexDisambiguator, which can either be LAST_OP_WAS_WRITE or LAST_OP_WAS_READ + + + + + + with m.If(self.bus.fault == 0): + # read index update: + with m.If(read_happens_this_cycle == 1): + 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 bits case analysis: + 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 + )) + + # write index update: + with m.If(write_happens_this_cycle == 1): + with m.If(write_ptr + self.in_width <= len_storage): + m.d.sync += write_ptr.eq(write_ptr + self.in_width - len_storage) + with m.Else(): + m.d.sync += write_ptr.eq(write_ptr + self.in_width) + + # actually write-in the bits + + with m.If(write_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(write_ptr): + for cand_wptr in range(len_storage - self.out_width + 1, len_storage): + with m.Case(cand_wptr): + m.d.comb += loop.eq(1) + non_wrapping_bitwidth = len_storage - cand_wptr + wrapping_bitwidth = self.out_width + cand_wptr - len_storage + m.d.comb += self.bus.data_out.eq(Cat( + storage.bit_select(cand_wptr, non_wrapping_bitwidth), # the non-wrapping bits are less-significant + storage.bit_select(0, wrapping_bitwidth) # ...than the wrapping bit + )) + + + + return m @@ -118,12 +216,12 @@ class DummyPlug(Elaboratable): def elaborate(self, platform): m = Module() - m.submodules.table = table = ArbitraryGearbox(in_width=(9-3), out_width=3) + m.submodules.gearbox = gearbox = 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) + m.d.comb += gearbox.bus.data_in.eq(1) return m -- GitLab