blob: 95294a0c6ffeff252d49c1af20ef79cc5bcad73d [file]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright 2020-2022 F4PGA Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
"""
Convert a Verilog simulation model to a VPR `model.xml`
The following Verilog attributes are considered on ports:
- `(* CLOCK *)` or `(* CLOCK=1 *)` : force a given port to be a clock
- `(* CLOCK=0 *)` : force a given port not to be a clock
- `(* ASSOC_CLOCK="RDCLK" *)` : force a port's associated
clock to a given value
- `(* COMB_INCLUDE_CLOCKS *)` : When specified on a clock input port
allows it to have combinational relations
with other ports.
- `(* NO_COMB *)` : Forces removal of all combinational relations of an
input port.
- `(* NO_SEQ *)` : Forces removal of all sequential relations of an input
port.
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 os
import re
import sys
import lxml.etree as ET
from .yosys import run
from .yosys.json import YosysJSON
from .yosys import utils as utils
from .xmlinc 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):
# Check Yosys version
pfx = run.determine_select_prefix()
if pfx != "=":
print(
"ERROR The version of Yosys found is outdated and not supported"
" by V2X")
sys.exit(-1)
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 ("input", "output", "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:
port_attrs = tmod.port_attrs(name)
is_clock = name in clocks or utils.is_clock_name(name)
if "CLOCK" in port_attrs:
is_clock = int(port_attrs["CLOCK"]) != 0
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)
if is_clock:
attrs["is_clock"] = "1"
# Remove comb sinks that do not have "COMB_INCLUDE_CLOCKS"
# attribute
for sink in sinks:
sink_attrs = tmod.port_attrs(sink)
if int(sink_attrs.get("COMB_INCLUDE_CLOCKS", 0)) == 0:
sinks.remove(sink)
else:
clks = list()
for clk in clocks:
if is_clock_assoc(infiles, top, clk, name, iodir):
clks.append(clk)
if clks and int(port_attrs.get("NO_SEQ", 0)) == 0:
attrs["clock"] = " ".join(clks)
if len(sinks) > 0 and iodir == "input" and \
int(port_attrs.get("NO_COMB", 0)) == 0:
attrs["combinational_sink_ports"] = " ".join(sinks)
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"))
return ET.tostring(models_xml, pretty_print=True).decode('utf-8')