blob: 91fc0e0c43e5dc1b06321b82216f4ddf75f89c68 [file]
#!/usr/bin/env python3
"""
Convert a Verilog simulation model to a VPR `model.xml`
The following Verilog attributes are considered on ports:
- `(* CLOCK *)` : force a given port to be a clock
- `(* ASSOC_CLOCK="RDCLK" *)` : force a port's associated clock to a given value
The following Verilog attributes are considered on modules:
- `(* MODEL_NAME="model" *)` : override the name used for <model> and for
".subckt name" in the BLIF model. Mostly intended for use with w.py, when several
different pb_types implement the same model.
- `(* CLASS="lut|routing|mux|flipflop|mem" *)` : specify the class of an given
instance. A model will not be generated for the `lut`, `routing` or `flipflop`
class.
"""
import argparse
import os
import re
import sys
import lxml.etree as ET
from .yosys import run
from .yosys.json import YosysJSON
from .xml import xmlinc
def is_clock_assoc(infiles, module, clk, port, direction):
"""Checks if a specific port is associated with a clk clock
Returns a boolean value
-------
is_clock_assoc: bool
"""
clock_assoc_signals = run.get_clock_assoc_signals(
infiles, module, clk
)
if direction == "input":
assoc_outputs = run.get_related_output_for_input(
infiles, module, port
)
for out in assoc_outputs:
if out in clock_assoc_signals:
return True
elif direction == "output":
if port in clock_assoc_signals:
return True
else:
assert False, "Bidirectional ports are not supported yet"
return False
def is_registered_path(tmod, pin, pout):
"""Checks if a i/o path is sequential. If that is the case
no combinational_sink_port is needed
Returns a boolean value
"""
for cell, ctype in tmod.all_cells:
if ctype != "$dff":
continue
if tmod.port_conns(pin) == tmod.cell_conn_list(
cell, "D") and tmod.port_conns(pout) == tmod.cell_conn_list(
cell, "Q"):
return True
return False
def vlog_to_model(infiles, includes, top, outfile=None):
iname = os.path.basename(infiles[0])
if outfile is None:
outfile = "model.xml"
if includes:
for include in includes.split(','):
run.add_include(include)
aig_json = run.vlog_to_json(infiles, flatten=True, aig=True)
if top is not None:
top = top.upper()
else:
yj = YosysJSON(aig_json)
if yj.top is not None:
top = yj.top
else:
wm = re.match(r"([A-Za-z0-9_]+)\.sim\.v", iname)
if wm:
top = wm.group(1).upper()
else:
print(
"""\
ERROR file name not of format %.sim.v ({}), cannot detect top level.
Manually specify the top level module using --top"""
).format(iname)
sys.exit(1)
assert top is not None
yj = YosysJSON(aig_json, top)
if top is None:
print(
"""\
ERROR: more than one module in design, cannot detect top level.
Manually specify the top level module using --top"""
)
sys.exit(1)
tmod = yj.top_module
models_xml = ET.Element("models", nsmap={'xi': xmlinc.xi_url})
inc_re = re.compile(r'^\s*`include\s+"([^"]+)"')
deps_files = set()
# XML dependencies need to correspond 1:1 with Verilog includes, so we have
# to do this manually rather than using Yosys
with open(infiles[0], 'r') as f:
for line in f:
im = inc_re.match(line)
if not im:
continue
deps_files.add(im.group(1))
if len(deps_files) > 0:
# Has dependencies, not a leaf model
for df in sorted(deps_files):
abs_base = os.path.dirname(os.path.abspath(infiles[0]))
abs_dep = os.path.normpath(os.path.join(abs_base, df))
module_path = os.path.dirname(abs_dep)
module_basename = os.path.basename(abs_dep)
wm = re.match(r"([A-Za-z0-9_]+)\.sim\.v", module_basename)
if wm:
model_path = "{}/{}.model.xml".format(
module_path,
wm.group(1).lower()
)
else:
assert False, "included Verilog file name {} does not follow pattern %%.sim.v".format(
module_basename
)
xmlinc.include_xml(
parent=models_xml,
href=model_path,
outfile=outfile,
xptr="xpointer(models/child::node())"
)
else:
# Is a leaf model
topname = tmod.attr("MODEL_NAME", top)
assert topname == topname.upper(
), "Leaf model names should be all uppercase!"
modclass = tmod.attr("CLASS", "")
if modclass not in ("lut", "routing", "flipflop"):
model_xml = ET.SubElement(models_xml, "model", {'name': topname})
ports = tmod.ports
inports_xml = ET.SubElement(model_xml, "input_ports")
outports_xml = ET.SubElement(model_xml, "output_ports")
clocks = run.list_clocks(infiles, top)
for name, width, bits, iodir in ports:
attrs = dict(name=name)
sinks = run.get_combinational_sinks(infiles, top, name)
# Removes comb sinks if path from in to out goes through a dff
for sink in sinks:
if is_registered_path(tmod, name, sink):
sinks.remove(sink)
# FIXME: Check if ignoring clock for "combination_sink_ports" is a
# valid thing to do.
if name in clocks or "clk" in name.lower():
attrs["is_clock"] = "1"
else:
clks = list()
if len(sinks) > 0 and iodir == "input":
attrs["combinational_sink_ports"] = " ".join(sinks)
for clk in clocks:
if is_clock_assoc(infiles, top, clk, name, iodir):
clks.append(clk)
if clks:
attrs["clock"] = " ".join(clks)
if iodir == "input":
ET.SubElement(inports_xml, "port", attrs)
elif iodir == "output":
ET.SubElement(outports_xml, "port", attrs)
else:
assert False, "bidirectional ports not permitted in VPR models"
if len(models_xml) == 0:
models_xml.insert(0, ET.Comment("this file is intentionally left blank"))
f = open(outfile, 'w')
f.write(ET.tostring(models_xml, pretty_print=True).decode('utf-8'))
f.close()
print("Generated {} from {}".format(outfile, iname))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__.strip())
parser.add_argument(
'infiles',
metavar='input.v',
type=str,
nargs='+',
help="""\
One or more Verilog input files, that will be passed to Yosys internally.
They should be enough to generate a flattened representation of the model,
so that paths through the model can be determined.
"""
)
parser.add_argument(
'--top',
help="""\
Top level module, will usually be automatically determined from the file name
%.sim.v
"""
)
parser.add_argument(
'--includes',
help="""\
Comma separate list of include directories.
""",
default=""
)
parser.add_argument('-o', help="""\
Output filename, default 'model.xml'
""")
args = parser.parse_args()