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