| #!/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)) |