Skip to content

Commit 48e000f

Browse files
committed
hdl.ast.Signal: allow arbitrary Unicode for names, except separator/control
1 parent 9ed83b6 commit 48e000f

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
@@ -1900,8 +1900,7 @@ def __init__(self, shape=None, *, name=None, init=None, reset=None, reset_less=F
19001900
attrs=None, decoder=None, src_loc_at=0):
19011901
super().__init__(src_loc_at=src_loc_at)
19021902

1903-
if name is not None and not isinstance(name, str):
1904-
raise TypeError(f"Name must be a string, not {name!r}")
1903+
validate_name(name, "Name", none_ok=True)
19051904
self.name = name or tracer.get_var_name(depth=2 + src_loc_at, default="$signal")
19061905

19071906
orig_shape = shape

amaranth/sim/pysim.py

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

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

121121
field_name = var_name
122122
for item in repr.path:

tests/test_hdl_ast.py

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

11841193
def test_init(self):
11851194
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
@@ -1188,19 +1188,6 @@ def process():
11881188
sim.add_testbench(process)
11891189
sim.run()
11901190

1191-
def test_bug_595(self):
1192-
dut = Module()
1193-
dummy = Signal()
1194-
with dut.FSM(name="name with space"):
1195-
with dut.State(0):
1196-
dut.d.comb += dummy.eq(1)
1197-
sim = Simulator(dut)
1198-
with self.assertRaisesRegex(NameError,
1199-
r"^Signal 'bench\.top\.name with space_state' contains a whitespace character$"):
1200-
with open(os.path.devnull, "w") as f:
1201-
with sim.write_vcd(f):
1202-
sim.run()
1203-
12041191
def test_bug_588(self):
12051192
dut = Module()
12061193
a = Signal(32)

0 commit comments

Comments
 (0)