diff --git a/docs/blocks.rst b/docs/blocks.rst index 41b5b3ff..f0d6067f 100644 --- a/docs/blocks.rst +++ b/docs/blocks.rst @@ -52,5 +52,5 @@ GateGraphs .. autoclass:: pyrtl.GateGraph :members: - :special-members: __init__, __str__ + :special-members: __init__, __iter__, __str__ diff --git a/pyrtl/__init__.py b/pyrtl/__init__.py index 2b75673a..ecb6a2b2 100644 --- a/pyrtl/__init__.py +++ b/pyrtl/__init__.py @@ -103,7 +103,6 @@ from .importexport import ( input_from_verilog, output_to_verilog, - OutputToVerilog, output_verilog_testbench, input_from_blif, output_to_firrtl, @@ -242,7 +241,6 @@ # importexport "input_from_verilog", "output_to_verilog", - "OutputToVerilog", "output_verilog_testbench", "input_from_blif", "output_to_firrtl", diff --git a/pyrtl/compilesim.py b/pyrtl/compilesim.py index a30cbe1e..01500503 100644 --- a/pyrtl/compilesim.py +++ b/pyrtl/compilesim.py @@ -141,10 +141,9 @@ def __init__( rval = self.default_value self._regmap[r] = rval - # Passing the dictionary objects themselves since they aren't updated anywhere. - # If that's ever not the case, will need to pass in deep copies of them like - # done for the normal Simulation so we retain the initial values that had. - self.tracer._set_initial_values(default_value, self._regmap, self._memmap) + self.tracer._set_initial_values( + default_value, register_value_map, memory_value_map + ) self._create_dll() self._initialize_mems() diff --git a/pyrtl/core.py b/pyrtl/core.py index 7d73ba5d..b9008487 100644 --- a/pyrtl/core.py +++ b/pyrtl/core.py @@ -1193,58 +1193,108 @@ def next_index(self): class _NameSanitizer(_NameIndexer): - """Sanitizes the names so that names can be used in places that don't allow for - arbitrary names while not mangling valid names. + """Sanitizes names so they can be used in contexts that don't allow arbitrary names. - Put the values you want to validate into make_valid_string the first time you want - to sanitize a particular string (or before the first time), and retrieve from the - _NameSanitizer through indexing directly thereafter eg: sani["__&sfhs"] for - retrieval after the first time + For example, ``a.b`` is a valid ``WireVector`` name, but ``a.b`` is not a valid name + for a Python variable. If we want to generate Python code for ``a.b`` (like + ``FastSimulation``), the name must be sanitized. + + Sanitization first attempts to replace non-word characters (anything that's not + alphanumeric or an underscore) with an underscore. If that didn't work, we try + appending a unique integer value. If that still doesn't work, we generate an + entirely new name consisting of ``internal_prefix`` followed by a unique integer + value. + + ``make_valid_string`` must be called once to generate the sanitized version of a + name. + + .. doctest only:: + + >>> import pyrtl + + After ``make_valid_string`` has been called, the sanitized name can be retrieved + with ``__getitem__`` any number of times. For example:: + + >>> sanitizer = pyrtl.core._NameSanitizer(pyrtl.core._py_regex) + + >>> sanitizer.make_valid_string("foo.bar") + 'foo_bar' + >>> sanitizer["foo.bar"] + 'foo_bar' + >>> sanitizer["foo.bar"] + 'foo_bar' + + >>> sanitizer.make_valid_string("foo_bar") + 'foo_bar0' """ def __init__( self, identifier_regex_str, internal_prefix="_sani_temp", - map_valid_vals=True, extra_checks=lambda _string: True, - allow_duplicates=False, ): if identifier_regex_str[-1] != "$": identifier_regex_str += "$" self.identifier = re.compile(identifier_regex_str) + # Map from un-sanitized name to sanitized name. self.val_map = {} - self.map_valid = map_valid_vals + # Set of all generated sanitized names. + self.sanitized_names = set() self.extra_checks = extra_checks - self.allow_dups = allow_duplicates super().__init__(internal_prefix) - def __getitem__(self, item): - """Get a value from the sanitizer""" - if not self.map_valid and self.is_valid_str(item): - return item - return self.val_map[item] + def __getitem__(self, name: str) -> str: + """Return the sanitized name for an un-sanitized name that was generated by + ``make_valid_string``. + """ + return self.val_map[name] - def is_valid_str(self, string): + def is_valid_str(self, string: str) -> bool: + """Return ``True`` iff ``string`` matches ``identifier_regex_str`` and satisfies + ``extra_checks``. + """ return self.identifier.match(string) and self.extra_checks(string) - def make_valid_string(self, string=""): - """Inputting a value for the first time.""" - if not self.is_valid_str(string): - if string in self.val_map and not self.allow_dups: - msg = f"Value {string} has already been given to the sanitizer" - raise IndexError(msg) - internal_name = super().make_valid_string() - self.val_map[string] = internal_name - return internal_name - if self.map_valid: - self.val_map[string] = string - return string + def make_valid_string(self, string: str = "") -> str: + """Generate a sanitized name from an un-sanitized name.""" + if string in self.val_map: + msg = f"Value {string} has already been given to the sanitizer" + raise IndexError(msg) + + def is_usable(name: str) -> bool: + """Return ``True`` iff ``name`` can be used as a sanitized name. + + A sanitized name is usable if it ``is_valid_str``, and isn't already in use. + """ + return self.is_valid_str(name) and name not in self.sanitized_names + + internal_name = string + if not is_usable(internal_name): + # Try replacing non-word characters with ``_``. + internal_name = re.sub(r"\W", "_", string) + + if not is_usable(internal_name): + # If that didn't work, try appending the next ``internal_index``. + internal_name = f"{internal_name}{self.next_index()}" + + if not is_usable(internal_name): + # If that didn't work, generate an entirely new name starting with + # ``internal_prefix``. + internal_name = super().make_valid_string() + + if not is_usable(internal_name): + msg = f"Could not generate a usable sanitized name for {string}" + raise PyrtlError(msg) + + self.val_map[string] = internal_name + self.sanitized_names.add(internal_name) + return internal_name class _PythonSanitizer(_NameSanitizer): """Name Sanitizer specifically built for Python identifers.""" - def __init__(self, internal_prefix="_sani_temp", map_valid_vals=True): - super().__init__(_py_regex, internal_prefix, map_valid_vals) + def __init__(self, internal_prefix="_sani_temp"): + super().__init__(_py_regex, internal_prefix) self.extra_checks = lambda s: not keyword.iskeyword(s) diff --git a/pyrtl/gate_graph.py b/pyrtl/gate_graph.py index 8de6feef..b2d6b347 100644 --- a/pyrtl/gate_graph.py +++ b/pyrtl/gate_graph.py @@ -712,6 +712,9 @@ class GateGraph: gates: set[Gate] """A :class:`set` of all :class:`Gates` in the ``GateGraph``. + Similar to :attr:`~GateGraph.__iter__`, except that ``gates`` is a :class:`set` + rather than an :class:`~collections.abc.Iterable`. + .. doctest only:: >>> import pyrtl @@ -1114,3 +1117,28 @@ def __str__(self) -> str: self.gates, key=lambda gate: gate.name if gate.name else "~~~" ) return "\n".join([str(gate) for gate in sorted_gates]) + + def __iter__(self): + """Iterate over each gate in the :class:`GateGraph`. + + Similar to :attr:`~GateGraph.gates`, except that ``__iter__`` returns an + :class:`~collections.abc.Iterable` rather than a :class:`set`. + + .. doctest only:: + + >>> import pyrtl + >>> pyrtl.reset_working_block() + + Example:: + + >>> a = pyrtl.Input(name="a", bitwidth=2) + >>> b = pyrtl.Input(name="b", bitwidth=2) + >>> sum = a + b + >>> sum.name = "sum" + + >>> gate_graph = pyrtl.GateGraph() + + >>> sorted(gate.name for gate in gate_graph) + ['a', 'b', 'sum'] + """ + return iter(self.gates) diff --git a/pyrtl/importexport.py b/pyrtl/importexport.py index 0605d81f..53c0ea17 100644 --- a/pyrtl/importexport.py +++ b/pyrtl/importexport.py @@ -16,11 +16,12 @@ import subprocess import sys import tempfile -from typing import TYPE_CHECKING +from typing import IO, TYPE_CHECKING from pyrtl.core import Block, _NameSanitizer, working_block from pyrtl.corecircuits import concat_list, rtl_all, rtl_any, select -from pyrtl.memory import RomBlock +from pyrtl.gate_graph import Gate, GateGraph +from pyrtl.memory import MemBlock, RomBlock from pyrtl.passes import one_bit_selects, two_way_concat from pyrtl.pyrtlexceptions import PyrtlError, PyrtlInternalError from pyrtl.wire import Const, Input, Output, Register, WireVector, next_tempvar_name @@ -737,69 +738,6 @@ def input_from_verilog( os.remove(tmp_blif_path) -def output_to_verilog( - dest_file, - add_reset: bool | str = True, - block: Block = None, - initialize_registers: bool = False, -): - """A function to walk the ``block`` and output it in Verilog format to the open - file. - - The Verilog module will be named ``toplevel``, with a clock input named ``clk``. - - When possible, wires keep their names in the Verilog output. Wire names that do not - satisfy Verilog's naming requirements, and wires that conflict with Verilog keywords - are given new temporary names in the Verilog output. - - :param dest_file: Open file where the Verilog output will be written. - :param add_reset: If reset logic should be added. Allowable options are: ``False`` - (meaning no reset logic is added), ``True`` (default, for adding synchronous - reset logic), and ``'asynchronous'`` (for adding asynchronous reset logic). The - reset input will be named ``rst``, and when ``rst`` is high, registers will be - reset to their ``reset_value``. - :param initialize_registers: Initialize Verilog registers to their ``reset_value``. - When this argument is ``True``, a register like ``Register(name='foo', - bitwidth=8, reset_value=4)`` generates Verilog like ``reg[7:0] foo = 8'd4;``. - :param block: Block to be walked and exported. Defaults to the :ref:`working_block`. - """ - - if not isinstance(add_reset, bool) and add_reset != "asynchronous": - msg = ( - f"Invalid add_reset option {add_reset}. Acceptable options are False, " - "True, and 'asynchronous'" - ) - raise PyrtlError(msg) - - block = working_block(block) - file = dest_file - internal_names = _VerilogSanitizer("_ver_out_tmp_") - - if add_reset and block.get_wirevector_by_name("rst") is not None: - msg = ( - "Found a user-defined wire named 'rst'. Pass in 'add_reset=False' to use " - "your existing reset logic." - ) - raise PyrtlError(msg) - - for wire in block.wirevector_set: - internal_names.make_valid_string(wire.name) - - def varname(wire: WireVector) -> str: - return internal_names[wire.name] - - _to_verilog_header(file, block, varname, add_reset, initialize_registers) - _to_verilog_combinational(file, block, varname) - _to_verilog_sequential(file, block, varname, add_reset) - _to_verilog_memories(file, block, varname) - _to_verilog_footer(file) - - -def OutputToVerilog(dest_file, block=None): - """A deprecated function to output Verilog, use "output_to_verilog" instead.""" - return output_to_verilog(dest_file, block) - - class _VerilogSanitizer(_NameSanitizer): _ver_regex = r"[_A-Za-z][_a-zA-Z0-9\$]*$" @@ -817,253 +755,691 @@ class _VerilogSanitizer(_NameSanitizer): unsigned use vectored wait wand weak0 weak1 while wire wor xnor xor """ - def __init__(self, internal_prefix="_sani_temp", map_valid_vals=True): + def __init__(self, internal_prefix): self._verilog_reserved_set = frozenset(self._verilog_reserved.split()) - super().__init__( - self._ver_regex, internal_prefix, map_valid_vals, self._extra_checks - ) + super().__init__(self._ver_regex, internal_prefix, self._extra_checks) def _extra_checks(self, str): return ( str not in self._verilog_reserved_set # is not a Verilog reserved keyword and str != "clk" # not the clock signal - and len(str) <= 1024 - ) # not too long to be a Verilog id - + and len(str) <= 1024 # not too long to be a Verilog id + ) -def _verilog_vector_size_decl(n: int) -> str: - return "" if n == 1 else f"[{n - 1}:0]" +class _VerilogOutput: + def __init__(self, block: Block, add_reset: bool | str): + block = working_block(block) + self.gate_graph = GateGraph(block) + self.add_reset = add_reset -def _verilog_block_parts(block): - inputs = block.wirevector_subset(Input) - outputs = block.wirevector_subset(Output) - registers = block.wirevector_subset(Register) - wires = block.wirevector_subset() - (inputs | outputs | registers) - memories = {n.op_param[1] for n in block.logic_subset("m@")} - return inputs, outputs, registers, wires, memories + if not isinstance(self.add_reset, bool) and self.add_reset != "asynchronous": + msg = ( + f"Invalid add_reset option {self.add_reset}. Acceptable options are " + "False, True, and 'asynchronous'" + ) + raise PyrtlError(msg) + if self.add_reset and self.gate_graph.get_gate("rst") is not None: + msg = ( + "Found a user-defined wire named 'rst'. Pass in 'add_reset=False' to " + "use your existing reset logic." + ) + raise PyrtlError(msg) -def _to_verilog_header(file, block, varname, add_reset, initialize_registers): - """Print the header of the verilog implementation.""" + self.internal_names = _VerilogSanitizer("_ver_out_tmp_") - def name_sorted(wires): - return _name_sorted(wires, name_mapper=varname) + def gate_key(gate: Gate) -> str: + """Sort Gates by name. - def name_list(wires): - return [varname(w) for w in wires] + MemBlocks have no name, so use their ``wr_en`` name instead. + """ + if gate.name: + return gate.name + return gate.args[2].name - print("// Generated automatically via PyRTL", file=file) - print("// As one initial test of synthesis, map to FPGA with:", file=file) - print('// yosys -p "synth_xilinx -top toplevel" thisfile.v\n', file=file) + # Sanitize all Gate names. + for gate in sorted(self.gate_graph.gates, key=gate_key): + if gate.name: + self.internal_names.make_valid_string(gate.name) - inputs, outputs, registers, wires, memories = _verilog_block_parts(block) + self.inputs = self._name_sorted(self.gate_graph.inputs) + self.outputs = self._name_sorted(self.gate_graph.outputs) - # module name - io_list = ["clk", *name_list(name_sorted(inputs)), *name_list(name_sorted(outputs))] - if add_reset: - io_list.insert(1, "rst") - if any(w.startswith("tmp") for w in io_list): - msg = 'input or output with name starting with "tmp" indicates unnamed IO' - raise PyrtlError(msg) - io_list_str = ", ".join(io_list) - print(f"module toplevel({io_list_str});", file=file) - - # inputs and outputs - print(" input clk;", file=file) - if add_reset: - print(" input rst;", file=file) - for w in name_sorted(inputs): - print( - f" input{_verilog_vector_size_decl(w.bitwidth)} {varname(w)};", file=file - ) - for w in name_sorted(outputs): - print( - f" output{_verilog_vector_size_decl(w.bitwidth)} {varname(w)};", - file=file, - ) - print(file=file) - - # memories and registers - for m in sorted(memories, key=lambda m: m.id): - print( - f" reg{_verilog_vector_size_decl(m.bitwidth)} " - f"mem_{m.id}{_verilog_vector_size_decl(1 << m.addrwidth)}; //{m.name}", - file=file, - ) - for reg in name_sorted(registers): - register_initialization = "" - if initialize_registers: - reset_value = 0 - if reg.reset_value is not None: - reset_value = reg.reset_value - register_initialization = f" = {reg.bitwidth}'d{reset_value}" - print( - f" reg{_verilog_vector_size_decl(reg.bitwidth)} {varname(reg)}" - f"{register_initialization};", - file=file, + self.io_list = [ + "clk", + *[self._verilog_name(input.name) for input in self.inputs], + *[self._verilog_name(output.name) for output in self.outputs], + ] + if self.add_reset: + self.io_list.insert(1, "rst") + if any(io_name.startswith("tmp") for io_name in self.io_list): + msg = 'input or output with name starting with "tmp" indicates unnamed IO' + raise PyrtlError(msg) + + self.registers = self._name_sorted(self.gate_graph.registers) + + # List of all unique MemBlocks and RomBlocks, sorted by memid. + self.all_memblocks = sorted( + # Build a set of unique MemBlocks first, to avoid duplicates. + { + mem_gate.op_param[1] + for mem_gate in self.gate_graph.mem_reads | self.gate_graph.mem_writes + }, + key=lambda memblock: memblock.id, ) - if memories or registers: - print(file=file) - # wires - for w in name_sorted(wires): - print( - f" wire{_verilog_vector_size_decl(w.bitwidth)} {varname(w)};", file=file + # Sanitize all MemBlock names. + for memblock in self.all_memblocks: + self.internal_names.make_valid_string(memblock.name) + + # List of unique MemBlocks (not RomBlocks!), sorted by memid. + self.memblocks = [ + memblock for memblock in self.all_memblocks if type(memblock) is MemBlock + ] + + # List of unique RomBlocks (not MemBlocks!), sorted by memid. + self.romblocks = [ + romblock + for romblock in self.all_memblocks + if isinstance(romblock, RomBlock) + ] + + def _verilog_name(self, name: str) -> str: + """Return the sanitized Verilog identifier name for ``name``.""" + return self.internal_names[name] + + def _name_sorted(self, gates: set[Gate]) -> list[Gate]: + def name_mapper(gate: Gate) -> str: + if gate.name is None: + # MemBlock writes have no name, so instead sort by the name of + # ``wr_en``, since this particular net is used within 'always begin ... + # end' blocks for memory update logic. + return gate.args[2].name + return self._verilog_name(gate.name) + + return _name_sorted(gates, name_mapper=name_mapper) + + def _verilog_size(self, bitwidth: int) -> str: + """Return a Verilog size declaration for ``bitwidth`` bits.""" + return "" if bitwidth == 1 else f"[{bitwidth - 1}:0]" + + def _is_sliced(self, gate: Gate) -> bool: + """Return True iff gate has bitwidth 2 or more, and is an arg to a bit-slice. + + Such gates must be declared, because Verilog's bit-selection operator ``[]`` + only works on wires and registers, not arbitrary expressions. + """ + return ( + gate.bitwidth + and gate.bitwidth > 1 + and any(dest.op == "s" for dest in gate.dests) ) - print(file=file) - - # Write the initial values for read-only memories. If we ever add support outside of - # simulation for initial values for MemBlocks, that would also go here. - roms = {m for m in memories if isinstance(m, RomBlock)} - for m in sorted(roms, key=lambda m: m.id): - print(" initial begin", file=file) - for i in range(1 << m.addrwidth): - print( - f" mem_{m.id}[{i}]={m.bitwidth}'h{m._get_read_data(i):x};", - file=file, - ) - print(" end", file=file) - print(file=file) + def _should_declare_const(self, gate: Gate) -> bool: + """Determine if we should declare a constant Verilog wire for ``gate``. + + This function determines which constant Gates will be declared in Verilog. Any + constant gate without a corresponding Verilog declaration will be inlined. + + Constant Verilog gates are declared for: + + 1. Named constant ``Gates``. + + 2. ``Gates`` that are ``args`` for a ``s`` bit-selection ``Gate``, because + Verilog's bit-selection operator ``[]`` only works on wires and registers, + not arbitrary expressions. + """ + is_const = gate.op == "C" + is_named = not gate.name.startswith("const_") + is_sliced = self._is_sliced(gate) + return is_const and (is_named or is_sliced) + + def _should_declare_wire(self, gate: Gate) -> bool: + """Determine if we should declare a temporary Verilog wire for ``gate``. + + This function determines which temporary Gates will be declared in Verilog. Any + temporary gate without a corresponding Verilog declaration will be inlined. + + Temporary Verilog wires are never declared for Outputs, Inputs, Consts, or + Registers, because those declarations are handled separately by + ``_to_verilog_header``. + + Temporary Verilog wires are never declared for memory writes, because writes + generate no output. + + Otherwise, temporary Verilog wires are declared for: + + 1. Named ``Gates``. -def _to_verilog_combinational(file, block, varname): - """Print the combinational logic of the verilog implementation.""" + 2. ``Gates`` with multiple users. + + 3. ``MemBlock`` reads, because ``_to_verilog_memories`` expects a declaration. + + 4. ``Gates`` that are ``args`` for a ``s`` bit-selection ``Gate``, because + Verilog's bit-selection operator ``[]`` only works on wires and registers, + not arbitrary expressions. + """ + excluded = gate.is_output or gate.op in "ICr@" + is_named = gate.name and not gate.name.startswith("tmp") + multiple_users = len(gate.dests) > 1 + is_read = gate.op == "m" + is_sliced = self._is_sliced(gate) + + return not excluded and (is_named or multiple_users or is_read or is_sliced) + + def _name_and_comment(self, name: str, kind="") -> tuple[str, str]: + """Return the sanitized version of ``name`` and a Verilog comment with the + un-sanitized name. If ``kind`` is provided, it will always be included in the + comment. + """ + sanitized_name = self._verilog_name(name) + if kind: + if sanitized_name != name: + comment = f" // {kind} {name}" + else: + comment = f" // {kind}" + else: + if sanitized_name != name: + comment = f" // {name}" + else: + comment = "" - def name_sorted(wires): - return _name_sorted(wires, name_mapper=varname) + return sanitized_name, comment - print(" // Combinational", file=file) + def _to_verilog_header(self, file: IO, initialize_registers: bool): + """Print the header of the verilog implementation.""" + print("// Generated automatically via PyRTL", file=file) + print("// As one initial test of synthesis, map to FPGA with:", file=file) + print('// yosys -p "synth_xilinx -top toplevel" thisfile.v\n', file=file) - # assign constants (these could be folded for readability later) - for const in name_sorted(block.wirevector_subset(Const)): - print(f" assign {varname(const)} = {const.val:d};", file=file) + # ``declared_gates`` is the set of Gates with corresponding Verilog reg/wire + # declarations. Generated Verilog code can refer to these Gates by name. + self.declared_gates = self.gate_graph.inputs | self.gate_graph.outputs - # walk the block and output combination logic - for net in _net_sorted(block.logic, varname): - assign = None - if net.dests: - assign = f" assign {varname(net.dests[0])}" - if net.op in "w~": # unary ops - opstr = "" if net.op == "w" else net.op - print(f"{assign} = {opstr}{varname(net.args[0])};", file=file) - elif net.op in "&|^+-*<>": # binary ops + # Module name. + print(f"module toplevel({', '.join(self.io_list)});", file=file) + + # Declare Inputs and Outputs. + print(" input clk;", file=file) + if self.add_reset: + print(" input rst;", file=file) + + for input_gate in self.inputs: + sanitized_name, comment = self._name_and_comment(input_gate.name) print( - f"{assign} = {varname(net.args[0])} {net.op} {varname(net.args[1])};", + f" input{self._verilog_size(input_gate.bitwidth)} {sanitized_name};" + f"{comment}", file=file, ) - elif net.op == "=": + for output_gate in self.outputs: + sanitized_name, comment = self._name_and_comment(output_gate.name) print( - f"{assign} = {varname(net.args[0])} == {varname(net.args[1])};", + f" output{self._verilog_size(output_gate.bitwidth)} " + f"{sanitized_name};{comment}", file=file, ) - elif net.op == "x": - # note that the argument order for 'x' is backwards from the ternary - # operator + + # Declare MemBlocks and RomBlocks. + if self.all_memblocks: + print("\n // Memories", file=file) + for memblock in self.all_memblocks: + kind = "MemBlock" + if isinstance(memblock, RomBlock): + kind = "RomBlock" + sanitized_name, comment = self._name_and_comment(memblock.name, kind) print( - f"{assign} = {varname(net.args[0])} ? " - f"{varname(net.args[2])} : {varname(net.args[1])};", + f" reg{self._verilog_size(memblock.bitwidth)} " + f"{sanitized_name}{self._verilog_size(1 << memblock.addrwidth)};" + f"{comment}", file=file, ) - elif net.op == "c": - catlist = ", ".join([varname(w) for w in net.args]) - print(f"{assign} = {{{catlist}}};", file=file) - elif net.op == "s": - # someone please check if we need this special handling for scalars - catlist = ", ".join( - [ - varname(net.args[0]) + f"[{i}]" - if len(net.args[0]) > 1 - else varname(net.args[0]) - for i in reversed(net.op_param) - ] + + # Declare Registers. + self.declared_gates |= self.gate_graph.registers + if self.registers: + print("\n // Registers", file=file) + for reg_gate in self.registers: + register_initialization = "" + if initialize_registers: + reset_value = 0 + if reg_gate.op_param[0] is not None: + reset_value = reg_gate.op_param[0] + register_initialization = f" = {reg_gate.bitwidth}'d{reset_value}" + sanitized_name, comment = self._name_and_comment(reg_gate.name) + print( + f" reg{self._verilog_size(reg_gate.bitwidth)} " + f"{sanitized_name}{register_initialization};{comment}", + file=file, ) - print(f"{assign} = {{{catlist}}};", file=file) - elif net.op in "rm@": - pass # do nothing for registers and memories - else: - msg = f"nets with op '{net.op}' not supported" - raise PyrtlInternalError(msg) - print(file=file) - - -def _to_verilog_sequential(file, block, varname, add_reset): - """Print the sequential logic of the verilog implementation.""" - if not block.logic_subset(op="r"): - return - - print(" // Registers", file=file) - if add_reset == "asynchronous": - print(" always @(posedge clk or posedge rst)", file=file) - else: - print(" always @(posedge clk)", file=file) - print(" begin", file=file) - if add_reset: - print(" if (rst) begin", file=file) - for net in _net_sorted(block.logic, varname): - if net.op == "r": - rval = net.dests[0].reset_value - if rval is None: - rval = 0 - print(f" {varname(net.dests[0])} <= {rval:d};", file=file) - print(" end", file=file) - print(" else begin", file=file) - else: - print(" begin", file=file) - - for net in _net_sorted(block.logic, varname): - if net.op == "r": + + # Declare constants. + const_gates = [] + for const_gate in self._name_sorted(self.gate_graph.consts): + if self._should_declare_const(const_gate): + const_gates.append(const_gate) + self.declared_gates |= set(const_gates) + + if const_gates: + print("\n // Constants", file=file) + for const_gate in const_gates: + sanitized_name, comment = self._name_and_comment(const_gate.name) print( - f" {varname(net.dests[0])} <= {varname(net.args[0])};", + f" wire{self._verilog_size(const_gate.bitwidth)} " + f"{sanitized_name} = {const_gate.bitwidth}'d{const_gate.op_param[0]};" + f"{comment}", file=file, ) - print(" end", file=file) - print(" end", file=file) - print(file=file) - - -def _to_verilog_memories(file, block, varname): - """Print the memories of the verilog implementation.""" - memories = {n.op_param[1] for n in block.logic_subset("m@")} - for m in sorted(memories, key=lambda m: m.id): - print(f" // Memory mem_{m.id}: {m.name}", file=file) - writes = [ - net - for net in _net_sorted(block.logic_subset("@"), varname) - if net.op_param[1] == m - ] - if writes: - print(" always @(posedge clk)", file=file) - print(" begin", file=file) - for net in writes: + + # Declare any needed temporary wires. + temp_gates = [] + for gate in self.gate_graph: + if self._should_declare_wire(gate): + temp_gates.append(gate) + temp_gates = self._name_sorted(temp_gates) + self.declared_gates |= set(temp_gates) + + if temp_gates: + print("\n // Temporaries", file=file) + for temp_gate in temp_gates: + sanitized_name, comment = self._name_and_comment(temp_gate.name) + print( + f" wire{self._verilog_size(temp_gate.bitwidth)} {sanitized_name};" + f"{comment}", + file=file, + ) + + # Write the initial values for read-only memories. If we ever add support + # outside of simulation for initial values for MemBlocks, that would also go + # here. + if self.romblocks: + print("\n // Read-only memory data", file=file) + for romblock in self.romblocks: + print(" initial begin", file=file) + for addr in range(1 << romblock.addrwidth): print( - f" if ({varname(net.args[2])}) begin\n" - f" mem_{net.op_param[0]}[{varname(net.args[0])}] <= " - f"{varname(net.args[1])};\n" - " end", + f" {self._verilog_name(romblock.name)}[{addr}] = " + f"{romblock.bitwidth}'h{romblock._get_read_data(addr):x};", file=file, ) print(" end", file=file) - reads = [ - net - for net in _net_sorted(block.logic_subset("m"), varname) - if net.op_param[1] == m - ] - for net in reads: + + # combinational_gates is the set of Gates that must be assigned by + # ``_to_verilog_combinational``. + self.combinational_gates = ( + self.gate_graph.outputs | set(temp_gates) - self.gate_graph.mem_reads + ) + + def _verilog_expr(self, gate: Gate, lhs: Gate | None = None) -> str: + """Returns a Verilog expression for ``gate`` and its arguments, recursively. + + The returned expression will be used as the right hand side ("rhs") of + assignment statements, and inputs for registers and memories. + + :param lhs: Left-hand side of the assignment statement. This determines when we + return the name of a declared gate, and when we return the expression that + specifies the declared gate's value. + + For example, suppose we have a ``declared_gate`` that says ``x = a + b``. If + we are currently processing another Gate that says ``y = x - 2``, we could + emit ``y = x - 2`` or ``y = (a + b) - 2``. ``x`` is a ``declared_gate``, so + we must emit ``y = x - 2``. So when we generate the ``_verilog_expr`` for + ``x`` in this scenario, we know to just emit ``x`` because ``x`` is a + ``declared_gate``, and we are not currently defining ``x``, because the + assignment's ``lhs`` is not ``x``. + + On the other hand, if we are currently processing the Gate ``x = a + b``, we + must emit ``x = a + b`` instead of the unhelpful ``x = x``. We emit the + former, rather than the latter, for the assignment's right-hand side because + the assignment's ``lhs`` is ``x`` + """ + if gate in self.declared_gates and lhs is not gate: + # If a Verilog wire/reg has been declared for the gate, and we are not + # currently defining the Gate's value, just return the wire's Verilog name. + return self._verilog_name(gate.name) + if gate.op == "C": + # Return the constant's Verilog value. + return f"{gate.bitwidth}'d{gate.op_param[0]}" + + # Convert each of the Gate's args to a Verilog expression. + verilog_args = [self._verilog_expr(arg, lhs) for arg in gate.args] + + # Return an expression that combines ``verilog_args`` with the appropriate + # Verilog operator for ``gate``. + if gate.op == "w": + return verilog_args[0] + if gate.op == "~": + return f"~({verilog_args[0]})" + if gate.op in "&|^+-*<>": + return f"({verilog_args[0]} {gate.op} {verilog_args[1]})" + if gate.op == "=": + return f"({verilog_args[0]} == {verilog_args[1]})" + if gate.op == "x": + return f"({verilog_args[0]} ? {verilog_args[2]} : {verilog_args[1]})" + if gate.op == "c": + if len(verilog_args) == 1: + return verilog_args[0] + return f"{{{', '.join(verilog_args)}}}" + if gate.op == "s": + selections = [] + for sel in reversed(gate.op_param): + if gate.args[0].bitwidth == 1: + selections.append(verilog_args[0]) + else: + selections.append(f"{verilog_args[0]}[{sel}]") + if len(gate.op_param) == 1: + return f"({selections[0]})" + # Special case: slicing multiple copies of the same gate. + if all(sel == gate.op_param[0] for sel in gate.op_param): + return f"{{{len(selections)} {{{selections[0]}}}}}" + # Special case: slicing a consecutive subset. + if tuple(range(gate.op_param[0], gate.op_param[-1] + 1)) == gate.op_param: + return f"({verilog_args[0]}[{gate.op_param[-1]}:{gate.op_param[0]}])" + return f"{{{', '.join(selections)}}}" + + msg = f"Unimplemented op {gate.op} in Gate {gate}" + raise PyrtlError(msg) + + def _to_verilog_combinational(self, file: IO): + """Generate Verilog combinational logic. + + Emits combinational Verilog logic for ``combinational_gates``. This function + only generates assignments for Outputs and temporaries. The other wires and regs + declared by ``_to_verilog_header`` are handled elsewhere: + + - Constant assignments are handled by ``_to_verilog_header``. + + - Register assignments are handled by ``_to_verilog_sequential``. + + - MemBlock reads are handled by ``_to_verilog_memories``. + + :param declared_gates: Set of Gates with corresponding Verilog wire/reg + declarations. Generated Verilog code can refer to these Gates by name. + :param combinational_gates: Set of Gates that must be assigned by + ``_to_verilog_combinational``. + """ + if self.combinational_gates: + print("\n // Combinational logic", file=file) + for assignment_gate in self._name_sorted(self.combinational_gates): + print( + f" assign {self._verilog_name(assignment_gate.name)} = " + f"{self._verilog_expr(assignment_gate, lhs=assignment_gate)};", + file=file, + ) + + def _to_verilog_sequential(self, file: IO): + """Print the sequential logic of the verilog implementation.""" + if not self.gate_graph.registers: + return + + print("\n // Register logic", file=file) + if self.add_reset == "asynchronous": + print(" always @(posedge clk or posedge rst) begin", file=file) + else: + print(" always @(posedge clk) begin", file=file) + if self.add_reset: + print(" if (rst) begin", file=file) + for register in self._name_sorted(self.gate_graph.registers): + reset_value = register.op_param[0] + print( + f" {self._verilog_name(register.name)} <= " + f"{register.bitwidth}'d{reset_value};", + file=file, + ) + print(" end else begin", file=file) + indent = " " + else: + indent = "" + + for register in self._name_sorted(self.gate_graph.registers): print( - f" assign {varname(net.dests[0])} = " - f"mem_{net.op_param[0]}[{varname(net.args[0])}];", + f" {indent}{self._verilog_name(register.name)} <= " + f"{self._verilog_expr(register.args[0])};", file=file, ) - print(file=file) + if self.add_reset: + print(" end", file=file) + print(" end", file=file) + + def _to_verilog_memories(self, file: IO): + """Generate Verilog logic for MemBlock and RomBlock reads and writes.""" + for memblock in self.all_memblocks: + kind = "MemBlock" + if isinstance(memblock, RomBlock): + kind = "RomBlock" + print(f"\n // {kind} {memblock.name} logic", file=file) + + # Find writes to ``memblock``. + write_gates = [] + for write_gate in self._name_sorted(self.gate_graph.mem_writes): + if write_gate.op_param[1] is memblock: + write_gates.append(write_gate) + if write_gates: + print(" always @(posedge clk) begin", file=file) + for write_gate in write_gates: + enable = write_gate.args[2] + verilog_enable = self._verilog_expr(write_gate.args[2]) + verilog_addr = self._verilog_expr(write_gate.args[0]) + verilog_rhs = self._verilog_expr(write_gate.args[1]) + # Simplify the assignment if the enable bit is a constant ``1``. + if enable.op == "C" and enable.op_param[0] == 1: + print( + f" {self._verilog_name(memblock.name)}" + f"[{verilog_addr}] <= {verilog_rhs};", + file=file, + ) + else: + print( + f" if ({verilog_enable}) begin\n" + f" {self._verilog_name(memblock.name)}" + f"[{verilog_addr}] <= {verilog_rhs};\n" + " end", + file=file, + ) + print(" end", file=file) + + # Find reads from ``memblock``. The ``read_gate`` should have been declared + # by ``_to_verilog_header``. + read_gates = [] + for read_gate in self._name_sorted(self.gate_graph.mem_reads): + if read_gate.op_param[1] is memblock: + read_gates.append(read_gate) + for read_gate in self._name_sorted(read_gates): + print( + f" assign {self._verilog_name(read_gate.name)} = " + f"{self._verilog_name(memblock.name)}" + f"[{self._verilog_expr(read_gate.args[0])}];", + file=file, + ) + + def _to_verilog_footer(self, file: IO): + print("endmodule", file=file) + + def output_to_verilog(self, dest_file: IO, initialize_registers: bool): + self._to_verilog_header(dest_file, initialize_registers) + self._to_verilog_combinational(dest_file) + self._to_verilog_sequential(dest_file) + self._to_verilog_memories(dest_file) + self._to_verilog_footer(dest_file) + + def output_verilog_testbench( + self, + dest_file: IO, + simulation_trace: SimulationTrace = None, + toplevel_include: str | None = None, + vcd: str = "waveform.vcd", + cmd: str | None = None, + ): + # Output an include, if given. + if toplevel_include: + print(f'`include "{toplevel_include}"', file=dest_file) + print(file=dest_file) + + # Output header. + print("module tb();", file=dest_file) + + # Declare all block inputs as reg. + print(" reg clk;", file=dest_file) + if self.add_reset: + print(" reg rst;", file=dest_file) + if self.inputs: + print("\n // block Inputs", file=dest_file) + for input_gate in self.inputs: + sanitized_name, comment = self._name_and_comment(input_gate.name) + print( + f" reg{self._verilog_size(input_gate.bitwidth)} {sanitized_name};" + f"{comment}", + file=dest_file, + ) + + # Declare all block outputs as wires. + if self.outputs: + print("\n // block Outputs", file=dest_file) + for output_gate in self.outputs: + sanitized_name, comment = self._name_and_comment(output_gate.name) + print( + f" wire{self._verilog_size(output_gate.bitwidth)} {sanitized_name};" + f"{comment}", + file=dest_file, + ) + print(file=dest_file) + + # Declare an integer for MemBlock initialization. + if len(self.memblocks) > 0: + print(" integer tb_addr;", file=dest_file) + + io_list_str = [f".{io}({io})" for io in self.io_list] + print(f" toplevel block({', '.join(io_list_str)});\n", file=dest_file) + + # Generate the clock signal. + print(" always", file=dest_file) + print(" #5 clk = ~clk;\n", file=dest_file) + + # Generate Input assignments for each cycle in the trace. + print(" initial begin", file=dest_file) + + # If a VCD output is requested, set that up. + if vcd: + print(f' $dumpfile ("{vcd}");', file=dest_file) + print(" $dumpvars;\n", file=dest_file) + + # Initialize clk, and all the registers and memories. + print(" clk = 1'd0;", file=dest_file) + if self.add_reset: + print(" rst = 1'd0;", file=dest_file) + + def default_value() -> int: + """Returns the Simulation's default value for Registers and MemBlocks.""" + if not simulation_trace: + return 0 + return simulation_trace.default_value + + # simulation_trace.register_value_map maps from Register to initial value. Make + # a copy that maps from Register name to initial value. + register_value_map = {} + if simulation_trace: + register_value_map = { + register.name: value + for register, value in simulation_trace.register_value_map.items() + } + + if self.registers: + print("\n // Initialize Registers", file=dest_file) + for reg_gate in self.registers: + # Try using register_value_map first. + initial_value = register_value_map.get(reg_gate.name) + # If that didn't work, use the Register's reset_value. + if not initial_value: + initial_value = reg_gate.op_param[0] + # If there is no reset_value, use the default_value(). + if not initial_value: + initial_value = default_value() + print( + f" block.{self._verilog_name(reg_gate.name)} = " + f"{reg_gate.bitwidth}'d{initial_value};", + file=dest_file, + ) + # Initialize MemBlocks. + if self.memblocks: + print("\n // Initialize MemBlocks", file=dest_file) + for memblock in self.memblocks: + max_addr = 1 << memblock.addrwidth + print( + f" for (tb_addr = 0; tb_addr < {max_addr}; tb_addr++) " + f"begin block.{self._verilog_name(memblock.name)}[tb_addr] = " + f"{memblock.bitwidth}'d{default_value()}; end", + file=dest_file, + ) + if not simulation_trace: + continue + memory_value_map = simulation_trace.memory_value_map.get(memblock) + if not memory_value_map: + continue + for addr, initial_data in memory_value_map.items(): + # The generated Verilog ``for`` loop above just initialized every + # address in the ``MemBlock`` to ``default_value()``, so skip redundant + # initializations. + if initial_data == default_value(): + continue + print( + f" block.{self._verilog_name(memblock.name)}[{addr}] = " + f"{memblock.bitwidth}'d{initial_data};", + file=dest_file, + ) + + # Set Input values for each cycle. + if simulation_trace: + tracelen = max(len(t) for t in simulation_trace.trace.values()) + for i in range(tracelen): + for input_gate in self.inputs: + input_value = simulation_trace.trace[input_gate.name][i] + print( + f" {self._verilog_name(input_gate.name)} = " + f"{input_gate.bitwidth}'d{input_value};", + file=dest_file, + ) + print("\n #10", file=dest_file) + if cmd: + print(f" {cmd}", file=dest_file) -def _to_verilog_footer(file): - print("endmodule\n", file=file) + # Footer. + print(" $finish;", file=dest_file) + print(" end", file=dest_file) + print("endmodule", file=dest_file) + + +def output_to_verilog( + dest_file: IO, + add_reset: bool | str = True, + block: Block = None, + initialize_registers: bool = False, +): + """A function to walk the ``block`` and output it in Verilog format to the open + file. + + The Verilog module will be named ``toplevel``, with a clock input named ``clk``. + + When possible, wires keep their names in the Verilog output. Wire names that do not + satisfy Verilog's naming requirements, and wires that conflict with Verilog keywords + are given new temporary names in the Verilog output. + + :param dest_file: Open file where the Verilog output will be written. + :param add_reset: If reset logic should be added. Allowable options are: ``False`` + (meaning no reset logic is added), ``True`` (default, for adding synchronous + reset logic), and ``'asynchronous'`` (for adding asynchronous reset logic). The + reset input will be named ``rst``, and when ``rst`` is high, registers will be + reset to their ``reset_value``. + :param initialize_registers: Initialize Verilog registers to their ``reset_value``. + When this argument is ``True``, a register like ``Register(name='foo', + bitwidth=8, reset_value=4)`` generates Verilog like ``reg[7:0] foo = 8'd4;``. + :param block: Block to be walked and exported. Defaults to the :ref:`working_block`. + """ + _VerilogOutput(block, add_reset).output_to_verilog(dest_file, initialize_registers) def output_verilog_testbench( - dest_file, + dest_file: IO, simulation_trace: SimulationTrace = None, toplevel_include: str | None = None, vcd: str = "waveform.vcd", @@ -1124,151 +1500,9 @@ def output_verilog_testbench( :func:`output_to_verilog`. :param block: Block containing design to test. Defaults to the :ref:`working_block`. """ - if not isinstance(add_reset, bool) and add_reset != "asynchronous": - msg = ( - f"Invalid add_reset option {add_reset}. Acceptable options are False, " - "True, and 'asynchronous'" - ) - raise PyrtlError(msg) - - block = working_block(block) - - if add_reset and block.get_wirevector_by_name("rst") is not None: - msg = ( - "Found a user-defined wire named 'rst'. Pass in 'add_reset=False' to use " - "your existing reset logic." - ) - raise PyrtlError(msg) - - inputs, outputs, registers, wires, memories = _verilog_block_parts(block) - - ver_name = _VerilogSanitizer("_ver_out_tmp_") - for wire in block.wirevector_set: - ver_name.make_valid_string(wire.name) - - def name_sorted(wires): - return _name_sorted(wires, name_mapper=lambda w: ver_name[w.name]) - - def name_list(wires): - return [ver_name[w.name] for w in wires] - - def init_regvalue(r): - if simulation_trace: - rval = simulation_trace.init_regvalue.get(r) - # Currently, the simulation stores the initial value for all registers in - # init_regvalue, so rval should not be None at this point. For the strange - # case where the trace was made by hand/other special use cases, check it - # against None anyway. - if rval is None: - rval = r.reset_value - if rval is None: - rval = simulation_trace.default_value - return rval - return 0 - - def init_memvalue(m, ix): - # Return None if not present, or if already equal to default value, so we know - # not to emit any additional Verilog initing this mem address. - if simulation_trace: - if m not in simulation_trace.init_memvalue: - return None - v = simulation_trace.init_memvalue[m].get( - ix, simulation_trace.default_value - ) - return None if v == simulation_trace.default_value else v - return None - - def default_value(): - return simulation_trace.default_value if simulation_trace else 0 - - # Output an include, if given - if toplevel_include: - print(f'`include "{toplevel_include}"', file=dest_file) - print(file=dest_file) - - # Output header - print("module tb();", file=dest_file) - - # Declare all block inputs as reg - print(" reg clk;", file=dest_file) - if add_reset: - print(" reg rst;", file=dest_file) - for w in name_sorted(inputs): - print( - f" reg{_verilog_vector_size_decl(w.bitwidth)} {ver_name[w.name]};", - file=dest_file, - ) - - # Declare all block outputs as wires - for w in name_sorted(outputs): - print( - f" wire{_verilog_vector_size_decl(w.bitwidth)} {ver_name[w.name]};", - file=dest_file, - ) - print(file=dest_file) - - # Declare an integer used for init of memories - if len(memories) > 0: - print(" integer tb_iter;", file=dest_file) - - # Instantiate logic block - io_list = ["clk", *name_list(name_sorted(inputs)), *name_list(name_sorted(outputs))] - if add_reset: - io_list.insert(1, "rst") - io_list_str = [f".{w:s}({w:s})" for w in io_list] - print(" toplevel block({:s});\n".format(", ".join(io_list_str)), file=dest_file) - - # Generate clock signal - print(" always", file=dest_file) - print(" #5 clk = ~clk;\n", file=dest_file) - - # Move through all steps of trace, writing out input assignments per cycle - print(" initial begin", file=dest_file) - - # If a VCD output is requested, set that up - if vcd: - print(f' $dumpfile ("{vcd}");', file=dest_file) - print(" $dumpvars;\n", file=dest_file) - - # Initialize clk, and all the registers and memories - print(" clk = 0;", file=dest_file) - if add_reset: - print(" rst = 0;", file=dest_file) - for r in name_sorted(registers): - print( - f" block.{ver_name[r.name]} = {init_regvalue(r)};", - file=dest_file, - ) - for m in sorted(memories, key=lambda m: m.id): - max_iter = 1 << m.addrwidth - print( - f" for (tb_iter = 0; tb_iter < {max_iter}; tb_iter++) " - f"begin block.mem_{m.id}[tb_iter] = {default_value()}; end", - file=dest_file, - ) - for ix in range(max_iter): - # Now just individually update the memory values that aren't the default - val = init_memvalue(m.id, ix) - if val is not None: - print(f" block.mem_{m.id}[{ix}] = {val};", file=dest_file) - - if simulation_trace: - tracelen = max(len(t) for t in simulation_trace.trace.values()) - for i in range(tracelen): - for w in name_sorted(inputs): - print( - f" {ver_name[w.name]} = " - f"{w.bitwidth}'d{simulation_trace.trace[w.name][i]};", - file=dest_file, - ) - print("\n #10", file=dest_file) - if cmd: - print(f" {cmd}", file=dest_file) - - # Footer - print(" $finish;", file=dest_file) - print(" end", file=dest_file) - print("endmodule", file=dest_file) + _VerilogOutput(block, add_reset).output_verilog_testbench( + dest_file, simulation_trace, toplevel_include, vcd, cmd + ) # ---------------------------------------------------------------- @@ -1276,8 +1510,6 @@ def default_value(): # |___ | |__) |__) | | # | | | \ | \ | |___ # - - def output_to_firrtl( open_file, rom_blocks: list[RomBlock] | None = None, block: Block = None ): diff --git a/pyrtl/simulation.py b/pyrtl/simulation.py index 6e610656..0da5ac36 100644 --- a/pyrtl/simulation.py +++ b/pyrtl/simulation.py @@ -2,7 +2,6 @@ from __future__ import annotations -import copy import math import numbers import os @@ -174,13 +173,11 @@ def _initialize(self, register_value_map=None, memory_value_map=None): raise PyrtlError(msg) if isinstance(self.block, PostSynthBlock): mem = self.block.mem_map[mem] - self.memvalue[mem.id] = mem_map + self.memvalue[mem.id] = mem_map.copy() + max_addr_val = 2**mem.addrwidth - mem_map = { - addr: infer_val_and_bitwidth(val, bitwidth=mem.bitwidth).value - for addr, val in mem_map.items() - } - for addr, _val in mem_map.items(): + for addr, val in mem_map.items(): + val = infer_val_and_bitwidth(val, bitwidth=mem.bitwidth).value if addr < 0 or addr >= max_addr_val: msg = f"error, address {addr} in {mem.name} outside of bounds" raise PyrtlError(msg) @@ -196,7 +193,7 @@ def _initialize(self, register_value_map=None, memory_value_map=None): if self.tracer is not None: self.tracer._set_initial_values( - self.default_value, self.regvalue.copy(), copy.deepcopy(self.memvalue) + self.default_value, register_value_map, memory_value_map ) def step(self, provided_inputs: dict[str, int] | None = None): @@ -673,7 +670,7 @@ def _initialize(self, register_value_map=None, memory_value_map=None): if self.tracer is not None: self.tracer._set_initial_values( - self.default_value, self.regs.copy(), copy.deepcopy(self.mems) + self.default_value, register_value_map, memory_value_map ) context = {} @@ -687,7 +684,7 @@ def _initialize_mems(self, memory_value_map): msg = "error, one or more of the memories in the map is a RomBlock" raise PyrtlError(msg) name = self._mem_varname(mem) - self.mems[name] = mem_map + self.mems[name] = mem_map.copy() for net in self.block.logic_subset("m@"): mem = net.op_param[1] @@ -1586,8 +1583,8 @@ def is_internal_name(name): self._wires = {wv.name: wv for wv in wires_to_track} # remember for initializing during Verilog testbench output self.default_value = 0 - self.init_regvalue = {} - self.init_memvalue = {} + self.register_value_map = {} + self.memory_value_map = {} def __len__(self): """Return the current length of the trace in cycles.""" @@ -1904,7 +1901,12 @@ def formatted_trace_line(wire, trace): for trace_name in trace_list: print(formatted_trace_line(trace_name, self.trace[trace_name]), file=file) - def _set_initial_values(self, default_value, init_regvalue, init_memvalue): + def _set_initial_values( + self, + default_value: int, + register_value_map: dict[Register, int], + memory_value_map: dict[MemBlock, dict[int, int]], + ): """Remember the default values that were used when starting the trace. This is needed when using this trace for outputting a Verilog testbench, and is @@ -1912,12 +1914,15 @@ def _set_initial_values(self, default_value, init_regvalue, init_memvalue): :param default_value: Default value to be used for all registers and memory locations if not found in the other passed in maps - :param init_regvalue: Default value for all the registers - :param init_memvvalue: Default value for memory locations of given maps + :param register_value_map: Default value for each ``Register``. Maps from + ``Register`` to the ``Register``'s initial value. + :param memory_value_map: Default value for each ``MemBlock``. Maps from + ``MemBlock`` to a ``{addr: data}`` ``dict`` with the ``MemBlock``'s initial + values. """ self.default_value = default_value - self.init_regvalue = init_regvalue - self.init_memvalue = init_memvalue + self.register_value_map = register_value_map + self.memory_value_map = memory_value_map def print_perf_counters(self, *trace_names: str, file=sys.stdout): """Print performance counter statistics for ``trace_names``. diff --git a/tests/rtllib/test_aes.py b/tests/rtllib/test_aes.py index 2de8c648..e196c09a 100644 --- a/tests/rtllib/test_aes.py +++ b/tests/rtllib/test_aes.py @@ -65,24 +65,6 @@ def test_inv_mix_columns(self): ) self.assertEqual(calculated_result, real_res) - @unittest.skip - def test_key_expansion(self): - # This is not at all correct. Needs to be completely rewritten - self.out_vector <<= pyrtl.concat_list(self.aes_decrypt._key_gen(self.in_vector)) - - in_vals = [ - 0xD1876C0F79C4300AB45594ADD66FF41F, - 0xFA636A2825B339C940668A3157244D17, - ] - true_result = [ - 0x3E175076B61C04678DFC2295F6A8BFC0, - 0x2DFB02343F6D12DD09337EC75B36E3F0, - ] - calculated_result = testingutils.sim_and_ret_out( - self.out_vector, (self.in_vector,), (in_vals,) - ) - self.assertEqual(calculated_result, true_result) - def test_aes_full(self): aes_key = pyrtl.Input(bitwidth=128, name="aes_key") self.out_vector <<= self.aes_decrypt.decryption(self.in_vector, aes_key) @@ -214,24 +196,6 @@ def test_mix_columns(self): ) self.assertEqual(calculated_result, real_res) - @unittest.skip - def test_key_expansion(self): - # This is not at all correct. Needs to be completely rewritten - self.out_vector <<= pyrtl.concat_list(self.aes_encrypt._key_gen(self.in_vector)) - - in_vals = [ - 0x4C9C1E66F771F0762C3F868E534DF256, - 0xC57E1C159A9BD286F05F4BE098C63439, - ] - true_result = [ - 0x3BD92268FC74FB735767CBE0C0590E2D, - 0xB458124C68B68A014B99F82E5F15554C, - ] - calculated_result = testingutils.sim_and_ret_out( - self.out_vector, (self.in_vector,), (in_vals,) - ) - self.assertEqual(calculated_result, true_result) - def test_aes_full(self): aes_key = pyrtl.Input(bitwidth=128, name="aes_key") self.out_vector <<= self.aes_encrypt.encryption(self.in_vector, aes_key) diff --git a/tests/test_compilesim.py b/tests/test_compilesim.py index 88931e39..d020c6fc 100644 --- a/tests/test_compilesim.py +++ b/tests/test_compilesim.py @@ -225,9 +225,8 @@ def test_reg_directly_before_reg(self): pass def test_weird_wire_names(self): - """ - Some simulations need to be careful when handling special names (eg Fastsim June - 2016) + """Some simulations need to be careful when handling special names (eg Fastsim + June 2016) """ i = pyrtl.Input(8, '"182&!!!\n') o = pyrtl.Output(8, "*^*)#*$'*") @@ -528,10 +527,9 @@ def setUp(self): def test_adder_simulation(self): sim_trace = pyrtl.SimulationTrace(wires_to_track=[self.o]) - on_reset = {} # signal states to be set when reset is asserted # build the actual simulation environment - sim = self.sim(register_value_map=on_reset, default_value=0, tracer=sim_trace) + sim = self.sim(tracer=sim_trace) sim.step_multiple(nsteps=15) output = io.StringIO() @@ -609,10 +607,9 @@ def setUp(self): def test_vcd_output(self): sim_trace = pyrtl.SimulationTrace(wires_to_track=[self.o]) - on_reset = {} # signal states to be set when reset is asserted # build the actual simulation environment - sim = self.sim(register_value_map=on_reset, default_value=0, tracer=sim_trace) + sim = self.sim(tracer=sim_trace) sim.step_multiple(nsteps=15) test_output = io.StringIO() @@ -829,6 +826,7 @@ def test_mem_val_map(self): self.assertEqual( sim.inspect_mem(self.mem1), {0: 0, 1: 2, 2: 3, 3: 3, 4: 4, 5: 5} ) + self.assertEqual(sim.tracer.memory_value_map, mem_val_map) def test_mem_val_map_defaults(self): read_addr3 = pyrtl.Input(self.addrwidth) @@ -849,6 +847,7 @@ def test_mem_val_map_defaults(self): output = io.StringIO() sim.tracer.print_trace(output, compact=True) self.assertEqual(output.getvalue(), "o1 000000\no2 000000\no3 000000\n") + self.assertEqual(sim.tracer.memory_value_map, mem_val_map) def test_mem_val_map_empty_mapping(self): read_addr3 = pyrtl.Input(self.addrwidth) @@ -869,6 +868,7 @@ def test_mem_val_map_empty_mapping(self): output = io.StringIO() sim.tracer.print_trace(output, compact=True) self.assertEqual(output.getvalue(), "o1 000000\no2 000000\no3 000000\n") + self.assertEqual(sim.tracer.memory_value_map, mem_val_map) class MemBlockLargeBase(unittest.TestCase): @@ -944,23 +944,32 @@ def check_trace(self, correct_string, **kwargs): output = io.StringIO() sim.tracer.print_trace(output, compact=True) self.assertEqual(output.getvalue(), correct_string) + return sim.tracer def test_reset_value(self): self.check_trace("o 70012345\n") def test_register_map_overrides_reset_value(self): - self.check_trace("o 36012345\n", register_value_map={self.r1: 6, self.r2: 3}) + register_value_map = {self.r1: 6, self.r2: 3} + tracer = self.check_trace("o 36012345\n", register_value_map=register_value_map) + self.assertEqual(tracer.register_value_map, register_value_map) def test_partial_map(self): - self.check_trace("o 76012345\n", register_value_map={self.r1: 6}) + register_value_map = {self.r1: 6} + tracer = self.check_trace("o 76012345\n", register_value_map=register_value_map) + self.assertEqual(tracer.register_value_map, register_value_map) def test_partial_map_overrides_default(self): - self.check_trace( - "o 76012345\n", default_value=5, register_value_map={self.r1: 6} + register_value_map = {self.r1: 6} + tracer = self.check_trace( + "o 76012345\n", default_value=5, register_value_map=register_value_map ) + self.assertEqual(tracer.default_value, 5) + self.assertEqual(tracer.register_value_map, register_value_map) def test_default_used_for_non_reset_value(self): - self.check_trace("o 75012345\n", default_value=5) + tracer = self.check_trace("o 75012345\n", default_value=5) + self.assertEqual(tracer.default_value, 5) class RomBlockSimBase(unittest.TestCase): @@ -1168,9 +1177,7 @@ def test_unsigned_memory_value_map(self): class InspectBase(unittest.TestCase): - """ - Unittests for sim.inspect_mem - """ + """Unittests for sim.inspect_mem.""" def setUp(self): pyrtl.reset_working_block() @@ -1220,9 +1227,7 @@ def test_invalid_base(self): def make_unittests(): - """ - Generates separate unittests for each of the simulators - """ + """Generates separate unittests for each of the simulators.""" g = globals() unittests = {} base_tests = { diff --git a/tests/test_core.py b/tests/test_core.py index 4cb2b9af..77fc510f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -785,5 +785,37 @@ def test_wire_not_in_net_connections(self): dst_nets[w] +class TestNameSanitizer(unittest.TestCase): + def test_name_collision(self): + # Test a sanitized name that collides with an unsanitized name. + sanitizer = pyrtl.core._NameSanitizer( + identifier_regex_str=pyrtl.core._py_regex, + internal_prefix="sanitized", + ) + # This name should be sanitized by replacing the dot with an underscore. + sanitized_dot = sanitizer.make_valid_string("foo.bar") + self.assertEqual(sanitized_dot, "foo_bar") + self.assertEqual(sanitizer["foo.bar"], "foo_bar") + + # This name collides with the sanitized name we just generated, so the first + # internal_index should be appended. + sanitized_underscore = sanitizer.make_valid_string("foo_bar") + self.assertEqual(sanitized_underscore, "foo_bar0") + self.assertEqual(sanitizer["foo_bar"], "foo_bar0") + + # This name does not require sanitization, but it will collide with the next + # foo.bar variant. + sanitized_one = sanitizer.make_valid_string("foo_bar1") + self.assertEqual(sanitized_one, "foo_bar1") + self.assertEqual(sanitizer["foo_bar1"], "foo_bar1") + + # Attempting to sanitize foo!bar by appending internal_index collides with the + # name we just registered. _NameSanitizer should give up and use + # internal_prefix. + sanitized_bang = sanitizer.make_valid_string("foo!bar") + self.assertEqual(sanitized_bang, "sanitized2") + self.assertEqual(sanitizer["foo!bar"], "sanitized2") + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_gate_graph.py b/tests/test_gate_graph.py index 82bcab4a..d4ad5b4a 100644 --- a/tests/test_gate_graph.py +++ b/tests/test_gate_graph.py @@ -28,6 +28,11 @@ def test_gate_retrieval(self): gate_graph = pyrtl.GateGraph() + self.assertEqual( + sorted(gate.name for gate in gate_graph), + ["a", "ab", "abc", "b", "c"], + ) + self.assertEqual( sorted([gate.name for gate in gate_graph.gates]), ["a", "ab", "abc", "b", "c"], diff --git a/tests/test_importexport.py b/tests/test_importexport.py index 13b4ed25..c55bdeb5 100644 --- a/tests/test_importexport.py +++ b/tests/test_importexport.py @@ -772,20 +772,12 @@ def test_blif_nor_gate_correct(self): input clk; output[12:0] o; - wire[3:0] const_0_12; - wire[2:0] const_1_3; - wire[5:0] k; - wire[12:0] tmp0; - - // Combinational - assign const_0_12 = 12; - assign const_1_3 = 3; - assign k = 38; - assign o = tmp0; - assign tmp0 = {const_0_12, const_1_3, k}; + // Constants + wire[5:0] k = 6'd38; + // Combinational logic + assign o = {4'd12, 3'd3, k}; endmodule - """ @@ -800,585 +792,202 @@ def test_blif_nor_gate_correct(self): input[3:0] a; output[5:0] o; - reg[3:0] mem_0[3:0]; //z - reg[3:0] mem_1[3:0]; //tmp0 - reg[3:0] mem_2[3:0]; //tmp1 - reg[3:0] mem_3[3:0]; //tmp2 - reg[3:0] mem_4[3:0]; //tmp3 - reg[3:0] mem_5[3:0]; //tmp4 - reg[3:0] mem_6[3:0]; //tmp5 - reg[3:0] mem_7[3:0]; //tmp6 - reg[3:0] mem_8[3:0]; //tmp7 - reg[3:0] mem_9[3:0]; //tmp8 - reg[3:0] mem_10[3:0]; //tmp9 - reg[3:0] mem_11[3:0]; //tmp10 - reg[3:0] mem_12[3:0]; //tmp11 + // Memories + reg[3:0] z[3:0]; // MemBlock + reg[3:0] tmp0[3:0]; // MemBlock + reg[3:0] tmp1[3:0]; // MemBlock + reg[3:0] tmp2[3:0]; // MemBlock + reg[3:0] tmp3[3:0]; // MemBlock + reg[3:0] tmp4[3:0]; // MemBlock + reg[3:0] tmp5[3:0]; // MemBlock + reg[3:0] tmp6[3:0]; // MemBlock + reg[3:0] tmp7[3:0]; // MemBlock + reg[3:0] tmp8[3:0]; // MemBlock + reg[3:0] tmp9[3:0]; // MemBlock + reg[3:0] tmp10[3:0]; // MemBlock + reg[3:0] tmp11[3:0]; // MemBlock + + // Registers reg[3:0] r; reg[3:0] s; - wire[1:0] const_0_0; - wire[1:0] const_1_0; - wire const_2_1; - wire[1:0] const_3_1; - wire const_4_1; - wire const_5_0; - wire[1:0] const_6_1; - wire const_7_1; - wire[1:0] const_8_0; - wire[1:0] const_9_0; - wire const_10_1; - wire[1:0] const_11_1; - wire const_12_1; - wire const_13_0; - wire[1:0] const_14_1; - wire const_15_1; - wire[1:0] const_16_0; - wire[1:0] const_17_0; - wire const_18_1; - wire[1:0] const_19_1; - wire const_20_1; - wire const_21_0; - wire[1:0] const_22_1; - wire const_23_1; - wire[1:0] const_24_0; - wire[1:0] const_25_0; - wire const_26_1; - wire[1:0] const_27_1; - wire const_28_1; - wire const_29_0; - wire[1:0] const_30_1; - wire const_31_1; - wire[1:0] const_32_0; - wire[1:0] const_33_0; - wire const_34_1; - wire[1:0] const_35_1; - wire const_36_1; - wire const_37_0; - wire[1:0] const_38_1; - wire const_39_1; - wire[1:0] const_40_0; - wire[1:0] const_41_0; - wire const_42_1; - wire[1:0] const_43_1; - wire const_44_1; - wire const_45_0; - wire[1:0] const_46_1; - wire const_47_1; - wire[1:0] const_48_0; - wire[1:0] const_49_0; - wire const_50_1; - wire[1:0] const_51_1; - wire const_52_1; - wire const_53_0; - wire[1:0] const_54_1; - wire const_55_1; - wire[1:0] const_56_0; - wire[1:0] const_57_0; - wire const_58_1; - wire[1:0] const_59_1; - wire const_60_1; - wire const_61_0; - wire[1:0] const_62_1; - wire const_63_1; - wire[1:0] const_64_0; - wire[1:0] const_65_0; - wire const_66_1; - wire[1:0] const_67_1; - wire const_68_1; - wire const_69_0; - wire[1:0] const_70_1; - wire const_71_1; - wire[1:0] const_72_0; - wire[1:0] const_73_0; - wire const_74_1; - wire[1:0] const_75_1; - wire const_76_1; - wire const_77_0; - wire[1:0] const_78_1; - wire const_79_1; - wire[1:0] const_80_0; - wire[1:0] const_81_0; - wire const_82_1; - wire[1:0] const_83_1; - wire const_84_1; - wire const_85_0; - wire[1:0] const_86_1; - wire const_87_1; - wire[1:0] const_88_0; - wire[1:0] const_89_0; - wire const_90_1; - wire[1:0] const_91_1; - wire const_92_1; - wire const_93_0; - wire[1:0] const_94_1; - wire const_95_1; - wire const_96_1; - wire const_97_0; - wire const_98_0; - wire const_99_1; - wire const_100_0; - wire[1:0] const_101_0; - wire[1:0] const_102_0; - wire const_103_1; - wire[3:0] const_104_9; - wire[1:0] const_105_0; - wire const_106_0; - wire[1:0] const_107_0; - wire const_108_0; - wire[2:0] tmp12; - wire[3:0] tmp13; + // Temporaries wire[4:0] tmp14; - wire[3:0] tmp15; - wire[2:0] tmp16; - wire[3:0] tmp17; wire[4:0] tmp18; - wire[3:0] tmp19; - wire[2:0] tmp20; - wire[3:0] tmp21; wire[4:0] tmp22; - wire[3:0] tmp23; - wire[2:0] tmp24; - wire[3:0] tmp25; wire[4:0] tmp26; - wire[3:0] tmp27; - wire[2:0] tmp28; - wire[3:0] tmp29; wire[4:0] tmp30; - wire[3:0] tmp31; - wire[2:0] tmp32; - wire[3:0] tmp33; wire[4:0] tmp34; - wire[3:0] tmp35; - wire[2:0] tmp36; - wire[3:0] tmp37; wire[4:0] tmp38; - wire[3:0] tmp39; - wire[2:0] tmp40; - wire[3:0] tmp41; wire[4:0] tmp42; - wire[3:0] tmp43; - wire[2:0] tmp44; - wire[3:0] tmp45; wire[4:0] tmp46; - wire[3:0] tmp47; - wire[2:0] tmp48; - wire[3:0] tmp49; wire[4:0] tmp50; - wire[3:0] tmp51; - wire[2:0] tmp52; - wire[3:0] tmp53; wire[4:0] tmp54; - wire[3:0] tmp55; - wire[2:0] tmp56; - wire[3:0] tmp57; wire[4:0] tmp58; - wire[3:0] tmp59; wire[4:0] tmp60; - wire[3:0] tmp61; - wire[4:0] tmp62; - wire[5:0] tmp63; - wire[1:0] tmp64; - wire[5:0] tmp65; wire[6:0] tmp66; - wire[3:0] tmp67; - wire[2:0] tmp68; - wire[3:0] tmp69; wire[4:0] tmp70; - wire[3:0] tmp71; wire[3:0] tmp72; - wire tmp73; - wire[4:0] tmp74; - wire[5:0] tmp75; wire[3:0] tmp76; - wire[1:0] tmp77; - wire[5:0] tmp78; wire[6:0] tmp79; - wire[5:0] tmp80; - - // Combinational - assign const_0_0 = 0; - assign const_1_0 = 0; - assign const_2_1 = 1; - assign const_3_1 = 1; - assign const_4_1 = 1; - assign const_5_0 = 0; - assign const_6_1 = 1; - assign const_7_1 = 1; - assign const_8_0 = 0; - assign const_9_0 = 0; - assign const_10_1 = 1; - assign const_11_1 = 1; - assign const_12_1 = 1; - assign const_13_0 = 0; - assign const_14_1 = 1; - assign const_15_1 = 1; - assign const_16_0 = 0; - assign const_17_0 = 0; - assign const_18_1 = 1; - assign const_19_1 = 1; - assign const_20_1 = 1; - assign const_21_0 = 0; - assign const_22_1 = 1; - assign const_23_1 = 1; - assign const_24_0 = 0; - assign const_25_0 = 0; - assign const_26_1 = 1; - assign const_27_1 = 1; - assign const_28_1 = 1; - assign const_29_0 = 0; - assign const_30_1 = 1; - assign const_31_1 = 1; - assign const_32_0 = 0; - assign const_33_0 = 0; - assign const_34_1 = 1; - assign const_35_1 = 1; - assign const_36_1 = 1; - assign const_37_0 = 0; - assign const_38_1 = 1; - assign const_39_1 = 1; - assign const_40_0 = 0; - assign const_41_0 = 0; - assign const_42_1 = 1; - assign const_43_1 = 1; - assign const_44_1 = 1; - assign const_45_0 = 0; - assign const_46_1 = 1; - assign const_47_1 = 1; - assign const_48_0 = 0; - assign const_49_0 = 0; - assign const_50_1 = 1; - assign const_51_1 = 1; - assign const_52_1 = 1; - assign const_53_0 = 0; - assign const_54_1 = 1; - assign const_55_1 = 1; - assign const_56_0 = 0; - assign const_57_0 = 0; - assign const_58_1 = 1; - assign const_59_1 = 1; - assign const_60_1 = 1; - assign const_61_0 = 0; - assign const_62_1 = 1; - assign const_63_1 = 1; - assign const_64_0 = 0; - assign const_65_0 = 0; - assign const_66_1 = 1; - assign const_67_1 = 1; - assign const_68_1 = 1; - assign const_69_0 = 0; - assign const_70_1 = 1; - assign const_71_1 = 1; - assign const_72_0 = 0; - assign const_73_0 = 0; - assign const_74_1 = 1; - assign const_75_1 = 1; - assign const_76_1 = 1; - assign const_77_0 = 0; - assign const_78_1 = 1; - assign const_79_1 = 1; - assign const_80_0 = 0; - assign const_81_0 = 0; - assign const_82_1 = 1; - assign const_83_1 = 1; - assign const_84_1 = 1; - assign const_85_0 = 0; - assign const_86_1 = 1; - assign const_87_1 = 1; - assign const_88_0 = 0; - assign const_89_0 = 0; - assign const_90_1 = 1; - assign const_91_1 = 1; - assign const_92_1 = 1; - assign const_93_0 = 0; - assign const_94_1 = 1; - assign const_95_1 = 1; - assign const_96_1 = 1; - assign const_97_0 = 0; - assign const_98_0 = 0; - assign const_99_1 = 1; - assign const_100_0 = 0; - assign const_101_0 = 0; - assign const_102_0 = 0; - assign const_103_1 = 1; - assign const_104_9 = 9; - assign const_105_0 = 0; - assign const_106_0 = 0; - assign const_107_0 = 0; - assign const_108_0 = 0; - assign o = tmp80; - assign tmp12 = {const_5_0, const_5_0, const_5_0}; - assign tmp13 = {tmp12, const_4_1}; - assign tmp14 = r + tmp13; - assign tmp15 = {tmp14[3], tmp14[2], tmp14[1], tmp14[0]}; - assign tmp16 = {const_13_0, const_13_0, const_13_0}; - assign tmp17 = {tmp16, const_12_1}; - assign tmp18 = r + tmp17; - assign tmp19 = {tmp18[3], tmp18[2], tmp18[1], tmp18[0]}; - assign tmp20 = {const_21_0, const_21_0, const_21_0}; - assign tmp21 = {tmp20, const_20_1}; - assign tmp22 = r + tmp21; - assign tmp23 = {tmp22[3], tmp22[2], tmp22[1], tmp22[0]}; - assign tmp24 = {const_29_0, const_29_0, const_29_0}; - assign tmp25 = {tmp24, const_28_1}; - assign tmp26 = r + tmp25; - assign tmp27 = {tmp26[3], tmp26[2], tmp26[1], tmp26[0]}; - assign tmp28 = {const_37_0, const_37_0, const_37_0}; - assign tmp29 = {tmp28, const_36_1}; - assign tmp30 = r + tmp29; - assign tmp31 = {tmp30[3], tmp30[2], tmp30[1], tmp30[0]}; - assign tmp32 = {const_45_0, const_45_0, const_45_0}; - assign tmp33 = {tmp32, const_44_1}; - assign tmp34 = r + tmp33; - assign tmp35 = {tmp34[3], tmp34[2], tmp34[1], tmp34[0]}; - assign tmp36 = {const_53_0, const_53_0, const_53_0}; - assign tmp37 = {tmp36, const_52_1}; - assign tmp38 = r + tmp37; - assign tmp39 = {tmp38[3], tmp38[2], tmp38[1], tmp38[0]}; - assign tmp40 = {const_61_0, const_61_0, const_61_0}; - assign tmp41 = {tmp40, const_60_1}; - assign tmp42 = r + tmp41; - assign tmp43 = {tmp42[3], tmp42[2], tmp42[1], tmp42[0]}; - assign tmp44 = {const_69_0, const_69_0, const_69_0}; - assign tmp45 = {tmp44, const_68_1}; - assign tmp46 = r + tmp45; - assign tmp47 = {tmp46[3], tmp46[2], tmp46[1], tmp46[0]}; - assign tmp48 = {const_77_0, const_77_0, const_77_0}; - assign tmp49 = {tmp48, const_76_1}; - assign tmp50 = r + tmp49; - assign tmp51 = {tmp50[3], tmp50[2], tmp50[1], tmp50[0]}; - assign tmp52 = {const_85_0, const_85_0, const_85_0}; - assign tmp53 = {tmp52, const_84_1}; - assign tmp54 = r + tmp53; - assign tmp55 = {tmp54[3], tmp54[2], tmp54[1], tmp54[0]}; - assign tmp56 = {const_93_0, const_93_0, const_93_0}; - assign tmp57 = {tmp56, const_92_1}; - assign tmp58 = r + tmp57; - assign tmp59 = {tmp58[3], tmp58[2], tmp58[1], tmp58[0]}; - assign tmp60 = a + r; - assign tmp61 = {const_97_0, const_97_0, const_97_0, const_97_0}; - assign tmp62 = {tmp61, const_96_1}; - assign tmp63 = tmp60 + tmp62; - assign tmp64 = {const_98_0, const_98_0}; - assign tmp65 = {tmp64, s}; - assign tmp66 = tmp63 - tmp65; - assign tmp67 = {tmp66[3], tmp66[2], tmp66[1], tmp66[0]}; - assign tmp68 = {const_100_0, const_100_0, const_100_0}; - assign tmp69 = {tmp68, const_99_1}; - assign tmp70 = a - tmp69; - assign tmp71 = {tmp70[3], tmp70[2], tmp70[1], tmp70[0]}; - assign tmp73 = {const_106_0}; - assign tmp74 = {tmp73, tmp72}; - assign tmp75 = tmp60 + tmp74; - assign tmp77 = {const_108_0, const_108_0}; - assign tmp78 = {tmp77, tmp76}; - assign tmp79 = tmp75 + tmp78; - assign tmp80 = {tmp79[5], tmp79[4], tmp79[3], tmp79[2], tmp79[1], tmp79[0]}; - // Registers - always @(posedge clk) - begin + // Combinational logic + assign o = (tmp79[5:0]); + assign tmp14 = (r + {{3 {1'd0}}, 1'd1}); + assign tmp18 = (r + {{3 {1'd0}}, 1'd1}); + assign tmp22 = (r + {{3 {1'd0}}, 1'd1}); + assign tmp26 = (r + {{3 {1'd0}}, 1'd1}); + assign tmp30 = (r + {{3 {1'd0}}, 1'd1}); + assign tmp34 = (r + {{3 {1'd0}}, 1'd1}); + assign tmp38 = (r + {{3 {1'd0}}, 1'd1}); + assign tmp42 = (r + {{3 {1'd0}}, 1'd1}); + assign tmp46 = (r + {{3 {1'd0}}, 1'd1}); + assign tmp50 = (r + {{3 {1'd0}}, 1'd1}); + assign tmp54 = (r + {{3 {1'd0}}, 1'd1}); + assign tmp58 = (r + {{3 {1'd0}}, 1'd1}); + assign tmp60 = (a + r); + assign tmp66 = ((tmp60 + {{4 {1'd0}}, 1'd1}) - {{2 {1'd0}}, s}); + assign tmp70 = (a - {{3 {1'd0}}, 1'd1}); + assign tmp79 = ((tmp60 + {(1'd0), tmp72}) + {{2 {1'd0}}, tmp76}); + + // Register logic + always @(posedge clk) begin if (rst) begin - r <= 0; - s <= 13; - end - else begin - r <= tmp67; - s <= tmp71; + r <= 4'd0; + s <= 4'd13; + end else begin + r <= (tmp66[3:0]); + s <= (tmp70[3:0]); end end - // Memory mem_0: z - always @(posedge clk) - begin - if (const_103_1) begin - mem_0[const_102_0] <= const_104_9; - end + // MemBlock z logic + always @(posedge clk) begin + z[2'd0] <= 4'd9; end - // Memory mem_1: tmp0 - always @(posedge clk) - begin - if (const_2_1) begin - mem_1[const_1_0] <= a; - end - if (const_7_1) begin - mem_1[const_6_1] <= tmp15; - end + // MemBlock tmp0 logic + always @(posedge clk) begin + tmp0[2'd0] <= a; + tmp0[2'd1] <= (tmp14[3:0]); end - assign tmp72 = mem_1[const_105_0]; + assign tmp72 = tmp0[2'd0]; - // Memory mem_2: tmp1 - always @(posedge clk) - begin - if (const_10_1) begin - mem_2[const_9_0] <= a; - end - if (const_15_1) begin - mem_2[const_14_1] <= tmp19; - end + // MemBlock tmp1 logic + always @(posedge clk) begin + tmp1[2'd0] <= a; + tmp1[2'd1] <= (tmp18[3:0]); end - assign tmp76 = mem_2[const_107_0]; + assign tmp76 = tmp1[2'd0]; - // Memory mem_3: tmp2 - always @(posedge clk) - begin - if (const_18_1) begin - mem_3[const_17_0] <= a; - end - if (const_23_1) begin - mem_3[const_22_1] <= tmp23; - end + // MemBlock tmp2 logic + always @(posedge clk) begin + tmp2[2'd0] <= a; + tmp2[2'd1] <= (tmp22[3:0]); end - // Memory mem_4: tmp3 - always @(posedge clk) - begin - if (const_26_1) begin - mem_4[const_25_0] <= a; - end - if (const_31_1) begin - mem_4[const_30_1] <= tmp27; - end + // MemBlock tmp3 logic + always @(posedge clk) begin + tmp3[2'd0] <= a; + tmp3[2'd1] <= (tmp26[3:0]); end - // Memory mem_5: tmp4 - always @(posedge clk) - begin - if (const_34_1) begin - mem_5[const_33_0] <= a; - end - if (const_39_1) begin - mem_5[const_38_1] <= tmp31; - end + // MemBlock tmp4 logic + always @(posedge clk) begin + tmp4[2'd0] <= a; + tmp4[2'd1] <= (tmp30[3:0]); end - // Memory mem_6: tmp5 - always @(posedge clk) - begin - if (const_42_1) begin - mem_6[const_41_0] <= a; - end - if (const_47_1) begin - mem_6[const_46_1] <= tmp35; - end + // MemBlock tmp5 logic + always @(posedge clk) begin + tmp5[2'd0] <= a; + tmp5[2'd1] <= (tmp34[3:0]); end - // Memory mem_7: tmp6 - always @(posedge clk) - begin - if (const_50_1) begin - mem_7[const_49_0] <= a; - end - if (const_55_1) begin - mem_7[const_54_1] <= tmp39; - end + // MemBlock tmp6 logic + always @(posedge clk) begin + tmp6[2'd0] <= a; + tmp6[2'd1] <= (tmp38[3:0]); end - // Memory mem_8: tmp7 - always @(posedge clk) - begin - if (const_58_1) begin - mem_8[const_57_0] <= a; - end - if (const_63_1) begin - mem_8[const_62_1] <= tmp43; - end + // MemBlock tmp7 logic + always @(posedge clk) begin + tmp7[2'd0] <= a; + tmp7[2'd1] <= (tmp42[3:0]); end - // Memory mem_9: tmp8 - always @(posedge clk) - begin - if (const_66_1) begin - mem_9[const_65_0] <= a; - end - if (const_71_1) begin - mem_9[const_70_1] <= tmp47; - end + // MemBlock tmp8 logic + always @(posedge clk) begin + tmp8[2'd0] <= a; + tmp8[2'd1] <= (tmp46[3:0]); end - // Memory mem_10: tmp9 - always @(posedge clk) - begin - if (const_74_1) begin - mem_10[const_73_0] <= a; - end - if (const_79_1) begin - mem_10[const_78_1] <= tmp51; - end + // MemBlock tmp9 logic + always @(posedge clk) begin + tmp9[2'd0] <= a; + tmp9[2'd1] <= (tmp50[3:0]); end - // Memory mem_11: tmp10 - always @(posedge clk) - begin - if (const_82_1) begin - mem_11[const_81_0] <= a; - end - if (const_87_1) begin - mem_11[const_86_1] <= tmp55; - end + // MemBlock tmp10 logic + always @(posedge clk) begin + tmp10[2'd0] <= a; + tmp10[2'd1] <= (tmp54[3:0]); end - // Memory mem_12: tmp11 - always @(posedge clk) - begin - if (const_90_1) begin - mem_12[const_89_0] <= a; - end - if (const_95_1) begin - mem_12[const_94_1] <= tmp59; - end + // MemBlock tmp11 logic + always @(posedge clk) begin + tmp11[2'd0] <= a; + tmp11[2'd1] <= (tmp58[3:0]); end - endmodule - """ -verilog_output_counter_sync_reset = """\ +verilog_output_mems_with_no_writes = """\ // Generated automatically via PyRTL // As one initial test of synthesis, map to FPGA with: // yosys -p "synth_xilinx -top toplevel" thisfile.v -module toplevel(clk, rst, o); +module toplevel(clk, rst, in1, out1); input clk; input rst; - output[3:0] o; + input[2:0] in1; + output[7:0] out1; - reg[3:0] tmp0; + // Memories + reg[7:0] rom[7:0]; // RomBlock + reg[7:0] mem[255:0]; // MemBlock - wire const_0_1; - wire const_1_0; - wire[2:0] tmp1; - wire[3:0] tmp2; - wire[4:0] tmp3; - wire[3:0] tmp4; + // Temporaries + wire[7:0] tmp0; - // Combinational - assign const_0_1 = 1; - assign const_1_0 = 0; - assign o = tmp0; - assign tmp1 = {const_1_0, const_1_0, const_1_0}; - assign tmp2 = {tmp1, const_0_1}; - assign tmp3 = tmp0 + tmp2; - assign tmp4 = {tmp3[3], tmp3[2], tmp3[1], tmp3[0]}; - - // Registers - always @(posedge clk) - begin - if (rst) begin - tmp0 <= 2; - end - else begin - tmp0 <= tmp4; - end + // Read-only memory data + initial begin + rom[0] = 8'ha; + rom[1] = 8'h14; + rom[2] = 8'h1e; + rom[3] = 8'h28; + rom[4] = 8'h32; + rom[5] = 8'h3c; + rom[6] = 8'h0; + rom[7] = 8'h0; end -endmodule + // Combinational logic + assign out1 = tmp0; + + // RomBlock rom logic + assign tmp0 = rom[in1]; + // MemBlock mem logic + always @(posedge clk) begin + mem[tmp0] <= 8'd42; + end +endmodule """ -verilog_output_counter_async_reset = """\ +verilog_output_counter_sync_reset = """\ // Generated automatically via PyRTL // As one initial test of synthesis, map to FPGA with: // yosys -p "synth_xilinx -top toplevel" thisfile.v @@ -1388,87 +997,57 @@ def test_blif_nor_gate_correct(self): input rst; output[3:0] o; + // Registers reg[3:0] tmp0; - wire const_0_1; - wire const_1_0; - wire[2:0] tmp1; - wire[3:0] tmp2; + // Temporaries wire[4:0] tmp3; - wire[3:0] tmp4; - // Combinational - assign const_0_1 = 1; - assign const_1_0 = 0; + // Combinational logic assign o = tmp0; - assign tmp1 = {const_1_0, const_1_0, const_1_0}; - assign tmp2 = {tmp1, const_0_1}; - assign tmp3 = tmp0 + tmp2; - assign tmp4 = {tmp3[3], tmp3[2], tmp3[1], tmp3[0]}; + assign tmp3 = (tmp0 + {{3 {1'd0}}, 1'd1}); - // Registers - always @(posedge clk or posedge rst) - begin + // Register logic + always @(posedge clk) begin if (rst) begin - tmp0 <= 2; - end - else begin - tmp0 <= tmp4; + tmp0 <= 4'd2; + end else begin + tmp0 <= (tmp3[3:0]); end end - endmodule - """ -verilog_output_mems_with_no_writes = """\ +verilog_output_counter_async_reset = """\ // Generated automatically via PyRTL // As one initial test of synthesis, map to FPGA with: // yosys -p "synth_xilinx -top toplevel" thisfile.v -module toplevel(clk, rst, in1, out1); +module toplevel(clk, rst, o); input clk; input rst; - input[2:0] in1; - output[7:0] out1; - - reg[7:0] mem_0[7:0]; //tmp0 - reg[7:0] mem_1[255:0]; //tmp1 - - wire const_0_1; - wire[7:0] const_1_42; - wire[7:0] tmp2; + output[3:0] o; - initial begin - mem_0[0]=8'ha; - mem_0[1]=8'h14; - mem_0[2]=8'h1e; - mem_0[3]=8'h28; - mem_0[4]=8'h32; - mem_0[5]=8'h3c; - mem_0[6]=8'h0; - mem_0[7]=8'h0; - end + // Registers + reg[3:0] tmp0; - // Combinational - assign const_0_1 = 1; - assign const_1_42 = 42; - assign out1 = tmp2; + // Temporaries + wire[4:0] tmp3; - // Memory mem_0: tmp0 - assign tmp2 = mem_0[in1]; + // Combinational logic + assign o = tmp0; + assign tmp3 = (tmp0 + {{3 {1'd0}}, 1'd1}); - // Memory mem_1: tmp1 - always @(posedge clk) - begin - if (const_0_1) begin - mem_1[tmp2] <= const_1_42; + // Register logic + always @(posedge clk or posedge rst) begin + if (rst) begin + tmp0 <= 4'd2; + end else begin + tmp0 <= (tmp3[3:0]); end end - endmodule - """ @@ -1481,34 +1060,21 @@ def test_blif_nor_gate_correct(self): input clk; output[3:0] o; + // Registers reg[3:0] tmp0; - wire const_0_1; - wire const_1_0; - wire[2:0] tmp1; - wire[3:0] tmp2; + // Temporaries wire[4:0] tmp3; - wire[3:0] tmp4; - // Combinational - assign const_0_1 = 1; - assign const_1_0 = 0; + // Combinational logic assign o = tmp0; - assign tmp1 = {const_1_0, const_1_0, const_1_0}; - assign tmp2 = {tmp1, const_0_1}; - assign tmp3 = tmp0 + tmp2; - assign tmp4 = {tmp3[3], tmp3[2], tmp3[1], tmp3[0]}; + assign tmp3 = (tmp0 + {{3 {1'd0}}, 1'd1}); - // Registers - always @(posedge clk) - begin - begin - tmp0 <= tmp4; - end + // Register logic + always @(posedge clk) begin + tmp0 <= (tmp3[3:0]); end - endmodule - """ @@ -1521,43 +1087,20 @@ def test_blif_nor_gate_correct(self): input clk; input rst; + // Registers reg[3:0] r; - wire const_0_1; - wire const_1_0; - wire const_2_0; - wire const_3_0; - wire[2:0] tmp0; - wire[3:0] tmp1; - wire[4:0] tmp2; - wire[3:0] tmp3; - wire[4:0] tmp4; + // Temporaries wire[4:0] tmp5; - wire[3:0] tmp6; - - // Combinational - assign const_0_1 = 1; - assign const_1_0 = 0; - assign const_2_0 = 0; - assign const_3_0 = 0; - assign tmp0 = {const_1_0, const_1_0, const_1_0}; - assign tmp1 = {tmp0, const_0_1}; - assign tmp2 = r + tmp1; - assign tmp3 = {const_3_0, const_3_0, const_3_0, const_3_0}; - assign tmp4 = {tmp3, const_2_0}; - assign tmp5 = rst ? tmp4 : tmp2; - assign tmp6 = {tmp5[3], tmp5[2], tmp5[1], tmp5[0]}; - // Registers - always @(posedge clk) - begin - begin - r <= tmp6; - end - end + // Combinational logic + assign tmp5 = (rst ? {{4 {1'd0}}, 1'd0} : (r + {{3 {1'd0}}, 1'd1})); + // Register logic + always @(posedge clk) begin + r <= (tmp5[3:0]); + end endmodule - """ @@ -1601,24 +1144,29 @@ def setUp(self): pyrtl.wire._reset_wire_indexers() pyrtl.memory._reset_memory_indexer() + self.maxDiff = 30000 + def test_romblock_does_not_throw_error(self): a = pyrtl.Input(bitwidth=3, name="a") b = pyrtl.Input(bitwidth=3, name="b") o = pyrtl.Output(bitwidth=3, name="o") - res = _basic_add(a, b) - rdat = {0: 1, 1: 2, 2: 5, 5: 0} mixtable = pyrtl.RomBlock( - addrwidth=3, bitwidth=3, pad_with_zeros=True, romdata=rdat + addrwidth=3, + bitwidth=3, + pad_with_zeros=True, + romdata={0: 1, 1: 2, 2: 5, 5: 0}, + asynchronous=True, ) + res = _basic_add(a, b) o <<= mixtable[res[:-1]] with io.StringIO() as testbuffer: pyrtl.output_to_verilog(testbuffer) def test_textual_consistency_small(self): - i = pyrtl.Const(0b1100) - j = pyrtl.Const(0b011, bitwidth=3) - k = pyrtl.Const(0b100110, name="k") - o = pyrtl.Output(13, "o") + i = pyrtl.Const(12) + j = pyrtl.Const(3, bitwidth=3) + k = pyrtl.Const(38, name="k") + o = pyrtl.Output(bitwidth=13, name="o") o <<= pyrtl.concat(i, j, k) buffer = io.StringIO() @@ -1633,12 +1181,15 @@ def test_textual_consistency_large(self): # Hence it creates many memories, and makes sure at least two lines of code are # created in the always @ blocks associated with them (so we have many different # wire names to deal with and test against). - a = pyrtl.Input(4, "a") - r = pyrtl.Register(4, name="r") - s = pyrtl.Register(4, name="s", reset_value=13) + a = pyrtl.Input(bitwidth=4, name="a") + r = pyrtl.Register(bitwidth=4, name="r") + s = pyrtl.Register(bitwidth=4, name="s", reset_value=13) # This will have mem id 0, so prints first despite actual name - mt = pyrtl.MemBlock(4, 2, name="z") - m = [pyrtl.MemBlock(4, 2, max_write_ports=2) for _ in range(12)] + mt = pyrtl.MemBlock(bitwidth=4, addrwidth=2, name="z") + m = [ + pyrtl.MemBlock(bitwidth=4, addrwidth=2, max_write_ports=2) + for _ in range(12) + ] for mem in m: mem[0] <<= a mem[1] <<= (r + 1).truncate(4) @@ -1656,10 +1207,12 @@ def test_textual_consistency_large(self): def test_mems_with_no_writes(self): rdata = {0: 10, 1: 20, 2: 30, 3: 40, 4: 50, 5: 60} - rom = pyrtl.RomBlock(8, 3, rdata, pad_with_zeros=True) - mem = pyrtl.MemBlock(8, 8) - in1 = pyrtl.Input(3, "in1") - out1 = pyrtl.Output(8, "out1") + rom = pyrtl.RomBlock( + name="rom", bitwidth=8, addrwidth=3, romdata=rdata, pad_with_zeros=True + ) + mem = pyrtl.MemBlock(name="mem", bitwidth=8, addrwidth=8) + in1 = pyrtl.Input(bitwidth=3, name="in1") + out1 = pyrtl.Output(bitwidth=8, name="out1") w = rom[in1] out1 <<= w mem[w] <<= 42 @@ -1670,9 +1223,9 @@ def test_mems_with_no_writes(self): self.assertEqual(buffer.getvalue(), verilog_output_mems_with_no_writes) def check_counter_text(self, add_reset, expected): - r = pyrtl.Register(4, reset_value=2) + r = pyrtl.Register(bitwidth=4, reset_value=2) r.next <<= r + 1 - o = pyrtl.Output(4, "o") + o = pyrtl.Output(bitwidth=4, name="o") o <<= r buffer = io.StringIO() @@ -1695,7 +1248,7 @@ def test_error_invalid_add_reset(self): def test_error_existing_reset_wire(self): buffer = io.StringIO() - _rst = pyrtl.Input(1, "rst") + _rst = pyrtl.Input(bitwidth=1, name="rst") with self.assertRaisesRegex( pyrtl.PyrtlError, "Found a user-defined wire named 'rst'." ): @@ -1703,15 +1256,17 @@ def test_error_existing_reset_wire(self): def test_existing_reset_wire_without_add_reset(self): buffer = io.StringIO() - rst = pyrtl.Input(1, "rst") - r = pyrtl.Register(4, "r") + rst = pyrtl.Input(bitwidth=1, name="rst") + r = pyrtl.Register(bitwidth=4, name="r") r.next <<= pyrtl.select(rst, 0, r + 1) pyrtl.output_to_verilog(buffer, add_reset=False) self.assertEqual(buffer.getvalue(), verilog_custom_reset) def test_register_reset_value(self): - _ = pyrtl.Register(name="register0", bitwidth=8, reset_value=0) - _ = pyrtl.Register(name="register1", bitwidth=4, reset_value=1) + register0 = pyrtl.Register(name="register0", bitwidth=8, reset_value=0) + register1 = pyrtl.Register(name="register1", bitwidth=4, reset_value=1) + register0.next <<= 0 + register1.next <<= 1 buffer = io.StringIO() pyrtl.output_to_verilog(buffer, add_reset=False, initialize_registers=True) @@ -1719,6 +1274,38 @@ def test_register_reset_value(self): self.assertTrue("reg[7:0] register0 = 8'd0" in buffer.getvalue()) self.assertTrue("reg[3:0] register1 = 4'd1" in buffer.getvalue()) + def test_bit_slice_inputs(self): + """Verify that wires are always declared for bit-slice inputs, even Consts.""" + a = pyrtl.Input(name="a", bitwidth=1) + b = pyrtl.Input(name="b", bitwidth=1) + c = pyrtl.Input(name="c", bitwidth=2) + + x = pyrtl.Output(name="x", bitwidth=1) + y = pyrtl.Output(name="y", bitwidth=1) + z = pyrtl.Output(name="z", bitwidth=1) + + x <<= pyrtl.Const(42)[1] + + y <<= (a + b)[1] + + t = pyrtl.WireVector() + t <<= c + z <<= t[1] + + buffer = io.StringIO() + pyrtl.output_to_verilog(buffer) + + # A constant should be declared for ``42``, even though it has no user-specified + # name and only has one user, because that user is a bit-slice. + self.assertTrue("wire[5:0] const_0_42 = 6'd42" in buffer.getvalue()) + # A temporary wire should be declared for ``a + b``, even though it has no name + # and only has one user, because that user is a bit-slice. + self.assertTrue("assign tmp1 = (a + b)" in buffer.getvalue()) + # A temporary wire should be declared for ``t``, even though it has no name and + # only has one user and just passes through ``c``, because that user is a + # bit-slice. + self.assertTrue("assign tmp3 = c" in buffer.getvalue()) + verilog_input_counter = """\ module counter (clk, rst, en, count); @@ -1750,8 +1337,7 @@ def test_register_reset_value(self): output [1:0] o; foo f1(a, b, o); - always @(posedge clk) - begin + always @(posedge clk) begin a <= ~a; b <= ~b; end @@ -1810,13 +1396,17 @@ def test_error_import_bad_file(self): module tb(); reg clk; reg rst; + + // block Inputs reg[1:0] a100; reg[3:0] w1; reg[2:0] w12; + + // block Outputs wire[1:0] out1; wire[8:0] out10; - integer tb_iter; + integer tb_addr; toplevel block(.clk(clk), .rst(rst), .a100(a100), .w1(w1), .w12(w12), .out1(out1), .out10(out10)); always @@ -1826,14 +1416,18 @@ def test_error_import_bad_file(self): $dumpfile ("waveform.vcd"); $dumpvars; - clk = 0; - rst = 0; - block.r1 = 2; - block.r2 = 3; - block.tmp0 = 0; - for (tb_iter = 0; tb_iter < 32; tb_iter++) begin block.mem_0[tb_iter] = 0; end - block.mem_0[2] = 9; - block.mem_0[9] = 12; + clk = 1'd0; + rst = 1'd0; + + // Initialize Registers + block.r1 = 3'd2; + block.r2 = 4'd3; + block.tmp0 = 8'd0; + + // Initialize MemBlocks + for (tb_addr = 0; tb_addr < 32; tb_addr++) begin block.mem[tb_addr] = 4'd0; end + block.mem[2] = 4'd9; + block.mem[9] = 4'd12; a100 = 2'd0; w1 = 4'd0; w12 = 3'd0; @@ -1862,13 +1456,17 @@ def test_error_import_bad_file(self): verilog_testbench_no_reset = """\ module tb(); reg clk; + + // block Inputs reg[1:0] a100; reg[3:0] w1; reg[2:0] w12; + + // block Outputs wire[1:0] out1; wire[8:0] out10; - integer tb_iter; + integer tb_addr; toplevel block(.clk(clk), .a100(a100), .w1(w1), .w12(w12), .out1(out1), .out10(out10)); always @@ -1878,13 +1476,17 @@ def test_error_import_bad_file(self): $dumpfile ("waveform.vcd"); $dumpvars; - clk = 0; - block.r1 = 2; - block.r2 = 3; - block.tmp0 = 0; - for (tb_iter = 0; tb_iter < 32; tb_iter++) begin block.mem_0[tb_iter] = 0; end - block.mem_0[2] = 9; - block.mem_0[9] = 12; + clk = 1'd0; + + // Initialize Registers + block.r1 = 3'd2; + block.r2 = 4'd3; + block.tmp0 = 8'd0; + + // Initialize MemBlocks + for (tb_addr = 0; tb_addr < 32; tb_addr++) begin block.mem[tb_addr] = 4'd0; end + block.mem[2] = 4'd9; + block.mem[9] = 4'd12; a100 = 2'd0; w1 = 4'd0; w12 = 3'd0; @@ -1913,6 +1515,8 @@ def test_error_import_bad_file(self): verilog_testbench_custom_reset = """\ module tb(); reg clk; + + // block Inputs reg rst; toplevel block(.clk(clk), .rst(rst)); @@ -1924,8 +1528,10 @@ def test_error_import_bad_file(self): $dumpfile ("waveform.vcd"); $dumpvars; - clk = 0; - block.r = 0; + clk = 1'd0; + + // Initialize Registers + block.r = 4'd0; $finish; end endmodule @@ -1939,6 +1545,7 @@ def setUp(self): # index for all automatically created names. pyrtl.wire._reset_wire_indexers() pyrtl.memory._reset_memory_indexer() + self.maxDiff = 10000 def test_verilog_testbench_does_not_throw_error(self): zero = pyrtl.Input(1, "zero") @@ -1957,8 +1564,8 @@ def create_design(self): # each time i1, i2, i3 = pyrtl.input_list("w1/4 w12/3 a100/2") r1, r2 = pyrtl.register_list("r1/3 r2/4") - r3 = pyrtl.Register(8) - mem = pyrtl.MemBlock(4, 5) + r3 = pyrtl.Register(bitwidth=8) + mem = pyrtl.MemBlock(name="mem", bitwidth=4, addrwidth=5) o1, o2 = pyrtl.output_list("out1/2 out10/9") r1.next <<= i1 + i2 r2.next <<= r1 * i3 @@ -2013,13 +1620,37 @@ def test_error_verilog_testbench_existing_reset_wire(self): pyrtl.output_verilog_testbench(tbfile) def test_verilog_testbench_existing_reset_wire_without_add_reset(self): - buffer = io.StringIO() rst = pyrtl.Input(1, "rst") r = pyrtl.Register(4, "r") r.next <<= pyrtl.select(rst, 0, r + 1) + + buffer = io.StringIO() pyrtl.output_verilog_testbench(buffer, add_reset=False) self.assertEqual(buffer.getvalue(), verilog_testbench_custom_reset) + def test_only_initialize_memblocks(self): + """Test that RomBlocks are not re-initialized by the testbench.""" + romblock = pyrtl.RomBlock( + name="my_rom", bitwidth=3, addrwidth=2, romdata=[1, 2, 3, 4] + ) + addr = pyrtl.Input(name="addr", bitwidth=2) + data = pyrtl.Output(name="data", bitwidth=3) + data <<= romblock[addr] + + buffer = io.StringIO() + pyrtl.output_to_verilog(buffer) + # The Verilog code should declare the RomBlock and define its values. + self.assertTrue("reg[2:0] my_rom[3:0]; // RomBlock" in buffer.getvalue()) + self.assertTrue("my_rom[0] = 3'h1;" in buffer.getvalue()) + self.assertTrue("my_rom[1] = 3'h2;" in buffer.getvalue()) + self.assertTrue("my_rom[2] = 3'h3;" in buffer.getvalue()) + self.assertTrue("my_rom[3] = 3'h4;" in buffer.getvalue()) + + buffer = io.StringIO() + pyrtl.output_verilog_testbench(buffer, add_reset=False) + # The testbench should not touch the RomBlock. + self.assertTrue("my_rom" not in buffer.getvalue()) + firrtl_output_concat_test = """\ circuit Example : diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 31100a02..c503c76c 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -243,7 +243,6 @@ def test_long_decimal_trace(self): """Check that long cycle names are truncated. The most significant digits should be omitted. - """ expected = ( " |0|1|2|3|4|5|6|7|8|9|0|1|2|3|4|5|6|7|8|9\n" @@ -440,9 +439,8 @@ def test_reg_directly_before_reg(self): pass def test_weird_wire_names(self): - """ - Some simulations need to be careful when handling special names (eg Fastsim June - 2016) + """Some simulations need to be careful when handling special names (eg Fastsim + June 2016) """ i = pyrtl.Input(8, '"182&!!!\n') o = pyrtl.Output(8, "*^*)#*$'*") @@ -470,7 +468,7 @@ def test_weird_wire_names(self): def test_fastsim_wire_names(self): """ - Testing both Simulation classes' ability to use wire names instead of wires + Testing both Simulation classes' ability to use wire names instead of wires. """ in1 = pyrtl.Input(8, "in1") in2 = pyrtl.Input(8, "in2") @@ -559,11 +557,13 @@ def test_reset_value_overridden_in_simulation(self): o = pyrtl.Output(4, "o") o <<= r - sim = self.sim(register_value_map={r: 1}) + register_value_map = {r: 1} + sim = self.sim(register_value_map=register_value_map) sim.step_multiple(nsteps=7) output = io.StringIO() sim.tracer.print_trace(output, compact=True) self.assertEqual(output.getvalue(), "o 1230123\n") + self.assertEqual(sim.tracer.register_value_map, register_value_map) def test_default_value_for_registers_without_reset_value(self): r = pyrtl.Register(2, name="r", reset_value=3) @@ -579,6 +579,7 @@ def test_default_value_for_registers_without_reset_value(self): output = io.StringIO() sim.tracer.print_trace(output, compact=True) self.assertEqual(output.getvalue(), "o 6222622\nr 3012301\ns 3210321\n") + self.assertEqual(sim.tracer.default_value, 3) class SimInputValidationBase(unittest.TestCase): @@ -1192,6 +1193,7 @@ def test_mem_val_map(self): self.assertEqual( sim.inspect_mem(self.mem1), {0: 0, 1: 2, 2: 3, 3: 3, 4: 4, 5: 5} ) + self.assertEqual(sim.tracer.memory_value_map, mem_val_map) def test_mem_val_map_defaults(self): read_addr3 = pyrtl.Input(self.addrwidth) @@ -1498,9 +1500,7 @@ def test_simultaneous_read_write(self): class InspectBase(unittest.TestCase): - """ - Unittests for both sim.inspect and sim.inspect_mem - """ + """Unittests for both sim.inspect and sim.inspect_mem.""" def setUp(self): pyrtl.reset_working_block() @@ -1580,9 +1580,7 @@ def test_no_tracer(self): def make_unittests(): - """ - Generates separate unittests for each of the simulators - """ + """Generates separate unittests for each of the simulators.""" g = globals() unittests = {} base_tests = {