diff --git a/.gitignore b/.gitignore index d855b3f..ebfc574 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # Generated build outputs. /out/ /soc/out/ +/hardware/generated/ # Python runtime caches and privacy-sensitive raw board captures. __pycache__/ diff --git a/Makefile b/Makefile index c8c3d0d..c0182e6 100644 --- a/Makefile +++ b/Makefile @@ -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: diff --git a/hardware/fpga/ti375c529/constraints/ti375c529_rocket.isf b/hardware/fpga/ti375c529/constraints/ti375c529_rocket.isf new file mode 100644 index 0000000..096f150 --- /dev/null +++ b/hardware/fpga/ti375c529/constraints/ti375c529_rocket.isf @@ -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") diff --git a/hardware/fpga/ti375c529/constraints/ti375c529_rocket.sdc b/hardware/fpga/ti375c529/constraints/ti375c529_rocket.sdc new file mode 100644 index 0000000..a5f092f --- /dev/null +++ b/hardware/fpga/ti375c529/constraints/ti375c529_rocket.sdc @@ -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}] diff --git a/hardware/fpga/ti375c529/constraints/ti375c529_rocket_clock_groups.sdc b/hardware/fpga/ti375c529/constraints/ti375c529_rocket_clock_groups.sdc new file mode 100644 index 0000000..c0a1e08 --- /dev/null +++ b/hardware/fpga/ti375c529/constraints/ti375c529_rocket_clock_groups.sdc @@ -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}] diff --git a/hardware/fpga/ti375c529/rtl/Makefile b/hardware/fpga/ti375c529/rtl/Makefile new file mode 100644 index 0000000..9d9fb78 --- /dev/null +++ b/hardware/fpga/ti375c529/rtl/Makefile @@ -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 diff --git a/targets/fpga/ti375c529/.gitignore b/targets/fpga/ti375c529/.gitignore new file mode 100644 index 0000000..21d7d8e --- /dev/null +++ b/targets/fpga/ti375c529/.gitignore @@ -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_* diff --git a/targets/fpga/ti375c529/Makefile b/targets/fpga/ti375c529/Makefile new file mode 100644 index 0000000..1ba5bd8 --- /dev/null +++ b/targets/fpga/ti375c529/Makefile @@ -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: diff --git a/targets/fpga/ti375c529/README.md b/targets/fpga/ti375c529/README.md new file mode 100644 index 0000000..dd2b042 --- /dev/null +++ b/targets/fpga/ti375c529/README.md @@ -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 | diff --git a/targets/fpga/ti375c529/board.me b/targets/fpga/ti375c529/board.me new file mode 100644 index 0000000..24ca87c --- /dev/null +++ b/targets/fpga/ti375c529/board.me @@ -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 diff --git a/targets/fpga/ti375c529/flow/debug/gdb_attach.gdb b/targets/fpga/ti375c529/flow/debug/gdb_attach.gdb new file mode 100644 index 0000000..f027923 --- /dev/null +++ b/targets/fpga/ti375c529/flow/debug/gdb_attach.gdb @@ -0,0 +1 @@ +monitor halt diff --git a/targets/fpga/ti375c529/flow/debug/gdb_load_and_run.gdb b/targets/fpga/ti375c529/flow/debug/gdb_load_and_run.gdb new file mode 100644 index 0000000..db56f1c --- /dev/null +++ b/targets/fpga/ti375c529/flow/debug/gdb_load_and_run.gdb @@ -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 diff --git a/targets/fpga/ti375c529/flow/debug/openocd-0.12-ti375-bscan.patch b/targets/fpga/ti375c529/flow/debug/openocd-0.12-ti375-bscan.patch new file mode 100644 index 0000000..a5ccf3d --- /dev/null +++ b/targets/fpga/ti375c529/flow/debug/openocd-0.12-ti375-bscan.patch @@ -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, diff --git a/targets/fpga/ti375c529/flow/debug/openocd_ti375c529_bscan.cfg b/targets/fpga/ti375c529/flow/debug/openocd_ti375c529_bscan.cfg new file mode 100644 index 0000000..3c183b0 --- /dev/null +++ b/targets/fpga/ti375c529/flow/debug/openocd_ti375c529_bscan.cfg @@ -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 diff --git a/targets/fpga/ti375c529/flow/efinity/README.md b/targets/fpga/ti375c529/flow/efinity/README.md new file mode 100644 index 0000000..50b1f0f --- /dev/null +++ b/targets/fpga/ti375c529/flow/efinity/README.md @@ -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. diff --git a/targets/fpga/ti375c529/flow/efinity/ti375c529_rocket.xml b/targets/fpga/ti375c529/flow/efinity/ti375c529_rocket.xml new file mode 100644 index 0000000..4d1aad7 --- /dev/null +++ b/targets/fpga/ti375c529/flow/efinity/ti375c529_rocket.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/targets/fpga/ti375c529/tools/gen_ti375c529_wrapper.py b/targets/fpga/ti375c529/tools/gen_ti375c529_wrapper.py new file mode 100644 index 0000000..3fdaa69 --- /dev/null +++ b/targets/fpga/ti375c529/tools/gen_ti375c529_wrapper.py @@ -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() diff --git a/targets/fpga/ti375c529/tools/make_flash_hex.py b/targets/fpga/ti375c529/tools/make_flash_hex.py new file mode 100644 index 0000000..e2888ec --- /dev/null +++ b/targets/fpga/ti375c529/tools/make_flash_hex.py @@ -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() diff --git a/targets/fpga/ti375c529/tools/prepare_efinity_rtl.py b/targets/fpga/ti375c529/tools/prepare_efinity_rtl.py new file mode 100644 index 0000000..d97684c --- /dev/null +++ b/targets/fpga/ti375c529/tools/prepare_efinity_rtl.py @@ -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\s*)automatic logic \[3:0\]\[3:0\] (?P_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() diff --git a/tools/project.mk b/tools/project.mk index d80222a..27cb321 100644 --- a/tools/project.mk +++ b/tools/project.mk @@ -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.