Skip to content

Commit fd25635

Browse files
authored
Merge pull request #393 from pllab/verilog-import-from-blif
Convenience function for importing Verilog into PyRTL via the Yosys/BLIF workflow
2 parents 0542429 + 09e1b13 commit fd25635

File tree

3 files changed

+177
-2
lines changed

3 files changed

+177
-2
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/importexport.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
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
@@ -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
@@ -427,6 +433,89 @@ def instantiate(subckt):
427433
#
428434

429435

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+
430519
def output_to_verilog(dest_file, add_reset=True, block=None):
431520
""" A function to walk the block and output it in Verilog format to the open file.
432521

tests/test_importexport.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1505,7 +1505,7 @@ def test_verilog_check_valid_name_bad(self):
15051505
self.assert_invalid_name('a' * 2000)
15061506

15071507

1508-
class TestVerilog(unittest.TestCase):
1508+
class TestVerilogOutput(unittest.TestCase):
15091509
def setUp(self):
15101510
pyrtl.reset_working_block()
15111511
# To compare textual consistency, need to make
@@ -1607,6 +1607,91 @@ def test_existing_reset_wire_without_add_reset(self):
16071607
self.assertEqual(buffer.getvalue(), verilog_custom_reset)
16081608

16091609

1610+
verilog_input_counter = """\
1611+
module counter (clk, rst, en, count);
1612+
1613+
input clk, rst, en;
1614+
output reg [3:0] count;
1615+
1616+
always @(posedge clk)
1617+
if (rst)
1618+
count <= 4'd0;
1619+
else if (en)
1620+
count <= count + 4'd1;
1621+
1622+
endmodule
1623+
"""
1624+
1625+
verilog_input_multi_module = """\
1626+
module foo (a, b, o);
1627+
1628+
input a, b;
1629+
output [1:0] o;
1630+
assign o = a + b;
1631+
1632+
endmodule
1633+
1634+
module top (clk, o);
1635+
input clk;
1636+
reg a, b;
1637+
output [1:0] o;
1638+
foo f1(a, b, o);
1639+
1640+
always @(posedge clk)
1641+
begin
1642+
a <= ~a;
1643+
b <= ~b;
1644+
end
1645+
endmodule
1646+
"""
1647+
1648+
1649+
class TestVerilogInput(unittest.TestCase):
1650+
def setUp(self):
1651+
import subprocess
1652+
try:
1653+
version = subprocess.check_output(['yosys', '--version'])
1654+
except OSError:
1655+
raise unittest.SkipTest('Testing Verilog input requires yosys')
1656+
pyrtl.reset_working_block()
1657+
1658+
def test_import_counter(self):
1659+
pyrtl.input_from_verilog(verilog_input_counter)
1660+
sim = pyrtl.Simulation()
1661+
sim.step_multiple({'rst': '10000', 'en': '01111'})
1662+
self.assertEqual(sim.tracer.trace['count'], [0, 0, 1, 2, 3])
1663+
1664+
def test_import_small(self):
1665+
pyrtl.input_from_verilog(verilog_output_small)
1666+
sim = pyrtl.Simulation()
1667+
sim.step({})
1668+
self.assertEqual(sim.tracer.trace['o'][0], 0b1100011100110)
1669+
1670+
def test_import_counter_with_reset(self):
1671+
pyrtl.input_from_verilog(verilog_output_counter_sync_reset)
1672+
sim = pyrtl.Simulation()
1673+
sim.step_multiple({'rst': '1000'})
1674+
self.assertEqual(sim.tracer.trace['o'], [0, 2, 3, 4])
1675+
1676+
def test_import_multi_module_specified_module(self):
1677+
# Import foo module because occurs first in file
1678+
pyrtl.input_from_verilog(verilog_input_multi_module, toplevel="foo")
1679+
sim = pyrtl.Simulation()
1680+
sim.step_multiple({'a': '0011', 'b': '0101'})
1681+
self.assertEqual(sim.tracer.trace['o'], [0, 1, 1, 2])
1682+
1683+
def test_import_multi_module_auto_select_top_module(self):
1684+
pyrtl.input_from_verilog(verilog_input_multi_module)
1685+
sim = pyrtl.Simulation()
1686+
sim.step_multiple(nsteps=5)
1687+
self.assertEqual(sim.tracer.trace['o'], [0, 2, 0, 2, 0])
1688+
1689+
def test_error_import_bad_file(self):
1690+
with self.assertRaisesRegex(pyrtl.PyrtlError,
1691+
"input_from_verilog expecting either open file or string"):
1692+
pyrtl.input_from_verilog(3)
1693+
1694+
16101695
verilog_testbench = """\
16111696
module tb();
16121697
reg clk;

0 commit comments

Comments
 (0)