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