|  | #!/usr/bin/env python3 | 
|  | """ | 
|  | The generator | 
|  | """ | 
|  | import argparse | 
|  |  | 
|  | # ============================================================================= | 
|  |  | 
|  | # This is a "fake" pinout. It is not intended to work on any HW, just for the | 
|  | # toolchain verification. | 
|  | PINOUT = { | 
|  | "arty-full": | 
|  | { | 
|  | "clocks": [ | 
|  | "E3", | 
|  | "F4",  # This one is fake | 
|  | ], | 
|  | "generic": | 
|  | [ | 
|  | # All these are fake | 
|  | "C6", | 
|  | "C5", | 
|  | "B7", | 
|  | "B6", | 
|  | "A6", | 
|  | "A5", | 
|  | "D8", | 
|  | "C7", | 
|  | ], | 
|  | "single-ended": [ | 
|  | # Bank 15 | 
|  | "J17", | 
|  | "J18", | 
|  | "K15", | 
|  | "J15", | 
|  | ], | 
|  | "differential": [ | 
|  | # Bank 15 | 
|  | ("E15", "E16"), | 
|  | ("D15", "C15"), | 
|  | ], | 
|  | "idelayctrl": "IDELAYCTRL_X0Y1" | 
|  | }, | 
|  | "basys3-bottom": | 
|  | { | 
|  | "clocks": [ | 
|  | "M18", | 
|  | "L17", | 
|  | ], | 
|  | "generic": | 
|  | [ | 
|  | "N17", | 
|  | "P17", | 
|  | "P18", | 
|  | "R18", | 
|  | "U19", | 
|  | "V19", | 
|  | "W18", | 
|  | "W19", | 
|  | ], | 
|  | "single-ended": [ | 
|  | "V2", | 
|  | "W2", | 
|  | "V3", | 
|  | "W3", | 
|  | ], | 
|  | "differential": [ | 
|  | ("V2", "W2"), | 
|  | ("V3", "W3"), | 
|  | ], | 
|  | "idelayctrl": "IDELAYCTRL_X0Y0", | 
|  | }, | 
|  | } | 
|  |  | 
|  | # ============================================================================= | 
|  |  | 
|  | INPUT_IOBS = frozenset(( | 
|  | "IBUF", | 
|  | "IBUFDS", | 
|  | )) | 
|  | OUTPUT_IOBS = frozenset(( | 
|  | "OBUF", | 
|  | "OBUFT", | 
|  | "OBUFDS", | 
|  | "OBUFTDS", | 
|  | )) | 
|  | INOUT_IOBS = frozenset(( | 
|  | "IOBUF", | 
|  | "IOBUFDS", | 
|  | )) | 
|  |  | 
|  | TRISTATE_IOBS = frozenset(( | 
|  | "OBUFT", | 
|  | "IOBUF", | 
|  | "OBUFTDS", | 
|  | "IOBUFDS", | 
|  | )) | 
|  | DIFF_IOBS = frozenset(( | 
|  | "IBUFDS", | 
|  | "OBUFDS", | 
|  | "OBUFTDS", | 
|  | "IOBUFDS", | 
|  | )) | 
|  |  | 
|  | VALID_IOBS = INPUT_IOBS | OUTPUT_IOBS | INOUT_IOBS | 
|  |  | 
|  | # ============================================================================= | 
|  |  | 
|  |  | 
|  | def make_header(board, iob_type, use_idelay, use_iserdes, use_oserdes): | 
|  |  | 
|  | ck_pads = iter(PINOUT[board]["clocks"]) | 
|  | ge_pads = iter(PINOUT[board]["generic"]) | 
|  | se_pads = iter(PINOUT[board]["single-ended"]) | 
|  | df_pads = iter(PINOUT[board]["differential"]) | 
|  |  | 
|  | inp_ports = [] | 
|  | out_ports = [] | 
|  | ino_ports = [] | 
|  |  | 
|  | verilog = "" | 
|  | pcf = "" | 
|  | xdc = "" | 
|  |  | 
|  | # Single-ended IOB | 
|  | if iob_type not in DIFF_IOBS: | 
|  | if iob_type in INPUT_IOBS: | 
|  | inp_ports.append("pad") | 
|  | elif iob_type in OUTPUT_IOBS: | 
|  | out_ports.append("pad") | 
|  | elif iob_type in INOUT_IOBS: | 
|  | ino_ports.append("pad") | 
|  |  | 
|  | # LOC | 
|  | pad = next(se_pads) | 
|  | pcf += "set_io pad {}\n".format(pad) | 
|  |  | 
|  | # Differential IOB | 
|  | else: | 
|  | if iob_type in INPUT_IOBS: | 
|  | inp_ports.extend(["pad_p", "pad_n"]) | 
|  | elif iob_type in OUTPUT_IOBS: | 
|  | out_ports.extend(["pad_p", "pad_n"]) | 
|  | elif iob_type in INOUT_IOBS: | 
|  | ino_ports.extend(["pad_p", "pad_n"]) | 
|  |  | 
|  | # LOC | 
|  | pads = next(df_pads) | 
|  | pcf += "set_io pad_p {}\n".format(pads[0]) | 
|  | pcf += "set_io pad_n {}\n".format(pads[1]) | 
|  |  | 
|  | # IO standard | 
|  | xdc += "set_property IOSTANDARD DIFF_SSTL135 [get_ports pad_p]\n" | 
|  | xdc += "set_property IOSTANDARD DIFF_SSTL135 [get_ports pad_n]\n" | 
|  |  | 
|  | # Clock | 
|  | if use_iserdes or use_oserdes or use_idelay: | 
|  | inp_ports.append("clk") | 
|  |  | 
|  | pad = next(ck_pads) | 
|  | pcf += "set_io clk {}\n".format(pad) | 
|  |  | 
|  | if use_iserdes or use_oserdes: | 
|  | inp_ports.append("clkdiv") | 
|  |  | 
|  | pad = next(ck_pads) | 
|  | pcf += "set_io clkdiv {}\n".format(pad) | 
|  |  | 
|  | # Out | 
|  | if iob_type in INPUT_IOBS or iob_type in INOUT_IOBS: | 
|  | out_ports.append("out") | 
|  |  | 
|  | # LOC | 
|  | pad = next(ge_pads) | 
|  | pcf += "set_io out {}\n".format(pad) | 
|  |  | 
|  | # Tristate control | 
|  | if iob_type in TRISTATE_IOBS: | 
|  | inp_ports.append("oen") | 
|  |  | 
|  | # LOC | 
|  | pad = next(ge_pads) | 
|  | pcf += "set_io oen {}\n".format(pad) | 
|  |  | 
|  | # In | 
|  | if iob_type in OUTPUT_IOBS or iob_type in INOUT_IOBS: | 
|  | inp_ports.append("inp") | 
|  |  | 
|  | # LOC | 
|  | pad = next(ge_pads) | 
|  | pcf += "set_io inp {}\n".format(pad) | 
|  |  | 
|  | # IDELAYCTRL RDY out | 
|  | if use_idelay: | 
|  | out_ports.append("rdy") | 
|  |  | 
|  | # LOC | 
|  | pad = next(ge_pads) | 
|  | pcf += "set_io rdy {}\n".format(pad) | 
|  |  | 
|  | # IOSTANDARD | 
|  | for port in (*inp_ports, *out_ports, *ino_ports): | 
|  | if "_p" not in port and "_n" not in port: | 
|  | xdc += "set_property IOSTANDARD LVCMOS33 [get_ports {}]\n".format( | 
|  | port | 
|  | ) | 
|  |  | 
|  | # Make the Verilog header | 
|  | verilog += "module top (\n" | 
|  |  | 
|  | for port in inp_ports: | 
|  | verilog += "    input  wire {},\n".format(port) | 
|  | for port in out_ports: | 
|  | verilog += "    output wire {},\n".format(port) | 
|  | for port in ino_ports: | 
|  | verilog += "    inout  wire {},\n".format(port) | 
|  | verilog = verilog[:-2] + "\n" | 
|  |  | 
|  | verilog += ");\n" | 
|  |  | 
|  | # If there is a clock add a BUFG | 
|  | for clk in ["clk", "clkdiv"]: | 
|  | if clk in inp_ports: | 
|  | verilog += """ | 
|  | wire {clk}_bufg; | 
|  | BUFG bufg_{clk} (.I({clk}), .O({clk}_bufg)); | 
|  | """.format(clk=clk) | 
|  |  | 
|  | return verilog, pcf, xdc | 
|  |  | 
|  |  | 
|  | def make_iob(iob_type): | 
|  |  | 
|  | verilog = """ | 
|  | wire iob_i; // From IOB | 
|  | wire iob_o; // To IOB | 
|  | wire iob_t; | 
|  | """ | 
|  |  | 
|  | # Instantiate the IOB | 
|  | if iob_type == "IBUF": | 
|  |  | 
|  | verilog += """ | 
|  | IBUF iob ( | 
|  | .I(pad), | 
|  | .O(iob_i) | 
|  | ); | 
|  | """ | 
|  |  | 
|  | elif iob_type == "OBUF": | 
|  |  | 
|  | verilog += """ | 
|  | OBUF iob ( | 
|  | .I(iob_o), | 
|  | .O(pad) | 
|  | ); | 
|  | """ | 
|  |  | 
|  | elif iob_type == "OBUFT": | 
|  |  | 
|  | verilog += """ | 
|  | OBUFT iob ( | 
|  | .I(iob_o), | 
|  | .T(iob_t), | 
|  | .O(pad) | 
|  | ); | 
|  | """ | 
|  |  | 
|  | elif iob_type == "IOBUF": | 
|  |  | 
|  | verilog += """ | 
|  | IOBUF iob ( | 
|  | .I(iob_o), | 
|  | .T(iob_t), | 
|  | .O(iob_i), | 
|  | .IO(pad) | 
|  | ); | 
|  | """ | 
|  |  | 
|  | elif iob_type == "IBUFDS": | 
|  |  | 
|  | verilog += """ | 
|  | IBUFDS iob ( | 
|  | .I(pad_p), | 
|  | .IB(pad_n), | 
|  | .O(iob_i) | 
|  | ); | 
|  | """ | 
|  |  | 
|  | elif iob_type == "OBUFDS": | 
|  |  | 
|  | verilog += """ | 
|  | OBUFDS iob ( | 
|  | .I(iob_o), | 
|  | .O(pad_p), | 
|  | .OB(pad_n) | 
|  | ); | 
|  | """ | 
|  |  | 
|  | elif iob_type == "OBUFTDS": | 
|  |  | 
|  | verilog += """ | 
|  | OBUFTDS iob ( | 
|  | .I(iob_o), | 
|  | .T(iob_t), | 
|  | .O(pad_p), | 
|  | .OB(pad_n) | 
|  | ); | 
|  | """ | 
|  |  | 
|  | elif iob_type == "IOBUFDS": | 
|  |  | 
|  | verilog += """ | 
|  | IOBUFDS iob ( | 
|  | .I(iob_o), | 
|  | .T(iob_t), | 
|  | .O(iob_i), | 
|  | .IO(pad_p), | 
|  | .IOB(pad_n) | 
|  | ); | 
|  | """ | 
|  |  | 
|  | else: | 
|  | raise RuntimeError("Unsupported IOB type '{}'".format(iob_type)) | 
|  |  | 
|  | return verilog | 
|  |  | 
|  |  | 
|  | def make_idelay(board, use_idelay): | 
|  |  | 
|  | if use_idelay: | 
|  | loc = PINOUT[board]["idelayctrl"] | 
|  |  | 
|  | return """ | 
|  | wire dly_i; | 
|  |  | 
|  | IDELAYE2 #( | 
|  | .DELAY_SRC      ("IDATAIN"), | 
|  | .IDELAY_TYPE    ("FIXED"), | 
|  | .IDELAY_VALUE   (16) | 
|  | ) idelay ( | 
|  | .IDATAIN        (iob_i), | 
|  | .DATAOUT        (dly_i) | 
|  | ); | 
|  |  | 
|  | (* LOC="{}" *) | 
|  | IDELAYCTRL idelayctrl ( | 
|  | .REFCLK         (clk_bufg), | 
|  | .RDY            (rdy) | 
|  | ); | 
|  | """.format(loc) | 
|  |  | 
|  | else: | 
|  | return """ | 
|  | wire dly_i = iob_i; | 
|  | """ | 
|  |  | 
|  |  | 
|  | def make_iserdes(use_idelay, use_iserdes): | 
|  |  | 
|  | if use_iserdes: | 
|  |  | 
|  | if use_idelay: | 
|  | return """ | 
|  | wire dat_i; | 
|  |  | 
|  | ISERDESE2 #( | 
|  | .IOBDELAY("BOTH"), | 
|  | .DATA_RATE("SDR"), | 
|  | .DATA_WIDTH(4'd2), | 
|  | .INTERFACE_TYPE("NETWORKING") | 
|  | ) iserdes ( | 
|  | .DDLY(dly_i), | 
|  | .Q1(dat_i), | 
|  | .CLK(clk_bufg), | 
|  | .CLKB(clk_bufg), | 
|  | .CLKDIV(clkdiv_bufg) | 
|  | ); | 
|  | """ | 
|  | else: | 
|  | return """ | 
|  | wire dat_i; | 
|  |  | 
|  | ISERDESE2 #( | 
|  | .IOBDELAY("NONE"), | 
|  | .DATA_RATE("SDR"), | 
|  | .DATA_WIDTH(4'd2), | 
|  | .INTERFACE_TYPE("NETWORKING") | 
|  | ) iserdes ( | 
|  | .D(dly_i), | 
|  | .Q1(dat_i), | 
|  | .CLK(clk_bufg), | 
|  | .CLKB(clk_bufg), | 
|  | .CLKDIV(clkdiv_bufg) | 
|  | ); | 
|  | """ | 
|  |  | 
|  | else: | 
|  | return """ | 
|  | wire dat_i = dly_i; | 
|  | """ | 
|  |  | 
|  |  | 
|  | def make_oserdes(use_oserdes): | 
|  |  | 
|  | if use_oserdes: | 
|  | return """ | 
|  | wire dat_o; | 
|  | wire dat_t; | 
|  |  | 
|  | OSERDESE2 #( | 
|  | .DATA_RATE_OQ("SDR"), | 
|  | .DATA_RATE_TQ("SDR"), | 
|  | .DATA_WIDTH(2), | 
|  | .TRISTATE_WIDTH(1) | 
|  | ) oserdes ( | 
|  | .OQ(iob_o), | 
|  | .TQ(iob_t), | 
|  | .D1(dat_o), | 
|  | .T1(dat_t), | 
|  | .CLK(clk_bufg), | 
|  | .CLKDIV(clkdiv_bufg) | 
|  | ); | 
|  | """ | 
|  |  | 
|  | else: | 
|  | return """ | 
|  | wire dat_o; | 
|  | wire dat_t; | 
|  |  | 
|  | assign iob_o = dat_o; | 
|  | assign iob_t = dat_t; | 
|  | """ | 
|  |  | 
|  |  | 
|  | # ============================================================================= | 
|  |  | 
|  |  | 
|  | def main(): | 
|  |  | 
|  | # Parse arguments | 
|  | parser = argparse.ArgumentParser() | 
|  |  | 
|  | parser.add_argument("-o", required=True, help="Output design name") | 
|  | parser.add_argument("--board", required=True, help="Board") | 
|  | parser.add_argument("--iob", required=True, help="IOB cell type to use") | 
|  | parser.add_argument("--iserdes", action="store_true", help="Use ISERDES") | 
|  | parser.add_argument("--idelay", action="store_true", help="Use IDELAY") | 
|  | parser.add_argument("--oserdes", action="store_true", help="Use OSERDES") | 
|  |  | 
|  | args = parser.parse_args() | 
|  |  | 
|  | # Check args | 
|  | if args.idelay and args.iob not in INPUT_IOBS and args.iob not in INOUT_IOBS: | 
|  | raise RuntimeError("Cannot have {} and IDELAY".format(args.iob)) | 
|  |  | 
|  | if args.iserdes and args.iob not in INPUT_IOBS and args.iob not in INOUT_IOBS: | 
|  | raise RuntimeError("Cannot have {} and ISERDES".format(args.iob)) | 
|  |  | 
|  | if args.oserdes and args.iob not in OUTPUT_IOBS and args.iob not in INOUT_IOBS: | 
|  | raise RuntimeError("Cannot have {} and OSERDES".format(args.iob)) | 
|  |  | 
|  | # Verilog header | 
|  | verilog, pcf, xdc = make_header( | 
|  | args.board, args.iob, args.idelay, args.iserdes, args.oserdes | 
|  | ) | 
|  |  | 
|  | # IOB | 
|  | verilog += make_iob(args.iob) | 
|  |  | 
|  | # IDELAY | 
|  | verilog += make_idelay(args.board, args.idelay) | 
|  |  | 
|  | # ISERDES | 
|  | verilog += make_iserdes(args.idelay, args.iserdes) | 
|  |  | 
|  | # OSERDES | 
|  | verilog += make_oserdes(args.oserdes) | 
|  |  | 
|  | # Final connections | 
|  | if args.iob in INPUT_IOBS or args.iob in INOUT_IOBS: | 
|  | verilog += "    assign out = dat_i;\n" | 
|  | if args.iob in OUTPUT_IOBS or args.iob in INOUT_IOBS: | 
|  | verilog += "    assign dat_o = inp;\n" | 
|  | if args.iob in TRISTATE_IOBS: | 
|  | verilog += "    assign dat_t = oen;\n" | 
|  |  | 
|  | # Verilog footer | 
|  | verilog += "endmodule\n" | 
|  |  | 
|  | # Write verilog | 
|  | with open(args.o + ".v", "w") as fp: | 
|  | fp.write(verilog) | 
|  |  | 
|  | # Write PCF | 
|  | with open(args.o + ".pcf", "w") as fp: | 
|  | fp.write(pcf) | 
|  |  | 
|  | # Write XDC | 
|  | with open(args.o + ".xdc", "w") as fp: | 
|  | fp.write(xdc) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main() |