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
+1
View File
@@ -7,6 +7,7 @@
# Generated build outputs.
/out/
/soc/out/
/hardware/generated/
# Python runtime caches and privacy-sensitive raw board captures.
__pycache__/
+3 -2
View File
@@ -19,7 +19,7 @@ help:
' make doctor # check local JDK/Mill/CIRCT and RISC-V tools' \
' make soc-hw # emit shared SoC FIRRTL through FPGA config' \
' make fpga-rtl # emit selected FPGA SystemVerilog and wrapper' \
' make fpga-bitstream # run selected FPGA Vivado bitstream flow' \
' make fpga-bitstream # run selected board vendor bitstream flow' \
' make fpga-elf-flash-image' \
' # build APP ELF and board flash-format app image' \
' make fpga-mcs # generate combined bitstream + app MCS/BIN' \
@@ -39,7 +39,7 @@ help:
' make asic-sim # run ASIC-style Questa sanity simulation' \
'' \
'Config:' \
' FPGA_BOARD=$(FPGA_BOARD) # vcu118 or genesys2' \
' FPGA_BOARD=$(FPGA_BOARD) # vcu118, genesys2, or ti375c529' \
' UART_BAUD=460800 # FPGA firmware and host listener default' \
' GPS_ESKF_ENABLE=1 # gps_system: 1 ESKF, 0 GPS-only' \
' IMU_SENSOR=auto # gps_system: auto, tdk, st or stm' \
@@ -49,6 +49,7 @@ help:
' make -C hardware/soc help' \
' make -C targets/fpga/vcu118 help' \
' make -C targets/fpga/genesys2 help' \
' make -C targets/fpga/ti375c529 help' \
' make -C targets/asic/rocket help'
doctor:
@@ -0,0 +1,226 @@
# Efinity Interface Configuration for the Ti375C529 development kit.
# Pin assignments follow the kit user guide and the Efinity 2025.2 device DB.
# Board I/O bank voltages. Keep these aligned with the Efinix out-of-box
# Ti375C529 development-kit project.
design.set_device_property("2A", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("2A", "2A_MODE_SEL", "2A")
design.set_device_property("2A", "VOLTAGE", "1.8", "IOBANK")
design.set_device_property("2B", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("2B", "2B_MODE_SEL", "2B")
design.set_device_property("2B", "VOLTAGE", "1.8", "IOBANK")
design.set_device_property("2C", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("2C", "2C_MODE_SEL", "2C")
design.set_device_property("2C", "VOLTAGE", "1.8", "IOBANK")
design.set_device_property("2D", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("2D", "2D_MODE_SEL", "2D")
design.set_device_property("2D", "VOLTAGE", "1.8", "IOBANK")
design.set_device_property("2E", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("2E", "2E_MODE_SEL", "2E")
design.set_device_property("2E", "VOLTAGE", "1.8", "IOBANK")
design.set_device_property("4A_4B", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("4A_4B", "4A_4B_MODE_SEL", "4A_4B")
design.set_device_property("4A_4B", "VOLTAGE", "1.8", "IOBANK")
design.set_device_property("4C", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("4C", "4C_MODE_SEL", "4C")
design.set_device_property("4C", "VOLTAGE", "1.8", "IOBANK")
design.set_device_property("4D", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("4D", "4D_MODE_SEL", "4D")
design.set_device_property("4D", "VOLTAGE", "1.8", "IOBANK")
design.set_device_property("BL2_BL3", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("BL2_BL3", "BL2_MODE_SEL", "BL2")
design.set_mode_sel_name("BL2_BL3", "BL3_MODE_SEL", "BL3")
design.set_device_property("BL2_BL3", "VOLTAGE", "3.3", "IOBANK")
design.set_device_property("BR0", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("BR0", "BR0_MODE_SEL", "BR0")
design.set_device_property("BR0", "VOLTAGE", "3.3", "IOBANK")
design.set_device_property("BR3_BR4", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("BR3_BR4", "BR3_MODE_SEL", "BR3")
design.set_mode_sel_name("BR3_BR4", "BR4_MODE_SEL", "BR4")
design.set_device_property("BR3_BR4", "VOLTAGE", "3.3", "IOBANK")
design.set_device_property("TL1_TL5", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("TL1_TL5", "TL1_MODE_SEL", "TL1")
design.set_mode_sel_name("TL1_TL5", "TL5_MODE_SEL", "TL5")
design.set_device_property("TL1_TL5", "VOLTAGE", "3.3", "IOBANK")
design.set_device_property("TR0", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("TR0", "TR0_MODE_SEL", "TR0")
design.set_device_property("TR0", "VOLTAGE", "3.3", "IOBANK")
design.set_device_property("TR1", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("TR1", "TR1_MODE_SEL", "TR1")
design.set_device_property("TR1", "VOLTAGE", "3.3", "IOBANK")
design.set_device_property("TR2", "DYNAMIC_VOLTAGE", "0", "IOBANK")
design.set_mode_sel_name("TR2", "TR2_MODE_SEL", "TR2")
design.set_device_property("TR2", "VOLTAGE", "3.3", "IOBANK")
# External GPIO.
design.create_input_gpio("reset_n")
design.set_property("reset_n", "PULL_OPTION", "WEAK_PULLUP")
design.create_input_gpio("uart_rx")
design.create_output_gpio("uart_tx")
# Lower 512-Mbit configuration flash. After active configuration completes,
# these dedicated SPI pins become fabric GPIO and are driven by Rocket's MMIO
# SPI controller so the BootROM can read the application region.
design.create_output_gpio("flash_sck")
design.create_output_gpio("flash_csn")
design.create_inout_gpio("flash_dq", 3, 0)
# The AX2771 sample clock is routed to J13 pin 10 / GPIOR_95_CLK12 so it
# enters the fabric through a dedicated global-clock input instead of being
# promoted from normal GPIO routing by the placer.
design.create_input_clock_gpio("rf_clk")
design.create_input_gpio("rf_data", 3, 0)
design.create_output_gpio("pmod_spi_sck")
design.create_output_gpio("pmod_spi_csn")
design.create_inout_gpio("pmod_spi_dq", 3, 0)
design.create_input_gpio("pmod_irq")
design.create_output_gpio("gps_pps")
design.create_output_gpio("led", 4, 0)
# The two PMOD headers, FTDI UART, reset switch, 100 MHz oscillator, and
# LED9/LED10 use the development kit's 3.3 V banks. GPIOs otherwise default
# to 1.8 V LVCMOS, so state these assignments explicitly.
design.set_property("reset_n", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("uart_rx", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("uart_tx", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("flash_sck", "IO_STANDARD", "1.8_V_LVCMOS")
design.set_property("flash_csn", "IO_STANDARD", "1.8_V_LVCMOS")
design.set_property("flash_dq[0]", "IO_STANDARD", "1.8_V_LVCMOS")
design.set_property("flash_dq[1]", "IO_STANDARD", "1.8_V_LVCMOS")
design.set_property("flash_dq[2]", "IO_STANDARD", "1.8_V_LVCMOS")
design.set_property("flash_dq[3]", "IO_STANDARD", "1.8_V_LVCMOS")
design.set_property("flash_csn", "PULL_OPTION", "WEAK_PULLUP")
design.set_property("flash_dq[0]", "PULL_OPTION", "WEAK_PULLUP")
design.set_property("flash_dq[1]", "PULL_OPTION", "WEAK_PULLUP")
design.set_property("flash_dq[2]", "PULL_OPTION", "WEAK_PULLUP")
design.set_property("flash_dq[3]", "PULL_OPTION", "WEAK_PULLUP")
design.set_property("rf_clk", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("rf_data[0]", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("rf_data[1]", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("rf_data[2]", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("rf_data[3]", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("pmod_spi_sck", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("pmod_spi_csn", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("pmod_spi_dq[0]", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("pmod_spi_dq[1]", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("pmod_spi_dq[2]", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("pmod_spi_dq[3]", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("pmod_irq", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("gps_pps", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("led[0]", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("led[1]", "IO_STANDARD", "3.3_V_LVCMOS")
# Rocket/fabric clock: 100 MHz board oscillator -> 100 MHz GCLK.
design.create_block("fabric_pll", "PLL")
design.gen_pll_ref_clock("fabric_pll", pll_res="PLL_BL0", refclk_src="EXTERNAL", refclk_name="board_clk_100", ext_refclk_no="1")
design.set_property("board_clk_100", "IO_STANDARD", "3.3_V_LVCMOS")
design.set_property("fabric_pll", "REFCLK_FREQ", "100", "PLL")
design.set_property("fabric_pll", "CLKOUT0_PIN", "fabric_clk", "PLL")
design.set_property("fabric_pll", "CLKOUT0_CONN_TYPE", "gclk", "PLL")
design.set_property("fabric_pll", "LOCKED_PIN", "fabric_pll_locked", "PLL")
design.auto_calc_pll_clock("fabric_pll", {"CLKOUT0_FREQ": "100.0", "CLKOUT0_PHASE": "0"})
# RF-coherent GPS processing clocks. The AX2771's 16.367981 MHz clock enters
# the fabric on J13 pin 10 / A12 (GPIOR_95_CLK12) and drives the same two-stage
# clock chain used by the Xilinx targets:
# RF -> ~55.55556 MHz -> 100 MHz acquisition / 125 MHz tracking.
design.create_block("gps_ref_pll", "PLL")
design.gen_pll_ref_clock("gps_ref_pll", pll_res="PLL_TR0", refclk_src="CORE")
design.set_property("gps_ref_pll", "CORE_CLK_PIN", "rf_clk", "PLL")
design.set_property("gps_ref_pll", "REFCLK_FREQ", "16.367981", "PLL")
design.set_property("gps_ref_pll", "CLKOUT1_EN", "1", "PLL")
design.set_property("gps_ref_pll", "CLKOUT0_PIN", "gps_ref_clk_gen", "PLL")
design.set_property("gps_ref_pll", "CLKOUT0_CONN_TYPE", "gclk", "PLL")
design.set_property("gps_ref_pll", "CLKOUT1_PIN", "gps_ref_feedback", "PLL")
design.set_property("gps_ref_pll", "CLKOUT1_CONN_TYPE", "gclk", "PLL")
design.set_property("gps_ref_pll", "LOCKED_PIN", "gps_ref_pll_locked", "PLL")
design.set_property("gps_ref_pll", "FEEDBACK_MODE", "LOCAL", "PLL")
design.set_property("gps_ref_pll", "FEEDBACK_CLK", "CLK1", "PLL")
design.set_property("gps_ref_pll", "FRACTIONAL_MODE_EN", "1", "PLL")
design.set_property("gps_ref_pll", "FRACTIONAL_COEFFICIENT", "10715375", "PLL")
design.set_property("gps_ref_pll", "M", "4", "PLL")
design.set_property("gps_ref_pll", "N", "1", "PLL")
design.set_property("gps_ref_pll", "O", "1", "PLL")
design.set_property("gps_ref_pll", "CLKOUT0_DIV", "42", "PLL")
design.set_property("gps_ref_pll", "CLKOUT1_DIV", "35", "PLL")
design.set_property("gps_ref_pll", "CLKOUT1_PDIV", "5", "PLL")
design.set_property("gps_ref_pll", "CLKOUT1_SDIV", "30", "PLL")
design.calc_pll_clock("gps_ref_pll")
design.create_block("gps_domain_pll", "PLL")
design.gen_pll_ref_clock("gps_domain_pll", pll_res="PLL_TR1", refclk_src="CORE")
design.set_property("gps_domain_pll", "CORE_CLK_PIN", "gps_ref_clk_gen", "PLL")
design.set_property("gps_domain_pll", "REFCLK_FREQ", "55.55555593", "PLL")
design.set_property("gps_domain_pll", "CLKOUT3_EN", "1", "PLL")
design.set_property("gps_domain_pll", "CLKOUT4_EN", "1", "PLL")
design.set_property("gps_domain_pll", "CLKOUT0_PIN", "gps_domain_feedback", "PLL")
design.set_property("gps_domain_pll", "CLKOUT0_CONN_TYPE", "gclk", "PLL")
design.set_property("gps_domain_pll", "CLKOUT3_PIN", "gps_acq_clk_gen", "PLL")
design.set_property("gps_domain_pll", "CLKOUT3_CONN_TYPE", "gclk", "PLL")
design.set_property("gps_domain_pll", "CLKOUT4_PIN", "gps_trck_clk_gen", "PLL")
design.set_property("gps_domain_pll", "CLKOUT4_CONN_TYPE", "gclk", "PLL")
design.set_property("gps_domain_pll", "LOCKED_PIN", "gps_domain_pll_locked", "PLL")
design.set_property("gps_domain_pll", "FEEDBACK_MODE", "LOCAL", "PLL")
design.set_property("gps_domain_pll", "FEEDBACK_CLK", "CLK0", "PLL")
design.set_property("gps_domain_pll", "M", "2", "PLL")
design.set_property("gps_domain_pll", "N", "1", "PLL")
design.set_property("gps_domain_pll", "O", "2", "PLL")
design.set_property("gps_domain_pll", "CLKOUT0_DIV", "18", "PLL")
design.set_property("gps_domain_pll", "CLKOUT3_DIV", "20", "PLL")
design.set_property("gps_domain_pll", "CLKOUT4_DIV", "16", "PLL")
design.calc_pll_clock("gps_domain_pll")
# Hard JTAG USER1 exposes the TAP handshake at the FPGA core boundary.
design.create_block("rocket_jtag", "JTAG")
design.set_property("rocket_jtag", "NAME", "rocket_jtag", "JTAG")
design.set_property("rocket_jtag", "RESOURCE", "JTAG_USER1", "JTAG")
design.set_property("rocket_jtag", "CAPTURE", "jtag_CAPTURE", "JTAG")
design.set_property("rocket_jtag", "DRCK", "jtag_DRCK", "JTAG")
design.set_property("rocket_jtag", "RESET", "jtag_RESET", "JTAG")
design.set_property("rocket_jtag", "RUNTEST", "jtag_RUNTEST", "JTAG")
design.set_property("rocket_jtag", "SEL", "jtag_SEL", "JTAG")
design.set_property("rocket_jtag", "SHIFT", "jtag_SHIFT", "JTAG")
design.set_property("rocket_jtag", "TCK", "jtag_TCK", "JTAG")
design.set_property("rocket_jtag", "TDI", "jtag_TDI", "JTAG")
design.set_property("rocket_jtag", "TMS", "jtag_TMS", "JTAG")
design.set_property("rocket_jtag", "UPDATE", "jtag_UPDATE", "JTAG")
design.set_property("rocket_jtag", "TDO", "jtag_TDO", "JTAG")
design.assign_resource("rocket_jtag", "JTAG_USER1", "JTAG")
# Package assignments.
design.assign_pkg_pin("board_clk_100", "U4")
design.assign_pkg_pin("reset_n", "U19")
design.assign_pkg_pin("uart_rx", "E10")
design.assign_pkg_pin("uart_tx", "E9")
# Lower configuration flash U47: CDI0..3, CCK, and SSL_N. These assignments
# match Efinix's Ti375C529 JTAG-to-SPI loader reference design.
design.assign_pkg_pin("flash_dq[0]", "H4")
design.assign_pkg_pin("flash_dq[1]", "H5")
design.assign_pkg_pin("flash_dq[2]", "G5")
design.assign_pkg_pin("flash_dq[3]", "B7")
design.assign_pkg_pin("flash_sck", "G6")
design.assign_pkg_pin("flash_csn", "A7")
# PMOD0/J13 RF input. J13 pin 10 is the header's dedicated GCLK-capable pin;
# J13 pin 1 / G15 is intentionally unused.
design.assign_pkg_pin("rf_clk", "A12")
design.assign_pkg_pin("rf_data[0]", "G16")
design.assign_pkg_pin("rf_data[1]", "F16")
design.assign_pkg_pin("rf_data[2]", "F17")
design.assign_pkg_pin("rf_data[3]", "G17")
# PMOD1/J14 SPI + interrupt + PPS.
design.assign_pkg_pin("pmod_spi_csn", "B12")
design.assign_pkg_pin("pmod_spi_dq[0]", "C14")
design.assign_pkg_pin("pmod_spi_dq[1]", "C13")
design.assign_pkg_pin("pmod_spi_sck", "C12")
design.assign_pkg_pin("pmod_spi_dq[2]", "D12")
design.assign_pkg_pin("pmod_spi_dq[3]", "F12")
design.assign_pkg_pin("pmod_irq", "D13")
design.assign_pkg_pin("gps_pps", "E13")
# User LEDs LED9..LED13.
design.assign_pkg_pin("led[0]", "V4")
design.assign_pkg_pin("led[1]", "U6")
design.assign_pkg_pin("led[2]", "L18")
design.assign_pkg_pin("led[3]", "R20")
design.assign_pkg_pin("led[4]", "E19")
@@ -0,0 +1,8 @@
# AX2771 sample clock on PMOD J13. The 100/125 MHz GPS clocks generated by the
# two Efinity PLLs are frequency-related to this input; the Rocket fabric clock
# comes from the independent on-board 100 MHz oscillator.
create_clock -period 61.09495 -name rf_clk [get_ports {rf_clk}]
# The FTDI adapter is normally run at 1 MHz. Constraining the hard-TAP fabric
# interface to 10 MHz leaves margin if the debug adapter speed is increased.
create_clock -period 100.000 -name jtag_TCK [get_ports {jtag_TCK}]
@@ -0,0 +1,10 @@
# Efinity's generated .pt.sdc is read before this file and defines the fabric
# and PLL-output clocks. The Rocket oscillator, AX2771-derived GPS clocks, and
# hard-TAP JTAG clock are independent domains.
set_clock_groups -asynchronous \
-group [get_clocks {fabric_clk}] \
-group [get_clocks {rf_clk}] \
-group [get_clocks {gps_ref_clk_gen}] \
-group [get_clocks {gps_acq_clk_gen}] \
-group [get_clocks {gps_trck_clk_gen}] \
-group [get_clocks {jtag_TCK}]
+18
View File
@@ -0,0 +1,18 @@
.PHONY: help all shell generated clean
help:
@echo "Ti375C529 RTL targets:"
@echo " make shell # list hand-written FPGA shell RTL"
@echo " make generated # generate Rocket SystemVerilog and Efinix wrapper"
@echo " make clean # remove generated wrapper/verilog"
all: generated
shell:
@find . -type f -name '*.sv' -print | sort
generated:
@$(MAKE) -C ../../../../targets/fpga/ti375c529 rtl
clean:
rm -rf ../../../generated/ti375c529-soc-verilog ../../../../targets/fpga/ti375c529/out/build/generated
+12
View File
@@ -0,0 +1,12 @@
/out/
/flow/efinity/ti375c529_rocket.isf
/flow/efinity/ti375c529_rocket.peri.xml
/flow/efinity/ti375c529_rocket.pt.sdc
/flow/efinity/ti375c529_rocket.pt.rpt
/flow/efinity/ti375c529_rocket.pinout.*
/flow/efinity/ti375c529_rocket.interface.csv
/flow/efinity/ti375c529_rocket_template.v
/flow/efinity/outflow/
/flow/efinity/work_pnr/
/flow/efinity/work_syn/
/flow/efinity/RUNPNR_*
+408
View File
@@ -0,0 +1,408 @@
SHELL := /bin/bash
.SHELLFLAGS := -euo pipefail -c
.ONESHELL:
FPGA_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
REPO_DIR := $(abspath $(FPGA_DIR)/../../..)
SOC_SW_DIR := $(REPO_DIR)/soc/software
SOC_OUT_DIR := $(REPO_DIR)/soc/out
HW_DIR := $(REPO_DIR)/hardware
HW_GENERATED_DIR := $(HW_DIR)/generated
GPS_SYSTEM_DIR ?= $(HW_DIR)/external/gps/rtl
FLOW_DIR := $(FPGA_DIR)/flow/efinity
DEBUG_DIR := $(FPGA_DIR)/flow/debug
TOOL_SCRIPT_DIR := $(FPGA_DIR)/tools
OUT_DIR := $(FPGA_DIR)/out
LOG_DIR := $(OUT_DIR)/logs
include $(REPO_DIR)/tools/project.mk
BOARD ?= ti375c529
DEVICE ?= Ti375C529
TIMING_MODEL ?= C4
SOC_CLK_HZ ?= 100000000
GPS_ACQ_CLK_HZ ?= 100000000
GPS_TRCK_CLK_HZ ?= 125000000
GPS_RF_CLK_HZ ?= 16367981
RF_INPUT_MODE ?= pmod
UART_PORT ?= /dev/ttyUSB2
UART_BAUD ?= 460800
OPENOCD ?= $(shell command -v openocd)
OPENOCD_ADAPTER_SPEED ?= 1000
GDB_HOST ?= localhost
GDB_PORT ?= 3333
GDB ?= $(firstword $(wildcard /tools/toolchain/bin/riscv64-unknown-elf-gdb) $(CROSS)gdb)
# Leave empty for Efinity's board-profile auto-detection. Boards without an
# FTDI serial number require a bus/device-qualified URL, which changes after
# re-enumeration; users with multiple boards can override this explicitly.
FTDI_URL ?=
BSP_UART_CLOCK_HZ ?= $(SOC_CLK_HZ)
BSP_UART_BAUDRATE ?= $(UART_BAUD)
BSP_CLINT_TIMEBASE_HZ ?= 1000000
DTIM_BASE ?= 0x80000000
DTIM_SIZE ?= 0x00100000
# The compressed Ti375 configuration currently occupies about 5.2 MB.
# Keep the application on a 64-KiB boundary with ample growth margin.
APP_FLASH_OFFSET ?= 0x00800000
FLASH_SIZE ?= 0x04000000
GPS_DEBUG ?= 1
GPS_DEBUG_LEVEL ?= 4
GPS_ESKF_ENABLE ?= 1
IMU_SENSOR ?= auto
app ?=
ifneq ($(strip $(app)),)
APP ?= $(app)
else
APP ?= hello_uart
endif
FIRRTL_DIR ?= $(HW_GENERATED_DIR)/ti375c529-soc
VERILOG_DIR ?= $(HW_GENERATED_DIR)/ti375c529-soc-verilog
WRAPPER := $(OUT_DIR)/build/generated/ti375c529_rocket_top.sv
GPS_EFINITY_DIR := $(OUT_DIR)/build/generated/gps_efinity
SOURCE_LIST := $(OUT_DIR)/build/efinity_sources.f
PROJECT_XML := $(FLOW_DIR)/ti375c529_rocket.xml
INTERFACE_SOURCE := $(HW_DIR)/fpga/ti375c529/constraints/ti375c529_rocket.isf
INTERFACE_SCRIPT := $(FLOW_DIR)/ti375c529_rocket.isf
BIT_FILE ?= $(OUT_DIR)/bitstream/ti375c529_rocket.bit
CONFIG_HEX ?= $(OUT_DIR)/bitstream/ti375c529_rocket.hex
APP_ELF ?= $(SOC_OUT_DIR)/software/$(APP)/$(APP).elf
APP_FLASH_IMAGE ?= $(SOC_OUT_DIR)/flash/$(APP).flash.bin
FLASH_DIR := $(OUT_DIR)/flash
FLASH_REGION_DIR := $(FLASH_DIR)/regions
APP_HEX ?= $(FLASH_REGION_DIR)/app.hex
FLASH_HEX ?= $(FLASH_DIR)/ti375c529_rocket.hex
FLASH_MANIFEST ?= $(FLASH_DIR)/ti375c529_rocket.json
FLASH_LOADER ?= $(EFINITY_HOME)/pgm/fli/titanium/u006A0A79.bit
FLASH_VERIFY ?= onchipx2
OPENOCD_CFG ?= $(DEBUG_DIR)/openocd_ti375c529_bscan.cfg
GDB_LOAD_SCRIPT ?= $(DEBUG_DIR)/gdb_load_and_run.gdb
GDB_ATTACH_SCRIPT ?= $(DEBUG_DIR)/gdb_attach.gdb
.PHONY: help all fpga-config detect software chisel rtl soc interface map bitstream \
program-fpga debug-openocd debug-load debug-gdb uart-listen \
flash-image flash-regions program-flash program-flash-elf \
program-flash-bitstream program-flash-regions program-flash-all \
program-flash-combined program-flash-loader program-app program-bitstream \
clean clean-sw clean-fpga FORCE
help:
@printf '%s\n' \
'Ti375C529 Efinity targets:' \
' make detect Scan USB and read the Ti375 JTAG IDCODE' \
' make software Build the Rocket boot ROM and selected RV64 app' \
' make chisel Emit Rocket FIRRTL with the existing FPGA config' \
' make rtl Emit split SystemVerilog, Efinix shell, and file list' \
' make interface Generate Efinity pin/PLL/JTAG interface data' \
' make map Run Efinity synthesis only' \
' make bitstream Run the complete Efinity flow' \
' make program-fpga Load BIT_FILE into Ti375 SRAM over JTAG' \
' make flash-image Generate combined Lower-flash Efinity HEX' \
' make program-flash Program only the application flash region' \
' make program-flash-bitstream' \
' Program only the configuration region' \
' make program-flash-combined' \
' Program configuration + application HEX' \
' make debug-openocd Start Rocket DMI/GDB server through JTAG_USER1' \
' make debug-load Load APP ELF into DTIM through GDB' \
' make uart-listen Listen to FTDI UART interface 2' \
' make fpga-config Print resolved tools, clocks, files, and USB config'
all: bitstream
fpga-config:
@printf '%s\n' \
'Ti375C529 FPGA config:' \
' EFINITY_HOME=$(EFINITY_HOME)' \
' EFX_RUN=$(EFX_RUN)' \
' EFX_PGM=$(EFX_PGM)' \
' OPENOCD=$(OPENOCD)' \
' GDB=$(GDB)' \
' DEVICE=$(DEVICE)' \
' TIMING_MODEL=$(TIMING_MODEL)' \
' SOC_CLK_HZ=$(SOC_CLK_HZ)' \
' GPS_ACQ_CLK_HZ=$(GPS_ACQ_CLK_HZ)' \
' GPS_TRCK_CLK_HZ=$(GPS_TRCK_CLK_HZ)' \
' GPS_RF_CLK_HZ=$(GPS_RF_CLK_HZ)' \
' RF_INPUT_MODE=$(RF_INPUT_MODE)' \
' BIT_FILE=$(BIT_FILE)' \
' CONFIG_HEX=$(CONFIG_HEX)' \
' FLASH_HEX=$(FLASH_HEX)' \
' APP_FLASH_OFFSET=$(APP_FLASH_OFFSET)' \
' FLASH_LOADER=$(FLASH_LOADER)' \
' FTDI_URL=$(FTDI_URL)' \
' UART_PORT=$(UART_PORT)' \
' UART_BAUD=$(UART_BAUD)'
detect:
@set +u
source "$(EFINITY_SETUP)" >/dev/null 2>&1
set -u
"$(EFX_PGM)" --scan_usb
software:
@$(MAKE) -C "$(SOC_SW_DIR)" \
TOOLCHAIN="$(TOOLCHAIN)" APP="$(APP)" \
APP_FLASH_OFFSET="$(APP_FLASH_OFFSET)" \
BSP_UART_CLOCK_HZ="$(BSP_UART_CLOCK_HZ)" \
BSP_UART_BAUDRATE="$(BSP_UART_BAUDRATE)" \
GPS_SYSTEM_UART_BAUDRATE="$(BSP_UART_BAUDRATE)" \
BSP_CLINT_TIMEBASE_HZ="$(BSP_CLINT_TIMEBASE_HZ)" \
BSP_DTIM_BASE="$(DTIM_BASE)" BSP_DTIM_SIZE="$(DTIM_SIZE)" \
GPS_DEBUG="$(GPS_DEBUG)" GPS_DEBUG_LEVEL="$(GPS_DEBUG_LEVEL)" \
GPS_ESKF_ENABLE="$(GPS_ESKF_ENABLE)" IMU_SENSOR="$(IMU_SENSOR)" \
bootloader flash-image
$(APP_ELF): FORCE
@$(MAKE) -C "$(SOC_SW_DIR)" \
TOOLCHAIN="$(TOOLCHAIN)" APP="$(APP)" \
BSP_UART_CLOCK_HZ="$(BSP_UART_CLOCK_HZ)" \
BSP_UART_BAUDRATE="$(BSP_UART_BAUDRATE)" \
GPS_SYSTEM_UART_BAUDRATE="$(BSP_UART_BAUDRATE)" \
BSP_CLINT_TIMEBASE_HZ="$(BSP_CLINT_TIMEBASE_HZ)" \
BSP_DTIM_BASE="$(DTIM_BASE)" BSP_DTIM_SIZE="$(DTIM_SIZE)" \
GPS_DEBUG="$(GPS_DEBUG)" GPS_DEBUG_LEVEL="$(GPS_DEBUG_LEVEL)" \
GPS_ESKF_ENABLE="$(GPS_ESKF_ENABLE)" IMU_SENSOR="$(IMU_SENSOR)" app
chisel: software
@cd "$(REPO_DIR)"
"$(MILL)" -i 'rocketchip[6.7.0].assembly'
rm -rf "$(FIRRTL_DIR)"
mkdir -p "$(FIRRTL_DIR)"
"$(JAVA_HOME)/bin/java" \
-DROCKET_DTIM_BASE="$(DTIM_BASE)" \
-DROCKET_DTIM_SIZE="$(DTIM_SIZE)" \
-jar out/rocketchip/6.7.0/assembly.dest/out.jar \
--dir "$(FIRRTL_DIR)" \
--top freechips.rocketchip.fpga.VCU118RocketSystem \
--config freechips.rocketchip.fpga.VCU118RocketConfig
rtl: chisel
@cd "$(REPO_DIR)"
rm -rf "$(VERILOG_DIR)"
mkdir -p "$(VERILOG_DIR)" "$(dir $(WRAPPER))"
$(FIRTOOL_ENV) "$(FIRTOOL)" \
"$(FIRRTL_DIR)/VCU118RocketSystem.fir" \
--split-verilog -o "$(VERILOG_DIR)" \
--preserve-aggregate=none \
--disable-annotation-unknown \
--disable-all-randomization \
--strip-debug-info
find "$(VERILOG_DIR)" -maxdepth 1 -type f \( -name '*.sv' -o -name '*.v' \) \
-printf './%f\n' | sort > "$(VERILOG_DIR)/filelist.f"
perl -0pi -e 's/^([ \t]*)if \([^\n]*\)\n[ \t]*assert__[^\n]*:\s*assert\([^\n]*\);\n//mg; s/^[ \t]*assert__[^\n]*:\s*assert\([^\n]*\);\n//mg' "$(VERILOG_DIR)"/*.sv
python3 "$(TOOL_SCRIPT_DIR)/prepare_efinity_rtl.py" \
--verilog-dir "$(VERILOG_DIR)" \
--gps-source-dir "$(GPS_SYSTEM_DIR)" \
--gps-output-dir "$(GPS_EFINITY_DIR)"
python3 "$(TOOL_SCRIPT_DIR)/gen_ti375c529_wrapper.py" \
--rocket-top "$(VERILOG_DIR)/VCU118RocketSystem.sv" \
--out "$(WRAPPER)"
: > "$(SOURCE_LIST)"
while IFS= read -r rel; do
rel="$${rel#./}"
printf '%s\n' "$(VERILOG_DIR)/$$rel" >> "$(SOURCE_LIST)"
done < "$(VERILOG_DIR)/filelist.f"
printf '%s\n' "$(HW_DIR)/upstream/rocket-chip/src/main/resources/vsrc/plusarg_reader.v" >> "$(SOURCE_LIST)"
while IFS= read -r rel; do
[[ -z "$$rel" || "$$rel" == \#* ]] && continue
case "$$rel" in
trck_src/xil_fifo.sv|trck_src/pulp_fifo/top.sv) continue ;;
acq_src/acq_nco_carr.sv)
printf '%s\n' "$(GPS_EFINITY_DIR)/acq_nco_carr.sv" >> "$(SOURCE_LIST)"
continue ;;
acq_src/twiddle_ram.sv)
printf '%s\n' "$(GPS_EFINITY_DIR)/twiddle_ram.sv" >> "$(SOURCE_LIST)"
continue ;;
trck_src/NCO_carr.sv)
printf '%s\n' "$(GPS_EFINITY_DIR)/NCO_carr.sv" >> "$(SOURCE_LIST)"
continue ;;
esac
printf '%s\n' "$(GPS_SYSTEM_DIR)/$$rel" >> "$(SOURCE_LIST)"
done < "$(GPS_SYSTEM_DIR)/filelist.f"
# Efinix JTAG_USER1 has the same two-bit TDO lead-in as the Kintex-7
# BSCANE2 path, so use the Genesys2-aligned Rocket DMI tunnel.
printf '%s\n' "$(HW_DIR)/fpga/genesys2/rtl/rocket_bscan_dmi_tunnel.sv" >> "$(SOURCE_LIST)"
$(MAKE) "$(INTERFACE_SCRIPT)"
soc: rtl
$(INTERFACE_SCRIPT): $(INTERFACE_SOURCE)
cp "$<" "$@"
interface: $(INTERFACE_SCRIPT)
@mkdir -p "$(LOG_DIR)"
cd "$(FLOW_DIR)"
# Efinity prefers a matching generated .peri.xml over the source .isf.
# Remove it so pin/PLL changes in the checked-in interface script are applied.
rm -f "$(FLOW_DIR)/ti375c529_rocket.peri.xml"
set +u
source "$(EFINITY_SETUP)" >/dev/null 2>&1
set -u
python3 "$(EFX_RUN)" "$(PROJECT_XML)" --flow interface --un_flow \
--pt_opts "peri_scripts=$(INTERFACE_SCRIPT)" 2>&1 | tee "$(LOG_DIR)/interface.log"
map: rtl interface
@mkdir -p "$(LOG_DIR)"
cd "$(FLOW_DIR)"
set +u
source "$(EFINITY_SETUP)" >/dev/null 2>&1
set -u
python3 "$(EFX_RUN)" "$(PROJECT_XML)" --flow map 2>&1 | tee "$(LOG_DIR)/map.log"
bitstream: rtl interface
@mkdir -p "$(LOG_DIR)" "$(dir $(BIT_FILE))"
cd "$(FLOW_DIR)"
set +u
source "$(EFINITY_SETUP)" >/dev/null 2>&1
set -u
: > "$(LOG_DIR)/full.log"
python3 "$(EFX_RUN)" "$(PROJECT_XML)" --flow map 2>&1 | tee -a "$(LOG_DIR)/full.log"
python3 "$(EFX_RUN)" "$(PROJECT_XML)" --flow pnr --work_dir work_pnr 2>&1 | tee -a "$(LOG_DIR)/full.log"
python3 "$(EFX_RUN)" "$(PROJECT_XML)" --flow pgm --work_dir work_pnr 2>&1 | tee -a "$(LOG_DIR)/full.log"
generated_bit="$$(find "$(FLOW_DIR)/outflow" "$(OUT_DIR)" -type f -name 'ti375c529_rocket.bit' 2>/dev/null | head -n 1)"
if [[ -z "$$generated_bit" ]]; then
echo "Efinity completed without a discoverable ti375c529_rocket.bit" >&2
exit 1
fi
if [[ "$$generated_bit" != "$(BIT_FILE)" ]]; then
cp "$$generated_bit" "$(BIT_FILE)"
fi
generated_hex="$$(find "$(FLOW_DIR)/outflow" "$(OUT_DIR)" -type f -name 'ti375c529_rocket.hex' ! -path '$(FLASH_DIR)/*' 2>/dev/null | head -n 1)"
if [[ -z "$$generated_hex" ]]; then
echo "Efinity completed without a discoverable ti375c529_rocket.hex" >&2
exit 1
fi
if [[ "$$generated_hex" != "$(CONFIG_HEX)" ]]; then
cp "$$generated_hex" "$(CONFIG_HEX)"
fi
printf 'Bitstream: %s\nConfiguration HEX: %s\n' "$(BIT_FILE)" "$(CONFIG_HEX)"
program-fpga:
@if [[ ! -f "$(BIT_FILE)" ]]; then
echo "Missing bitstream: $(BIT_FILE)" >&2
echo "Run: make -C $(FPGA_DIR) bitstream" >&2
exit 1
fi
set +u
source "$(EFINITY_SETUP)" >/dev/null 2>&1
set -u
ftdi_args=()
if [[ -n "$(FTDI_URL)" ]]; then
ftdi_args+=(--url "$(FTDI_URL)")
fi
"$(EFX_PGM)" --mode jtag "$${ftdi_args[@]}" "$(BIT_FILE)"
debug-openocd:
@"$(OPENOCD)" \
-c "set ADAPTER_SPEED $(OPENOCD_ADAPTER_SPEED)" \
-c "gdb_port $(GDB_PORT)" \
-f "$(OPENOCD_CFG)"
debug-load: $(APP_ELF)
@"$(GDB)" -q "$(APP_ELF)" \
-ex "set confirm off" \
-ex "set pagination off" \
-ex "target extended-remote $(GDB_HOST):$(GDB_PORT)" \
-x "$(GDB_LOAD_SCRIPT)"
debug-gdb: $(APP_ELF)
@"$(GDB)" -q "$(APP_ELF)" \
-ex "set confirm off" \
-ex "set pagination off" \
-ex "target extended-remote $(GDB_HOST):$(GDB_PORT)" \
-x "$(GDB_ATTACH_SCRIPT)"
uart-listen:
@stty -F "$(UART_PORT)" "$(UART_BAUD)" cs8 -cstopb -parenb -ixon -ixoff raw
@cat "$(UART_PORT)"
flash-image: flash-regions
flash-regions: software
@if [[ ! -f "$(CONFIG_HEX)" ]]; then
echo "Missing Efinity configuration HEX: $(CONFIG_HEX)" >&2
echo "Run: make -C $(FPGA_DIR) bitstream" >&2
exit 1
fi
mkdir -p "$(FLASH_DIR)" "$(FLASH_REGION_DIR)"
python3 "$(TOOL_SCRIPT_DIR)/make_flash_hex.py" \
--config-hex "$(CONFIG_HEX)" \
--app-bin "$(APP_FLASH_IMAGE)" \
--app-offset "$(APP_FLASH_OFFSET)" \
--flash-size "$(FLASH_SIZE)" \
--combined-hex "$(FLASH_HEX)" \
--app-hex "$(APP_HEX)" \
--manifest "$(FLASH_MANIFEST)"
program-flash-loader:
@if [[ ! -f "$(FLASH_LOADER)" ]]; then
echo "Missing Ti375 JTAG-to-SPI loader: $(FLASH_LOADER)" >&2
exit 1
fi
set +u
source "$(EFINITY_SETUP)" >/dev/null 2>&1
set -u
ftdi_args=()
if [[ -n "$(FTDI_URL)" ]]; then
ftdi_args+=(--url "$(FTDI_URL)")
fi
"$(EFX_PGM)" --mode jtag "$${ftdi_args[@]}" "$(FLASH_LOADER)"
program-flash program-flash-elf program-app: flash-regions
@$(MAKE) -C "$(FPGA_DIR)" program-flash-loader
set +u
source "$(EFINITY_SETUP)" >/dev/null 2>&1
set -u
ftdi_args=()
if [[ -n "$(FTDI_URL)" ]]; then
ftdi_args+=(--url "$(FTDI_URL)")
fi
"$(EFX_PGM)" --mode jtag_bridge --jtag_bridge_mode all \
--verify_method "$(FLASH_VERIFY)" --address "$(APP_FLASH_OFFSET)" \
"$${ftdi_args[@]}" "$(APP_HEX)"
program-flash-bitstream program-bitstream:
@if [[ ! -f "$(CONFIG_HEX)" ]]; then
echo "Missing Efinity configuration HEX: $(CONFIG_HEX)" >&2
echo "Run: make -C $(FPGA_DIR) bitstream" >&2
exit 1
fi
$(MAKE) -C "$(FPGA_DIR)" program-flash-loader
set +u
source "$(EFINITY_SETUP)" >/dev/null 2>&1
set -u
ftdi_args=()
if [[ -n "$(FTDI_URL)" ]]; then
ftdi_args+=(--url "$(FTDI_URL)")
fi
"$(EFX_PGM)" --mode jtag_bridge --jtag_bridge_mode all \
--verify_method "$(FLASH_VERIFY)" --address 0 \
"$${ftdi_args[@]}" "$(CONFIG_HEX)"
program-flash-regions program-flash-all program-flash-combined: flash-image
@$(MAKE) -C "$(FPGA_DIR)" program-flash-loader
set +u
source "$(EFINITY_SETUP)" >/dev/null 2>&1
set -u
ftdi_args=()
if [[ -n "$(FTDI_URL)" ]]; then
ftdi_args+=(--url "$(FTDI_URL)")
fi
"$(EFX_PGM)" --mode jtag_bridge --jtag_bridge_mode all \
--verify_method "$(FLASH_VERIFY)" --address 0 \
"$${ftdi_args[@]}" "$(FLASH_HEX)"
clean-sw:
@$(MAKE) -C "$(SOC_SW_DIR)" clean
clean-fpga:
rm -rf "$(OUT_DIR)" "$(FIRRTL_DIR)" "$(VERILOG_DIR)"
rm -f "$(INTERFACE_SCRIPT)" \
"$(FLOW_DIR)/ti375c529_rocket.peri.xml" \
"$(FLOW_DIR)/ti375c529_rocket.pt.sdc" \
"$(FLOW_DIR)/ti375c529_rocket.pt.rpt"
clean: clean-fpga
FORCE:
+160
View File
@@ -0,0 +1,160 @@
# Ti375C529 Rocket FPGA flow
This target keeps the project's RV64 Rocket/GPS subsystem in FPGA fabric and
replaces only the Xilinx board shell and build flow with Efinix equivalents.
It does not use the Ti375 hardened RV32 subsystem.
Detailed porting, synthesis, clocking, JTAG, and verification notes are in
[`flow/efinity/README.md`](flow/efinity/README.md).
## Build and program
```bash
make FPGA_BOARD=ti375c529 fpga-config
make FPGA_BOARD=ti375c529 fpga-rtl
make FPGA_BOARD=ti375c529 fpga-bitstream
make FPGA_BOARD=ti375c529 fpga-program
```
The Makefile auto-discovers Efinity under `/tools/efinix/efinity`. Override
`EFINITY_HOME` when another installation is required.
Programming auto-selects the connected Ti375 development kit. If more than
one compatible FTDI device is attached, run `make FPGA_BOARD=ti375c529
fpga-detect` to list the bus-qualified URLs and pass the selected one as
`FTDI_URL=...`.
Volatile JTAG programming and GDB loading into Rocket DTIM remain available.
Persistent boot uses the board's Lower 512-Mbit SPI flash with this map:
| Region | Flash offset | Contents |
|---|---:|---|
| FPGA configuration | `0x00000000` | Efinity active-mode configuration HEX |
| Rocket application | `0x00800000` | Boot header and ELF-derived payload |
The application starts at 8 MiB because the compressed Ti375 configuration is
currently about 5.19 MB; the previous 4 MiB trial offset would overlap it.
Generate or program the Efinity raw HEX images with:
```bash
make FPGA_BOARD=ti375c529 fpga-mcs APP=hello_uart
make FPGA_BOARD=ti375c529 fpga-flash-app APP=hello_uart
make FPGA_BOARD=ti375c529 fpga-flash-bitstream
make FPGA_BOARD=ti375c529 fpga-flash-mcs APP=hello_uart
```
`fpga-mcs` is the repository-wide compatibility target name. For this Efinix
board it produces `.hex`, not Xilinx `.mcs`. `fpga-flash-app` changes only the
application region; `fpga-flash-mcs` erases/programs configuration plus the
application through Efinity's pre-built JTAG-to-SPI bridge and verifies the
readback. Cold boot from this layout was verified by a complete board power
cycle: Ti375 configured from flash and Rocket loaded and completed
`hello_uart` from the application region without a JTAG/GDB load.
## Debug and load
Use the host OpenOCD build, not the OpenOCD shipped with Efinity. The build
must include `riscv set_bscan_tunnel_ir`; Ti375 uses a 5-bit outer TAP and
selects `JTAG_USER1` with instruction `0x08`.
OpenOCD 0.12 can be given that upstream-compatible command with
`flow/debug/openocd-0.12-ti375-bscan.patch`.
In one terminal:
```bash
make FPGA_BOARD=ti375c529 fpga-debug-openocd
```
In a second terminal:
```bash
make FPGA_BOARD=ti375c529 fpga-debug-load APP=hello_uart
make FPGA_BOARD=ti375c529 fpga-uart
```
The target deliberately selects `/tools/toolchain/bin/riscv64-unknown-elf-gdb`
when it exists. The medany compiler toolchain is still used to build software,
but it is not expected to contain GDB.
Efinity 2025.2 leaves Rocket's block-local packed store-data array undriven
(`VDB-1002`). `tools/prepare_efinity_rtl.py` rewrites that generated RTL
expression as an equivalent explicit `case`. Without the rewrite, DMI itself
works, but GPR/CSR values stored into Debug Module data registers read as zero;
OpenOCD consequently reports a misleading `misa=0`.
The patch is deliberately post-generation: Chisel and `firtool` first emit the
Ti375 SystemVerilog tree, then `prepare_efinity_rtl.py` modifies only the
generated Ti375 `Rocket.sv` before Efinity synthesis. It does not edit Rocket's
native/upstream Chisel sources.
Two equivalent four-entry packed temporaries in the generated pbus/cbus
`TLAtomicAutomata` modules are rewritten for the same reason, preserving the
four atomic-operation mappings while removing their `VDB-1002` risk.
The same script generates Ti375-only copies of the acquisition carrier NCO,
tracking carrier NCO, and FFT twiddle ROM under
`out/build/generated/gps_efinity/`. This avoids Efinity 2025.2
whole-array/`initial`/`$sin` elaboration failures without editing the shared GPS
RTL. Only the Ti375 source list selects these copies; Genesys2 and VCU118 still
use the originals. Detailed source paths, warning interpretation, and Questa
RTL/post-map equivalence results are in the Efinity flow notes.
The affected generated source is
`hardware/generated/ti375c529-soc-verilog/Rocket.sv`; Efinity records the
original diagnostic in
`targets/fpga/ti375c529/flow/efinity/outflow/ti375c529_rocket.warn.log`.
The checked-in repair is
`targets/fpga/ti375c529/tools/prepare_efinity_rtl.py` and runs after every
`firtool` invocation, before Efinity synthesis.
## Board connections
PMOD0/J13 carries the GPS RF sampler:
| Signal | PMOD pin | FPGA resource | Package pin |
|---|---:|---|---|
| `rf_clk` | 10 | GPIOR_95_CLK12 | A12 |
| `rf_data[0]` | 2 | GPIOR_89 | G16 |
| `rf_data[1]` | 3 | GPIOR_90 | F16 |
| `rf_data[2]` | 4 | GPIOR_91 | F17 |
| `rf_data[3]` | 7 | GPIOR_92 | G17 |
`rf_clk` must be wired to J13 pin 10, not J13 pin 1. Pin 10 is the PMOD0
header's dedicated 3.3 V global-clock input. J13 pin 1 (`G15`,
`GPIOR_88_PLLIN1`) can feed a PLL but is not a dedicated fabric GCLK input;
using it as the raw AFE clock makes Efinity promote normal GPIO routing into a
GBUF and issue an extra-clock-insertion-delay warning.
As on Genesys2 and VCU118, the AX2771 clock is both the raw AFE sample clock
and the source of the GPS processing-clock chain. Two Titanium PLLs generate
an intermediate 55.5556 MHz clock, then 100 MHz acquisition and 125 MHz
tracking clocks. The Rocket fabric uses the independent on-board 100 MHz
oscillator.
PMOD1/J14 carries the sensor SPI, interrupt, and PPS:
| Signal | PMOD pin | FPGA resource | Package pin |
|---|---:|---|---|
| `pmod_spi_csn` | 1 | GPIOR_96_CLK13 | B12 |
| `pmod_spi_dq[0]` | 2 | GPIOR_97 | C14 |
| `pmod_spi_dq[1]` | 3 | GPIOR_98 | C13 |
| `pmod_spi_sck` | 4 | GPIOR_99 | C12 |
| `pmod_spi_dq[2]` | 7 | GPIOR_100 | D12 |
| `pmod_spi_dq[3]` | 8 | GPIOR_101_PLLIN1 | F12 |
| `pmod_irq` | 9 | GPIOR_102 | D13 |
| `gps_pps` | 10 | GPIOR_103 | E13 |
The FTDI UART is `/dev/ttyUSB2` on the current Linux enumeration. SW3 is the
active-low Rocket reset. JTAG uses Efinix `JTAG_USER1` and the existing Rocket
DMI tunnel protocol.
The Lower flash user-mode connection is internal to the development kit:
| Signal | FPGA resource | Package pin |
|---|---|---|
| `flash_csn` | GPIOB_N_38 / SSL_N | A7 |
| `flash_sck` | GPIOB_P_39 / CCK | G6 |
| `flash_dq[0]` | GPIOB_N_40 / CDI0 | H4 |
| `flash_dq[1]` | GPIOB_P_40 / CDI1 | H5 |
| `flash_dq[2]` | GPIOB_N_39 / CDI2 | G5 |
| `flash_dq[3]` | GPIOB_P_38 / CDI3 | B7 |
+12
View File
@@ -0,0 +1,12 @@
Board name: Efinix Titanium Ti375 C529 Development Kit
Official link: https://www.efinixinc.com/products-devkits-titaniumti375c529.html
FPGA device: Ti375C529
Efinity family: Titanium
Efinity timing model: C4
Initial soft-Rocket flow clocks:
SoC/fabric: 100 MHz from the on-board 100 MHz oscillator
GPS RF: external AX2771 PMOD0/J13 clock, nominally 16.367981 MHz
GPS acquisition: 100 MHz, PLL-derived from the GPS RF clock
GPS tracking: 125 MHz, PLL-derived from the GPS RF clock
@@ -0,0 +1 @@
monitor halt
@@ -0,0 +1,13 @@
monitor halt
monitor reset halt
set $mie = 0
set $mstatus = 0x6000
set {unsigned long long}0x02004000 = 0xffffffffffffffff
load
set $mie = 0
set $mstatus = 0x6000
set $sp = (unsigned long)&_stack_top
set $pc = _start
monitor resume
disconnect
quit
@@ -0,0 +1,62 @@
--- a/src/target/riscv/riscv.c
+++ b/src/target/riscv/riscv.c
@@ -127,6 +127,7 @@
static bscan_tunnel_type_t bscan_tunnel_type;
int bscan_tunnel_ir_width; /* if zero, then tunneling is not present/active */
+static int bscan_tunnel_ir_id; /* outer TAP IR used to access the tunnel */
static const uint8_t bscan_zero[4] = {0};
static const uint8_t bscan_one[4] = {1};
@@ -451,8 +452,12 @@
select_idcode.num_bits = target->tap->ir_length;
if (bscan_tunnel_ir_width != 0) {
- assert(target->tap->ir_length >= 6);
- uint32_t ir_user4_raw = 0x23 << (target->tap->ir_length - 6);
+ uint32_t ir_user4_raw = bscan_tunnel_ir_id;
+ /* Preserve the legacy Xilinx USER4 default when no IR was supplied. */
+ if (ir_user4_raw == 0) {
+ assert(target->tap->ir_length >= 6);
+ ir_user4_raw = 0x23 << (target->tap->ir_length - 6);
+ }
h_u32_to_le(ir_user4, ir_user4_raw);
select_user4.num_bits = target->tap->ir_length;
bscan_tunneled_ir_width[0] = bscan_tunnel_ir_width;
@@ -2859,6 +2864,21 @@
return ERROR_OK;
}
+COMMAND_HANDLER(riscv_set_bscan_tunnel_ir)
+{
+ int ir_id = 0;
+
+ if (CMD_ARGC > 1)
+ return ERROR_COMMAND_SYNTAX_ERROR;
+
+ if (CMD_ARGC == 1)
+ COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], ir_id);
+
+ LOG_INFO("Bscan tunnel IR 0x%x selected", ir_id);
+ bscan_tunnel_ir_id = ir_id;
+ return ERROR_OK;
+}
+
COMMAND_HANDLER(riscv_set_enable_virt2phys)
{
if (CMD_ARGC != 1) {
@@ -3072,6 +3092,14 @@
"1: DATA_REGISTER}"
},
{
+ .name = "set_bscan_tunnel_ir",
+ .handler = riscv_set_bscan_tunnel_ir,
+ .mode = COMMAND_ANY,
+ .usage = "[value]",
+ .help = "Specify the outer JTAG TAP IR used to access the BSCAN tunnel. "
+ "The default remains the Xilinx FPGA USER4 instruction."
+ },
+ {
.name = "set_enable_virt2phys",
.handler = riscv_set_enable_virt2phys,
.mode = COMMAND_ANY,
@@ -0,0 +1,32 @@
# Rocket debug through the Ti375 hard TAP, JTAG_USER1, and the project's DMI tunnel.
adapter driver ftdi
ftdi vid_pid 0x0403 0x6011
ftdi channel 1
ftdi layout_init 0x08 0x0b
if {![info exists ADAPTER_SPEED]} {
set ADAPTER_SPEED 1000
}
adapter speed $ADAPTER_SPEED
jtag newtap ti375 fpga -expected-id 0x006A0A79 -irlen 5 -ircapture 0x5 -irmask 0xF
target create rocket.cpu riscv -chain-position ti375.fpga -coreid 0
# The outer Ti375 USER1 instruction is 0x08. The inner tunnel implements the
# same 5-bit Rocket DTM IR and framing used by the Xilinx targets.
riscv set_ir dtmcs 0x10
riscv set_ir dmi 0x11
riscv use_bscan_tunnel 5 0
# Ti375 requires an OpenOCD build with the upstream configurable outer BSCAN
# instruction support. Keep the catch only so older builds produce a readable
# diagnostic before their legacy Xilinx-specific BSCAN path rejects this TAP.
if {[catch {riscv set_bscan_tunnel_ir 8}]} {
echo "OpenOCD lacks 'riscv set_bscan_tunnel_ir'; Ti375 requires that command"
}
riscv set_reset_timeout_sec 10
riscv set_command_timeout_sec 10
init
halt
@@ -0,0 +1,327 @@
# Ti375C529 Efinity flow notes
This directory contains the Efinity project for the Ti375C529 development kit.
The port keeps the same RV64 Rocket/GPS subsystem used by the Genesys2 and
VCU118 targets. Only the FPGA shell, clocks, pin constraints, programming flow,
and JTAG transport are vendor-specific. The hardened RV32 subsystem in the
Ti375 device is not used.
## What was added
- Added `ti375c529` to the root FPGA board selection and build targets.
- Added Efinity 2025.2 discovery through `EFINITY_HOME`; the current installation
is `/tools/efinix/efinity/2025.2`.
- Added a generated Ti375 wrapper around `VCU118RocketSystem` so the existing
Rocket, DTIM, UART, GPS acquisition, tracking, PPS, and SPI logic is reused.
- Added the Efinity XML project, interface script, SDC constraints, PLLs, pin
assignments, and JTAG programming target.
- Added FTDI UART operation at 460800 baud. On the current host it enumerates as
`/dev/ttyUSB2`.
- Added host OpenOCD/GDB debug through the Ti375 hard TAP and `JTAG_USER1`.
- Added Efinity-specific generated-RTL rewrites for Rocket's store-data path and
the GPS acquisition/tracking lookup tables that Efinity 2025.2 mis-elaborates.
## Clock structure
The Rocket fabric and GPS processing clocks have separate sources:
- The on-board 100 MHz oscillator drives the fabric PLL and the 100 MHz Rocket
system clock.
- AX2771 `rf_clk` enters through the dedicated global-clock input at J13 pin 10
/ package pin A12 (`GPIOR_95_CLK12`). It is the raw AFE sample clock and feeds
the GPS PLL chain. J13 pin 1 / G15 is not used for the RF clock because it is
a PLL input, not a dedicated fabric GCLK input.
- The GPS PLL chain generates 100 MHz acquisition and 125 MHz tracking clocks.
- Acquisition and tracking reset release is gated by the relevant PLL lock
signals.
The four AX2771 data bits use J13 pins 2, 3, 4, and 7, corresponding to package
pins G16, F16, F17, and G17. The AX2771 clock must be jumpered to J13 pin 10;
do not connect it to the former J13 pin 1 location.
## Efinity synthesis adaptation
This adaptation does **not** modify Rocket's native/upstream Chisel sources or
the shared GPS SystemVerilog. The normal Chisel and `firtool` stages run first
and emit SystemVerilog into `hardware/generated/ti375c529-soc-verilog/`.
Immediately afterward,
`targets/fpga/ti375c529/tools/prepare_efinity_rtl.py` patches the generated
Ti375 `Rocket.sv` and writes Efinity-only GPS replacements under
`targets/fpga/ti375c529/out/build/generated/gps_efinity/`. The Ti375 source-list
recipe substitutes those generated copies for exactly these three shared GPS
files:
- `acq_src/acq_nco_carr.sv`
- `trck_src/NCO_carr.sv`
- `acq_src/twiddle_ram.sv`
All other GPS RTL remains shared. Efinity consumes the replacements; Genesys2
and VCU118 continue to consume the original GPS files. Deleting either
generated directory and running `make FPGA_BOARD=ti375c529 fpga-rtl` repeats
the same deterministic generation and substitution.
### Rocket store-data path
The CIRCT-generated `Rocket.sv` contains this block-local packed temporary for
store-data byte-lane replication:
```systemverilog
automatic logic [3:0][63:0] _GEN_24 = ...;
mem_reg_rs2 <= _GEN_24[ex_reg_mem_size];
```
Vivado synthesizes this construct correctly. Efinity 2025.2 reports `_GEN_24`
as `VDB-1002: net does not have a driver`, leaving Rocket store data at zero.
This initially looked like a Debug Module failure because abstract GPR/CSR
reads execute a hart instruction that stores the result into Debug Module data
registers. DMI was working, but OpenOCD received zeros and reported the
misleading result `misa=0`.
`../../tools/prepare_efinity_rtl.py` replaces only that generated expression
with an equivalent explicit `case` for byte, half-word, word, and double-word
stores. The script runs automatically after `firtool`; generated RTL is not
edited by hand. After the rewrite, the Rocket `VDB-1002` warning disappears
from the current synthesis run. `ti375c529_rocket.warn.log` is reused by
Efinity and can contain entries from older runs, so the generated source and
the final warning block should be checked together.
The same Efinity limitation also affected the four-entry packed atomic-operation
LUT emitted into generated `TLAtomicAutomata_pbus.sv` and
`TLAtomicAutomata_cbus.sv`. Those `_GEN_4`/`_GEN_13` temporaries select the
TileLink atomic ALU operation; they are unrelated to GPS acquisition but could
break software atomics. The post-generation script rewrites both to an
equivalent four-way `case`. Questa confirmed all four index mappings, and their
`VDB-1002` diagnostics disappear from the current single-run `map.out`.
The exact paths are:
- Generated failing/fixed source:
`hardware/generated/ti375c529-soc-verilog/Rocket.sv`
- Generated TileLink atomic sources:
`hardware/generated/ti375c529-soc-verilog/TLAtomicAutomata_pbus.sv` and
`hardware/generated/ti375c529-soc-verilog/TLAtomicAutomata_cbus.sv`
- Efinity synthesis warning log:
`targets/fpga/ti375c529/flow/efinity/outflow/ti375c529_rocket.warn.log`
- Deterministic repair:
`targets/fpga/ti375c529/tools/prepare_efinity_rtl.py`
- Invocation point:
`targets/fpga/ti375c529/Makefile`, in the `rtl` recipe immediately after
`firtool`
### GPS lookup-table paths
The original acquisition carrier NCO is
`hardware/external/gps/rtl/acq_src/acq_nco_carr.sv`. It initializes the
frequency and pull-in arrays with whole-package-array assignments and builds
its sine table in an `initial` loop using `$sin`. Efinity 2025.2 reports the
loop variable as never assigned and the three arrays as undriven. Stand-alone
post-map comparison confirmed that this is functional, not cosmetic: the
unpatched Efinity netlist produced incorrect sine/cosine samples in all 18,040
tested production-parameter cycles.
The generated Ti375 copy replaces the frequency, pull-in, sine, and cosine
reads with constant functions generated from `definitions.sv`. Questa compared
41 frequency bins, 11 pull-in bins, and 40 cycles per combination: all 18,040
RTL samples and all 18,040 Efinity post-map samples matched the original RTL.
The current full-project synthesis no longer reports the acquisition arrays or
its `$sin` loop as undriven.
The separate tracking carrier NCO is
`hardware/external/gps/rtl/trck_src/NCO_carr.sv`. Its
`sinwav_lut = TRCK_SINWAV_LUT` package-array initializer triggers the same
Efinity `VDB-1002` behavior. The Ti375 copy uses an exact 128-entry constant
function generated from `TRCK_SINWAV_LUT`. Questa matched every table entry at
RTL and then matched 5,000 Efinity post-map cycles while visiting all 128
addresses.
The acquisition FFT twiddle source is
`hardware/external/gps/rtl/acq_src/twiddle_ram.sv`. Its whole-array
`COS_ROM_ARRAY` and `SIN_ROM_ARRAY` assignments are converted only for Ti375 to
two generated, exactly 15-bit-wide `$readmemb` files. Efinity infers 30
`EFX_RAM10` blocks with embedded INIT data. Efinity still emits its generic
`sin_rom/cos_rom does not have a driver` elaboration warning for this recognized
ROM form, but this one is demonstrably benign: Questa matched all 8,192 RTL
addresses and all 8,192 addresses in the Efinity post-map RAM netlist. Check
the current `ti375c529_rocket.map.out` for a single run; the `.warn.log` and
`.info.log` files retain records from older builds and can otherwise make fixed
source paths look current.
## JTAG and OpenOCD
Ti375 and the Xilinx boards expose the same inner Rocket DTM protocol, but the
outer FPGA TAP is different:
| Item | Xilinx flow | Ti375 flow |
|---|---|---|
| Outer TAP IR width | Xilinx-specific | 5 bits |
| User instruction | legacy USER4 handling | `JTAG_USER1 = 0x08` |
| Inner DTM IR width | 5 bits | 5 bits |
| DTMCS / DMI IR | `0x10` / `0x11` | `0x10` / `0x11` |
The host OpenOCD must therefore implement:
```text
riscv set_bscan_tunnel_ir 8
```
A fixed OpenOCD pathname is **not** required. The target Makefile evaluates
`command -v openocd`, so the first compatible binary in `PATH` is used. On the
current machine this resolves to:
```text
/home/onur/.local/bin/openocd
```
That location is only the installation choice for the patched host OpenOCD; it
is not part of the hardware flow. Another compatible build can be selected
without changing files:
```bash
make FPGA_BOARD=ti375c529 fpga-debug-openocd OPENOCD=/path/to/openocd
```
The original `/usr/local/bin/openocd` 0.12 build assumes the Xilinx USER4
instruction and asserts that the outer TAP IR is at least six bits. It cannot
drive the Ti375 5-bit TAP unchanged. `../debug/openocd-0.12-ti375-bscan.patch`
adds the configurable outer instruction to OpenOCD 0.12. The OpenOCD shipped
with Efinity is not required by this flow.
The selected paths can be checked with:
```bash
make FPGA_BOARD=ti375c529 fpga-config
command -v openocd
strings "$(command -v openocd)" | grep set_bscan_tunnel_ir
```
The line `Disabling abstract command reads/writes from CSRs` is informational,
not a failure. OpenOCD falls back to Rocket's Program Buffer for CSR access.
## GDB selection
Software is still compiled with the medany toolchain. That toolchain does not
contain GDB, so the debug target deliberately selects:
```text
/tools/toolchain/bin/riscv64-unknown-elf-gdb
```
This can also be overridden with `GDB=/path/to/riscv64-unknown-elf-gdb`.
## Build, program, and debug
From the repository root:
```bash
make FPGA_BOARD=ti375c529 fpga-config
make FPGA_BOARD=ti375c529 fpga-rtl
make FPGA_BOARD=ti375c529 fpga-bitstream
make FPGA_BOARD=ti375c529 fpga-program
```
Start the host OpenOCD server in one terminal:
```bash
make FPGA_BOARD=ti375c529 fpga-debug-openocd
```
Load and run an application, then monitor UART, from another terminal:
```bash
make FPGA_BOARD=ti375c529 fpga-debug-load APP=hello_uart
make FPGA_BOARD=ti375c529 fpga-uart
```
## Persistent Lower-flash boot
The kit has Lower and Upper 512-Mbit SPI NOR devices. This port intentionally
uses one MCS-like combined layout in the Lower ISSI IS25WP512 because Efinity's
command-line `jtag_bridge` mode directly addresses that flash:
| Offset | Image |
|---:|---|
| `0x00000000` | Compressed, active-x1 FPGA configuration |
| `0x00800000` | Rocket boot header followed by the application payload |
The application offset is 64-KiB-sector aligned and leaves roughly 3.2 MB
between the current 5,190,596-byte configuration and the firmware region.
`spi_low_power_mode=off` is embedded in the configuration so the Lower flash
stays available when Rocket enters user mode.
Rocket's existing MMIO SPI controller is connected to the configuration pins
after configuration completes:
```text
spi_sck/spi_csn/spi_dq_* -> A7/G6/H4/H5/G5/B7 -> Lower flash U47
```
Generate the configuration, per-application, and combined raw Efinity HEX
files:
```bash
make FPGA_BOARD=ti375c529 fpga-bitstream
make FPGA_BOARD=ti375c529 fpga-mcs APP=hello_uart
```
Outputs are:
- `out/bitstream/ti375c529_rocket.hex`: configuration region
- `out/flash/regions/app.hex`: application region
- `out/flash/ti375c529_rocket.hex`: combined image
- `out/flash/ti375c529_rocket.json`: offsets, sizes, and SHA-256 manifest
Program only the application, only the configuration, or the combined image:
```bash
make FPGA_BOARD=ti375c529 fpga-flash-app APP=hello_uart
make FPGA_BOARD=ti375c529 fpga-flash-bitstream
make FPGA_BOARD=ti375c529 fpga-flash-mcs APP=hello_uart
```
The Makefile first loads Efinity's device-matched bridge
`/tools/efinix/efinity/2025.2/pgm/fli/titanium/u006A0A79.bit`, then uses
`jtag_bridge` programming with on-chip verification. A fixed Efinity install
path is not required; `FLASH_LOADER` is derived from `EFINITY_HOME` and can be
overridden.
After bridge programming, the FPGA still contains the bridge design. Power
cycle the kit for a true active-flash boot, or use `fpga-program` to load the
normal design immediately and test the BootROM/application flash path.
## On-board verification
The completed flow was checked on the connected Ti375C529 kit with AX2771
attached:
- Efinity interface, map, PNR, and programming-image stages passed.
- Final patched timing passed with WNS `+0.101 ns` and WHS `+0.021 ns`.
- The Ti375 JTAG IDCODE was read as `0x006a0a79`.
- OpenOCD found one RV64 hart and read `misa=0x800000000080112d`.
- A raw Program Buffer GPR/store test returned `0x5a` through DMI.
- `/tools/toolchain/bin/riscv64-unknown-elf-gdb` loaded 40,880 bytes into DTIM.
- `hello_uart` printed the expected Rocket/DTIM/UART messages at 460800 baud.
- The persistent image passed with setup slack `+0.094 ns` and hold slack
`+0.021 ns`.
- The Lower flash was detected as ISSI IS25WP512, JEDEC `0x9d701a`.
- The combined 8,429,552-byte HEX completed erase, write, readback, and verify.
- BootROM read the payload at `0x00800040`, verified CRC `0xed3bbdf5`, copied
40,880 bytes into DTIM, and ran `hello_uart` without GDB.
- A full board power cycle then configured Ti375 from Lower flash. Host OpenOCD
found the RV64 hart through `JTAG_USER1`; its PC was at
`0x8000017e <_exit>` after `hello_uart` returned, and GDB `compare-sections`
matched the complete ELF `.text` range in DTIM. This confirms cold boot of
both hardware and software without a volatile JTAG/GDB load.
## Relevant files
- `ti375c529_rocket.xml`: Efinity project definition.
- `ti375c529_rocket.isf`: generated/copy of the checked-in interface script.
- `../../../../../hardware/fpga/ti375c529/constraints/`: source interface and SDC
constraints.
- `../../tools/gen_ti375c529_wrapper.py`: Efinix board-shell generator.
- `../../tools/prepare_efinity_rtl.py`: deterministic Rocket and GPS
Efinity-only RTL generation/rewrite.
- `../../tools/make_flash_hex.py`: validated Efinity raw-HEX image builder.
- `../debug/openocd_ti375c529_bscan.cfg`: Ti375 hard-TAP/OpenOCD configuration.
- `../debug/openocd-0.12-ti375-bscan.patch`: host OpenOCD 0.12 compatibility
patch.
@@ -0,0 +1,71 @@
<efx:project xmlns:efx="http://www.efinixinc.com/enf_proj" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="ti375c529_rocket" description="RV64 Rocket and GPS on the Ti375C529 development kit" sw_version="2025.2.288.2.10" xsi:schemaLocation="http://www.efinixinc.com/enf_proj enf_proj.xsd" last_run_state="pass" last_run_flow="bitstream" last_change="1785370142" config_result_in_sync="sync" design_ood="sync" place_ood="sync" route_ood="sync">
<efx:device_info>
<efx:family name="Titanium" />
<efx:device name="Ti375C529" />
<efx:timing_model name="C4" />
</efx:device_info>
<efx:design_info def_veri_version="verilog_2k" def_vhdl_version="vhdl_2008" unified_flow="true">
<efx:top_module name="ti375c529_rocket_top" />
<efx:design_file name="../../out/build/generated/ti375c529_rocket_top.sv" version="default" library="default" />
<efx:design_file name="../../out/build/efinity_sources.f" version="na" library="default" />
<efx:top_vhdl_arch name="" />
</efx:design_info>
<efx:constraint_info>
<efx:sdc_file name="../../../../../hardware/fpga/ti375c529/constraints/ti375c529_rocket.sdc" />
<efx:sdc_file name="outflow/ti375c529_rocket.pt.sdc" />
<efx:sdc_file name="../../../../../hardware/fpga/ti375c529/constraints/ti375c529_rocket_clock_groups.sdc" />
<efx:inter_file name="" />
</efx:constraint_info>
<efx:isf_info>
<efx:isf_file name="ti375c529_rocket.isf" />
</efx:isf_info>
<efx:sim_info />
<efx:misc_info />
<efx:ooc_info />
<efx:ip_info />
<efx:synthesis tool_name="efx_map">
<efx:param name="work_dir" value="../../out/build/work_syn" value_type="e_string" />
<efx:param name="write_efx_verilog" value="on" value_type="e_bool" />
<efx:param name="blackbox-error" value="1" value_type="e_option" />
<efx:param name="hdl-compile-unit" value="1" value_type="e_option" />
<efx:param name="infer-clk-enable" value="3" value_type="e_option" />
<efx:param name="infer-sync-set-reset" value="1" value_type="e_option" />
<efx:param name="mode" value="speed" value_type="e_option" />
<efx:param name="retiming" value="1" value_type="e_option" />
<efx:param name="seq_opt" value="1" value_type="e_option" />
<efx:param name="use-logic-for-small-mem" value="64" value_type="e_integer" />
<efx:param name="use-logic-for-small-rom" value="64" value_type="e_integer" />
<efx:param name="max_threads" value="-1" value_type="e_integer" />
<efx:param name="include" value="../../../../../hardware/external/gps/rtl/trck_src/pulp_fifo" value_type="e_string" />
<efx:param name="include" value="../../out/build/generated/gps_efinity" value_type="e_string" />
</efx:synthesis>
<efx:place_and_route tool_name="efx_pnr">
<efx:param name="work_dir" value="work_pnr" value_type="e_string" />
<efx:param name="seed" value="2" value_type="e_integer" />
<efx:param name="placer_effort_level" value="2" value_type="e_option" />
<efx:param name="max_threads" value="-1" value_type="e_integer" />
<efx:param name="beneficial_skew" value="on" value_type="e_option" />
<efx:param name="print_critical_path" value="10" value_type="e_integer" />
</efx:place_and_route>
<efx:bitstream_generation tool_name="efx_pgm">
<efx:param name="mode" value="active" value_type="e_option" />
<efx:param name="width" value="1" value_type="e_option" />
<efx:param name="enable_roms" value="smart" value_type="e_option" />
<efx:param name="bitstream_compression" value="on" value_type="e_bool" />
<efx:param name="spi_low_power_mode" value="off" value_type="e_bool" />
<efx:param name="release_tri_then_reset" value="on" value_type="e_bool" />
<efx:param name="generate_bit" value="on" value_type="e_bool" />
<efx:param name="generate_hex" value="on" value_type="e_bool" />
<efx:param name="jtag_usercode" value="0x52564B54" value_type="e_string" />
</efx:bitstream_generation>
<efx:debugger>
<efx:param name="work_dir" value="../../out/build/work_dbg" value_type="e_string" />
<efx:param name="auto_instantiation" value="off" value_type="e_bool" />
<efx:param name="profile" value="NONE" value_type="e_string" />
</efx:debugger>
<efx:security>
<efx:param name="enable_bitstream_encrypt" value="off" value_type="e_bool" />
<efx:param name="enable_bitstream_auth" value="off" value_type="e_bool" />
<efx:param name="randomize_iv_value" value="on" value_type="e_bool" />
</efx:security>
</efx:project>
@@ -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()
+4
View File
@@ -16,6 +16,10 @@ FIRTOOL ?= $(firstword $(wildcard $(SOC_TOOLS_ROOT)/circt-current/bin/firtool) $
Z3_LIB_DIR ?= $(firstword $(wildcard $(SOC_TOOLS_ROOT)/z3-current/bin) $(wildcard $(PROJECT_TOOLS_DIR)/z3/*/bin))
CIRCT_LIB_DIR ?= $(patsubst %/bin/firtool,%/lib,$(FIRTOOL))
VIVADO ?= $(lastword $(sort $(wildcard /tools/Xilinx/*/Vivado/bin/vivado) $(shell command -v vivado 2>/dev/null)))
EFINITY_HOME ?= $(lastword $(sort $(wildcard /tools/efinix/efinity/*)))
EFINITY_SETUP ?= $(EFINITY_HOME)/bin/setup.sh
EFX_RUN ?= $(EFINITY_HOME)/scripts/efx_run.py
EFX_PGM ?= $(EFINITY_HOME)/pgm/bin/ftdi_pgm.sh
# RISCV_TOOLCHAIN may point either to the toolchain root or its bin directory.
# TOOLCHAIN keeps compatibility with the existing software Makefiles.