From 82275a0b67afa7156ae1d7339cbef0e228b0a2b7 Mon Sep 17 00:00:00 2001 From: klin02 Date: Wed, 17 Jun 2026 03:48:35 +0800 Subject: [PATCH] feat(fpga_diff): support JTAG flash writes Add a flash-only JTAG AXI path to the BRAM-backed boot flash and keep the COE-initialized 32 KiB blk_mem_gen target writable from Vivado. Use make write_jtag_flash WORKLOAD= to write a raw image and sample-check readback. --- fpga_diff/Makefile | 6 +- fpga_diff/src/rtl/common/core_def_xdma.sv | 51 ++--- fpga_diff/src/tcl/common/AXI_bridge.tcl | 26 ++- fpga_diff/src/tcl/common/blk_mem_gen_0.tcl | 3 +- fpga_diff/tools/jtag_write_flash.tcl | 234 +++++++++++++++++++++ 5 files changed, 287 insertions(+), 33 deletions(-) create mode 100644 fpga_diff/tools/jtag_write_flash.tcl diff --git a/fpga_diff/Makefile b/fpga_diff/Makefile index 85778e7..246e3c8 100755 --- a/fpga_diff/Makefile +++ b/fpga_diff/Makefile @@ -22,7 +22,7 @@ PRJ_NAME = fpga_$(CPU)$(if $(strip $(SUFFIX)),-$(strip $(SUFFIX)),) PRJ_DIR ?= $(ENV_SCRIPTS_HOME)/$(PRJ_NAME) PRJ ?= $(PRJ_DIR)/$(PRJ_NAME).xpr -.PHONY: bitstream vivado dump_ila +.PHONY: bitstream vivado dump_ila write_jtag_flash # Get Vivado version VIVADO_VERSION := $(shell vivado -version 2>/dev/null | head -1 | grep -o '[0-9]\{4\}\.[0-9]' || echo "unknown") @@ -73,6 +73,10 @@ halt_soc: write_jtag_ddr: vivado -mode tcl -source tools/jtag_write_ddr.tcl -tclargs $(WORKLOAD) $(AXI_WIDTH) +# Write a raw binary image to the BRAM-backed flash via the flash-only JTAG AXI +write_jtag_flash: + vivado -mode tcl -source tools/jtag_write_flash.tcl -tclargs $(WORKLOAD) + # Reset CPU on FPGA reset_cpu: vivado -mode tcl -source tools/reset_cpu.tcl -tclargs $(FPGA_BIT_HOME)/fpga_top_debug.ltx diff --git a/fpga_diff/src/rtl/common/core_def_xdma.sv b/fpga_diff/src/rtl/common/core_def_xdma.sv index dcd5e5b..adea724 100755 --- a/fpga_diff/src/rtl/common/core_def_xdma.sv +++ b/fpga_diff/src/rtl/common/core_def_xdma.sv @@ -1051,32 +1051,33 @@ wire [1 : 0] rom_axi_rresp ; `ifdef XS_QSPI2ROM blk_mem_gen_0 u_rom ( - .rsta_busy (rsta_busy), // output wire rsta_busy - .rstb_busy (rstb_busy), // output wire rstb_busy - .s_aclk (sys_clk_i), // input wire s_aclk - .s_aresetn (axi_bclk_sync_rstn ), // input wire s_aresetn - .s_axi_awaddr (rom_axi_awaddr ), // input wire [31 : 0] s_axi_awaddr - .s_axi_awlen (rom_axi_awlen ), // input wire [7 : 0] s_axi_awlen - .s_axi_awvalid (rom_axi_awvalid ), // input wire s_axi_awvalid - .s_axi_awready (rom_axi_awready ), // output wire s_axi_awready - .s_axi_wdata (rom_axi_wdata ), // input wire [31 : 0] s_axi_wdata - .s_axi_wstrb (rom_axi_wstrb ), // input wire [3 : 0] s_axi_wstrb - .s_axi_wlast (rom_axi_wlast ), // input wire s_axi_wlast - .s_axi_wvalid (rom_axi_wvalid ), // input wire s_axi_wvalid - .s_axi_wready (rom_axi_wready ), // output wire s_axi_wready - .s_axi_bresp (rom_axi_bresp ), // output wire [1 : 0] s_axi_bresp - .s_axi_bvalid (rom_axi_bvalid ), // output wire s_axi_bvalid - .s_axi_bready (rom_axi_bready ), // input wire s_axi_bready - .s_axi_araddr (rom_axi_araddr ), // input wire [31 : 0] s_axi_araddr - .s_axi_arlen (rom_axi_arlen ), // input wire [7 : 0] s_axi_arlen - .s_axi_arvalid (rom_axi_arvalid ), // input wire s_axi_arvalid - .s_axi_arready (rom_axi_arready ), // output wire s_axi_arready - .s_axi_rdata (rom_axi_rdata ), // output wire [31 : 0] s_axi_rdata - .s_axi_rresp (rom_axi_rresp ), // output wire [1 : 0] s_axi_rresp - .s_axi_rlast (rom_axi_rlast ), // output wire s_axi_rlast - .s_axi_rvalid (rom_axi_rvalid ), // output wire s_axi_rvalid - .s_axi_rready (rom_axi_rready ) // input wire s_axi_rready + .rsta_busy (rsta_busy ), + .rstb_busy (rstb_busy ), + .s_aclk (sys_clk_i ), + .s_aresetn (axi_bclk_sync_rstn ), + .s_axi_awaddr (rom_axi_awaddr ), + .s_axi_awlen (rom_axi_awlen ), + .s_axi_awvalid (rom_axi_awvalid ), + .s_axi_awready (rom_axi_awready ), + .s_axi_wdata (rom_axi_wdata ), + .s_axi_wstrb (rom_axi_wstrb ), + .s_axi_wlast (rom_axi_wlast ), + .s_axi_wvalid (rom_axi_wvalid ), + .s_axi_wready (rom_axi_wready ), + .s_axi_bresp (rom_axi_bresp ), + .s_axi_bvalid (rom_axi_bvalid ), + .s_axi_bready (rom_axi_bready ), + .s_axi_araddr (rom_axi_araddr ), + .s_axi_arlen (rom_axi_arlen ), + .s_axi_arvalid (rom_axi_arvalid ), + .s_axi_arready (rom_axi_arready ), + .s_axi_rdata (rom_axi_rdata ), + .s_axi_rresp (rom_axi_rresp ), + .s_axi_rlast (rom_axi_rlast ), + .s_axi_rvalid (rom_axi_rvalid ), + .s_axi_rready (rom_axi_rready ) ); + `endif `ifndef XS_XDMA diff --git a/fpga_diff/src/tcl/common/AXI_bridge.tcl b/fpga_diff/src/tcl/common/AXI_bridge.tcl index 41c00b3..5d675ce 100644 --- a/fpga_diff/src/tcl/common/AXI_bridge.tcl +++ b/fpga_diff/src/tcl/common/AXI_bridge.tcl @@ -125,6 +125,7 @@ if { $bCheckIPs == 1 } { set list_check_ips "\ xilinx.com:ip:axi_apb_bridge:3.0\ xilinx.com:ip:axi_uart16550:2.0\ +xilinx.com:ip:jtag_axi:1.2\ xilinx.com:ip:xlconstant:1.1\ " @@ -230,7 +231,7 @@ proc create_root_design { parentCell } { CONFIG.ADDR_WIDTH {32} \ CONFIG.DATA_WIDTH {32} \ CONFIG.FREQ_HZ {25000000} \ - CONFIG.HAS_BURST {0} \ + CONFIG.HAS_BURST {1} \ CONFIG.HAS_CACHE {0} \ CONFIG.HAS_LOCK {0} \ CONFIG.HAS_PROT {0} \ @@ -239,9 +240,9 @@ proc create_root_design { parentCell } { CONFIG.NUM_READ_OUTSTANDING {2} \ CONFIG.NUM_WRITE_OUTSTANDING {2} \ CONFIG.PROTOCOL {AXI4} \ + CONFIG.SUPPORTS_NARROW_BURST {1} \ ] $rom_axi - # Create ports set ACLK [ create_bd_port -dir I -type clk -freq_hz 25000000 ACLK ] set_property -dict [ list \ @@ -265,11 +266,23 @@ proc create_root_design { parentCell } { set axi_interconnect_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 axi_interconnect_0 ] set_property -dict [ list \ CONFIG.NUM_MI {3} \ + CONFIG.NUM_SI {2} \ ] $axi_interconnect_0 # Create instance: axi_uart16550_0, and set properties set axi_uart16550_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_uart16550:2.0 axi_uart16550_0 ] + # Create instance: jtag_axi_flash, and set properties + set jtag_axi_flash [ create_bd_cell -type ip -vlnv xilinx.com:ip:jtag_axi:1.2 jtag_axi_flash ] + set_property -dict [ list \ + CONFIG.M_AXI_ADDR_WIDTH {32} \ + CONFIG.M_AXI_DATA_WIDTH {32} \ + CONFIG.M_AXI_ID_WIDTH {1} \ + CONFIG.M_HAS_BURST {1} \ + CONFIG.RD_TXN_QUEUE_LENGTH {8} \ + CONFIG.WR_TXN_QUEUE_LENGTH {8} \ + ] $jtag_axi_flash + # Create instance: xlconstant_0, and set properties set xlconstant_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconstant:1.1 xlconstant_0 ] set_property -dict [ list \ @@ -284,11 +297,12 @@ proc create_root_design { parentCell } { connect_bd_intf_net -intf_net axi_interconnect_0_M01_AXI [get_bd_intf_pins axi_interconnect_0/M01_AXI] [get_bd_intf_pins axi_uart16550_0/S_AXI] set_property HDL_ATTRIBUTE.DEBUG {true} [get_bd_intf_nets axi_interconnect_0_M01_AXI] connect_bd_intf_net -intf_net axi_interconnect_0_M02_AXI [get_bd_intf_ports rom_axi] [get_bd_intf_pins axi_interconnect_0/M02_AXI] + connect_bd_intf_net -intf_net jtag_axi_flash_M_AXI [get_bd_intf_pins axi_interconnect_0/S01_AXI] [get_bd_intf_pins jtag_axi_flash/M_AXI] connect_bd_intf_net -intf_net axi_uart16550_0_UART [get_bd_intf_ports UART_0] [get_bd_intf_pins axi_uart16550_0/UART] # Create port connections - connect_bd_net -net ACLK_0_1 [get_bd_ports ACLK] [get_bd_pins axi_apb_bridge_0/s_axi_aclk] [get_bd_pins axi_interconnect_0/ACLK] [get_bd_pins axi_interconnect_0/M00_ACLK] [get_bd_pins axi_interconnect_0/M01_ACLK] [get_bd_pins axi_interconnect_0/M02_ACLK] [get_bd_pins axi_uart16550_0/s_axi_aclk] - connect_bd_net -net ARESETN_0_1 [get_bd_ports ARESETN] [get_bd_pins axi_apb_bridge_0/s_axi_aresetn] [get_bd_pins axi_interconnect_0/ARESETN] [get_bd_pins axi_interconnect_0/M00_ARESETN] [get_bd_pins axi_interconnect_0/M01_ARESETN] [get_bd_pins axi_interconnect_0/M02_ARESETN] [get_bd_pins axi_interconnect_0/S00_ARESETN] [get_bd_pins axi_uart16550_0/s_axi_aresetn] + connect_bd_net -net ACLK_0_1 [get_bd_ports ACLK] [get_bd_pins axi_apb_bridge_0/s_axi_aclk] [get_bd_pins axi_interconnect_0/ACLK] [get_bd_pins axi_interconnect_0/M00_ACLK] [get_bd_pins axi_interconnect_0/M01_ACLK] [get_bd_pins axi_interconnect_0/M02_ACLK] [get_bd_pins axi_interconnect_0/S01_ACLK] [get_bd_pins axi_uart16550_0/s_axi_aclk] [get_bd_pins jtag_axi_flash/aclk] + connect_bd_net -net ARESETN_0_1 [get_bd_ports ARESETN] [get_bd_pins axi_apb_bridge_0/s_axi_aresetn] [get_bd_pins axi_interconnect_0/ARESETN] [get_bd_pins axi_interconnect_0/M00_ARESETN] [get_bd_pins axi_interconnect_0/M01_ARESETN] [get_bd_pins axi_interconnect_0/M02_ARESETN] [get_bd_pins axi_interconnect_0/S00_ARESETN] [get_bd_pins axi_interconnect_0/S01_ARESETN] [get_bd_pins axi_uart16550_0/s_axi_aresetn] [get_bd_pins jtag_axi_flash/aresetn] connect_bd_net -net SYS_INTER_CLK_1 [get_bd_ports SYS_INTER_CLK] [get_bd_pins axi_interconnect_0/S00_ACLK] connect_bd_net -net axi_uart16550_0_ip2intc_irpt [get_bd_ports uart0_intc] [get_bd_pins axi_uart16550_0/ip2intc_irpt] connect_bd_net -net xlconstant_0_dout [get_bd_pins axi_uart16550_0/freeze] [get_bd_pins xlconstant_0/dout] @@ -297,6 +311,9 @@ proc create_root_design { parentCell } { assign_bd_address -offset 0x31200000 -range 0x00010000 -target_address_space [get_bd_addr_spaces S00_AXI] [get_bd_addr_segs SYS_CFG_APB/Reg] -force assign_bd_address -offset 0x310B0000 -range 0x00010000 -target_address_space [get_bd_addr_spaces S00_AXI] [get_bd_addr_segs axi_uart16550_0/S_AXI/Reg] -force assign_bd_address -offset 0x10000000 -range 0x10000000 -target_address_space [get_bd_addr_spaces S00_AXI] [get_bd_addr_segs rom_axi/Reg] -force + assign_bd_address -offset 0x10000000 -range 0x00100000 -target_address_space [get_bd_addr_spaces jtag_axi_flash/Data] [get_bd_addr_segs rom_axi/Reg] -force + exclude_bd_addr_seg -target_address_space [get_bd_addr_spaces jtag_axi_flash/Data] [get_bd_addr_segs SYS_CFG_APB/Reg] + exclude_bd_addr_seg -target_address_space [get_bd_addr_spaces jtag_axi_flash/Data] [get_bd_addr_segs axi_uart16550_0/S_AXI/Reg] # Restore current instance @@ -315,4 +332,3 @@ create_root_design "" common::send_gid_msg -ssname BD::TCL -id 2053 -severity "WARNING" "This Tcl script was generated from a block design that has not been validated. It is possible that design <$design_name> may result in errors during validation." - diff --git a/fpga_diff/src/tcl/common/blk_mem_gen_0.tcl b/fpga_diff/src/tcl/common/blk_mem_gen_0.tcl index ee56bdf..8d22184 100755 --- a/fpga_diff/src/tcl/common/blk_mem_gen_0.tcl +++ b/fpga_diff/src/tcl/common/blk_mem_gen_0.tcl @@ -91,7 +91,7 @@ set_property -dict { CONFIG.Byte_Size {8} CONFIG.Assume_Synchronous_Clk {true} CONFIG.Write_Width_A {32} - CONFIG.Write_Depth_A {1024} + CONFIG.Write_Depth_A {8192} CONFIG.Read_Width_A {32} CONFIG.Operating_Mode_A {READ_FIRST} CONFIG.Write_Width_B {32} @@ -114,4 +114,3 @@ set_property -dict { } $blk_mem_gen_0 ################################################################## - diff --git a/fpga_diff/tools/jtag_write_flash.tcl b/fpga_diff/tools/jtag_write_flash.tcl new file mode 100644 index 0000000..0b84914 --- /dev/null +++ b/fpga_diff/tools/jtag_write_flash.tcl @@ -0,0 +1,234 @@ +proc usage {} { + puts "Usage: vivado -mode tcl -source tools/jtag_write_flash.tcl -tclargs " + puts " flash base is 0x10000000, capacity is 32 KiB, max burst is 256 words." +} + +if {[llength $argv] < 1} { + usage + exit 1 +} + +set file_name [lindex $argv 0] +set flash_base 0x10000000 +set flash_size_bytes 0x8000 +set flash_axi_name flash +set max_burst_words 256 + +if {![file exists $file_name]} { + puts "Error: input binary not found: $file_name" + exit 1 +} + +proc parse_addr {value} { + if {[scan $value "%llx" parsed] == 1} { + return $parsed + } + error "invalid address: $value" +} + +proc parse_positive_int {value name} { + if {![string is integer -strict $value]} { + error "$name must be an integer: $value" + } + set parsed [expr {$value}] + if {$parsed < 1 || $parsed > 256} { + error "$name must be in range 1..256: $value" + } + return $parsed +} + +proc open_target {} { + if {[catch {open_hw_manager} errmsg]} { + puts "Error initializing LabTools system: $errmsg" + exit 1 + } + if {[catch {connect_hw_server} errmsg]} { + puts "Error connecting to hardware server: $errmsg" + exit 1 + } + if {[catch {open_hw_target} errmsg]} { + puts "Error opening hardware target: $errmsg" + exit 1 + } + + set_property PARAM.FREQUENCY 12000000 [current_hw_target] + + set hw_device [lindex [get_hw_devices] 0] + if {$hw_device eq ""} { + puts "Error: No hardware device found. Please check the hardware connection." + exit 1 + } + puts "Hardware device found: $hw_device" + refresh_hw_device $hw_device +} + +proc select_axi {axi_name} { + set hw_axi_list [get_hw_axis] + if {[llength $hw_axi_list] == 0} { + puts "Error: No AXI interfaces found. Please check the hardware design." + exit 1 + } + + puts "Available AXI interfaces:" + foreach axi $hw_axi_list { + puts " $axi" + } + + if {$axi_name eq "flash"} { + set flash_axi_list {} + foreach axi $hw_axi_list { + if {[get_property AXI_DATA_WIDTH $axi] == 32} { + lappend flash_axi_list $axi + } + } + if {[llength $flash_axi_list] != 1} { + puts "Error: flash AXI selection is ambiguous. Pass the flash AXI name explicitly." + exit 1 + } + return [lindex $flash_axi_list 0] + } + + set hw_axi [get_hw_axis $axi_name] + if {$hw_axi eq ""} { + puts "Error: $axi_name not found." + exit 1 + } + return $hw_axi +} + +proc check_flash_axi {hw_axi} { + set data_width [get_property AXI_DATA_WIDTH $hw_axi] + set core_uuid [get_property CORE_UUID $hw_axi] + puts "Selected AXI data width: $data_width" + puts "Selected AXI core UUID: $core_uuid" + if {$data_width != 32} { + error "$hw_axi is ${data_width}-bit, not the 32-bit flash JTAG AXI" + } +} + +proc read_binary {fn} { + set f [open $fn rb] + fconfigure $f -translation binary + set data [read $f] + close $f + binary scan $data c* signed_bytes + + set bytes {} + foreach b $signed_bytes { + lappend bytes [expr {$b & 0xff}] + } + return $bytes +} + +proc check_flash_size {bytes capacity} { + set byte_count [llength $bytes] + if {$byte_count > $capacity} { + error "binary size $byte_count bytes exceeds flash capacity $capacity bytes" + } + puts [format "Flash capacity: %d bytes (0x%x), input: %d bytes" $capacity $capacity $byte_count] +} + +proc word_hex {bytes start} { + set parts {} + for {set i 3} {$i >= 0} {incr i -1} { + set idx [expr {$start + $i}] + if {$idx < [llength $bytes]} { + set b [lindex $bytes $idx] + } else { + set b 0 + } + append parts [format "%02x" $b] + } + return $parts +} + +proc write_flash {bytes hw_axi base_addr max_len} { + set beat_bytes 4 + set total_words [expr {([llength $bytes] + $beat_bytes - 1) / $beat_bytes}] + if {$total_words == 0} { + puts "No data to write." + return + } + + puts "Writing [llength $bytes] bytes, $total_words 32-bit words, base [format 0x%08llx $base_addr], max burst $max_len" + set word_index 0 + set prev_percent -1 + set start_time [clock seconds] + + while {$word_index < $total_words} { + set chunk_len [expr {$total_words - $word_index}] + if {$chunk_len > $max_len} { + set chunk_len $max_len + } + + set data_hex "" + for {set i 0} {$i < $chunk_len} {incr i} { + append data_hex [word_hex $bytes [expr {($word_index + $i) * $beat_bytes}]] + } + + set addr [expr {$base_addr + $word_index * $beat_bytes}] + create_hw_axi_txn wr_flash_txn $hw_axi -address [format "%llx" $addr] -data $data_hex -len $chunk_len -burst INCR -type write + run_hw_axi wr_flash_txn + delete_hw_axi_txn wr_flash_txn + + incr word_index $chunk_len + set percent [expr {int(($word_index * 100) / $total_words)}] + if {$percent > $prev_percent || $word_index == $total_words} { + set end_time [clock seconds] + set time_diff [expr {$end_time - $start_time}] + set minutes [expr {$time_diff / 60}] + set seconds [expr {$time_diff % 60}] + puts [format " %3d%% | %d/%d words | %02d:%02d" $percent $word_index $total_words $minutes $seconds] + set prev_percent $percent + } + } +} + +proc read_word {hw_axi addr} { + create_hw_axi_txn rd_flash_txn $hw_axi -address [format "%llx" $addr] -len 1 -type read + run_hw_axi rd_flash_txn + set data [get_property DATA [get_hw_axi_txns rd_flash_txn]] + delete_hw_axi_txn rd_flash_txn + return [string tolower [string range $data 0 7]] +} + +proc verify_samples {bytes hw_axi base_addr} { + set total_words [expr {([llength $bytes] + 3) / 4}] + set samples [list 0] + if {$total_words > 2} { + lappend samples [expr {$total_words / 2}] + } + if {$total_words > 1} { + lappend samples [expr {$total_words - 1}] + } + + puts "Readback samples:" + foreach word_index $samples { + set addr [expr {$base_addr + $word_index * 4}] + set expected [word_hex $bytes [expr {$word_index * 4}]] + set actual [read_word $hw_axi $addr] + puts [format " addr 0x%08llx expected %s actual %s" $addr $expected $actual] + if {$actual ne $expected} { + error "readback mismatch at [format 0x%08llx $addr]: expected $expected actual $actual" + } + } +} + +if {[catch { + set base_addr [parse_addr $flash_base] + set max_len [parse_positive_int $max_burst_words "max_burst_words"] + set bytes [read_binary $file_name] + check_flash_size $bytes $flash_size_bytes + open_target + set hw_axi [select_axi $flash_axi_name] + puts "Using flash AXI interface: $hw_axi" + check_flash_axi $hw_axi + write_flash $bytes $hw_axi $base_addr $max_len + verify_samples $bytes $hw_axi $base_addr + puts "Flash write and sample readback passed." +} errmsg]} { + puts "ErrorMsg: $errmsg" + exit 1 +} + +exit 0