1dbd1b5151
Keep the current GPS software and Genesys2/VCU118 flows while integrating Ti375C529 Efinity, flash, programming, and debug support. Add board-specific SPI NOR profiles and persistent GPS flash layouts across all three boards.
277 lines
9.8 KiB
Python
277 lines
9.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate the Efinix Ti375 board shell around the shared Rocket subsystem."""
|
|
|
|
import argparse
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
def width_bits(width):
|
|
if not width:
|
|
return 1
|
|
match = re.fullmatch(r"\[(\d+):(\d+)\]", width.strip())
|
|
if not match:
|
|
raise ValueError(f"unsupported width: {width}")
|
|
return abs(int(match.group(1)) - int(match.group(2))) + 1
|
|
|
|
|
|
def parse_ports(verilog_path):
|
|
text = Path(verilog_path).read_text()
|
|
module_name = Path(verilog_path).stem
|
|
start = text.index(f"module {module_name}(")
|
|
end = text.index(");", start)
|
|
header = re.sub(r"//.*", "", text[start:end])
|
|
pieces = re.split(r"\b(input|output)\b", header)[1:]
|
|
ports = []
|
|
for direction, body in zip(pieces[0::2], pieces[1::2]):
|
|
body = body.replace("\n", " ")
|
|
body = re.split(r"\b(input|output)\b", body)[0]
|
|
default_width = ""
|
|
width_match = re.match(r"\s*(\[[^\]]+\])\s*(.*)", body)
|
|
if width_match:
|
|
default_width = width_match.group(1)
|
|
rest = width_match.group(2)
|
|
else:
|
|
rest = body
|
|
for item in rest.split(","):
|
|
item = item.strip()
|
|
if not item:
|
|
continue
|
|
item_match = re.match(r"(\[[^\]]+\])\s*(\w+)$", item)
|
|
if item_match:
|
|
width, name = item_match.groups()
|
|
else:
|
|
width = default_width
|
|
name = item.split()[-1]
|
|
if re.fullmatch(r"\w+", name):
|
|
ports.append((direction, width, name))
|
|
return ports
|
|
|
|
|
|
def const_for(width):
|
|
bits = width_bits(width)
|
|
return "1'b0" if bits == 1 else f"{bits}'d0"
|
|
|
|
|
|
def is_clock(name):
|
|
return name.endswith("_clock") or name == "debug_clock"
|
|
|
|
|
|
def is_reset(name):
|
|
return name.endswith("_reset") or name == "debug_reset"
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--rocket-top", required=True)
|
|
parser.add_argument("--out", required=True)
|
|
args = parser.parse_args()
|
|
|
|
ports = parse_ports(args.rocket_top)
|
|
port_by_name = {name: (direction, width) for direction, width, name in ports}
|
|
|
|
required = {
|
|
"uart_rxd", "uart_txd",
|
|
"spi_sck", "spi_csn", "spi_dq_i", "spi_dq_o", "spi_dq_oe",
|
|
"pmod_spi_sck", "pmod_spi_csn",
|
|
"pmod_spi_dq_i", "pmod_spi_dq_o", "pmod_spi_dq_oe",
|
|
"max2771_spi_sclk", "max2771_spi_csn",
|
|
"max2771_spi_sdata_i", "max2771_spi_sdata_o",
|
|
"max2771_spi_sdata_oe",
|
|
"pmod_irq", "gps_acq_clk", "gps_trck_clk",
|
|
"gps_rf_clk", "gps_rf_data", "gps_pps",
|
|
}
|
|
missing = sorted(required - port_by_name.keys())
|
|
if missing:
|
|
raise RuntimeError(f"Rocket top is missing required Ti375 ports: {', '.join(missing)}")
|
|
|
|
passthrough = {
|
|
"pmod_spi_sck", "pmod_spi_csn",
|
|
"max2771_spi_sclk", "max2771_spi_csn",
|
|
"pmod_irq", "gps_pps",
|
|
}
|
|
|
|
def decl(width, name):
|
|
return f" wire {width + ' ' if width else ''}{name};"
|
|
|
|
assigns = []
|
|
for direction, width, name in ports:
|
|
if direction != "input" or name in passthrough:
|
|
continue
|
|
if name.startswith("debug_clockeddmi_dmi_req"):
|
|
continue
|
|
if name in {
|
|
"debug_clockeddmi_dmi_resp_ready",
|
|
"debug_clockeddmi_dmiClock",
|
|
"debug_clockeddmi_dmiReset",
|
|
}:
|
|
continue
|
|
if name == "resetctrl_hartIsInReset_0":
|
|
value = "soc_reset"
|
|
elif name == "debug_reset":
|
|
value = "fabric_reset"
|
|
elif is_reset(name):
|
|
value = "soc_reset"
|
|
elif name == "gps_acq_clk":
|
|
value = "gps_acq_clk_gen"
|
|
elif name == "gps_trck_clk":
|
|
value = "gps_trck_clk_gen"
|
|
elif name in {"gps_rf_clk", "gps_afe_clk"}:
|
|
value = "rf_clk"
|
|
elif name == "gps_rf_data":
|
|
value = "rf_data"
|
|
elif is_clock(name):
|
|
value = "fabric_clk"
|
|
elif name == "debug_dmactiveAck":
|
|
value = "debug_dmactive"
|
|
elif name == "uart_rxd":
|
|
value = "uart_rx"
|
|
elif name == "spi_dq_i":
|
|
value = "flash_dq_IN"
|
|
elif name == "pmod_spi_dq_i":
|
|
value = "pmod_spi_dq_IN"
|
|
elif name == "max2771_spi_sdata_i":
|
|
value = "max2771_spi_sdata_IN"
|
|
elif name == "interrupts":
|
|
value = "2'b00"
|
|
else:
|
|
value = const_for(width)
|
|
assigns.append(f" assign {name} = {value};")
|
|
|
|
port_map = ",\n".join(f" .{name}({name})" for _, _, name in ports)
|
|
internal_decls = "\n".join(
|
|
decl(width, name) for _, width, name in ports if name not in passthrough
|
|
)
|
|
|
|
content = f"""`timescale 1ns/1ps
|
|
|
|
module ti375c529_rocket_top (
|
|
(* syn_peri_port = 0 *) input wire fabric_clk,
|
|
(* syn_peri_port = 0 *) input wire fabric_pll_locked,
|
|
(* syn_peri_port = 0 *) input wire board_clk_100,
|
|
(* syn_peri_port = 0 *) input wire gps_ref_clk_gen,
|
|
(* syn_peri_port = 0 *) input wire gps_acq_clk_gen,
|
|
(* syn_peri_port = 0 *) input wire gps_trck_clk_gen,
|
|
(* syn_peri_port = 0 *) input wire gps_domain_feedback,
|
|
(* syn_peri_port = 0 *) input wire gps_ref_pll_locked,
|
|
(* syn_peri_port = 0 *) input wire gps_domain_pll_locked,
|
|
(* syn_peri_port = 0 *) input wire reset_n,
|
|
(* syn_peri_port = 0 *) input wire uart_rx,
|
|
(* syn_peri_port = 0 *) output wire uart_tx,
|
|
(* syn_peri_port = 0 *) output wire flash_sck,
|
|
(* syn_peri_port = 0 *) output wire flash_csn,
|
|
(* syn_peri_port = 0 *) input wire [3:0] flash_dq_IN,
|
|
(* syn_peri_port = 0 *) output wire [3:0] flash_dq_OUT,
|
|
(* syn_peri_port = 0 *) output wire [3:0] flash_dq_OE,
|
|
(* syn_peri_port = 0 *) input wire rf_clk,
|
|
(* syn_peri_port = 0 *) input wire [3:0] rf_data,
|
|
(* syn_peri_port = 0 *) output wire pmod_spi_sck,
|
|
(* syn_peri_port = 0 *) output wire pmod_spi_csn,
|
|
(* syn_peri_port = 0 *) input wire [3:0] pmod_spi_dq_IN,
|
|
(* syn_peri_port = 0 *) output wire [3:0] pmod_spi_dq_OUT,
|
|
(* syn_peri_port = 0 *) output wire [3:0] pmod_spi_dq_OE,
|
|
(* syn_peri_port = 0 *) input wire pmod_irq,
|
|
(* syn_peri_port = 0 *) output wire max2771_spi_sclk,
|
|
(* syn_peri_port = 0 *) output wire max2771_spi_csn,
|
|
(* syn_peri_port = 0 *) input wire max2771_spi_sdata_IN,
|
|
(* syn_peri_port = 0 *) output wire max2771_spi_sdata_OUT,
|
|
(* syn_peri_port = 0 *) output wire max2771_spi_sdata_OE,
|
|
(* syn_peri_port = 0 *) output wire gps_pps,
|
|
(* syn_peri_port = 0 *) input wire jtag_CAPTURE,
|
|
(* syn_peri_port = 0 *) input wire jtag_DRCK,
|
|
(* syn_peri_port = 0 *) input wire jtag_RESET,
|
|
(* syn_peri_port = 0 *) input wire jtag_RUNTEST,
|
|
(* syn_peri_port = 0 *) input wire jtag_SEL,
|
|
(* syn_peri_port = 0 *) input wire jtag_SHIFT,
|
|
(* syn_peri_port = 0 *) input wire jtag_TCK,
|
|
(* syn_peri_port = 0 *) input wire jtag_TDI,
|
|
(* syn_peri_port = 0 *) input wire jtag_TMS,
|
|
(* syn_peri_port = 0 *) input wire jtag_UPDATE,
|
|
(* syn_peri_port = 0 *) output wire jtag_TDO,
|
|
(* syn_peri_port = 0 *) output wire [4:0] led
|
|
);
|
|
wire gps_clock_locked = gps_ref_pll_locked & gps_domain_pll_locked;
|
|
wire pll_ready_async = fabric_pll_locked & gps_clock_locked & reset_n;
|
|
(* async_reg = "true" *) reg [2:0] pll_ready_sync = 3'b000;
|
|
reg [15:0] reset_shift = 16'hffff;
|
|
|
|
always @(posedge fabric_clk) begin
|
|
pll_ready_sync <= {{pll_ready_sync[1:0], pll_ready_async}};
|
|
if (!pll_ready_sync[2])
|
|
reset_shift <= 16'hffff;
|
|
else
|
|
reset_shift <= {{reset_shift[14:0], 1'b0}};
|
|
end
|
|
|
|
wire fabric_reset = reset_shift[15];
|
|
reg [31:0] heartbeat = 32'd0;
|
|
always @(posedge fabric_clk) begin
|
|
if (fabric_reset)
|
|
heartbeat <= 32'd0;
|
|
else
|
|
heartbeat <= heartbeat + 32'd1;
|
|
end
|
|
|
|
{internal_decls}
|
|
|
|
wire soc_reset = fabric_reset | debug_ndreset;
|
|
|
|
rocket_bscan_dmi_tunnel u_rocket_dmi_tunnel (
|
|
.tck(jtag_TCK),
|
|
.reset(fabric_reset | jtag_RESET),
|
|
.bscan_capture(jtag_CAPTURE),
|
|
.bscan_sel(jtag_SEL),
|
|
.bscan_shift(jtag_SHIFT),
|
|
.bscan_tdi(jtag_TDI),
|
|
.bscan_update(jtag_UPDATE),
|
|
.bscan_tdo(jtag_TDO),
|
|
.dmi_clock(debug_clockeddmi_dmiClock),
|
|
.dmi_reset(debug_clockeddmi_dmiReset),
|
|
.dmi_req_ready(debug_clockeddmi_dmi_req_ready),
|
|
.dmi_req_valid(debug_clockeddmi_dmi_req_valid),
|
|
.dmi_req_addr(debug_clockeddmi_dmi_req_bits_addr),
|
|
.dmi_req_data(debug_clockeddmi_dmi_req_bits_data),
|
|
.dmi_req_op(debug_clockeddmi_dmi_req_bits_op),
|
|
.dmi_resp_ready(debug_clockeddmi_dmi_resp_ready),
|
|
.dmi_resp_valid(debug_clockeddmi_dmi_resp_valid),
|
|
.dmi_resp_data(debug_clockeddmi_dmi_resp_bits_data),
|
|
.dmi_resp_resp(debug_clockeddmi_dmi_resp_bits_resp)
|
|
);
|
|
|
|
assign pmod_spi_dq_OUT = pmod_spi_dq_o;
|
|
assign pmod_spi_dq_OE = pmod_spi_dq_oe;
|
|
assign max2771_spi_sdata_OUT = max2771_spi_sdata_o;
|
|
assign max2771_spi_sdata_OE = max2771_spi_sdata_oe;
|
|
assign flash_sck = spi_sck;
|
|
assign flash_csn = spi_csn;
|
|
assign flash_dq_OUT = spi_dq_o;
|
|
assign flash_dq_OE = spi_dq_oe;
|
|
assign uart_tx = uart_txd;
|
|
|
|
// The kit LEDs are active-low. Show heartbeat, PLL locks, reset release,
|
|
// and debug-module activity from LED13 down to LED9.
|
|
assign led = ~{{heartbeat[25], gps_clock_locked, fabric_pll_locked,
|
|
~fabric_reset, debug_dmactive}};
|
|
|
|
{chr(10).join(assigns)}
|
|
|
|
(* syn_preserve = 1 *)
|
|
{Path(args.rocket_top).stem} u_rocket (
|
|
{port_map}
|
|
);
|
|
|
|
// These hard-TAP outputs are intentionally unused by the DMI framing logic.
|
|
wire _unused_hard_signals = jtag_DRCK ^ jtag_RUNTEST ^ jtag_TMS ^
|
|
board_clk_100 ^ gps_ref_clk_gen ^
|
|
gps_domain_feedback;
|
|
endmodule
|
|
"""
|
|
|
|
output = Path(args.out)
|
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
output.write_text(content)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|