blob: 307ab93fc5b46878f38de60ecd23b59141eae3ba [file] [log] [blame]
#!/usr/bin/env python3
"""
Generate MUX.
MUXes come in two types,
1) Configurable via logic signals,
2) Statically configured by PnR (called "routing") muxes.
"""
import argparse
import itertools
import lxml.etree as ET
import os
import sys
from lib import mux as mux_lib
from lib.argparse_extra import ActionStoreBool
from lib.asserts import assert_eq
parser = argparse.ArgumentParser(
description='Generate a MUX wrapper.',
fromfile_prefix_chars='@',
prefix_chars='-~'
)
parser.add_argument(
'--verbose',
'--no-verbose',
action=ActionStoreBool,
default=os.environ.get('V', '') == '1',
help="Print lots of information about the generation."
)
parser.add_argument('--width', type=int, default=8, help="Width of the MUX.")
parser.add_argument(
'--data-width', type=int, default=1, help="data width of the MUX."
)
parser.add_argument(
'--type',
choices=['logic', 'routing'],
default='logic',
help="Type of MUX."
)
parser.add_argument(
'--split-inputs',
action=ActionStoreBool,
default=False,
help="Split the inputs into separate signals"
)
parser.add_argument(
'--split-selects',
action=ActionStoreBool,
default=False,
help="Split the selects into separate signals"
)
parser.add_argument(
'--name-mux', type=str, default='MUX', help="Name of the mux."
)
parser.add_argument(
'--name-input',
type=str,
default='I',
help="Name of the input values for the mux."
)
parser.name_inputs = parser.add_argument(
'--name-inputs',
type=str,
default=None,
help= # noqa: E251
"Comma deliminator list for the name of each input to the mux (implies --split-inputs)."
)
parser.add_argument(
'--name-output',
type=str,
default='O',
help="Name of the output value for the mux."
)
parser.add_argument(
'--name-select',
type=str,
default='S',
help="Name of the select parameter for the mux."
)
parser.name_selects = parser.add_argument(
'--name-selects',
type=str,
default=None,
help= # noqa: E251
"Comma deliminator list for the name of each select to the mux (implies --split-selects)."
)
parser.add_argument(
'--order',
choices=[''.join(x) for x in itertools.permutations('ios')] +
[''.join(x) for x in itertools.permutations('io')],
default='iso',
help= # noqa: E251
"""Order of the arguments for the MUX. (i - Inputs, o - Output, s - Select)"""
)
parser.add_argument(
'--outdir',
default=None,
help="""Directory to output generated content too."""
)
parser.add_argument(
'--outfilename',
default=None,
help="""Filename to output generated content too."""
)
parser.add_argument(
'--comment', default=None, help="""Add some type of comment to the mux."""
)
parser.add_argument(
'--num_pb', default=1, help="""Set the num_pb for the mux."""
)
parser.add_argument(
'--subckt', default=None, help="""Override the subcircuit name."""
)
def main(argv):
args = parser.parse_args()
def output_block(name, s):
if args.verbose:
print()
print(name, '-' * (75 - (len(name) + 1)))
print(s, end="")
if s[-1] != '\n':
print()
print('-' * 75)
args.width_bits = mux_lib.clog2(args.width)
def normpath(p, to=None):
p = os.path.realpath(os.path.abspath(p))
if to is None:
return p
return os.path.relpath(p, normpath(to))
mypath = normpath(__file__)
if not args.outdir:
outdir = os.path.join(".", args.name_mux.lower())
else:
outdir = args.outdir
outdir = normpath(outdir)
mydir = normpath(os.path.dirname(mypath), to=outdir)
mux_dir = normpath(os.path.join(mydir, '..', 'vpr', 'muxes'), to=outdir)
if args.data_width > 1 and not args.split_inputs:
assert False, "data_width(%d) > 1 requires using split_inputs" % (
args.data_width
)
if args.name_inputs:
assert_eq(args.name_input, parser.get_default("name_input"))
args.name_input = None
args.split_inputs = True
names = args.name_inputs.split(',')
assert len(names) == args.width, "%s input names, but %s needed." % (
names, args.width
)
args.name_inputs = names
elif args.split_inputs:
args.name_inputs = [
args.name_input + str(i) for i in range(args.width)
]
parser.name_inputs.default = args.name_inputs
assert_eq(parser.get_default("name_inputs"), args.name_inputs)
if args.name_selects:
assert_eq(args.name_select, parser.get_default("name_select"))
args.name_select = None
args.split_selects = True
names = args.name_selects.split(',')
assert len(names) == args.width_bits, (
"%s select names, but %s needed." % (names, args.width_bits)
)
args.name_selects = names
elif args.split_selects:
args.name_selects = [
args.name_select + str(i) for i in range(args.width_bits)
]
parser.name_selects.default = args.name_selects
assert_eq(parser.get_default("name_selects"), args.name_selects)
os.makedirs(outdir, exist_ok=True)
# Generated headers
generated_with = """
Generated with %s
""" % mypath
# XML Files can't have "--" in them, so instead we use ~~
xml_comment = """
Generated with %s
""" % "\n".join(argv).replace('--', '~~')
if not args.outfilename:
args.outfilename = args.name_mux.lower()
model_xml_filename = '%s.model.xml' % args.outfilename
pbtype_xml_filename = '%s.pb_type.xml' % args.outfilename
sim_filename = '%s.sim.v' % args.outfilename
# ------------------------------------------------------------------------
# Work out the port and their names
# ------------------------------------------------------------------------
port_names = []
for i in args.order:
if i == 'i':
if args.split_inputs:
port_names.extend(
mux_lib.ModulePort(
mux_lib.MuxPinType.INPUT, args.name_inputs[j], 1,
'[%i]' % j, args.data_width
) for j in range(args.width)
)
else:
# verilog range bounds are inclusive and convention is
# [<width-1>:0]
port_names.append(
mux_lib.ModulePort(
mux_lib.MuxPinType.INPUT, args.name_input, args.width,
'[%i:0]' % (args.width - 1)
)
)
elif i == 's':
if args.split_selects:
port_names.extend(
mux_lib.ModulePort(
mux_lib.MuxPinType.SELECT, args.name_selects[j], 1,
'[%i]' % j
) for j in range(args.width_bits)
)
else:
# verilog range bounds are inclusive and convention is
# [<width-1>:0]
assert args.name_select is not None
port_names.append(
mux_lib.ModulePort(
mux_lib.MuxPinType.SELECT, args.name_select,
args.width_bits, '[%i:0]' % (args.width_bits - 1)
)
)
elif i == 'o':
port_names.append(
mux_lib.ModulePort(
mux_lib.MuxPinType.OUTPUT, args.name_output, 1, '',
args.data_width
)
)
# ------------------------------------------------------------------------
# Generate the techmap Verilog module
# ------------------------------------------------------------------------
techmap_filename = '%s.techmap.v' % args.outfilename
techmap_pathname = os.path.join(outdir, techmap_filename)
if args.type == 'routing':
with open(techmap_pathname, "w") as f:
module_args = []
for port in port_names:
if args.type == 'routing' and port.pin_type == mux_lib.MuxPinType.SELECT:
continue
module_args.append(port.name)
f.write("/* ")
f.write("\n * ".join(generated_with.splitlines()))
f.write("\n */\n\n")
f.write(
"module %s(%s);\n" %
(args.name_mux.upper(), ", ".join(module_args))
)
f.write('\tparameter MODE = "";\n')
modes = [
port.name
for port in port_names
if port.pin_type in (mux_lib.MuxPinType.INPUT, )
]
outputs = [
port.name
for port in port_names
if port.pin_type in (mux_lib.MuxPinType.OUTPUT, )
]
for port in port_names:
if port.pin_type != mux_lib.MuxPinType.SELECT:
f.write(port.getDefinition())
f.write('\tgenerate\n')
for i, mode in enumerate(modes):
f.write(
'\t\t%s ( MODE == "%s" )\n' %
(('if', 'else if')[i > 0], mode)
)
f.write('\t\tbegin\n')
f.write('\t\t\tassign %s = %s;\n' % (outputs[0], mode))
f.write('\t\tend\n')
f.write('\t\telse\n')
f.write('\t\tbegin\n')
f.write('\t\t\twire _TECHMAP_FAIL_ = 1;\n')
f.write('\t\tend\n')
f.write("\tendgenerate\n")
f.write("endmodule")
# ------------------------------------------------------------------------
# Generate the sim.v Verilog module
# ------------------------------------------------------------------------
sim_pathname = os.path.join(outdir, sim_filename)
with open(sim_pathname, "w") as f:
module_args = []
for port in port_names:
if args.type == 'routing' and port.pin_type == mux_lib.MuxPinType.SELECT:
continue
module_args.append(port.name)
mux_class = {'logic': 'mux', 'routing': 'routing'}[args.type]
f.write("/* ")
f.write("\n * ".join(generated_with.splitlines()))
f.write("\n */\n\n")
f.write("`default_nettype none\n")
f.write("\n")
if args.type != 'routing':
f.write(
'`include "%s/%s/%smux%i/%smux%i.sim.v"\n' % (
mux_dir,
'logic',
'',
args.width,
'',
args.width,
)
)
f.write("\n")
f.write('(* CLASS="%s" *)\n' % mux_class)
if args.type == 'routing':
modes = [
port.name
for port in port_names
if port.pin_type in (mux_lib.MuxPinType.INPUT, )
]
outputs = [
port.name
for port in port_names
if port.pin_type in (mux_lib.MuxPinType.OUTPUT, )
]
assert len(
outputs
) == 1, "FIXME: routing muxes should only have 1 output."
f.write('(* MODES="%s" *)\n' % "; ".join(modes))
f.write('(* whitebox *)\n')
f.write(
"module %s(%s);\n" %
(args.name_mux.upper(), ", ".join(module_args))
)
previous_type = None
for port in port_names:
if previous_type != port.pin_type:
f.write("\n")
previous_type = port.pin_type
if args.type == 'routing' and port.pin_type == mux_lib.MuxPinType.SELECT:
f.write('\tparameter MODE = "";\n')
else:
f.write(port.getDefinition())
f.write("\n")
if args.data_width > 1:
f.write('\tgenvar\tii;\n')
f.write(
'\tfor(ii=0; ii<%d; ii++) begin: bitmux\n' % (args.data_width)
)
if args.type == 'logic':
f.write('\tMUX%s mux (\n' % args.width)
for i in range(0, args.width):
j = 0
for port in port_names:
if port.pin_type != mux_lib.MuxPinType.INPUT:
continue
if j + port.width <= i:
j += port.width
continue
break
if port.width == 1:
if args.data_width > 1:
f.write('\t\t.I%i(%s[ii]),\n' % (i, port.name))
else:
f.write('\t\t.I%i(%s),\n' % (i, port.name))
else:
f.write('\t\t.I%i(%s[%i]),\n' % (i, port.name, i - j))
for i in range(0, args.width_bits):
j = 0
for port in port_names:
if port.pin_type != mux_lib.MuxPinType.SELECT:
continue
if j + port.width < i:
j += port.width
continue
break
if port.width == 1:
f.write('\t\t.S%i(%s),\n' % (i, port.name))
else:
f.write('\t\t.S%i(%s[%i]),\n' % (i, port.name, i - j))
for port in port_names:
if port.pin_type != mux_lib.MuxPinType.OUTPUT:
continue
break
assert_eq(port.width, 1)
if args.data_width > 1:
f.write('\t\t.O(%s[ii])\n\t);\n' % port.name)
else:
f.write('\t\t.O(%s)\n\t);\n' % port.name)
elif args.type == 'routing':
f.write('\tgenerate\n')
for i, mode in enumerate(modes):
f.write(
'\t\t%s ( MODE == "%s" )\n' %
(('if', 'else if')[i > 0], mode)
)
f.write('\t\tbegin:SELECT_%s\n' % mode)
f.write('\t\t\tassign %s = %s;\n' % (outputs[0], mode))
f.write('\t\tend\n')
f.write('\t\telse\n')
f.write('\t\tbegin\n')
f.write(
'\t\t\t//$error("%s: Invalid routing value %%s (options are: %s)", MODE);\n'
% (args.name_mux, ", ".join(modes))
)
f.write('\t\tend\n')
f.write('\tendgenerate\n')
if args.data_width > 1:
f.write('end\n')
f.write('endmodule\n')
output_block(sim_filename, open(sim_pathname).read())
if args.type == 'logic':
subckt = args.subckt or args.name_mux
assert subckt
elif args.type == 'routing':
assert args.subckt is None
subckt = None
# ------------------------------------------------------------------------
# Generate the Model XML form.
# ------------------------------------------------------------------------
def xml_comment_indent(n, s):
return ("\n" + " " * n).join(s.splitlines() + [""])
if args.type == 'logic':
models_xml = ET.Element('models')
models_xml.append(ET.Comment(xml_comment_indent(4, xml_comment)))
model_xml = ET.SubElement(models_xml, 'model', {'name': subckt})
input_ports = ET.SubElement(model_xml, 'input_ports')
output_ports = ET.SubElement(model_xml, 'output_ports')
for port in port_names:
if port.pin_type in (mux_lib.MuxPinType.INPUT,
mux_lib.MuxPinType.SELECT):
ET.SubElement(
input_ports, 'port', {
'name':
port.name,
'combinational_sink_ports':
' '.join(
port.name for port in port_names if
port.pin_type in (mux_lib.MuxPinType.OUTPUT, )
),
}
)
elif port.pin_type in (mux_lib.MuxPinType.OUTPUT, ):
ET.SubElement(output_ports, 'port', {'name': port.name})
models_str = ET.tostring(models_xml, pretty_print=True).decode('utf-8')
else:
models_str = "<models><!-- No models for routing elements.--></models>"
output_block(model_xml_filename, models_str)
with open(os.path.join(outdir, model_xml_filename), "w") as f:
f.write(models_str)
# ------------------------------------------------------------------------
# Generate the pb_type XML form.
# ------------------------------------------------------------------------
pb_type_xml = mux_lib.pb_type_xml(
mux_lib.MuxType[args.type.upper()],
args.name_mux,
port_names,
subckt=subckt,
num_pb=args.num_pb,
comment=xml_comment_indent(4, xml_comment),
)
pb_type_str = ET.tostring(pb_type_xml, pretty_print=True).decode('utf-8')
output_block(pbtype_xml_filename, pb_type_str)
with open(os.path.join(outdir, pbtype_xml_filename), "w") as f:
f.write(pb_type_str)
print("Generated mux {} in {}".format(args.name_mux, outdir))
if __name__ == "__main__":
sys.exit(main(sys.argv))