From f243cea0fbba486cab13bc6988bee8210db0db4a Mon Sep 17 00:00:00 2001 From: Wanda Date: Mon, 15 Apr 2024 12:34:09 +0200 Subject: [PATCH] sim: implement `Format.*` for memories in VCD. --- amaranth/sim/pysim.py | 92 ++++++++++++++++++++++++++++++++----------- tests/test_sim.py | 23 ++++++++++- 2 files changed, 90 insertions(+), 25 deletions(-) diff --git a/amaranth/sim/pysim.py b/amaranth/sim/pysim.py index 5b17a13..64f86f4 100644 --- a/amaranth/sim/pysim.py +++ b/amaranth/sim/pysim.py @@ -172,23 +172,60 @@ class _VCDWriter: for memory, memory_name in memories.items(): self.vcd_memory_vars[memory] = vcd_vars = [] self.gtkw_memory_names[memory] = gtkw_names = [] - width = Shape.cast(memory.shape).width - if width > 1: - suffix = f"[{width - 1}:0]" - else: - suffix = "" - for idx, init in enumerate(memory._init._raw): - field_name = "\\" + memory_name[-1] + f"[{idx}]" - var_scope = memory_name[:-1] - vcd_var = self.vcd_writer.register_var( - scope=var_scope, name=field_name, - var_type="wire", size=width, init=init, - ) - vcd_vars.append(vcd_var) - gtkw_field_name = field_name + suffix - gtkw_name = ".".join((*var_scope, gtkw_field_name)) - gtkw_names.append(gtkw_name) + for idx, row in enumerate(memory): + row_vcd_vars = [] + row_gtkw_names = [] + var_scope = memory_name[:-1] + + def add_mem_var(path, var_type, var_size, var_init, value): + field_name = "\\" + memory_name[-1] + f"[{idx}]" + for item in path: + if isinstance(item, int): + field_name += f"[{item}]" + else: + field_name += f".{item}" + row_vcd_vars.append((self.vcd_writer.register_var( + scope=var_scope, name=field_name, var_type=var_type, + size=var_size, init=var_init + ), value)) + if var_size > 1: + suffix = f"[{var_size - 1}:0]" + else: + suffix = "" + row_gtkw_names.append(".".join((*var_scope, field_name)) + suffix) + + def add_mem_wire_var(path, value): + add_mem_var(path, "wire", len(value), eval_value(self.state, value), value) + + def add_mem_format_var(path, fmt): + add_mem_var(path, "string", 1, eval_format(self.state, fmt), fmt) + + def add_mem_format(path, fmt): + if isinstance(fmt, Format.Struct): + add_mem_wire_var(path, fmt._value) + for name, subfmt in fmt._fields.items(): + add_mem_format(path + (name,), subfmt) + elif isinstance(fmt, Format.Array): + add_mem_wire_var(path, fmt._value) + for idx, subfmt in enumerate(fmt._fields): + add_mem_format(path + (idx,), subfmt) + elif (isinstance(fmt, Format) and + len(fmt._chunks) == 1 and + isinstance(fmt._chunks[0], tuple) and + fmt._chunks[0][1] == ""): + add_mem_wire_var(path, fmt._chunks[0][0]) + else: + add_mem_format_var(path, fmt) + + if isinstance(memory._shape, ShapeCastable): + fmt = memory._shape.format(memory._shape(row), "") + add_mem_format((), fmt) + else: + add_mem_wire_var((), row) + + vcd_vars.append(row_vcd_vars) + gtkw_names.append(row_gtkw_names) self.vcd_process_vars = {} if fs_per_delta == 0: @@ -221,9 +258,15 @@ class _VCDWriter: var_value = repr(eval_value(self.state, signal)) self.vcd_writer.change(vcd_var, timestamp, var_value) - def update_memory(self, timestamp, memory, addr, value): - vcd_var = self.vcd_memory_vars[memory][addr] - self.vcd_writer.change(vcd_var, timestamp, value) + def update_memory(self, timestamp, memory, addr): + if memory not in self.vcd_memory_vars: + return + for vcd_var, repr in self.vcd_memory_vars[memory][addr]: + if isinstance(repr, Value): + var_value = eval_value(self.state, repr) + else: + var_value = eval_format(self.state, repr) + self.vcd_writer.change(vcd_var, timestamp, var_value) def update_process(self, timestamp, process, command): try: @@ -249,11 +292,12 @@ class _VCDWriter: for name in self.gtkw_signal_names[trace]: self.gtkw_save.trace(name) elif isinstance(trace, MemoryData): - for name in self.gtkw_memory_names[trace]: - self.gtkw_save.trace(name) + for row_names in self.gtkw_memory_names[trace]: + for name in row_names: + self.gtkw_save.trace(name) elif isinstance(trace, MemoryData._Row): - name = self.gtkw_memory_names[trace._memory][trace._index] - self.gtkw_save.trace(name) + for name in self.gtkw_memory_names[trace._memory][trace._index]: + self.gtkw_save.trace(name) else: assert False # :nocov: @@ -524,7 +568,7 @@ class PySimEngine(BaseEngine): signal_state.signal) elif isinstance(change, _PyMemoryChange): vcd_writer.update_memory(now_plus_deltas, change.state.memory, - change.addr, change.state.data[change.addr]) + change.addr) else: assert False # :nocov: diff --git a/tests/test_sim.py b/tests/test_sim.py index 7a86238..27eeda4 100644 --- a/tests/test_sim.py +++ b/tests/test_sim.py @@ -10,7 +10,8 @@ from amaranth.hdl._cd import * with warnings.catch_warnings(): warnings.filterwarnings(action="ignore", category=DeprecationWarning) from amaranth.hdl.rec import * -from amaranth.hdl._dsl import * +from amaranth.hdl._dsl import * +from amaranth.hdl._mem import MemoryData from amaranth.hdl._ir import * from amaranth.sim import * from amaranth.sim._pyeval import eval_format @@ -1355,6 +1356,26 @@ class SimulatorIntegrationTestCase(FHDLTestCase): with self.assertSimulation(Module(), traces=[sig]) as sim: sim.add_testbench(testbench) + def test_mem_shape(self): + class MyEnum(enum.Enum, shape=2): + A = 0 + B = 1 + C = 2 + + mem1 = MemoryData(shape=8, depth=4, init=[1, 2, 3]) + mem2 = MemoryData(shape=MyEnum, depth=4, init=[MyEnum.A, MyEnum.B, MyEnum.C]) + mem3 = MemoryData(shape=data.StructLayout({"a": signed(3), "b": 2}), depth=4, init=[{"a": 2, "b": 1}]) + + def testbench(): + yield Delay(1e-6) + yield mem1[0].eq(4) + yield mem2[3].eq(MyEnum.C) + yield mem3[2].eq(mem3._shape.const({"a": -1, "b": 2})) + yield Delay(1e-6) + + with self.assertSimulation(Module(), traces=[mem1, mem2, mem3]) as sim: + sim.add_testbench(testbench) + class SimulatorRegressionTestCase(FHDLTestCase): def test_bug_325(self):