feat(fpga): add Ti375 Efinity flow

This commit is contained in:
2026-07-30 03:11:05 +03:00
parent 347e1c671d
commit 50454f8c23
20 changed files with 2050 additions and 2 deletions
@@ -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()