Skip to content

Commit 98d2b5c

Browse files
authored
Update output_to_verilog to inline temporary wires, using GateGraph: (#471)
* Update `output_to_verilog` to inline temporary wires, using `GateGraph`: - Gates with user-specified names are never inlined. - Unnamed constant Gates are always inlined, unless they are args to a bit-slice Gate. - Unnamed non-constant Gates are inlined if: - The Gate has one user, AND - The Gate is not a MemBlock read, AND - The Gate is not an arg to a bit-slice Gate. This significantly reduces the amount of generated Verilog code. Existing bugs fixed: - `output_verilog_testbench` should not re-initialize RomBlocks. - FastSimulation was not updating `init_menvalues` correctly. - Specify bitwidths for Verilog initial register and memory values. They were previously unsized constants, which are implicitly 32-bit signed, which could cause surprises. Add tests for these bugs. Also: - Improve Verilog identifier naming. Try replacing non-word characters with underscores before giving up and generating a new random name. - When we mangle names, add a comment with the un-mangled name. - Use sanitized MemBlock and RomBlock names instead of `mem_{memid}` names. - Refactor `output_to_verilog` and `output_verilog_testbench` to share more code. - Update `output_verilog_testbench` to use `GateGraph`. - Define `GateGraph.__iter__` to make it easier to iterate over all Gates. - Delete some disabled tests in `test_aes.py`.
1 parent 3deeedd commit 98d2b5c

File tree

13 files changed

+1150
-1203
lines changed

13 files changed

+1150
-1203
lines changed

docs/blocks.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,5 @@ GateGraphs
5252

5353
.. autoclass:: pyrtl.GateGraph
5454
:members:
55-
:special-members: __init__, __str__
55+
:special-members: __init__, __iter__, __str__
5656

pyrtl/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@
103103
from .importexport import (
104104
input_from_verilog,
105105
output_to_verilog,
106-
OutputToVerilog,
107106
output_verilog_testbench,
108107
input_from_blif,
109108
output_to_firrtl,
@@ -242,7 +241,6 @@
242241
# importexport
243242
"input_from_verilog",
244243
"output_to_verilog",
245-
"OutputToVerilog",
246244
"output_verilog_testbench",
247245
"input_from_blif",
248246
"output_to_firrtl",

pyrtl/compilesim.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,9 @@ def __init__(
141141
rval = self.default_value
142142
self._regmap[r] = rval
143143

144-
# Passing the dictionary objects themselves since they aren't updated anywhere.
145-
# If that's ever not the case, will need to pass in deep copies of them like
146-
# done for the normal Simulation so we retain the initial values that had.
147-
self.tracer._set_initial_values(default_value, self._regmap, self._memmap)
144+
self.tracer._set_initial_values(
145+
default_value, register_value_map, memory_value_map
146+
)
148147

149148
self._create_dll()
150149
self._initialize_mems()

pyrtl/core.py

Lines changed: 80 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,58 +1193,108 @@ def next_index(self):
11931193

11941194

11951195
class _NameSanitizer(_NameIndexer):
1196-
"""Sanitizes the names so that names can be used in places that don't allow for
1197-
arbitrary names while not mangling valid names.
1196+
"""Sanitizes names so they can be used in contexts that don't allow arbitrary names.
11981197
1199-
Put the values you want to validate into make_valid_string the first time you want
1200-
to sanitize a particular string (or before the first time), and retrieve from the
1201-
_NameSanitizer through indexing directly thereafter eg: sani["__&sfhs"] for
1202-
retrieval after the first time
1198+
For example, ``a.b`` is a valid ``WireVector`` name, but ``a.b`` is not a valid name
1199+
for a Python variable. If we want to generate Python code for ``a.b`` (like
1200+
``FastSimulation``), the name must be sanitized.
1201+
1202+
Sanitization first attempts to replace non-word characters (anything that's not
1203+
alphanumeric or an underscore) with an underscore. If that didn't work, we try
1204+
appending a unique integer value. If that still doesn't work, we generate an
1205+
entirely new name consisting of ``internal_prefix`` followed by a unique integer
1206+
value.
1207+
1208+
``make_valid_string`` must be called once to generate the sanitized version of a
1209+
name.
1210+
1211+
.. doctest only::
1212+
1213+
>>> import pyrtl
1214+
1215+
After ``make_valid_string`` has been called, the sanitized name can be retrieved
1216+
with ``__getitem__`` any number of times. For example::
1217+
1218+
>>> sanitizer = pyrtl.core._NameSanitizer(pyrtl.core._py_regex)
1219+
1220+
>>> sanitizer.make_valid_string("foo.bar")
1221+
'foo_bar'
1222+
>>> sanitizer["foo.bar"]
1223+
'foo_bar'
1224+
>>> sanitizer["foo.bar"]
1225+
'foo_bar'
1226+
1227+
>>> sanitizer.make_valid_string("foo_bar")
1228+
'foo_bar0'
12031229
"""
12041230

12051231
def __init__(
12061232
self,
12071233
identifier_regex_str,
12081234
internal_prefix="_sani_temp",
1209-
map_valid_vals=True,
12101235
extra_checks=lambda _string: True,
1211-
allow_duplicates=False,
12121236
):
12131237
if identifier_regex_str[-1] != "$":
12141238
identifier_regex_str += "$"
12151239
self.identifier = re.compile(identifier_regex_str)
1240+
# Map from un-sanitized name to sanitized name.
12161241
self.val_map = {}
1217-
self.map_valid = map_valid_vals
1242+
# Set of all generated sanitized names.
1243+
self.sanitized_names = set()
12181244
self.extra_checks = extra_checks
1219-
self.allow_dups = allow_duplicates
12201245
super().__init__(internal_prefix)
12211246

1222-
def __getitem__(self, item):
1223-
"""Get a value from the sanitizer"""
1224-
if not self.map_valid and self.is_valid_str(item):
1225-
return item
1226-
return self.val_map[item]
1247+
def __getitem__(self, name: str) -> str:
1248+
"""Return the sanitized name for an un-sanitized name that was generated by
1249+
``make_valid_string``.
1250+
"""
1251+
return self.val_map[name]
12271252

1228-
def is_valid_str(self, string):
1253+
def is_valid_str(self, string: str) -> bool:
1254+
"""Return ``True`` iff ``string`` matches ``identifier_regex_str`` and satisfies
1255+
``extra_checks``.
1256+
"""
12291257
return self.identifier.match(string) and self.extra_checks(string)
12301258

1231-
def make_valid_string(self, string=""):
1232-
"""Inputting a value for the first time."""
1233-
if not self.is_valid_str(string):
1234-
if string in self.val_map and not self.allow_dups:
1235-
msg = f"Value {string} has already been given to the sanitizer"
1236-
raise IndexError(msg)
1237-
internal_name = super().make_valid_string()
1238-
self.val_map[string] = internal_name
1239-
return internal_name
1240-
if self.map_valid:
1241-
self.val_map[string] = string
1242-
return string
1259+
def make_valid_string(self, string: str = "") -> str:
1260+
"""Generate a sanitized name from an un-sanitized name."""
1261+
if string in self.val_map:
1262+
msg = f"Value {string} has already been given to the sanitizer"
1263+
raise IndexError(msg)
1264+
1265+
def is_usable(name: str) -> bool:
1266+
"""Return ``True`` iff ``name`` can be used as a sanitized name.
1267+
1268+
A sanitized name is usable if it ``is_valid_str``, and isn't already in use.
1269+
"""
1270+
return self.is_valid_str(name) and name not in self.sanitized_names
1271+
1272+
internal_name = string
1273+
if not is_usable(internal_name):
1274+
# Try replacing non-word characters with ``_``.
1275+
internal_name = re.sub(r"\W", "_", string)
1276+
1277+
if not is_usable(internal_name):
1278+
# If that didn't work, try appending the next ``internal_index``.
1279+
internal_name = f"{internal_name}{self.next_index()}"
1280+
1281+
if not is_usable(internal_name):
1282+
# If that didn't work, generate an entirely new name starting with
1283+
# ``internal_prefix``.
1284+
internal_name = super().make_valid_string()
1285+
1286+
if not is_usable(internal_name):
1287+
msg = f"Could not generate a usable sanitized name for {string}"
1288+
raise PyrtlError(msg)
1289+
1290+
self.val_map[string] = internal_name
1291+
self.sanitized_names.add(internal_name)
1292+
return internal_name
12431293

12441294

12451295
class _PythonSanitizer(_NameSanitizer):
12461296
"""Name Sanitizer specifically built for Python identifers."""
12471297

1248-
def __init__(self, internal_prefix="_sani_temp", map_valid_vals=True):
1249-
super().__init__(_py_regex, internal_prefix, map_valid_vals)
1298+
def __init__(self, internal_prefix="_sani_temp"):
1299+
super().__init__(_py_regex, internal_prefix)
12501300
self.extra_checks = lambda s: not keyword.iskeyword(s)

pyrtl/gate_graph.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,9 @@ class GateGraph:
712712
gates: set[Gate]
713713
"""A :class:`set` of all :class:`Gates<Gate>` in the ``GateGraph``.
714714
715+
Similar to :attr:`~GateGraph.__iter__`, except that ``gates`` is a :class:`set`
716+
rather than an :class:`~collections.abc.Iterable`.
717+
715718
.. doctest only::
716719
717720
>>> import pyrtl
@@ -1114,3 +1117,28 @@ def __str__(self) -> str:
11141117
self.gates, key=lambda gate: gate.name if gate.name else "~~~"
11151118
)
11161119
return "\n".join([str(gate) for gate in sorted_gates])
1120+
1121+
def __iter__(self):
1122+
"""Iterate over each gate in the :class:`GateGraph`.
1123+
1124+
Similar to :attr:`~GateGraph.gates`, except that ``__iter__`` returns an
1125+
:class:`~collections.abc.Iterable` rather than a :class:`set`.
1126+
1127+
.. doctest only::
1128+
1129+
>>> import pyrtl
1130+
>>> pyrtl.reset_working_block()
1131+
1132+
Example::
1133+
1134+
>>> a = pyrtl.Input(name="a", bitwidth=2)
1135+
>>> b = pyrtl.Input(name="b", bitwidth=2)
1136+
>>> sum = a + b
1137+
>>> sum.name = "sum"
1138+
1139+
>>> gate_graph = pyrtl.GateGraph()
1140+
1141+
>>> sorted(gate.name for gate in gate_graph)
1142+
['a', 'b', 'sum']
1143+
"""
1144+
return iter(self.gates)

0 commit comments

Comments
 (0)