Skip to content

Commit dd752dd

Browse files
committed
Merge branch 'development' of https://github.com/UCSBarchlab/PyRTL into development
2 parents bbab57e + 0e6fdae commit dd752dd

13 files changed

+1171
-244
lines changed

pyrtl/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
from .visualization import net_graph
9393

9494
# import from and export to file format routines
95+
from .importexport import input_from_verilog
9596
from .importexport import output_to_verilog
9697
from .importexport import OutputToVerilog
9798
from .importexport import output_verilog_testbench

pyrtl/compilesim.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def step_multiple(self, provided_inputs={}, expected_outputs={}, nsteps=None,
137137
138138
:param provided_inputs: a dictionary mapping wirevectors to their values for N steps
139139
:param expected_outputs: a dictionary mapping wirevectors to their expected values
140-
for N steps
140+
for N steps; use '?' to indicate you don't care what the value at that step is
141141
:param nsteps: number of steps to take (defaults to None, meaning step for each
142142
supplied input value)
143143
:param file: where to write the output (if there are unexpected outputs detected)
@@ -214,7 +214,10 @@ def step_multiple(self, provided_inputs={}, expected_outputs={}, nsteps=None,
214214
self.step({w: int(v[i]) for w, v in provided_inputs.items()})
215215

216216
for expvar in expected_outputs.keys():
217-
expected = int(expected_outputs[expvar][i])
217+
expected = expected_outputs[expvar][i]
218+
if expected == '?':
219+
continue
220+
expected = int(expected)
218221
actual = self.inspect(expvar)
219222
if expected != actual:
220223
failed.append((i, expvar, expected, actual))

pyrtl/corecircuits.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,10 @@ def bitfield_update(w, range_start, range_end, newvalue, truncating=False):
436436
w = bitfield_update(w, 20, 23, 0x7) # sets bits 20, 21, 22 to 1
437437
w = bitfield_update(w, 20, 23, 0x6) # sets bit 20 to 0, bits 21 and 22 to 1
438438
w = bitfield_update(w, 20, None, 0x7) # assuming w is 32 bits, sets bits 31..20 = 0x7
439-
w = bitfield_update(w, -1, None, 0x1) # set the LSB (bit) to 1
439+
w = bitfield_update(w, -1, None, 0x1) # set the MSB (bit) to 1
440+
w = bitfield_update(w, None, -1, 0x9) # set the bits before the MSB (bit) to 9
441+
w = bitfield_update(w, None, 1, 0x1) # set the LSB (bit) to 1
442+
w = bitfield_update(w, 1, None, 0x9) # set the bits after the LSB (bit) to 9
440443
"""
441444
from .corecircuits import concat_list
442445

@@ -482,8 +485,8 @@ def bitfield_update_set(w, update_set, truncating=False):
482485
w = bitfield_update_set(w, {
483486
(20, 23): 0x6, # sets bit 20 to 0, bits 21 and 22 to 1
484487
(26, None): 0x7, # assuming w is 32 bits, sets bits 31..26 to 0x7
485-
(-1, None): 0x0 # set the LSB (bit) to 0
486-
})
488+
(None, 1): 0x0, # set the LSB (bit) to 0
489+
})
487490
"""
488491
w = as_wires(w)
489492
# keep a list of bits that are updated to find overlaps

pyrtl/importexport.py

Lines changed: 152 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@
99
from __future__ import print_function, unicode_literals
1010
import re
1111
import collections
12+
import tempfile
13+
import os
14+
import subprocess
15+
import six
16+
import sys
1217

1318
from .pyrtlexceptions import PyrtlError, PyrtlInternalError
1419
from .core import working_block, _NameSanitizer
1520
from .wire import WireVector, Input, Output, Const, Register, next_tempvar_name
16-
from .corecircuits import concat_list
21+
from .corecircuits import concat_list, rtl_all, rtl_any
1722
from .memory import RomBlock
1823
from .passes import two_way_concat, one_bit_selects
1924

@@ -108,10 +113,11 @@ def input_from_blif(blif, block=None, merge_io_vectors=True, clock_name='clk', t
108113
wires will be Input wires of the block. This holds similarly for Outputs.
109114
110115
This assumes the following:
111-
* The BLIF has been flattened and there is only a single module
112116
* There is only one single shared clock and reset
113117
* Output is generated by Yosys with formals in a particular order
114118
119+
It currently supports multi-module (unflattened) BLIF, though we recommend importing a
120+
flattened BLIF with a single module when possible.
115121
It currently ignores the reset signal (which it assumes is input only to the flip flops).
116122
"""
117123
import pyparsing
@@ -315,26 +321,42 @@ def twire(w):
315321
elif command['cover_list'].asList() == ['11', '1']:
316322
output_wire = twire(netio[2])
317323
output_wire <<= twire(netio[0]) & twire(netio[1]) # and gate
318-
elif command['cover_list'].asList() == ['00', '1']:
319-
output_wire = twire(netio[2])
320-
output_wire <<= ~ (twire(netio[0]) | twire(netio[1])) # nor gate
321324
elif command['cover_list'].asList() == ['1-', '1', '-1', '1']:
322325
output_wire = twire(netio[2])
323326
output_wire <<= twire(netio[0]) | twire(netio[1]) # or gate
327+
elif command['cover_list'].asList() == ['0-', '1', '-0', '1']:
328+
output_wire = twire(netio[2])
329+
output_wire <<= twire(netio[0]).nand(twire(netio[1])) # nand gate
324330
elif command['cover_list'].asList() == ['10', '1', '01', '1']:
325331
output_wire = twire(netio[2])
326332
output_wire <<= twire(netio[0]) ^ twire(netio[1]) # xor gate
327-
elif command['cover_list'].asList() == ['1-0', '1', '-11', '1']:
328-
output_wire = twire(netio[3])
329-
output_wire <<= (twire(netio[0]) & ~ twire(netio[2])) \
330-
| (twire(netio[1]) & twire(netio[2])) # mux
331-
elif command['cover_list'].asList() == ['-00', '1', '0-0', '1']:
332-
output_wire = twire(netio[3])
333-
output_wire <<= (~twire(netio[1]) & ~twire(netio[2])) \
334-
| (~twire(netio[0]) & ~twire(netio[2]))
335333
else:
336-
raise PyrtlError('Blif file with unknown logic cover set "%s"'
337-
'(currently gates are hard coded)' % command['cover_list'])
334+
# Although the following is fully generic and thus encompasses all of the
335+
# special cases after the simple wire case above, we leave the above in because
336+
# they are commonly found and lead to a slightly cleaner (though equivalent) netlist,
337+
# because we can use nand/xor primitives, or avoid the extra fluff of concat/select
338+
# wires that might be created implicitly as part of rtl_all/rtl_any.
339+
def convert_val(ix, val):
340+
wire = twire(netio[ix])
341+
if val == '0':
342+
wire = ~wire
343+
return wire
344+
345+
cover = command['cover_list'].asList()
346+
output_wire = twire(netio[-1])
347+
conjunctions = []
348+
while cover:
349+
if len(cover) < 2:
350+
raise PyrtlError('BLIF file with malformed cover set "%s" '
351+
% command['cover_list'])
352+
input_plane, output_plane, cover = cover[0], cover[1], cover[2:]
353+
if output_plane != '1':
354+
raise PyrtlError('Off-set found in the output plane of BLIF cover set "%s" '
355+
'(only on-sets are supported)' % command['cover_list'])
356+
conj = rtl_all(*[convert_val(ix, val) for ix, val
357+
in enumerate(input_plane) if val != '-'])
358+
conjunctions.append(conj)
359+
output_wire <<= rtl_any(*conjunctions)
338360

339361
def extract_flop(subckt, command):
340362

@@ -411,6 +433,89 @@ def instantiate(subckt):
411433
#
412434

413435

436+
def input_from_verilog(verilog, clock_name='clk', toplevel=None, leave_in_dir=None, block=None):
437+
""" Read an open Verilog file or string as input via Yosys conversion, updating the block.
438+
439+
:param verilog: An open Verilog file to read
440+
:param clock_name: The name of the clock (defaults to 'clk')
441+
:param toplevel: Name of top-level module to instantiate; if None, defaults to first model
442+
defined in the Verilog file
443+
:param bool leave_in_dir: If True, save the intermediate BLIF file created in
444+
the given directory
445+
:param block: The block where the logic will be added
446+
447+
Note: This function is essentially a wrapper for `input_from_blif()`, with the added convenience
448+
of turning the Verilog into BLIF for import for you. This function passes a set of commands to
449+
Yosys as a script that normally produces BLIF files that can be successuflly imported into
450+
PyRTL via `input_from_blif()`. If the Yosys conversion fails here, we recommend you create your
451+
own custom Yosys script to try and produce BLIF yourself. Then you can import BLIF directly via
452+
`input_from_blif()`.
453+
"""
454+
455+
# Dev Notes:
456+
# 1) We pass in an open file or string to keep the same API as input_from_blif (even though
457+
# we are essentially putting that Verilog code back in a file for Yosys to operate on).
458+
# 2) The Yosys script does not have a call to `flatten`, since we now have support for
459+
# multi-module BLIFs. `flatten` replaces the .subckt (i.e. the module inclusions) with their
460+
# actual contents, so if things aren't working for some reason, add this command for help.
461+
# 3) The Yosys script *does* have a call to `setundef -zero -undriven`, which we use to set
462+
# undriven nets to 0; we do this so PyRTL doesn't complain about used but undriven wires.
463+
464+
try:
465+
verilog_string = verilog.read()
466+
except AttributeError:
467+
if isinstance(verilog, six.string_types):
468+
verilog_string = verilog
469+
else:
470+
raise PyrtlError('input_from_verilog expecting either open file or string')
471+
472+
block = working_block(block)
473+
474+
# Create a temporary Verilog file
475+
temp_vd, tmp_verilog_path = tempfile.mkstemp(prefix='pyrtl_verilog', suffix='.v',
476+
dir=leave_in_dir, text=True)
477+
with open(tmp_verilog_path, 'w') as f:
478+
f.write(verilog_string)
479+
480+
# Create a temporary BLIF file
481+
temp_bd, tmp_blif_path = tempfile.mkstemp(prefix='pyrtl_blif', suffix='.blif',
482+
dir=leave_in_dir, text=True)
483+
484+
yosys_arg_template = (
485+
"-p "
486+
"read_verilog %s; "
487+
"synth %s; "
488+
"setundef -zero -undriven; "
489+
"opt; "
490+
"write_blif %s; "
491+
)
492+
493+
yosys_arg = yosys_arg_template % (tmp_verilog_path,
494+
('-top ' + toplevel) if toplevel is not None else '-auto-top',
495+
tmp_blif_path)
496+
497+
try:
498+
os.close(temp_vd)
499+
os.close(temp_bd)
500+
# call yosys on the script, and grab the output
501+
_yosys_output = subprocess.check_output(['yosys', yosys_arg])
502+
with open(tmp_blif_path) as blif:
503+
input_from_blif(blif, block=block, clock_name=clock_name, top_model=toplevel)
504+
except (subprocess.CalledProcessError, ValueError) as e:
505+
print('Error with call to yosys...', file=sys.stderr)
506+
print('---------------------------------------------', file=sys.stderr)
507+
print(str(e.output).replace('\\n', '\n'), file=sys.stderr)
508+
print('---------------------------------------------', file=sys.stderr)
509+
raise PyrtlError('Yosys callfailed')
510+
except OSError as e:
511+
print('Error with call to yosys...', file=sys.stderr)
512+
raise PyrtlError('Call to yosys failed (not installed or on path?)')
513+
finally:
514+
os.remove(tmp_verilog_path)
515+
if leave_in_dir is None:
516+
os.remove(tmp_blif_path)
517+
518+
414519
def output_to_verilog(dest_file, add_reset=True, block=None):
415520
""" A function to walk the block and output it in Verilog format to the open file.
416521
@@ -432,6 +537,11 @@ def output_to_verilog(dest_file, add_reset=True, block=None):
432537
file = dest_file
433538
internal_names = _VerilogSanitizer('_ver_out_tmp_')
434539

540+
if add_reset: # True or 'asynchronous'
541+
if block.get_wirevector_by_name('rst') is not None:
542+
raise PyrtlError("Found a user-defined wire named 'rst'. Pass in "
543+
"'add_reset=False' to use your existing reset logic.")
544+
435545
for wire in block.wirevector_set:
436546
internal_names.make_valid_string(wire.name)
437547

@@ -672,7 +782,7 @@ def _to_verilog_footer(file):
672782

673783

674784
def output_verilog_testbench(dest_file, simulation_trace=None, toplevel_include=None,
675-
vcd="waveform.vcd", cmd=None, block=None):
785+
vcd="waveform.vcd", cmd=None, add_reset=True, block=None):
676786
""" Output a Verilog testbench for the block/inputs used in the simulation trace.
677787
678788
:param dest_file: an open file to which the test bench will be printed.
@@ -692,6 +802,16 @@ def output_verilog_testbench(dest_file, simulation_trace=None, toplevel_include=
692802
specific values out during testbench evaluation (e.g. cmd='$display("%d", out);'
693803
will instruct the testbench to print the value of 'out' every cycle which can then
694804
be compared easy with a reference).
805+
:param add_reset: If reset logic should be added. Allowable options are:
806+
False (meaning no reset logic is added), True (default, for adding synchronous
807+
reset logic), and 'asynchronous' (for adding asynchronous reset logic).
808+
The value passed in here should match the argument passed to `output_to_verilog()`.
809+
:param block: Block containing design to test.
810+
811+
If `add_reset` is not False, a `rst` wire is added and will passed as an input to the
812+
instantiated toplevel module. The `rst` wire will be held low in the testbench, because
813+
initialization here occurs via the `initial` block. It is provided for consistency with
814+
`output_to_verilog()`.
695815
696816
The test bench does not return any values.
697817
@@ -707,8 +827,18 @@ def output_verilog_testbench(dest_file, simulation_trace=None, toplevel_include=
707827
output_verilog_testbench(fp, sim.tracer, vcd=None, cmd='$display("%d", out);')
708828
709829
"""
830+
if not isinstance(add_reset, bool):
831+
if add_reset != 'asynchronous':
832+
raise PyrtlError("Invalid add_reset option %s. Acceptable options are "
833+
"False, True, and 'asynchronous'")
834+
710835
block = working_block(block)
711836

837+
if add_reset: # True or 'asynchronous'
838+
if block.get_wirevector_by_name('rst') is not None:
839+
raise PyrtlError("Found a user-defined wire named 'rst'. Pass in "
840+
"'add_reset=False' to use your existing reset logic.")
841+
712842
inputs, outputs, registers, wires, memories = _verilog_block_parts(block)
713843

714844
ver_name = _VerilogSanitizer('_ver_out_tmp_')
@@ -760,6 +890,8 @@ def default_value():
760890

761891
# Declare all block inputs as reg
762892
print(' reg clk;', file=dest_file)
893+
if add_reset:
894+
print(' reg rst;', file=dest_file)
763895
for w in name_sorted(inputs):
764896
print(' reg{:s} {:s};'.format(_verilog_vector_decl(w), ver_name[w.name]),
765897
file=dest_file)
@@ -775,6 +907,8 @@ def default_value():
775907

776908
# Instantiate logic block
777909
io_list = ['clk'] + name_list(name_sorted(inputs)) + name_list(name_sorted(outputs))
910+
if add_reset:
911+
io_list.insert(1, 'rst')
778912
io_list_str = ['.{0:s}({0:s})'.format(w) for w in io_list]
779913
print(' toplevel block({:s});\n'.format(', '.join(io_list_str)), file=dest_file)
780914

@@ -792,6 +926,8 @@ def default_value():
792926

793927
# Initialize clk, and all the registers and memories
794928
print(' clk = 0;', file=dest_file)
929+
if add_reset:
930+
print(' rst = 0;', file=dest_file)
795931
for r in name_sorted(registers):
796932
print(' block.%s = %d;' % (ver_name[r.name], init_regvalue(r)), file=dest_file)
797933
for m in sorted(memories, key=lambda m: m.id):

pyrtl/passes.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ def optimize(update_working_block=True, block=None, skip_sanity_check=False):
3131
"""
3232
Return an optimized version of a synthesized hardware block.
3333
34-
:param Boolean update_working_block: Don't copy the block and optimize the
35-
new block
34+
:param bool update_working_block: Don't copy the block and optimize the
35+
new block (defaults to True)
3636
:param Block block: the block to optimize (defaults to working block)
37+
:param bool skip_sanity_check: Don't perform sanity checks on the block
38+
before/during/after the optimization passes (defaults to False).
39+
Sanity checks will always be performed if in debug mode.
3740
3841
Note:
3942
optimize works on all hardware designs, both synthesized and non synthesized
@@ -45,8 +48,8 @@ def optimize(update_working_block=True, block=None, skip_sanity_check=False):
4548
with set_working_block(block, no_sanity_check=True):
4649
if (not skip_sanity_check) or _get_debug_mode():
4750
block.sanity_check()
48-
_remove_wire_nets(block)
49-
_remove_slice_nets(block)
51+
_remove_wire_nets(block, skip_sanity_check)
52+
_remove_slice_nets(block, skip_sanity_check)
5053
constant_propagation(block, True)
5154
_remove_unlistened_nets(block)
5255
common_subexp_elimination(block)
@@ -73,7 +76,7 @@ def find_producer(self, item):
7376
return item
7477

7578

76-
def _remove_wire_nets(block):
79+
def _remove_wire_nets(block, skip_sanity_check=False):
7780
""" Remove all wire nodes from the block. """
7881

7982
wire_src_dict = _ProducerList()
@@ -100,10 +103,11 @@ def _remove_wire_nets(block):
100103
for dead_wirevector in wire_removal_set:
101104
block.remove_wirevector(dead_wirevector)
102105

103-
block.sanity_check()
106+
if (not skip_sanity_check) or _get_debug_mode():
107+
block.sanity_check()
104108

105109

106-
def _remove_slice_nets(block):
110+
def _remove_slice_nets(block, skip_sanity_check=False):
107111
""" Remove all unneeded slice nodes from the block.
108112
109113
Unneeded here means that the source and destination wires of a slice net are exactly
@@ -161,7 +165,8 @@ def is_net_slicing_entire_wire(net):
161165
del block.wirevector_by_name[dead_wirevector.name]
162166
block.wirevector_set.remove(dead_wirevector)
163167

164-
block.sanity_check()
168+
if (not skip_sanity_check) or _get_debug_mode():
169+
block.sanity_check()
165170

166171

167172
def constant_propagation(block, silence_unexpected_net_warnings=False):

0 commit comments

Comments
 (0)