| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| # | |
| # Copyright (C) 2020 The SymbiFlow Authors. | |
| # | |
| # Use of this source code is governed by a ISC-style | |
| # license that can be found in the LICENSE file or at | |
| # https://opensource.org/licenses/ISC | |
| # | |
| # SPDX-License-Identifier: ISC | |
| """ | |
| 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 | |
| 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: | |
| nocomb = tmod.net_attr(name, "NO_COMB") | |
| is_clock = name in clocks or utils.is_clock_name(name) | |
| port_attrs = tmod.port_attrs(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) | |
| # FIXME: Check if ignoring clock for "combination_sink_ports" | |
| # is a valid thing to do. | |
| if is_clock: | |
| attrs["is_clock"] = "1" | |
| else: | |
| clks = list() | |
| if len(sinks) > 0 and iodir == "input" and nocomb is None: | |
| 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")) | |
| return ET.tostring(models_xml, pretty_print=True).decode('utf-8') |