Repository: amaranth-lang/amaranth
Pull request #1325 (2024-04-12 19:16:18)

sim: add `eval_format` function.
This will be used in an upcoming PR for VCD output.


Patch:
diff --git a/amaranth/sim/_pyeval.py b/amaranth/sim/_pyeval.py
index 32a4d0a..10ecb64 100644
--- a/amaranth/sim/_pyeval.py
+++ b/amaranth/sim/_pyeval.py
@@ -128,6 +128,33 @@ def eval_value(sim, value):
         assert False # :nocov:
 
 
+def value_to_string(value):
+    """Unpack a Verilog-like (but LSB-first) string of unknown width from an integer."""
+    msg = bytearray()
+    while value:
+        byte = value & 0xff
+        value >>= 8
+        if byte:
+            msg.append(byte)
+    return msg.decode()
+
+
+def eval_format(sim, fmt):
+    fmt = Format("{}", fmt)
+    chunks = []
+    for chunk in fmt._chunks:
+        if isinstance(chunk, str):
+            chunks.append(chunk)
+        else:
+            value, spec = chunk
+            value = eval_value(sim, value)
+            if spec.endswith("s"):
+                chunks.append(format(value_to_string(value), spec[:-1]))
+            else:
+                chunks.append(format(value, spec))
+    return "".join(chunks)
+
+
 def _eval_assign_inner(sim, lhs, lhs_start, rhs, rhs_len):
     if isinstance(lhs, Operator) and lhs.operator in ("u", "s"):
         _eval_assign_inner(sim, lhs.operands[0], lhs_start, rhs, rhs_len)
diff --git a/amaranth/sim/_pyrtl.py b/amaranth/sim/_pyrtl.py
index 5505cfb..0483dfa 100644
--- a/amaranth/sim/_pyrtl.py
+++ b/amaranth/sim/_pyrtl.py
@@ -8,6 +8,7 @@ from ..hdl._ast import SignalSet, _StatementList, Property
 from ..hdl._xfrm import ValueVisitor, StatementVisitor
 from ..hdl._mem import MemoryInstance
 from ._base import BaseProcess
+from ._pyeval import value_to_string
 
 
 __all__ = ["PyRTLProcess"]
@@ -356,17 +357,6 @@ class _LHSValueCompiler(_ValueCompiler):
         return gen
 
 
-def value_to_string(value):
-    """Unpack a Verilog-like (but LSB-first) string of unknown width from an integer."""
-    msg = bytearray()
-    while value:
-        byte = value & 0xff
-        value >>= 8
-        if byte:
-            msg.append(byte)
-    return msg.decode()
-
-
 def pin_blame(src_loc, exc):
     if src_loc is None:
         raise exc


Test patch:
diff --git a/tests/test_sim.py b/tests/test_sim.py
index 3bb52b6..6e43011 100644
--- a/tests/test_sim.py
+++ b/tests/test_sim.py
@@ -13,9 +13,9 @@ with warnings.catch_warnings():
 from amaranth.hdl._dsl import  *
 from amaranth.hdl._ir import *
 from amaranth.sim import *
+from amaranth.sim._pyeval import eval_format
 from amaranth.lib.memory import Memory
-from amaranth.lib.data import View, StructLayout
-from amaranth.lib import enum
+from amaranth.lib import enum, data
 
 from .utils import *
 from amaranth._utils import _ignore_deprecated
@@ -1302,6 +1302,44 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
         with sim.write_vcd("test.vcd", fs_per_delta=1):
             sim.run()
 
+    def test_eval_format(self):
+        class MyEnum(enum.Enum, shape=2):
+            A = 0
+            B = 1
+            C = 2
+
+        sig = Signal(8)
+        sig2 = Signal(MyEnum)
+        sig3 = Signal(data.StructLayout({"a": signed(3), "b": 2}))
+        sig4 = Signal(data.ArrayLayout(2, 4))
+        sig5 = Signal(32, init=0x44434241)
+
+        def testbench():
+            state = sim._engine._state
+            yield sig.eq(123)
+            self.assertEqual(eval_format(state, sig._format), "123")
+            self.assertEqual(eval_format(state, Format("{:#04x}", sig)), "0x7b")
+            self.assertEqual(eval_format(state, Format("sig={}", sig)), "sig=123")
+
+            self.assertEqual(eval_format(state, sig2.as_value()._format), "A")
+            yield sig2.eq(1)
+            self.assertEqual(eval_format(state, sig2.as_value()._format), "B")
+            yield sig2.eq(3)
+            self.assertEqual(eval_format(state, sig2.as_value()._format), "[unknown]")
+
+            yield sig3.eq(0xc)
+            self.assertEqual(eval_format(state, sig3.as_value()._format), "{a=-4, b=1}")
+
+            yield sig4.eq(0x1e)
+            self.assertEqual(eval_format(state, sig4.as_value()._format), "[2, 3, 1, 0]")
+
+            self.assertEqual(eval_format(state, Format("{:s}", sig5)), "ABCD")
+            self.assertEqual(eval_format(state, Format("{:<5s}", sig5)), "ABCD ")
+
+        sim = Simulator(Module())
+        sim.add_testbench(testbench)
+        with sim.write_vcd("test.vcd", fs_per_delta=1):
+            sim.run()
 
 class SimulatorRegressionTestCase(FHDLTestCase):
     def test_bug_325(self):