add memory_pipe_experiment
authorJacob Lifshay <programmerjake@gmail.com>
Fri, 17 Apr 2020 02:37:15 +0000 (19:37 -0700)
committerJacob Lifshay <programmerjake@gmail.com>
Fri, 17 Apr 2020 02:37:15 +0000 (19:37 -0700)
src/soc/memory_pipe_experiment/__init__.py [new file with mode: 0644]
src/soc/memory_pipe_experiment/config.py [new file with mode: 0644]
src/soc/memory_pipe_experiment/l1_cache_memory.py [new file with mode: 0644]
src/soc/memory_pipe_experiment/test_l1_cache_memory.py [new file with mode: 0644]

diff --git a/src/soc/memory_pipe_experiment/__init__.py b/src/soc/memory_pipe_experiment/__init__.py
new file mode 100644 (file)
index 0000000..14e0132
--- /dev/null
@@ -0,0 +1,6 @@
+from nmigen import Elaboratable, Module, Signal
+
+
+class L1Cache(Elaboratable):
+    def __init__(self, config):
+        self.config = config
diff --git a/src/soc/memory_pipe_experiment/config.py b/src/soc/memory_pipe_experiment/config.py
new file mode 100644 (file)
index 0000000..4a97519
--- /dev/null
@@ -0,0 +1,20 @@
+class MemoryPipeConfig:
+    def __init__(self, *,
+                 bytes_per_cache_line=32,
+                 l1_way_count=8,
+                 l1_set_count=64):
+        self.bytes_per_cache_line = bytes_per_cache_line
+        self.l1_way_count = l1_way_count
+        self.l1_set_count = l1_set_count
+
+    @property
+    def l1_line_count(self):
+        return self.l1_way_count * self.l1_set_count
+
+    @property
+    def l1_byte_count(self):
+        return self.l1_line_count * self.bytes_per_cache_line
+
+    @property
+    def bits_per_cache_line(self):
+        return 8 * self.bytes_per_cache_line
diff --git a/src/soc/memory_pipe_experiment/l1_cache_memory.py b/src/soc/memory_pipe_experiment/l1_cache_memory.py
new file mode 100644 (file)
index 0000000..cb0fc18
--- /dev/null
@@ -0,0 +1,92 @@
+from nmigen import Elaboratable, Signal, Module, Mux, Repl, Array
+from nmigen.hdl.mem import ReadPort, WritePort, Memory
+from .config import MemoryPipeConfig
+
+
+class L1CacheMemory(Elaboratable):
+    """ The data memory for the L1 cache.
+
+    It is conceptually organized into `config.l1_way_count` ways,
+    where each way has `config.l1_sets_count` sets,
+    where each set is a single cache line of `config.bytes_per_cache_line`
+    bytes. None of the dimensions must be powers of 2, but must all be at
+    least 1.
+
+    The memory has a single R/W port that can read or write
+    (but not both) an entire cache line each cycle.
+    When writing, writing to each byte can individually be enabled by setting
+    the corresponding bit in `write_byte_en`.
+
+    The results of reading are available after the next clock edge.
+
+    The address is divided into `set_index` and `way_index`.
+
+    Parameters:
+
+    config: MemoryPipeConfig
+        The configuration.
+
+    Attributes:
+
+    config: MemoryPipeConfig
+        The configuration.
+    set_index: Signal(range(config.l1_set_count))
+        The input index of the set to read/write.
+    way_index: Signal(range(config.l1_way_count))
+        The input index of the way to read/write.
+    write_byte_en: Signal(config.bytes_per_cache_line)
+        The per-byte write enable inputs.
+    write_enable: Signal()
+        The overall write enable input.
+        Set to 1 to write and to 0 to read.
+    read_data: Signal(config.bits_per_cache_line)
+        The read data output.
+    write_data: Signal(config.bits_per_cache_line)
+        The write data input.
+
+    """
+
+    def __init__(self, config: MemoryPipeConfig):
+        self.config = config
+        self.set_index = Signal(range(config.l1_set_count), reset_less=True)
+        self.way_index = Signal(range(config.l1_way_count),
+                                reset_less=True)
+        self.write_byte_en = Signal(config.bytes_per_cache_line,
+                                    reset_less=True)
+        self.write_enable = Signal(reset_less=True)
+        self.read_data = Signal(config.bits_per_cache_line,
+                                reset_less=True)
+        self.write_data = Signal(config.bits_per_cache_line,
+                                 reset_less=True)
+
+    def elaborate(self, platform):
+        m = Module()
+        read_data_signals = []
+        for way in range(self.config.l1_way_count):
+            way_memory_name = f"way_memory_{way}"
+            way_memory = Memory(width=self.config.bits_per_cache_line,
+                                depth=self.config.l1_set_count,
+                                name=way_memory_name)
+            write_port = WritePort(way_memory, granularity=8)
+            setattr(m.submodules, way_memory_name + '_write_port', write_port)
+            m.d.comb += write_port.addr.eq(self.set_index)
+            m.d.comb += write_port.data.eq(self.write_data)
+            way_enable = Signal(name=f"way_enable_{way}", reset_less=True)
+            m.d.comb += way_enable.eq(way == self.way_index)
+            way_write_enable = self.write_enable & way_enable
+            way_read_enable = ~self.write_enable & way_enable
+            m.d.comb += write_port.en.eq(
+                Repl(way_write_enable,
+                     self.config.bytes_per_cache_line
+                     ) & self.write_byte_en)
+            read_port = ReadPort(way_memory, transparent=False)
+            setattr(m.submodules, way_memory_name + '_read_port', read_port)
+            m.d.comb += read_port.addr.eq(self.set_index)
+            m.d.comb += read_port.en.eq(way_read_enable)
+            read_data_signals.append(read_port.data)
+
+        last_way_index = Signal.like(self.way_index)
+        m.d.sync += last_way_index.eq(self.way_index)
+        read_data = Array(read_data_signals)
+        m.d.comb += self.read_data.eq(read_data[last_way_index])
+        return m
diff --git a/src/soc/memory_pipe_experiment/test_l1_cache_memory.py b/src/soc/memory_pipe_experiment/test_l1_cache_memory.py
new file mode 100644 (file)
index 0000000..3bc9e92
--- /dev/null
@@ -0,0 +1,50 @@
+from nmigen.back import rtlil
+from nmigen.back.pysim import Simulator, Delay
+import unittest
+from .config import MemoryPipeConfig
+from .l1_cache_memory import L1CacheMemory
+
+
+class TestL1CacheMemory(unittest.TestCase):
+    def test_l1_cache_memory(self):
+        config = MemoryPipeConfig(bytes_per_cache_line=4,
+                                  l1_way_count=8,
+                                  l1_set_count=32)
+        base_name = "test_l1_cache_memory"
+        with self.subTest(part="synthesize"):
+            dut = L1CacheMemory(config)
+            vl = rtlil.convert(dut)
+            with open(f"{base_name}.il", "w") as f:
+                f.write(vl)
+        dut = L1CacheMemory(config)
+        sim = Simulator(dut)
+        clock_period = 1e-6
+        sim.add_clock(clock_period)
+
+        def process():
+            for set_index in range(config.l1_set_count):
+                for way_index in range(config.l1_way_count):
+                    yield dut.set_index.eq(set_index)
+                    yield dut.way_index.eq(way_index)
+                    yield dut.write_enable.eq(1)
+                    yield dut.write_byte_en.eq(0xF)
+                    write_data = set_index * 0x10 + way_index
+                    write_data *= 0x00010001
+                    write_data ^= 0x80808080
+                    yield dut.write_data.eq(write_data)
+                    yield
+                    yield dut.set_index.eq(set_index)
+                    yield dut.way_index.eq(way_index)
+                    yield dut.write_enable.eq(0)
+                    yield
+                    yield Delay(clock_period / 10)
+                    yield dut.set_index.eq(set_index + 1)
+                    yield dut.way_index.eq(way_index + 1)
+                    yield Delay(clock_period / 10)
+                    read_data = (yield dut.read_data)
+                    self.assertEqual(read_data, write_data)
+
+        sim.add_sync_process(process)
+        with sim.write_vcd(vcd_file=open(f"{base_name}.vcd", "w"),
+                           gtkw_file=open(f"{base_name}.gtkw", "w")):
+            sim.run()