--- /dev/null
+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
--- /dev/null
+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()