diff --git a/amaranth/hdl/_ast.py b/amaranth/hdl/_ast.py index 1844880..551fcc3 100644 --- a/amaranth/hdl/_ast.py +++ b/amaranth/hdl/_ast.py @@ -1884,7 +1884,8 @@ class Signal(Value, DUID, metaclass=_SignalMeta): If not specified, ``shape`` defaults to 1-bit and non-signed. name : str Name hint for this signal. If ``None`` (default) the name is inferred from the variable - name this ``Signal`` is assigned to. + name this ``Signal`` is assigned to. If the empty string, then this ``Signal`` is treated + as private and is generally hidden from view. init : int or integral Enum Reset (synchronous) or default (combinatorial) value. When this ``Signal`` is assigned to in synchronous context and the corresponding clock @@ -1920,7 +1921,10 @@ class Signal(Value, DUID, metaclass=_SignalMeta): if name is not None and not isinstance(name, str): raise TypeError(f"Name must be a string, not {name!r}") - self.name = name or tracer.get_var_name(depth=2 + src_loc_at, default="$signal") + if name is None: + self.name = tracer.get_var_name(depth=2 + src_loc_at, default="$signal") + else: + self.name = name orig_shape = shape if shape is None: @@ -2102,7 +2106,10 @@ class Signal(Value, DUID, metaclass=_SignalMeta): return SignalSet((self,)) def __repr__(self): - return f"(sig {self.name})" + if self.name != "": + return f"(sig {self.name})" + else: + return "(sig)" @final diff --git a/amaranth/hdl/_ir.py b/amaranth/hdl/_ir.py index e02bae2..f3e9fc9 100644 --- a/amaranth/hdl/_ir.py +++ b/amaranth/hdl/_ir.py @@ -552,6 +552,8 @@ class Design: assigned_names = {name for name, conn, dir in self.ports if name is not None} for name, conn, dir in self.ports: if name is None: + if conn.name == "": # Nothing to name this port! + raise TypeError("Signals with private names cannot be used in unnamed top-level ports") name = _add_name(assigned_names, conn.name) assigned_names.add(name) new_ports.append((name, conn, dir)) @@ -590,7 +592,7 @@ class Design: frag_info.io_port_names[conn] = name for signal in frag_info.used_signals: - if signal not in frag_info.signal_names: + if signal not in frag_info.signal_names and signal.name != "": # Private name shouldn't be added. frag_info.signal_names[signal] = _add_name(frag_info.assigned_names, signal.name) for port in frag_info.used_io_ports: if port not in frag_info.io_port_names: diff --git a/amaranth/sim/core.py b/amaranth/sim/core.py index 67c4f10..b54792e 100644 --- a/amaranth/sim/core.py +++ b/amaranth/sim/core.py @@ -4,6 +4,7 @@ import warnings from .._utils import deprecated from ..hdl._cd import * from ..hdl._ir import * +from ..hdl._ast import Value, ValueLike from ._base import BaseEngine @@ -238,5 +239,15 @@ class Simulator: file.close() raise ValueError("Cannot start writing waveforms after advancing simulation time") + for trace in traces: + if isinstance(trace, ValueLike): + trace_cast = Value.cast(trace) + for trace_signal in trace_cast._rhs_signals(): + if trace_signal.name == "": + if trace_signal is trace: + raise TypeError("Cannot trace signal with private name") + else: + raise TypeError(f"Cannot trace signal with private name (within {trace!r})") + return self._engine.write_vcd(vcd_file=vcd_file, gtkw_file=gtkw_file, traces=traces, fs_per_delta=fs_per_delta) diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py index d233b23..576aa90 100644 --- a/tests/test_hdl_ast.py +++ b/tests/test_hdl_ast.py @@ -1180,6 +1180,8 @@ class SignalTestCase(FHDLTestCase): self.assertEqual(s1.name, "s1") s2 = Signal(name="sig") self.assertEqual(s2.name, "sig") + s3 = Signal(name="") + self.assertEqual(s3.name, "") def test_init(self): s1 = Signal(4, init=0b111, reset_less=True) @@ -1294,6 +1296,8 @@ class SignalTestCase(FHDLTestCase): def test_repr(self): s1 = Signal() self.assertEqual(repr(s1), "(sig s1)") + s2 = Signal(name="") + self.assertEqual(repr(s2), "(sig)") def test_like(self): s1 = Signal.like(Signal(4)) diff --git a/tests/test_hdl_ir.py b/tests/test_hdl_ir.py index f968379..133c32f 100644 --- a/tests/test_hdl_ir.py +++ b/tests/test_hdl_ir.py @@ -962,6 +962,7 @@ class NamesTestCase(FHDLTestCase): o1 = Signal() o2 = Signal() o3 = Signal() + o4 = Signal(name="") i1 = Signal(name="i") f = Fragment() @@ -980,6 +981,7 @@ class NamesTestCase(FHDLTestCase): "o1": (o1, PortDirection.Output), "o2": (o2, PortDirection.Output), "o3": (o3, PortDirection.Output), + "o4": (o4, PortDirection.Output), } design = f.prepare(ports) self.assertEqual(design.fragments[design.fragment].signal_names, SignalDict([ @@ -988,12 +990,20 @@ class NamesTestCase(FHDLTestCase): (o1, "o1"), (o2, "o2"), (o3, "o3"), + # (o4, "o4"), # Signal has a private name. (cd_sync.clk, "clk"), - (cd_sync.rst, "rst$6"), + (cd_sync.rst, "rst$7"), (cd_sync_norst.clk, "sync_norst_clk"), - (i1, "i$7"), + (i1, "i$8"), ])) + def test_wrong_private_unnamed_toplevel_ports(self): + s = Signal(name="") + f = Fragment() + with self.assertRaisesRegex(TypeError, + r"^Signals with private names cannot be used in unnamed top-level ports$"): + Design(f, ports=((None, s, None),), hierarchy=("top",)) + def test_assign_names_to_fragments(self): f = Fragment() f.add_subfragment(a := Fragment()) diff --git a/tests/test_sim.py b/tests/test_sim.py index 978f8c0..9be4299 100644 --- a/tests/test_sim.py +++ b/tests/test_sim.py @@ -14,6 +14,7 @@ from amaranth.hdl._dsl import * from amaranth.hdl._ir import * from amaranth.sim import * from amaranth.lib.memory import Memory +from amaranth.lib.data import View, StructLayout from .utils import * from amaranth._utils import _ignore_deprecated @@ -1042,6 +1043,21 @@ class SimulatorIntegrationTestCase(FHDLTestCase): with sim.write_vcd(f): pass + def test_vcd_private_signal(self): + sim = Simulator(Module()) + with self.assertRaisesRegex(TypeError, + r"^Cannot trace signal with private name$"): + with open(os.path.devnull, "w") as f: + with sim.write_vcd(f, traces=(Signal(name=""),)): + pass + + sim = Simulator(Module()) + with self.assertRaisesRegex(TypeError, + r"^Cannot trace signal with private name \(within \(cat \(sig x\) \(sig\)\)\)$"): + with open(os.path.devnull, "w") as f: + with sim.write_vcd(f, traces=(Cat(Signal(name="x"), Signal(name="")),)): + pass + def test_no_negated_boolean_warning(self): m = Module() a = Signal()