Skip to content

Commit 7ea0f64

Browse files
committed
hdl.ast.Signal: allow arbitrary Unicode for names, except separator/control
1 parent fa2adbe commit 7ea0f64

File tree

5 files changed

+36
-18
lines changed

5 files changed

+36
-18
lines changed

amaranth/_utils.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
import warnings
44
import linecache
55
import re
6+
import unicodedata
67
from collections import OrderedDict
78
from collections.abc import Iterable
89

910

1011
__all__ = ["flatten", "union", "final", "deprecated", "get_linter_options",
11-
"get_linter_option"]
12+
"get_linter_option", "validate_name"]
1213

1314

1415
def flatten(i):
@@ -101,3 +102,25 @@ def get_linter_option(filename, name, type, default):
101102
except ValueError:
102103
return default
103104
assert False
105+
106+
_invalid_categories = {
107+
"Zs", # space separators
108+
"Zl", # line separators
109+
"Zp", # paragraph separators
110+
"Cc", # control codepoints (e.g. \0)
111+
"Cs", # UTF-16 surrogate pair codepoints (no thanks WTF-8)
112+
"Cn", # unassigned codepoints
113+
}
114+
115+
def validate_name(name, what, none_ok=False, empty_ok=False):
116+
if not isinstance(name, str):
117+
if name is None and none_ok: return
118+
raise TypeError(f"{what} must be a string, not {name!r}")
119+
if name == "" and not empty_ok:
120+
raise NameError(f"{what} must be a non-empty string")
121+
122+
# RTLIL allows bytes >= 33. In the same spirit we allow all characters that
123+
# Unicode does not declare as separator or control.
124+
for c in name:
125+
if unicodedata.category(c) in _invalid_categories:
126+
raise NameError(f"{what} {name!r} contains whitespace/control character {c!r}")

amaranth/hdl/_ast.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1919,12 +1919,11 @@ def __init__(self, shape=None, *, name=None, init=None, reset=None, reset_less=F
19191919
attrs=None, decoder=None, src_loc_at=0):
19201920
super().__init__(src_loc_at=src_loc_at)
19211921

1922-
if name is not None and not isinstance(name, str):
1923-
raise TypeError(f"Name must be a string, not {name!r}")
19241922
if name is None:
19251923
self.name = tracer.get_var_name(depth=2 + src_loc_at, default="$signal")
19261924
else:
19271925
self.name = name
1926+
validate_name(self.name, "Name", empty_ok=True)
19281927

19291928
orig_shape = shape
19301929
if shape is None:

amaranth/sim/pysim.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,9 @@ def __init__(self, design, *, vcd_file, gtkw_file=None, traces=(), fs_per_delta=
117117

118118
vcd_var = None
119119
for (*var_scope, var_name) in names:
120+
# Shouldn't happen, but avoid producing a corrupt file.
120121
if re.search(r"[ \t\r\n]", var_name):
121-
raise NameError("Signal '{}.{}' contains a whitespace character"
122-
.format(".".join(var_scope), var_name))
122+
assert False, f"invalid name {var_name!r} made it to writer"
123123

124124
field_name = var_name
125125
for item in repr.path:

tests/test_hdl_ast.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,15 @@ def test_name(self):
11821182
self.assertEqual(s2.name, "sig")
11831183
s3 = Signal(name="")
11841184
self.assertEqual(s3.name, "")
1185+
s4 = Signal(name="$\\1a!\U0001F33C")
1186+
self.assertEqual(s4.name, "$\\1a!\U0001f33c") # Astral plane emoji "Blossom"
1187+
1188+
def test_wrong_name(self):
1189+
for bad in [" ", "\r", "\n", "\t", "\0", "\u009d"]: # Control character OSC
1190+
name = f"sig{bad}"
1191+
with self.assertRaises(NameError,
1192+
msg="Name {name!r} contains whitespace/control character {bad!r}"):
1193+
Signal(name=name)
11851194

11861195
def test_init(self):
11871196
s1 = Signal(4, init=0b111, reset_less=True)

tests/test_sim.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,19 +1230,6 @@ def process():
12301230
sim.add_testbench(process)
12311231
sim.run()
12321232

1233-
def test_bug_595(self):
1234-
dut = Module()
1235-
dummy = Signal()
1236-
with dut.FSM(name="name with space"):
1237-
with dut.State(0):
1238-
dut.d.comb += dummy.eq(1)
1239-
sim = Simulator(dut)
1240-
with self.assertRaisesRegex(NameError,
1241-
r"^Signal 'bench\.top\.name with space_state' contains a whitespace character$"):
1242-
with open(os.path.devnull, "w") as f:
1243-
with sim.write_vcd(f):
1244-
sim.run()
1245-
12461233
def test_bug_588(self):
12471234
dut = Module()
12481235
a = Signal(32)

0 commit comments

Comments
 (0)