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