feat(fpga): add Ti375 Efinity flow
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
#!/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",
|
||||
"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", "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 == "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 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 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()
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build Efinity raw-HEX images for Ti375C529 persistent Rocket boot."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
HEX_BYTE = re.compile(r"[0-9a-fA-F]{2}")
|
||||
|
||||
|
||||
def parse_int(value):
|
||||
return int(value, 0)
|
||||
|
||||
|
||||
def read_raw_hex(path):
|
||||
data = bytearray()
|
||||
with path.open() as source:
|
||||
for line_number, line in enumerate(source, 1):
|
||||
value = line.strip()
|
||||
if not HEX_BYTE.fullmatch(value):
|
||||
raise SystemExit(
|
||||
f"{path}:{line_number}: expected one hexadecimal byte per line"
|
||||
)
|
||||
data.append(int(value, 16))
|
||||
return bytes(data)
|
||||
|
||||
|
||||
def write_raw_hex(path, chunks):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w") as output:
|
||||
for chunk in chunks:
|
||||
for value in chunk:
|
||||
output.write(f"{value:02X}\n")
|
||||
|
||||
|
||||
def sha256(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Combine an Efinity configuration HEX and Rocket app image"
|
||||
)
|
||||
parser.add_argument("--config-hex", type=Path, required=True)
|
||||
parser.add_argument("--app-bin", type=Path, required=True)
|
||||
parser.add_argument("--app-offset", type=parse_int, required=True)
|
||||
parser.add_argument("--flash-size", type=parse_int, default=0x04000000)
|
||||
parser.add_argument("--combined-hex", type=Path, required=True)
|
||||
parser.add_argument("--app-hex", type=Path, required=True)
|
||||
parser.add_argument("--manifest", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
config = read_raw_hex(args.config_hex)
|
||||
app = args.app_bin.read_bytes()
|
||||
app_end = args.app_offset + len(app)
|
||||
|
||||
if args.app_offset & 0xFFFF:
|
||||
raise SystemExit("application offset must be aligned to a 64-KiB sector")
|
||||
if len(config) > args.app_offset:
|
||||
raise SystemExit(
|
||||
f"configuration image (0x{len(config):x} bytes) overlaps application "
|
||||
f"at 0x{args.app_offset:x}"
|
||||
)
|
||||
if app_end > args.flash_size:
|
||||
raise SystemExit(
|
||||
f"application ends at 0x{app_end:x}, beyond flash size 0x{args.flash_size:x}"
|
||||
)
|
||||
|
||||
padding_size = args.app_offset - len(config)
|
||||
write_raw_hex(args.app_hex, (app,))
|
||||
write_raw_hex(args.combined_hex, (config, bytes([0xFF]) * padding_size, app))
|
||||
|
||||
manifest = {
|
||||
"format": "Efinity raw HEX (one byte per line)",
|
||||
"flash": "Ti375C529 lower SPI NOR (U47)",
|
||||
"flash_size": f"0x{args.flash_size:08x}",
|
||||
"configuration": {
|
||||
"source": str(args.config_hex),
|
||||
"offset": "0x00000000",
|
||||
"size": len(config),
|
||||
"sha256": sha256(config),
|
||||
},
|
||||
"application": {
|
||||
"source": str(args.app_bin),
|
||||
"offset": f"0x{args.app_offset:08x}",
|
||||
"size": len(app),
|
||||
"sha256": sha256(app),
|
||||
},
|
||||
"combined": {
|
||||
"image": str(args.combined_hex),
|
||||
"programmed_length": app_end,
|
||||
},
|
||||
}
|
||||
args.manifest.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.manifest.write_text(json.dumps(manifest, indent=2) + "\n")
|
||||
|
||||
print(f"configuration: 0x00000000..0x{len(config) - 1:08x}")
|
||||
print(f"application: 0x{args.app_offset:08x}..0x{app_end - 1:08x}")
|
||||
print(f"combined HEX: {args.combined_hex}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Apply small, deterministic Efinity synthesis adaptations to generated RTL."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import math
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROCKET_STORE_DATA = """ else begin
|
||||
automatic logic [3:0][63:0] _GEN_24 =
|
||||
{{ex_rs_1}, {{2{ex_rs_1[31:0]}}}, {{4{ex_rs_1[15:0]}}}, {{8{ex_rs_1[7:0]}}}};
|
||||
mem_reg_rs2 <= _GEN_24[ex_reg_mem_size];
|
||||
end"""
|
||||
|
||||
EFINITY_STORE_DATA = """ else begin
|
||||
// Efinity 2025.2 leaves the block-local packed automatic array above
|
||||
// undriven (VDB-1002). Spell out the byte-lane replication instead.
|
||||
case (ex_reg_mem_size)
|
||||
2'h0: mem_reg_rs2 <= {8{ex_rs_1[7:0]}};
|
||||
2'h1: mem_reg_rs2 <= {4{ex_rs_1[15:0]}};
|
||||
2'h2: mem_reg_rs2 <= {2{ex_rs_1[31:0]}};
|
||||
default: mem_reg_rs2 <= ex_rs_1;
|
||||
endcase
|
||||
end"""
|
||||
|
||||
ATOMIC_LUT_DECLARATION = re.compile(
|
||||
r"^(?P<indent>\s*)automatic logic \[3:0\]\[3:0\] (?P<name>_GEN_[0-9]+) "
|
||||
r"= '\{4'hC, 4'h8, 4'hE, 4'h6\};$",
|
||||
re.MULTILINE,
|
||||
)
|
||||
|
||||
NCO_ROM_START = '(*rom_style = "distributed" *) logic [PH_ACC_WL-1 : 0] freq_word_arr'
|
||||
NCO_ROM_END = "logic [PH_ACC_WL-1 : 0] sin_phase, cos_phase;"
|
||||
|
||||
|
||||
def parse_integer_parameter(text: str, name: str) -> int:
|
||||
match = re.search(rf"\bparameter\s+{re.escape(name)}\s*=\s*([0-9]+)\s*;", text)
|
||||
if match is None:
|
||||
raise RuntimeError(f"could not find integer parameter {name}")
|
||||
return int(match.group(1))
|
||||
|
||||
|
||||
def parse_sv_integer(token: str) -> tuple[int, int | None]:
|
||||
token = token.strip().replace("_", "")
|
||||
match = re.fullmatch(r"(-)?([0-9]+)'([dDhHbB])([0-9a-fA-F]+)", token)
|
||||
if match is not None:
|
||||
sign, width_text, radix_text, digits = match.groups()
|
||||
radix = {"d": 10, "h": 16, "b": 2}[radix_text.lower()]
|
||||
value = int(digits, radix)
|
||||
if sign:
|
||||
value = -value
|
||||
return value, int(width_text)
|
||||
if re.fullmatch(r"-?[0-9]+", token):
|
||||
return int(token), None
|
||||
raise RuntimeError(f"unsupported SystemVerilog integer literal: {token!r}")
|
||||
|
||||
|
||||
def parse_array_parameter(text: str, name: str) -> list[tuple[int, int | None]]:
|
||||
match = re.search(
|
||||
rf"\bparameter\s+\[[^\]]+\]\s+{re.escape(name)}\s*\[[^\]]+\]\s*=\s*\{{(.*?)\}}\s*;",
|
||||
text,
|
||||
re.DOTALL,
|
||||
)
|
||||
if match is None:
|
||||
raise RuntimeError(f"could not find array parameter {name}")
|
||||
return [parse_sv_integer(token) for token in match.group(1).split(",") if token.strip()]
|
||||
|
||||
|
||||
def sv_case_function(
|
||||
name: str,
|
||||
input_width: str,
|
||||
output_width: str,
|
||||
values: list[int],
|
||||
literal_width: int,
|
||||
) -> str:
|
||||
lines = [
|
||||
f"function automatic logic [{output_width}-1 : 0] {name};",
|
||||
f" input logic [{input_width}-1 : 0] index;",
|
||||
" begin",
|
||||
" case (index)",
|
||||
]
|
||||
for index, value in enumerate(values):
|
||||
literal = f"-{literal_width}'sd{-value}" if value < 0 else f"{literal_width}'d{value}"
|
||||
lines.append(f" {index}: {name} = {literal};")
|
||||
lines.extend(
|
||||
[
|
||||
f" default: {name} = '0;",
|
||||
" endcase",
|
||||
" end",
|
||||
"endfunction",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def round_sv_real(value: float) -> int:
|
||||
"""Match real-to-integral assignment rounding used by SystemVerilog."""
|
||||
return math.floor(value + 0.5) if value >= 0 else math.ceil(value - 0.5)
|
||||
|
||||
|
||||
def build_nco_rom_functions(definitions: str) -> str:
|
||||
phase_width = parse_integer_parameter(definitions, "ACQ_NCO_PH_ACC_WL")
|
||||
sine_integer_bits = parse_integer_parameter(definitions, "ACQ_SIN_IL")
|
||||
sine_fraction_bits = parse_integer_parameter(definitions, "ACQ_SIN_FL")
|
||||
lut_address_width = parse_integer_parameter(definitions, "ACQ_SIN_LUT_DEPTH_WL")
|
||||
freq_values = [value for value, _ in parse_array_parameter(definitions, "FREQ_WORD_ARRAY")]
|
||||
pullin_values = [value for value, _ in parse_array_parameter(definitions, "PULLIN_FREQ_WORD_ARRAY")]
|
||||
sine_width = sine_integer_bits + sine_fraction_bits
|
||||
sine_values = [
|
||||
round_sv_real(
|
||||
math.sin(2.0 * math.pi * index / (1 << lut_address_width))
|
||||
* (1 << sine_fraction_bits)
|
||||
)
|
||||
for index in range(1 << lut_address_width)
|
||||
]
|
||||
return "\n\n".join(
|
||||
[
|
||||
"// Efinity 2025.2 mis-elaborates the original initial/$sin ROMs.\n"
|
||||
"// These constant functions are generated from definitions.sv for Ti375 only.",
|
||||
sv_case_function(
|
||||
"efinity_freq_word", "FREQ_BIN_WL", "PH_ACC_WL", freq_values, phase_width
|
||||
),
|
||||
sv_case_function(
|
||||
"efinity_pullin_word", "NUM_PULLIN_WL", "PH_ACC_WL", pullin_values, phase_width
|
||||
),
|
||||
sv_case_function(
|
||||
"efinity_sine", "LUT_DEPTH_WL", "ACQ_SIN_IL + ACQ_SIN_FL", sine_values, sine_width
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def rewrite_acquisition_nco(source: Path, destination: Path, definitions: str) -> None:
|
||||
text = source.read_text(encoding="utf-8")
|
||||
start = text.find(NCO_ROM_START)
|
||||
end = text.find(NCO_ROM_END, start)
|
||||
if start < 0 or end < 0:
|
||||
raise RuntimeError(f"could not locate acquisition NCO ROM block in {source}")
|
||||
text = text[:start] + build_nco_rom_functions(definitions) + "\n\n" + text[end:]
|
||||
old_sum = (
|
||||
"freq_word_arr[freq_bin_idx_i] + "
|
||||
"pullin_freq_word_arr[pullin_freq_bin_idx_i]"
|
||||
)
|
||||
new_sum = (
|
||||
"efinity_freq_word(freq_bin_idx_i) + "
|
||||
"efinity_pullin_word(pullin_freq_bin_idx_i)"
|
||||
)
|
||||
if text.count(old_sum) != 2:
|
||||
raise RuntimeError(f"expected two NCO phase increments in {source}")
|
||||
text = text.replace(old_sum, new_sum)
|
||||
if text.count("sinwav_lut[truncated_sin_phase]") != 1:
|
||||
raise RuntimeError(f"expected one sine ROM read in {source}")
|
||||
if text.count("sinwav_lut[truncated_cos_phase]") != 1:
|
||||
raise RuntimeError(f"expected one cosine ROM read in {source}")
|
||||
text = text.replace("sinwav_lut[truncated_sin_phase]", "efinity_sine(truncated_sin_phase)")
|
||||
text = text.replace("sinwav_lut[truncated_cos_phase]", "efinity_sine(truncated_cos_phase)")
|
||||
destination.write_text(text, encoding="utf-8")
|
||||
|
||||
|
||||
def rewrite_tracking_nco(source: Path, destination: Path, definitions: str) -> None:
|
||||
text = source.read_text(encoding="utf-8")
|
||||
declaration = (
|
||||
'(*rom_style = "distributed" *) logic [SIN_IL-1 : -SIN_FL] '
|
||||
"sinwav_lut [0 : 2**LUT_DEPTH_WL-1];"
|
||||
)
|
||||
values = [value for value, _ in parse_array_parameter(definitions, "TRCK_SINWAV_LUT")]
|
||||
expected_entries = parse_integer_parameter(definitions, "NUM_TRCK_SINWAV_LUT")
|
||||
if len(values) != expected_entries:
|
||||
raise RuntimeError(
|
||||
f"expected {expected_entries} tracking sine entries, got {len(values)}"
|
||||
)
|
||||
function = (
|
||||
"// Efinity 2025.2 leaves the original package-array initialized ROM undriven.\n"
|
||||
"// This Ti375-only constant function preserves the exact definitions.sv values.\n"
|
||||
+ sv_case_function(
|
||||
"efinity_tracking_sine",
|
||||
"LUT_DEPTH_WL",
|
||||
"SIN_IL + SIN_FL",
|
||||
values,
|
||||
parse_integer_parameter(definitions, "TRCK_CARR_NCO_SIN_WL"),
|
||||
)
|
||||
)
|
||||
if text.count(declaration) != 1:
|
||||
raise RuntimeError(f"expected one tracking sine ROM declaration in {source}")
|
||||
text = text.replace(declaration, function)
|
||||
initializer = """initial begin
|
||||
sinwav_lut = TRCK_SINWAV_LUT;
|
||||
end
|
||||
|
||||
"""
|
||||
if text.count(initializer) != 1:
|
||||
raise RuntimeError(f"expected one tracking sine ROM initializer in {source}")
|
||||
text = text.replace(initializer, "")
|
||||
if text.count("sinwav_lut[accum_truncated_sin]") != 1:
|
||||
raise RuntimeError(f"expected one tracking sine ROM read in {source}")
|
||||
if text.count("sinwav_lut[accum_truncated_cos]") != 1:
|
||||
raise RuntimeError(f"expected one tracking cosine ROM read in {source}")
|
||||
text = text.replace(
|
||||
"sinwav_lut[accum_truncated_sin]", "efinity_tracking_sine(accum_truncated_sin)"
|
||||
)
|
||||
text = text.replace(
|
||||
"sinwav_lut[accum_truncated_cos]", "efinity_tracking_sine(accum_truncated_cos)"
|
||||
)
|
||||
destination.write_text(text, encoding="utf-8")
|
||||
|
||||
|
||||
def write_mem_file(path: Path, entries: list[tuple[int, int | None]], width: int) -> None:
|
||||
mask = (1 << width) - 1
|
||||
path.write_text(
|
||||
# Binary keeps each row exactly TWIDDLE_WIDTH bits. A hexadecimal row
|
||||
# would round 15 bits up to four digits and Efinity warns about a
|
||||
# spurious 16-to-15-bit truncation even though the value is correct.
|
||||
"".join(f"{value & mask:0{width}b}\n" for value, _ in entries),
|
||||
encoding="ascii",
|
||||
)
|
||||
|
||||
|
||||
def rewrite_twiddle_rom(source: Path, destination: Path) -> None:
|
||||
text = source.read_text(encoding="utf-8")
|
||||
old = """initial begin
|
||||
cos_rom = COS_ROM_ARRAY;
|
||||
sin_rom = SIN_ROM_ARRAY;
|
||||
end"""
|
||||
new = """initial begin
|
||||
// Generated Ti375 files are resolved through Efinity's memory-init include path.
|
||||
$readmemb("ti375_twiddle_cos_15.mem", cos_rom);
|
||||
$readmemb("ti375_twiddle_sin_15.mem", sin_rom);
|
||||
end"""
|
||||
if text.count(old) != 1:
|
||||
raise RuntimeError(f"expected one twiddle ROM initializer in {source}")
|
||||
destination.write_text(text.replace(old, new), encoding="utf-8")
|
||||
|
||||
|
||||
def prepare_gps_rtl(source_dir: Path, output_dir: Path) -> None:
|
||||
definitions_path = source_dir / "common" / "definitions.sv"
|
||||
definitions = definitions_path.read_text(encoding="utf-8")
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
rewrite_acquisition_nco(
|
||||
source_dir / "acq_src" / "acq_nco_carr.sv",
|
||||
output_dir / "acq_nco_carr.sv",
|
||||
definitions,
|
||||
)
|
||||
rewrite_tracking_nco(
|
||||
source_dir / "trck_src" / "NCO_carr.sv",
|
||||
output_dir / "NCO_carr.sv",
|
||||
definitions,
|
||||
)
|
||||
rewrite_twiddle_rom(
|
||||
source_dir / "acq_src" / "twiddle_ram.sv",
|
||||
output_dir / "twiddle_ram.sv",
|
||||
)
|
||||
cos_entries = parse_array_parameter(definitions, "COS_ROM_ARRAY")
|
||||
sin_entries = parse_array_parameter(definitions, "SIN_ROM_ARRAY")
|
||||
expected_entries = parse_integer_parameter(definitions, "NUM_TWIDDLE_ROM_IDX")
|
||||
if len(cos_entries) != expected_entries or len(sin_entries) != expected_entries:
|
||||
raise RuntimeError(
|
||||
f"expected {expected_entries} twiddle entries, got "
|
||||
f"cos={len(cos_entries)} sin={len(sin_entries)}"
|
||||
)
|
||||
write_mem_file(output_dir / "ti375_twiddle_cos_15.mem", cos_entries, 15)
|
||||
write_mem_file(output_dir / "ti375_twiddle_sin_15.mem", sin_entries, 15)
|
||||
|
||||
|
||||
def rewrite_rocket_store_data(path: Path) -> None:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
if EFINITY_STORE_DATA in text:
|
||||
return
|
||||
if text.count(ROCKET_STORE_DATA) != 1:
|
||||
raise RuntimeError(f"expected one Rocket store-data array in {path}")
|
||||
path.write_text(text.replace(ROCKET_STORE_DATA, EFINITY_STORE_DATA), encoding="utf-8")
|
||||
|
||||
|
||||
def rewrite_tilelink_atomic_lut(path: Path) -> None:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
if "// Avoid Efinity VDB-1002 on the block-local packed LUT." in text:
|
||||
return
|
||||
match = ATOMIC_LUT_DECLARATION.search(text)
|
||||
if match is None:
|
||||
raise RuntimeError(f"expected one TileLink atomic packed LUT in {path}")
|
||||
if ATOMIC_LUT_DECLARATION.search(text, match.end()) is not None:
|
||||
raise RuntimeError(f"expected only one TileLink atomic packed LUT in {path}")
|
||||
indent = match.group("indent")
|
||||
temporary = match.group("name")
|
||||
old_read = f"{indent}cam_a_0_lut <= {temporary}[auto_in_a_bits_param[1:0]];"
|
||||
new_read = f"""{indent}// Avoid Efinity VDB-1002 on the block-local packed LUT.
|
||||
{indent}case (auto_in_a_bits_param[1:0])
|
||||
{indent} 2'h0: cam_a_0_lut <= 4'h6;
|
||||
{indent} 2'h1: cam_a_0_lut <= 4'hE;
|
||||
{indent} 2'h2: cam_a_0_lut <= 4'h8;
|
||||
{indent} default: cam_a_0_lut <= 4'hC;
|
||||
{indent}endcase"""
|
||||
if text.count(old_read) != 1:
|
||||
raise RuntimeError(f"expected one TileLink atomic packed LUT read in {path}")
|
||||
text = text[: match.start()] + text[match.end() + 1 :]
|
||||
text = text.replace(old_read, new_read)
|
||||
path.write_text(text, encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--verilog-dir", type=Path, required=True)
|
||||
parser.add_argument("--gps-source-dir", type=Path, required=True)
|
||||
parser.add_argument("--gps-output-dir", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
rewrite_rocket_store_data(args.verilog_dir / "Rocket.sv")
|
||||
rewrite_tilelink_atomic_lut(args.verilog_dir / "TLAtomicAutomata_pbus.sv")
|
||||
rewrite_tilelink_atomic_lut(args.verilog_dir / "TLAtomicAutomata_cbus.sv")
|
||||
prepare_gps_rtl(args.gps_source_dir, args.gps_output_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user