blob: de5db80b678743a058b0f7e1de099d80bde2e656 [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 `pb_type.xml`
The following are allowed on a top level module:
- `(* blackbox *)` : specify that the module has no interconnect or child
pb_types (but if modes are used then its modes are allowed to have these).
This will also set the BLIF model to be `.subckt <name>` unless CLASS is
also specified.
- `(* CLASS="input|output|lut|routing|mux|flipflop|mem" *)` : specify
the class of an given instance.
- `(* MODES="mode1; mode2; ..." *)` : specify that the module has more
than one functional mode, each with a given name. The module will be
evaluated n times, each time setting the MODE parameter to the nth value
in the list of mode names. Each evaluation will be put in a pb_type
`<mode>` section named accordingly.
- `(* 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.
The following are allowed on nets within modules
(TODO: use proper Verilog timing):
- `(* SETUP="clk 10e-12" *)` : specify setup time for a given clock
- `(* HOLD="clk 10e-12" *)` : specify hold time for a given clock
- `(* CLK_TO_Q="clk 10e-12" *)` : specify clock-to-output time for
a given clock
- `(* DELAY_CONST_{input}="30e-12" *)` : specify a constant max delay
from an input (applied to the
output)
- `(* DELAY_MATRIX_{input}="30e-12 35e-12; 20e-12 25e-12; ..." *)` :
specify a VPR delay matrix (semicolons indicate rows). In this
format columns specify inputs bits and rows specify output bits.
This should be applied to the output.
- `(* carry = ADDER *)` : specify carry chain pack_pattern associated
with this wire
The following are allowed 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
- `(* PORT_CLASS="clock" *)` : specify the VPR "port_class"
- `(* carry = ADDER *)` : specify carry chain pack_pattern associated
with this port
The Verilog define "PB_TYPE" is set during generation.
"""
import os
import sys
import re
from typing import List, Dict, Tuple
from collections import defaultdict
import lxml.etree as ET
from .yosys import run
from .yosys.json import YosysJSON
from .yosys import utils as utils
from .xmlinc import xmlinc # noqa: E402
def normalize_pb_name(pb_name):
""" Some pb_type names generatedby the tool
are illegal in VPR. This function converts them to
legal ones e.g:
output_dffs_gen[0].q_out_ff -> output_dffs_gen_q_out_ff_0
"""
if pb_name is None:
return None
index = re.search(r'\[[0-9]+\](?!$)', pb_name)
normalized_name = pb_name.replace('.', '_')
if index is not None:
normalized_name = normalized_name.replace(
index.group(0), "") + index.group(0).replace('[', '_').replace(
']', '')
return normalized_name
def is_mod_blackbox(mod):
""" Returns true if module is annotated with blackbox (or equivalent).
Yosys supports 3 attributes that denote blackbox behavior:
"blackbox" - Blackbox with no internal wiring
"whitebox" - Blackbox with internal connections and timing.
"lib_whitebox" - Like "whitebox" when read with "-lib", otherwise
attribute is removed.
"""
return (mod.attr("lib_whitebox", 0) == 1) or \
(mod.attr("whitebox", 0) == 1) or \
(mod.attr("blackbox", 0) == 1)
# $genblock$/vlog/tests/multiple_instance/multiple_instance.sim.v:12$64[57].\comb
GENBLOCK_REGEX = re.compile(
"^\\$genblock\\$.*:[0-9]+\\$[0-9]+\\[(.*)\\]\\.\\\\(.*)")
def strip_name(name: str, include_index=True) -> str:
"""Convert generate block into normal array form.
>>> n = r"$genblock$/tests/multiple_instance/multiple_instance.sim.v:12$64[57].\\comb" # noqa: E501
>>> strip_name(n)
'comb[57]'
>>> strip_name(n, False)
'comb'
>>> n = r"$genblock$/tests/multiple_instance/multiple_instance.sim.v:15$10[3].\\comba" # noqa: #501
>>> strip_name(n)
'comba[3]'
"""
if name.startswith('$genblock$'):
index, name = GENBLOCK_REGEX.match(name).groups()
if include_index:
return "{}[{}]".format(name, index)
else:
return name
return name
CellName = str
CellType = str
PinName = str
CellPin = Tuple[CellName, PinName]
def create_port(
dir_xml: ET.Element, cell_pin: CellPin, direction: str,
metadata=None) -> ET.Element:
cell_name, pin_name = cell_pin
port = dict()
port['name'] = pin_name
port['type'] = direction
if cell_name:
port['from'] = cell_name
port_xml = ET.SubElement(dir_xml, 'port', port)
if metadata:
meta_root = ET.SubElement(port_xml, "metadata")
for name, value in metadata.items():
meta_type = ET.SubElement(meta_root, 'meta', {'name': name})
meta_type.text = value
return port_xml
def copy_attrs(dst, srcs):
# Find attributes which are on all srcs dictionaries
all_have = []
for attr in set(sum((list(s.keys()) for s in srcs), [])):
if len(srcs) != sum(bool(attr in s) for s in srcs):
continue
all_have.append(attr)
for attr in all_have:
avalue = srcs[0][attr]
avalues = [s[attr] for s in srcs[1:]]
for other_avalue in avalues:
if avalue == other_avalue:
continue
raise ValueError('{} values: {}'.format(attr, [avalue] + avalues))
if attr in dst:
assert avalue == dst[attr]
raise ValueError(
'{} on net has value {} but pins have {}'.format(
attr, dst[attr], avalue))
dst[attr] = avalue
def net_and_pin_attrs(yj, mod, driver: CellPin, sink: CellPin, netid: int):
def filter_src(x):
d = {}
for k, v in x.items():
if k == 'src':
continue
d[k] = v
return d
potential_attrs = []
driver_cell, driver_pin = driver
driver_type = mod.cell_type(driver_cell)
if driver_type is not None:
dmod = yj.module(driver_type)
potential_attrs.append(filter_src(dmod.port_attrs(driver_pin)))
sink_cell, sink_pin = sink
sink_type = mod.cell_type(sink_cell)
if sink_type is not None:
smod = yj.module(sink_type)
potential_attrs.append(filter_src(smod.port_attrs(sink_pin)))
net_attrs = filter_src(mod.net_attrs_by_netid(netid))
net_attrs_norm = {k.lower(): v for k, v in net_attrs.items()}
copy_attrs(net_attrs_norm, potential_attrs)
return net_attrs_norm
def make_direct_conn(
ic_xml: ET.Element, driver: CellPin, sink: CellPin,
path_attr: dict) -> ET.Element:
dir_xml = ET.SubElement(ic_xml, 'direct')
create_port(dir_xml, driver, "input")
create_port(dir_xml, sink, "output")
pack_name = path_attr.get('pack', False)
if pack_name:
pp_xml = ET.SubElement(
dir_xml, 'pack_pattern', {
'name': pack_name,
'type': 'pack'
})
create_port(pp_xml, driver, "input")
create_port(pp_xml, sink, "output")
carry_name = path_attr.get('carry', None)
if carry_name:
pp_xml = ET.SubElement(
dir_xml, 'pack_pattern', {
'name': carry_name,
'type': 'carry'
})
create_port(pp_xml, driver, "input")
create_port(pp_xml, sink, "output")
return dir_xml
def make_mux_conn(
ic_xml: ET.Element, mux_name: str, mux_inputs: Dict[CellPin, CellPin],
mux_outputs: Dict[CellPin, List[CellPin]]) -> ET.Element:
mux_xml = ET.SubElement(ic_xml, "mux", {"name": mux_name})
keys = sorted(list(mux_inputs.keys()))
for mux_input, driver in [(
k,
mux_inputs[k],
) for k in keys]:
metadata = {'fasm_mux': '{}.{}'.format(mux_name, mux_input)}
create_port(mux_xml, driver, "input", metadata=metadata)
assert len(mux_outputs) == 1, mux_outputs
keys = sorted(list(mux_outputs.keys()))
for mux_pin, sinks in [(
k,
mux_outputs[k],
) for k in keys]:
assert len(sinks) == 1, sinks
for sink_pin, path_attr in sinks:
create_port(mux_xml, sink_pin, "output")
# <metadata>
meta_root = ET.SubElement(mux_xml, 'metadata')
# <meta name="type">bel</meta>
meta_type = ET.SubElement(meta_root, 'meta', {'name': 'type'})
meta_type.text = "bel"
# <meta name="subtype">routing</meta>
meta_subtype = ET.SubElement(meta_root, 'meta', {'name': 'subtype'})
meta_subtype.text = "routing"
return mux_xml
def get_interconnects(yj, mod, mod_pname: str,
valid_names) -> Dict[CellPin, List[CellPin]]:
"""Get the connectivity of module.
Returns:
A dictionary containing with a list of sink pins for each driver pin.
"""
interconn = defaultdict(list)
# Cell connections
for cname, ctype in mod.cells:
pb_name = strip_name(cname)
assert pb_name in valid_names
if pb_name == mod_pname:
pb_name = None
# All interconnect going INTO a cell (top level or children).
inp_cons = mod.cell_conns(cname, "input")
for pin, net in inp_cons:
drvs = mod.net_drivers(net)
assert len(drvs) > 0, (
"ERROR: pin {}.{} has no driver, \
interconnect will be missing\n{}".format(cname, pin, mod))
assert len(drvs) < 2, (
"ERROR: pin {}.{} has multiple drivers, \
interconnect will be overspecified".format(cname, pin))
for drv_cell, drv_pin in drvs:
net_attr = net_and_pin_attrs(
yj, mod, (drv_cell, drv_pin), (pb_name, pin), net)
drv_cell_name = strip_name(drv_cell)
assert drv_cell_name in valid_names
if drv_cell_name == mod_pname:
drv_cell_name = None
interconn[(drv_cell_name, drv_pin)].append(
((pb_name, pin), net_attr))
# Only consider outputs from cell to top level IO.
# Inputs to other cells will be dealt with in those cells.
out_cons = mod.cell_conns(cname, "output")
for pin, net in out_cons:
sinks = mod.net_sinks(net)
for sink_cell, sink_pin in sinks:
if sink_cell != mod.name:
continue
net_attr = net_and_pin_attrs(
yj, mod, (pb_name, pin), (None, sink_pin), net)
interconn[(pb_name, pin)].append(((None, sink_pin), net_attr))
# Passthrough connections. Get ports along with connections
inp_ports = [p for p in mod.ports if p[3] == "input"]
out_ports = [p for p in mod.ports if p[3] == "output"]
# Loop over outputs and assign them with connected inputs
for out_port in out_ports:
for out_bit, out_net in enumerate(out_port[2]):
# Format full output port name
if out_port[1] == 1:
out_name = out_port[0]
else:
out_name = "{}[{}]".format(out_port[0], out_bit)
# Find input
for inp_port in inp_ports:
for inp_bit, inp_net in enumerate(inp_port[2]):
# Format full input port name
if inp_port[1] == 1:
inp_name = inp_port[0]
else:
inp_name = "{}[{}]".format(inp_port[0], inp_bit)
# Find matching nets
if out_net == inp_net:
key = (None, inp_name)
val = ((None, out_name), {})
interconn[key].append(val)
import pprint
pprint.pprint(list(interconn.values()))
def pin_sort(p):
pin, attr = p
if pin[0] is None:
return ('', pin[1])
else:
return pin
for v in interconn.values():
v.sort(key=pin_sort)
return interconn
def mode_interconnects(mod, mode_name) -> Dict[CellPin, List[CellPin]]:
"""
This function returns a definition of an interconnect used to connect
a child pb_type for the given mode with its parent pb_type that provides
the modes.
The returned dict is indexed by tuples containing source (driver) mode
names and pin names. Its values contain lists of sink modes and pin names
that are driven by the driver. If the mode name is None then the connection
refers to the parent pb_type.
"""
interconn = {}
for name, width, bits, iodir in mod.ports:
if iodir == "input":
interconn[(None, name)] = [(
(
mode_name,
name,
),
{},
)]
else:
interconn[(mode_name, name)] = [(
(
None,
name,
),
{},
)]
return interconn
CellPrefix = str
ChildrenDict = Dict[CellPrefix, Tuple[CellType, List[CellName]]]
def get_children(yj, mod) -> Tuple[ChildrenDict, ChildrenDict]:
routing = dict()
children = dict()
for cname, ctype in mod.cells:
# We currently special case routing muxes
cell = yj.module(ctype)
if cell.CLASS == "routing":
d = routing
else:
d = children
cname_prefix = strip_name(cname, False)
if cname_prefix not in children:
d[cname_prefix] = (ctype, [])
assert d[cname_prefix][
0
] == ctype, \
"Type of {} with prefix {} doesn't match \
existing. Type: {}, existing: {}".format(
cname, cname_prefix, ctype, children[cname_prefix]
)
d[cname_prefix][-1].append(strip_name(cname))
for d in (routing, children):
for _, l in children.values():
if len(l) > 1:
l.sort()
_, _ = get_list_name_and_length(l)
return routing, children
def get_cellname_from_shortname(shortname, mod):
for cname, ctype in mod.cells:
if shortname != strip_name(cname):
continue
return cname
raise NameError("No cell named {}".format(shortname))
def get_list_name_and_length(v: List[str]) -> Tuple[str, int]:
"""
>>> get_list_name_and_length(['i[{}]'.format(i) for i in range(10)])
('i', 10)
Assertion failure on missing value
>>> get_list_name_and_length(['i[0]', 'i[2]'])
Traceback (most recent call last):
...
AssertionError: index 1 expected: i[1] != actual: i[2]
['i[0]', 'i[2]']
Assertion failure when not starting at zero
>>> get_list_name_and_length(['i[1]', 'i[2]'])
Traceback (most recent call last):
...
AssertionError: index 0 expected: i[0] != actual: i[1]
['i[1]', 'i[2]']
Assertion failure when duplicate values
>>> get_list_name_and_length(['i[0]', 'i[0]'])
Traceback (most recent call last):
...
AssertionError: index 1 expected: i[1] != actual: i[0]
['i[0]', 'i[0]']
>>> get_list_name_and_length(['i[0]', 'i[1]', 'i[1]'])
Traceback (most recent call last):
...
AssertionError: index 2 expected: i[2] != actual: i[1]
['i[0]', 'i[1]', 'i[1]']
>>> get_list_name_and_length(['i[0]', 'i[1]', 'i[1]', 'i[2]'])
Traceback (most recent call last):
...
AssertionError: index 2 expected: i[2] != actual: i[1]
['i[0]', 'i[1]', 'i[1]', 'i[2]']
Assertion failure on incorrect formatting
>>> get_list_name_and_length(['i{}'.format(i) for i in range(4)])
Traceback (most recent call last):
...
AssertionError: No index brackets found in item 0: i0
['i0', 'i1', 'i2', 'i3']
Allow square brackets in name
>>> get_list_name_and_length(['i[1][{}]'.format(i) for i in range(4)])
('i[1]', 4)
"""
if not v:
return True
assert '[' in v[0], "No index brackets found in item 0: {}\n{}".format(
v[0], v)
list_name = v[0][:v[0].rfind('[')]
sl = sorted(v, key=len)
for i in range(0, len(v)):
expected_item = "{}[{}]".format(list_name, i)
assert expected_item == sl[
i], "index {} expected: {} != actual: {}\n{}".format(
i, expected_item, sl[i], sl)
return list_name, len(v)
def make_ports(clocks, mod, pb_type_xml, only_type=None):
for name, width, bits, iodir in mod.ports:
ioattrs = {"name": name, "num_pins": str(width)}
pclass = mod.net_attr(name, "PORT_CLASS")
if pclass is not None:
ioattrs["port_class"] = pclass
if name in clocks:
if only_type and only_type != "clocks":
continue
port_xml = ET.SubElement(pb_type_xml, "clock", ioattrs)
elif iodir == "input":
if only_type and only_type != "inputs":
continue
port_xml = ET.SubElement(pb_type_xml, "input", ioattrs)
elif iodir == "output":
if only_type and only_type != "outputs":
continue
port_xml = ET.SubElement(pb_type_xml, "output", ioattrs)
else:
assert False, "bidirectional ports not supported in VPR pb_types"
port_attrs = mod.port_attrs(name)
carry_name = port_attrs.get('carry', None)
if carry_name:
ET.SubElement(
port_xml, 'pack_pattern', {
'name': carry_name,
'type': 'carry'
})
def make_container_pb(
outfile, yj, mod, mod_pname, pb_type_xml, routing, children):
# Containers have to include children
# ------------------------------------------------------------
for child_prefix, (child_type, children_names) in children.items():
# Work out were the child pb_type file can be found
module_file = yj.get_module_file(child_type)
module_path = os.path.dirname(module_file)
module_basename = os.path.basename(module_file)
module_prefix = re.match(r"([A-Za-z0-9_]+)\.sim\.v",
module_basename).groups()[0]
pb_type_path = "{}/{}.pb_type.xml".format(module_path, module_prefix)
include_as_is = True
comment_str = ""
# Read the top level properties of the pb_type
with open(pb_type_path, 'r') as inc_xml:
xml_inc = ET.fromstring(inc_xml.read().encode('utf-8'))
inc_attrib = xml_inc.attrib
normalized_name = normalize_pb_name(child_prefix)
num_pb = str(len(children_names))
if normalized_name != inc_attrib['name']:
comment_str += "old_name {}".format(inc_attrib['name'])
inc_attrib['name'] = normalize_pb_name(child_prefix)
include_as_is = False
if num_pb != inc_attrib['num_pb']:
comment_str += " old_num_pb {}".format(inc_attrib['num_pb'])
inc_attrib['num_pb'] = str(len(children_names))
include_as_is = False
xptr = None
parent_xml = pb_type_xml
if include_as_is is not True:
xptr = "xpointer(pb_type/child::node())"
parent_xml = ET.SubElement(pb_type_xml, 'pb_type', inc_attrib)
parent_xml.append(ET.Comment(comment_str))
xmlinc.include_xml(
parent=parent_xml, href=pb_type_path, outfile=outfile, xptr=xptr)
# Contains need interconnect to their children
# ------------------------------------------------------------
# Work out valid names for cells to sanity check the interconnects.
valid_names = [mod_pname]
routing_cells = []
for _, routing_names in routing.values():
routing_cells.extend(routing_names)
valid_names.extend(routing_cells)
for _, children_names in children.values():
valid_names.extend(children_names)
# Extract the interconnect from the module
interconn = get_interconnects(yj, mod, mod_pname, valid_names)
import pprint
print(mod_pname)
print("--")
pprint.pprint(interconn)
print("--")
print(routing_cells)
pprint.pprint(routing)
print("--")
# Generate the actual interconnect
ic_xml = ET.SubElement(pb_type_xml, "interconnect")
for (driver_cell, driver_pin), sinks in interconn.items():
if driver_cell in routing_cells:
continue
non_routing_sinks = [
(sink, path_attr)
for sink, path_attr in sinks
if sink[0] not in routing_cells
]
is_forking = len(non_routing_sinks) > 1
for (sink_cell, sink_pin), path_attr in non_routing_sinks:
attrs = dict(**path_attr)
# If a net forks remove pack pattern annotations from the branch
# that goes to an output port of the pb_type
if is_forking and sink_cell is None:
if "pack" in attrs:
del attrs["pack"]
make_direct_conn(
ic_xml, (normalize_pb_name(driver_cell), driver_pin),
(normalize_pb_name(sink_cell), sink_pin), attrs)
# Generate the mux interconnects
for mux_cell in routing_cells:
mux_outputs = defaultdict(list)
for (driver_cell, driver_pin), sinks in interconn.items():
if driver_cell != mux_cell:
continue
mux_outputs[driver_pin].extend(sinks)
assert len(mux_outputs) == 1, """\
Mux {} has multiple outputs ({})!
Currently muxes can only drive a single output.""".format(
mux_cell, ", ".join(mux_outputs.keys()))
for mux_output_pin, sinks in mux_outputs.items():
assert len(sinks) == 1, """\
Mux {}.{} has multiple outputs ({})!
Currently muxes can only drive a single output.""".format(
mux_cell, mux_output_pin, ", ".join(
"{}.{}".format(*pin) for pin, path_attr in sinks))
for (sink_cell, sink_pin), path_attr in sinks:
assert sink_cell not in routing_names, """\
Mux {}.{} is trying to drive mux input pin {}.{}""".format(
mux_cell, mux_output_pin, sink_cell, sink_pin)
mux_inputs = {}
for (driver_cell, driver_pin), sinks in interconn.items():
for (sink_cell, mux_pin), path_attr in sinks:
if sink_cell != mux_cell:
continue
assert driver_cell not in routing_names, \
"Mux {}.{} is trying to drive mux {}.{}".format(
driver_cell, driver_pin, mux_cell, sink_pin
)
assert sink_pin not in mux_inputs, """\
Pin {}.{} is trying to drive mux pin {}.{} (already driving by {}.{})\
""".format(
driver_cell, driver_pin, mux_cell, mux_pin,
*mux_inputs[sink_pin])
mux_inputs[mux_pin] = (driver_cell, driver_pin)
make_mux_conn(ic_xml, mux_cell, mux_inputs, mux_outputs)
def make_leaf_pb(outfile, yj, mod, mod_pname, pb_type_xml):
# As leaf node with "blif_model" set is a site., need to generate timing
# information.
def process_clocked_tmg(tmgspec, port, iodir, xmltype, xml_parent):
"""Add a suitable timing spec if necessary to the pb_type"""
if tmgspec is not None:
splitspec = tmgspec.split(" ")
assert len(
splitspec
) == 2, 'bad timing specification "{}", must be of format \
"clock value"'.format(tmgspec)
attrs = {"port": port, "clock": splitspec[0]}
if xmltype == "T_clock_to_Q":
assert iodir == "output", \
"Only output ports can have T_clock_to_Q timing \
definition. Port {}, direction {}.".format(
port, iodir)
attrs["max"] = splitspec[1]
else:
attrs["value"] = splitspec[1]
ET.SubElement(xml_parent, xmltype, attrs)
for name, width, bits, iodir in mod.ports:
port = "{}".format(name)
# Clocked timing
Tsetup = mod.net_attr(name, "SETUP")
Thold = mod.net_attr(name, "HOLD")
Tctoq = mod.net_attr(name, "CLK_TO_Q")
process_clocked_tmg(Tsetup, port, iodir, "T_setup", pb_type_xml)
process_clocked_tmg(Thold, port, iodir, "T_hold", pb_type_xml)
process_clocked_tmg(Tctoq, port, iodir, "T_clock_to_Q", pb_type_xml)
# Combinational delays
dly_prefix = "DELAY_CONST_"
dly_mat_prefix = "DELAY_MATRIX_"
for attr, atvalue in mod.net_attrs(name).items():
if attr.startswith(dly_prefix):
# Single, constant delays
inp = attr[len(dly_prefix):]
inport = "{}".format(inp)
ET.SubElement(
pb_type_xml, "delay_constant", {
"in_port": inport,
"out_port": port,
"max": str(atvalue)
})
elif attr.startswith(dly_mat_prefix):
# Constant delay matrices
inp = attr[len(dly_mat_prefix):]
inport = "{}".format(inp)
mat = "\n" + atvalue.replace(";", "\n") + "\n"
xml_mat = ET.SubElement(
pb_type_xml, "delay_matrix", {
"in_port": inport,
"out_port": port,
"type": "max"
})
xml_mat.text = mat
def make_pb_type(
infiles,
outfile,
yj,
mod,
mode_processing=False,
mode_xml=None,
mode_name=None):
"""Build the pb_type for a given module. mod is the YosysModule object to
generate."""
modes = mod.attr("MODES", None)
if modes is not None:
modes = modes.split(";")
mod_pname = mod.name
assert mod_pname == mod_pname.upper(
), "pb_type name should be all uppercase. {}".format(mod_pname)
pb_attrs = dict()
# If we are a blackbox with no modes, then generate a blif_model
is_blackbox = is_mod_blackbox(mod) or not mod.cells
has_modes = modes is not None
print("is_blackbox", is_blackbox, "has_modes?", has_modes)
# Process type and class of module
model_name = mod.attr("MODEL_NAME", mod.name)
assert model_name == model_name.upper(
), "Model name should be uppercase. {}".format(model_name)
mod_cls = mod.CLASS
if mod_cls is not None:
if mod_cls == "input":
pb_attrs["blif_model"] = ".input"
elif mod_cls == "output":
pb_attrs["blif_model"] = ".output"
elif mod_cls == "lut":
pb_attrs["blif_model"] = ".names"
pb_attrs["class"] = "lut"
elif mod_cls == "routing":
# TODO: pb_attrs["class"] = "routing"
pass
elif mod_cls == "mux":
pb_attrs["blif_model"] = ".subckt " + model_name
pass
elif mod_cls == "flipflop":
pb_attrs["blif_model"] = ".latch"
pb_attrs["class"] = "flipflop"
else:
assert False, "unknown class {}".format(mod_cls)
elif is_blackbox and not has_modes:
pb_attrs["blif_model"] = ".subckt " + model_name
# set num_pb to 1, it will be updated if this pb_type
# will be included by another one
if mode_xml is None:
pb_type_xml = ET.Element(
"pb_type", {
"num_pb": "1",
"name": mod_pname
},
nsmap={'xi': xmlinc.xi_url})
else:
pb_type_xml = ET.SubElement(
mode_xml,
"pb_type", {
"num_pb": "1",
"name": mode_name
},
nsmap={'xi': xmlinc.xi_url})
if 'blif_model' in pb_attrs:
ET.SubElement(pb_type_xml, "blif_model",
{}).text = pb_attrs["blif_model"]
if 'class' in pb_attrs:
ET.SubElement(pb_type_xml, "pb_class", {}).text = pb_attrs["class"]
# Create the pins for this pb_type
clocks = set(run.list_clocks(infiles, mod.name))
# Add extra clocks inferred from port names
# Mask out clocks with the attribute "CLOCK" not equal to 1
for name, width, bits, iodir in mod.ports:
port_attrs = mod.port_attrs(name)
is_clock = utils.is_clock_name(name)
# In pb_type "clock" ports can be only inputs. Clock outputs must
# be declared as "output".
if iodir == "output":
is_clock = False
if "CLOCK" in port_attrs:
is_clock = int(port_attrs["CLOCK"]) != 0
if is_clock:
clocks.add(name)
else:
clocks.discard(name)
make_ports(clocks, mod, pb_type_xml, "clocks")
make_ports(clocks, mod, pb_type_xml, "inputs")
make_ports(clocks, mod, pb_type_xml, "outputs")
if modes and not mode_processing:
for mode in modes:
smode = mode.strip()
mode_xml = ET.SubElement(pb_type_xml, "mode", {"name": smode})
# Rerun Yosys with mode parameter
mode_yj = YosysJSON(
run.vlog_to_json(
infiles,
flatten=False,
aig=False,
mode=smode,
module_with_mode=mod.name))
mode_mod = mode_yj.module(mod.name)
inter = {}
# The mode has no children. Don't generate a pb_type then. Make
# only the interconnect instead.
if len(mode_mod.cells) == 0:
inter.update(
get_interconnects(mode_yj, mode_mod, smode, [smode]))
# The mode has children, recurse
else:
make_pb_type(
infiles, outfile, mode_yj, mode_mod, True, mode_xml, smode)
inter.update(mode_interconnects(mod, smode))
# Add or update the interconnect.
ic_xml = mode_xml.find("interconnect")
if ic_xml is None:
ic_xml = ET.SubElement(mode_xml, "interconnect")
for (driv_cell, driv_pin), sinks in inter.items():
for (sink_cell, sink_pin), attrs in sinks:
make_direct_conn(
ic_xml, (driv_cell, driv_pin), (sink_cell, sink_pin),
attrs)
if not modes or mode_processing:
routing = children = []
if not is_blackbox:
routing, children = get_children(yj, mod)
if routing or children:
make_container_pb(
outfile, yj, mod, mod_pname, pb_type_xml, routing, children)
else:
make_leaf_pb(outfile, yj, mod, mod_pname, pb_type_xml)
return pb_type_xml
def vlog_to_pbtype(infiles, outfile, top=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])
run.add_define("PB_TYPE")
vjson = run.vlog_to_json(infiles, flatten=False, aig=False)
yj = YosysJSON(vjson)
if top is not None:
top = 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)
top = top.upper()
tmod = yj.module(top)
pb_type_xml = make_pb_type(infiles, outfile, yj, tmod)
return ET.tostring(
pb_type_xml, pretty_print=True, encoding="utf-8",
xml_declaration=True).decode('utf-8')