From aa32464942dc9d4c701fbb096d5d96fbb1cc51ae Mon Sep 17 00:00:00 2001 From: blurbdust Date: Mon, 9 Feb 2026 09:28:42 -0600 Subject: [PATCH] Adding first pass at YPCB flash --- CMakeLists.txt | 4 + bpiOverJtag/bpiOverJtag_core.v | 278 ++++++++++ .../bpiOverJtag_xc7k480tffg1156.bit.gz | Bin 0 -> 131812 bytes bpiOverJtag/build_bpi_xc7k480t.sh | 39 ++ bpiOverJtag/build_bpi_xc7k480t.tcl | 46 ++ bpiOverJtag/constr_xc7k480t_bpi_ffg1156.xdc | 57 +++ bpiOverJtag/xilinx_bpiOverJtag.v | 84 +++ doc/boards.yml | 8 + src/board.hpp | 1 + src/bpiFlash.cpp | 482 ++++++++++++++++++ src/bpiFlash.hpp | 127 +++++ src/xilinx.cpp | 95 +++- src/xilinx.hpp | 16 + 13 files changed, 1226 insertions(+), 11 deletions(-) create mode 100644 bpiOverJtag/bpiOverJtag_core.v create mode 100644 bpiOverJtag/bpiOverJtag_xc7k480tffg1156.bit.gz create mode 100755 bpiOverJtag/build_bpi_xc7k480t.sh create mode 100644 bpiOverJtag/build_bpi_xc7k480t.tcl create mode 100644 bpiOverJtag/constr_xc7k480t_bpi_ffg1156.xdc create mode 100644 bpiOverJtag/xilinx_bpiOverJtag.v create mode 100644 src/bpiFlash.cpp create mode 100644 src/bpiFlash.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1044071..39b1242 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -232,6 +232,7 @@ list(APPEND OPENFPGALOADER_HEADERS # =========================== list(APPEND OPENFPGALOADER_SOURCE src/anlogic.cpp + src/bpiFlash.cpp src/spiFlash.cpp src/spiInterface.cpp src/epcq.cpp @@ -245,6 +246,7 @@ list(APPEND OPENFPGALOADER_SOURCE list(APPEND OPENFPGALOADER_HEADERS src/altera.hpp src/anlogic.hpp + src/bpiFlash.hpp src/jtag.hpp src/jtagInterface.hpp src/spiFlash.hpp @@ -619,6 +621,8 @@ install(TARGETS openFPGALoader DESTINATION bin) #################################################################################################### file(GLOB GZ_FILES spiOverJtag/spiOverJtag_*.*.gz) +file(GLOB BPI_GZ_FILES bpiOverJtag/bpiOverJtag_*.bit.gz) +list(APPEND GZ_FILES ${BPI_GZ_FILES}) # Compress rbf and bit files present into repository # TODO: test compat with Windows and MacOS diff --git a/bpiOverJtag/bpiOverJtag_core.v b/bpiOverJtag/bpiOverJtag_core.v new file mode 100644 index 0000000..3c76adc --- /dev/null +++ b/bpiOverJtag/bpiOverJtag_core.v @@ -0,0 +1,278 @@ +`default_nettype none +/* + * BPI (Parallel NOR) Flash over JTAG core + * + * Protocol (all in one DR shift): + * TX: [start=1][cmd:4][addr:25][wr_data:16] = 46 bits + * RX: Response appears after command bits, aligned to read_data position + * + * Commands: + * 0x1 = Write word to flash (addr + data) + * 0x2 = Read word from flash (addr), returns data + * 0x3 = NOP / get status + */ + +module bpiOverJtag_core ( + /* JTAG state/controls */ + input wire sel, + input wire capture, + input wire update, + input wire shift, + input wire drck, + input wire tdi, + output wire tdo, + + /* Version endpoint */ + input wire ver_sel, + input wire ver_cap, + input wire ver_shift, + input wire ver_drck, + input wire ver_tdi, + output wire ver_tdo, + + /* BPI Flash physical interface */ + output reg [25:1] bpi_addr, + inout wire [15:0] bpi_dq, + output reg bpi_ce_n, + output reg bpi_oe_n, + output reg bpi_we_n, + output reg bpi_adv_n +); + +/* Reset on capture when selected */ +wire rst = (capture & sel); + +/* Start bit detection */ +wire start_header = (tdi & shift & sel); + +/* State machine */ +localparam IDLE = 3'd0, + RECV_CMD = 3'd1, + RECV_ADDR = 3'd2, + RECV_DATA = 3'd3, + EXEC = 3'd4, + SEND_DATA = 3'd5, + DONE = 3'd6; + +reg [2:0] state, state_d; +reg [5:0] bit_cnt, bit_cnt_d; +reg [3:0] cmd_reg, cmd_reg_d; +reg [24:0] addr_reg, addr_reg_d; +reg [15:0] wr_data_reg, wr_data_reg_d; +reg [15:0] rd_data_reg, rd_data_reg_d; +reg [7:0] wait_cnt, wait_cnt_d; + +/* Data bus control */ +reg dq_oe; +reg [15:0] dq_out; +assign bpi_dq = dq_oe ? dq_out : 16'hzzzz; + +/* TDO output - shift out read data */ +assign tdo = rd_data_reg[0]; + +/* Command codes */ +localparam CMD_WRITE = 4'h1, + CMD_READ = 4'h2, + CMD_NOP = 4'h3; + +/* Next state logic */ +always @(*) begin + state_d = state; + bit_cnt_d = bit_cnt; + cmd_reg_d = cmd_reg; + addr_reg_d = addr_reg; + wr_data_reg_d = wr_data_reg; + rd_data_reg_d = rd_data_reg; + wait_cnt_d = wait_cnt; + + case (state) + IDLE: begin + bit_cnt_d = 3; /* 4 bits for command */ + if (start_header) + state_d = RECV_CMD; + end + + RECV_CMD: begin + cmd_reg_d = {tdi, cmd_reg[3:1]}; + bit_cnt_d = bit_cnt - 1'b1; + if (bit_cnt == 0) begin + bit_cnt_d = 24; /* 25 bits for address */ + state_d = RECV_ADDR; + end + end + + RECV_ADDR: begin + addr_reg_d = {tdi, addr_reg[24:1]}; + bit_cnt_d = bit_cnt - 1'b1; + if (bit_cnt == 0) begin + if (cmd_reg == CMD_WRITE) begin + bit_cnt_d = 15; /* 16 bits for data */ + state_d = RECV_DATA; + end else begin + wait_cnt_d = 8'd20; /* Wait cycles for read */ + state_d = EXEC; + end + end + end + + RECV_DATA: begin + wr_data_reg_d = {tdi, wr_data_reg[15:1]}; + bit_cnt_d = bit_cnt - 1'b1; + if (bit_cnt == 0) begin + wait_cnt_d = 8'd20; /* Wait cycles for write */ + state_d = EXEC; + end + end + + EXEC: begin + wait_cnt_d = wait_cnt - 1'b1; + if (wait_cnt == 8'd10 && cmd_reg == CMD_READ) begin + /* Sample read data mid-cycle */ + rd_data_reg_d = bpi_dq; + end + if (wait_cnt == 0) begin + bit_cnt_d = 15; + state_d = SEND_DATA; + end + end + + SEND_DATA: begin + rd_data_reg_d = {1'b1, rd_data_reg[15:1]}; + bit_cnt_d = bit_cnt - 1'b1; + if (bit_cnt == 0) + state_d = DONE; + end + + DONE: begin + /* Stay here until reset */ + end + + default: state_d = IDLE; + endcase +end + +/* State register */ +always @(posedge drck or posedge rst) begin + if (rst) + state <= IDLE; + else + state <= state_d; +end + +/* Data registers */ +always @(posedge drck) begin + bit_cnt <= bit_cnt_d; + cmd_reg <= cmd_reg_d; + addr_reg <= addr_reg_d; + wr_data_reg <= wr_data_reg_d; + rd_data_reg <= rd_data_reg_d; + wait_cnt <= wait_cnt_d; +end + +/* Address output */ +always @(posedge drck or posedge rst) begin + if (rst) + bpi_addr <= 25'd0; + else if (state == RECV_ADDR && bit_cnt == 0) + bpi_addr <= {tdi, addr_reg[24:1]}; +end + +/* BPI Flash control signals */ +always @(posedge drck or posedge rst) begin + if (rst) begin + bpi_ce_n <= 1'b1; + bpi_oe_n <= 1'b1; + bpi_we_n <= 1'b1; + bpi_adv_n <= 1'b1; + dq_oe <= 1'b0; + dq_out <= 16'h0000; + end else begin + case (state_d) + EXEC: begin + bpi_ce_n <= 1'b0; + bpi_adv_n <= 1'b0; + if (cmd_reg == CMD_READ) begin + bpi_oe_n <= 1'b0; + bpi_we_n <= 1'b1; + dq_oe <= 1'b0; + end else if (cmd_reg == CMD_WRITE) begin + bpi_oe_n <= 1'b1; + bpi_we_n <= (wait_cnt > 8'd5 && wait_cnt < 8'd15) ? 1'b0 : 1'b1; + dq_oe <= 1'b1; + dq_out <= wr_data_reg; + end + end + default: begin + bpi_ce_n <= 1'b1; + bpi_oe_n <= 1'b1; + bpi_we_n <= 1'b1; + bpi_adv_n <= 1'b1; + dq_oe <= 1'b0; + end + endcase + end +end + +/* ------------- */ +/* Version */ +/* ------------- */ +wire ver_rst = (ver_cap & ver_sel); +wire ver_start = (ver_tdi & ver_shift & ver_sel); + +localparam VER_VALUE = 40'h30_31_2E_30_30; // "01.00" + +reg [6:0] ver_cnt, ver_cnt_d; +reg [39:0] ver_shft, ver_shft_d; +reg [2:0] ver_state, ver_state_d; + +localparam VER_IDLE = 3'd0, + VER_RECV = 3'd1, + VER_XFER = 3'd2, + VER_WAIT = 3'd3; + +always @(*) begin + ver_state_d = ver_state; + ver_cnt_d = ver_cnt; + ver_shft_d = ver_shft; + case (ver_state) + VER_IDLE: begin + ver_cnt_d = 6; + if (ver_start) + ver_state_d = VER_RECV; + end + VER_RECV: begin + ver_cnt_d = ver_cnt - 1'b1; + if (ver_cnt == 0) begin + ver_state_d = VER_XFER; + ver_cnt_d = 39; + ver_shft_d = VER_VALUE; + end + end + VER_XFER: begin + ver_cnt_d = ver_cnt - 1; + ver_shft_d = {1'b1, ver_shft[39:1]}; + if (ver_cnt == 0) + ver_state_d = VER_WAIT; + end + VER_WAIT: begin + /* Wait for reset */ + end + default: ver_state_d = VER_IDLE; + endcase +end + +always @(posedge ver_drck) begin + ver_cnt <= ver_cnt_d; + ver_shft <= ver_shft_d; +end + +always @(posedge ver_drck or posedge ver_rst) begin + if (ver_rst) + ver_state <= VER_IDLE; + else + ver_state <= ver_state_d; +end + +assign ver_tdo = ver_shft[0]; + +endmodule diff --git a/bpiOverJtag/bpiOverJtag_xc7k480tffg1156.bit.gz b/bpiOverJtag/bpiOverJtag_xc7k480tffg1156.bit.gz new file mode 100644 index 0000000000000000000000000000000000000000..82af67f7ca832ab12869fb0811e09fd328c2006a GIT binary patch literal 131812 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 /dev/null; then + echo "Error: vivado not found in PATH" + echo "Source Vivado settings first:" + echo " source /tools/Xilinx/2025.1/Vivado/settings64.sh" + exit 1 +fi + +VIVADO="vivado" + +# Run Vivado in batch mode +$VIVADO -mode batch -source build_bpi_xc7k480t.tcl + +# Compress the bitstream +if [ -f output_bpi_xc7k480t/bpiOverJtag_xc7k480tffg1156.bit ]; then + echo "Compressing bitstream..." + gzip -9 -c output_bpi_xc7k480t/bpiOverJtag_xc7k480tffg1156.bit > bpiOverJtag_xc7k480tffg1156.bit.gz + echo "" + echo "Success! Bitstream created: bpiOverJtag_xc7k480tffg1156.bit.gz" + echo "" + echo "To install system-wide, copy to the openFPGALoader data directory:" + echo " sudo cp bpiOverJtag_xc7k480tffg1156.bit.gz /usr/local/share/openFPGALoader/" + echo "" + echo "Or set OPENFPGALOADER_SOJ_DIR to this directory:" + echo " export OPENFPGALOADER_SOJ_DIR=$SCRIPT_DIR" +else + echo "Error: Bitstream generation failed" + exit 1 +fi diff --git a/bpiOverJtag/build_bpi_xc7k480t.tcl b/bpiOverJtag/build_bpi_xc7k480t.tcl new file mode 100644 index 0000000..21dc5ed --- /dev/null +++ b/bpiOverJtag/build_bpi_xc7k480t.tcl @@ -0,0 +1,46 @@ +# Vivado TCL script to build bpiOverJtag for xc7k480tffg1156 +# Run with: vivado -mode batch -source build_bpi_xc7k480t.tcl + +set part "xc7k480tffg1156-2" +set project_name "bpiOverJtag_xc7k480tffg1156" +set output_dir "./output_bpi_xc7k480t" + +# Create output directory +file mkdir $output_dir + +# Create in-memory project +create_project -in_memory -part $part + +# Add source files +read_verilog bpiOverJtag_core.v +read_verilog xilinx_bpiOverJtag.v + +# Add constraints +read_xdc constr_xc7k480t_bpi_ffg1156.xdc + +# Synthesize +synth_design -top bpiOverJtag -part $part + +# Optimize +opt_design + +# Place +place_design + +# Route +route_design + +# Generate reports +report_utilization -file $output_dir/utilization.rpt +report_timing_summary -file $output_dir/timing.rpt + +# Write bitstream +write_bitstream -force $output_dir/$project_name.bit + +# Close project +close_project + +puts "Bitstream generated: $output_dir/$project_name.bit" +puts "" +puts "To install, run:" +puts " gzip -c $output_dir/$project_name.bit > bpiOverJtag_xc7k480tffg1156.bit.gz" diff --git a/bpiOverJtag/constr_xc7k480t_bpi_ffg1156.xdc b/bpiOverJtag/constr_xc7k480t_bpi_ffg1156.xdc new file mode 100644 index 0000000..5f7ebef --- /dev/null +++ b/bpiOverJtag/constr_xc7k480t_bpi_ffg1156.xdc @@ -0,0 +1,57 @@ +## BPI Flash over JTAG constraints for xc7k480tffg1156 +## Pin assignments from YPCB-00338-1P1 board + +set_property CFGBVS GND [current_design] +set_property CONFIG_VOLTAGE 1.8 [current_design] +set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design] + +## Address bus [25:1] +set_property -dict {PACKAGE_PIN AD26 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[1]}] +set_property -dict {PACKAGE_PIN AC25 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[2]}] +set_property -dict {PACKAGE_PIN AC29 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[3]}] +set_property -dict {PACKAGE_PIN AC28 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[4]}] +set_property -dict {PACKAGE_PIN AD27 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[5]}] +set_property -dict {PACKAGE_PIN AC27 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[6]}] +set_property -dict {PACKAGE_PIN AB25 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[7]}] +set_property -dict {PACKAGE_PIN AB28 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[8]}] +set_property -dict {PACKAGE_PIN AB27 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[9]}] +set_property -dict {PACKAGE_PIN AB26 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[10]}] +set_property -dict {PACKAGE_PIN AA26 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[11]}] +set_property -dict {PACKAGE_PIN AA31 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[12]}] +set_property -dict {PACKAGE_PIN AA30 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[13]}] +set_property -dict {PACKAGE_PIN AB33 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[14]}] +set_property -dict {PACKAGE_PIN AB32 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[15]}] +set_property -dict {PACKAGE_PIN Y32 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[16]}] +set_property -dict {PACKAGE_PIN P32 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[17]}] +set_property -dict {PACKAGE_PIN R32 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[18]}] +set_property -dict {PACKAGE_PIN U33 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[19]}] +set_property -dict {PACKAGE_PIN T31 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[20]}] +set_property -dict {PACKAGE_PIN T30 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[21]}] +set_property -dict {PACKAGE_PIN U31 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[22]}] +set_property -dict {PACKAGE_PIN U30 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[23]}] +set_property -dict {PACKAGE_PIN N34 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[24]}] +set_property -dict {PACKAGE_PIN P34 IOSTANDARD LVCMOS18} [get_ports {bpi_addr[25]}] + +## Data bus [15:0] - bidirectional +set_property -dict {PACKAGE_PIN AA33 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[0]}] +set_property -dict {PACKAGE_PIN AA34 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[1]}] +set_property -dict {PACKAGE_PIN Y33 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[2]}] +set_property -dict {PACKAGE_PIN Y34 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[3]}] +set_property -dict {PACKAGE_PIN V32 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[4]}] +set_property -dict {PACKAGE_PIN V33 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[5]}] +set_property -dict {PACKAGE_PIN W31 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[6]}] +set_property -dict {PACKAGE_PIN W32 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[7]}] +set_property -dict {PACKAGE_PIN W30 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[8]}] +set_property -dict {PACKAGE_PIN V25 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[9]}] +set_property -dict {PACKAGE_PIN W25 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[10]}] +set_property -dict {PACKAGE_PIN V29 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[11]}] +set_property -dict {PACKAGE_PIN W29 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[12]}] +set_property -dict {PACKAGE_PIN V28 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[13]}] +set_property -dict {PACKAGE_PIN W24 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[14]}] +set_property -dict {PACKAGE_PIN Y24 IOSTANDARD LVCMOS18} [get_ports {bpi_dq[15]}] + +## Control signals +set_property -dict {PACKAGE_PIN V30 IOSTANDARD LVCMOS18} [get_ports {bpi_ce_n}] +set_property -dict {PACKAGE_PIN T33 IOSTANDARD LVCMOS18} [get_ports {bpi_oe_n}] +set_property -dict {PACKAGE_PIN T34 IOSTANDARD LVCMOS18} [get_ports {bpi_we_n}] +set_property -dict {PACKAGE_PIN M31 IOSTANDARD LVCMOS18} [get_ports {bpi_adv_n}] diff --git a/bpiOverJtag/xilinx_bpiOverJtag.v b/bpiOverJtag/xilinx_bpiOverJtag.v new file mode 100644 index 0000000..c5357a5 --- /dev/null +++ b/bpiOverJtag/xilinx_bpiOverJtag.v @@ -0,0 +1,84 @@ +`default_nettype none +/* + * BPI Flash over JTAG for Xilinx 7-series FPGAs + * Uses BSCANE2 primitive to access USER1 JTAG register + */ + +module bpiOverJtag ( + /* BPI Flash interface */ + output wire [25:1] bpi_addr, + inout wire [15:0] bpi_dq, + output wire bpi_ce_n, + output wire bpi_oe_n, + output wire bpi_we_n, + output wire bpi_adv_n +); + + wire capture, drck, sel, update, shift; + wire tdi, tdo; + + /* Version Interface */ + wire ver_sel, ver_cap, ver_shift, ver_drck, ver_tdi, ver_tdo; + + bpiOverJtag_core bpiOverJtag_core_inst ( + /* JTAG state/controls */ + .sel(sel), + .capture(capture), + .update(update), + .shift(shift), + .drck(drck), + .tdi(tdi), + .tdo(tdo), + + /* Version endpoint */ + .ver_sel(ver_sel), + .ver_cap(ver_cap), + .ver_shift(ver_shift), + .ver_drck(ver_drck), + .ver_tdi(ver_tdi), + .ver_tdo(ver_tdo), + + /* BPI Flash physical interface */ + .bpi_addr(bpi_addr), + .bpi_dq(bpi_dq), + .bpi_ce_n(bpi_ce_n), + .bpi_oe_n(bpi_oe_n), + .bpi_we_n(bpi_we_n), + .bpi_adv_n(bpi_adv_n) + ); + + /* BSCANE2 for main data interface (USER1) */ + BSCANE2 #( + .JTAG_CHAIN(1) + ) bscane2_inst ( + .CAPTURE(capture), + .DRCK(drck), + .RESET(), + .RUNTEST(), + .SEL(sel), + .SHIFT(shift), + .TCK(), + .TDI(tdi), + .TMS(), + .UPDATE(update), + .TDO(tdo) + ); + + /* BSCANE2 for version interface (USER4) */ + BSCANE2 #( + .JTAG_CHAIN(4) + ) bscane2_version ( + .CAPTURE(ver_cap), + .DRCK(ver_drck), + .RESET(), + .RUNTEST(), + .SEL(ver_sel), + .SHIFT(ver_shift), + .TCK(), + .TDI(ver_tdi), + .TMS(), + .UPDATE(), + .TDO(ver_tdo) + ); + +endmodule diff --git a/doc/boards.yml b/doc/boards.yml index c9868e4..7c61cfc 100644 --- a/doc/boards.yml +++ b/doc/boards.yml @@ -987,6 +987,14 @@ Memory: OK Flash: OK +- ID: ypcb003381p1 + Description: YPCB-00338-1P1 Kintex-7 Accelerator Card + URL: https://www.tiferking.cn/index.php/2024/12/19/650/ + FPGA: Kintex7 xc7k480tffg1156 + Memory: OK + Flash: OK + Note: BPI parallel NOR flash (MT28GU512AAA1EGC) + - ID: zc702 Description: Xilinx ZC702 URL: https://www.xilinx.com/products/boards-and-kits/ek-z7-zc702-g.html diff --git a/src/board.hpp b/src/board.hpp index 502d268..0fcab92 100644 --- a/src/board.hpp +++ b/src/board.hpp @@ -270,6 +270,7 @@ static std::map board_list = { SPI_BOARD("xyloni_spi", "efinix", "trion", "efinix_spi_ft4232", DBUS4, DBUS5, DBUS7, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT), JTAG_BOARD("xtrx", "xc7a50tcpg236", "" , 0, 0, CABLE_DEFAULT), + JTAG_BOARD("ypcb003381p1", "xc7k480tffg1156", "", 0, 0, CABLE_DEFAULT), JTAG_BOARD("zc702", "xc7z020clg484", "digilent", 0, 0, CABLE_DEFAULT), JTAG_BOARD("zc706", "xc7z045ffg900", "jtag-smt2-nc", 0, 0, CABLE_DEFAULT), JTAG_BOARD("zcu102", "xczu9egffvb1156", "jtag-smt2-nc", 0, 0, CABLE_DEFAULT), diff --git a/src/bpiFlash.cpp b/src/bpiFlash.cpp new file mode 100644 index 0000000..2f710af --- /dev/null +++ b/src/bpiFlash.cpp @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2024 openFPGALoader contributors + * BPI (Parallel NOR) Flash support via JTAG bridge + */ + +#include "bpiFlash.hpp" + +#include +#include +#include + +#include "display.hpp" +#include "progressBar.hpp" + +/* Bit-reverse a byte (MSB <-> LSB). + * Required for BPI x16: the FPGA's D00 pin is the MSBit of each config byte + * (AR#7112), but flash DQ[0] is the LSBit. write_cfgmem applies this + * transformation; we must do the same. + */ +static inline uint8_t reverseByte(uint8_t b) +{ + b = ((b & 0xF0) >> 4) | ((b & 0x0F) << 4); + b = ((b & 0xCC) >> 2) | ((b & 0x33) << 2); + b = ((b & 0xAA) >> 1) | ((b & 0x55) << 1); + return b; +} + +BPIFlash::BPIFlash(Jtag *jtag, int8_t verbose) + : _jtag(jtag), _verbose(verbose), _irlen(6), + _capacity(0), _block_size(256 * 1024), + _manufacturer_id(0), _device_id(0) +{ +} + +BPIFlash::~BPIFlash() +{ +} + +/* + * Protocol: Single JTAG DR shift containing: + * TX: [start=1][cmd:4][addr:25][wr_data:16] = 46 bits + * RX: Data returned after execution delay + * + * Commands: + * 0x1 = Write word + * 0x2 = Read word + * 0x3 = NOP + */ + +uint16_t BPIFlash::bpi_read(uint32_t word_addr) +{ + /* Build packet: start(1) + cmd(4) + addr(25) + padding for read response */ + /* Extra bit needed due to pipeline delay in Verilog (data at offset 51, not 50) */ + const int total_bits = 1 + 4 + 25 + 20 + 16 + 1; /* 67 bits total */ + const int total_bytes = (total_bits + 7) / 8; + + uint8_t tx[total_bytes]; + uint8_t rx[total_bytes]; + memset(tx, 0, total_bytes); + memset(rx, 0, total_bytes); + + /* Pack: start=1, cmd=2 (read), addr (LSB first) */ + uint64_t packet = 1; /* start bit */ + packet |= ((uint64_t)CMD_READ) << 1; /* cmd at bits [4:1] */ + packet |= ((uint64_t)(word_addr & 0x1FFFFFF)) << 5; /* addr at bits [29:5] */ + + /* Convert to bytes (LSB first for JTAG) */ + for (int i = 0; i < 5; i++) { + tx[i] = (packet >> (i * 8)) & 0xFF; + } + + /* Select USER1 instruction */ + uint8_t user1[] = {0x02}; + _jtag->shiftIR(user1, NULL, _irlen); + + /* Shift data and get response */ + _jtag->shiftDR(tx, rx, total_bits); + _jtag->flush(); + + /* Extract read data from response - it appears after the command execution */ + /* Data starts at bit 51 (after start+cmd+addr+exec_delay+1 pipeline delay) */ + int data_offset = 51; + uint16_t data = 0; + for (int i = 0; i < 16; i++) { + int bit_pos = data_offset + i; + int byte_idx = bit_pos / 8; + int bit_idx = bit_pos % 8; + if (rx[byte_idx] & (1 << bit_idx)) + data |= (1 << i); + } + + return data; +} + +void BPIFlash::bpi_write(uint32_t word_addr, uint16_t data) +{ + /* Build packet: start(1) + cmd(4) + addr(25) + data(16) + exec delay */ + const int total_bits = 1 + 4 + 25 + 16 + 20; + const int total_bytes = (total_bits + 7) / 8; + + uint8_t tx[total_bytes]; + memset(tx, 0, total_bytes); + + /* Pack: start=1, cmd=1 (write), addr, data (all LSB first) */ + uint64_t packet = 1; /* start bit */ + packet |= ((uint64_t)CMD_WRITE) << 1; /* cmd at bits [4:1] */ + packet |= ((uint64_t)(word_addr & 0x1FFFFFF)) << 5; /* addr at bits [29:5] */ + packet |= ((uint64_t)data) << 30; /* data at bits [45:30] */ + + /* Convert to bytes (LSB first for JTAG) */ + for (int i = 0; i < 8; i++) { + tx[i] = (packet >> (i * 8)) & 0xFF; + } + + if (_verbose > 1) { + char buf[256]; + snprintf(buf, sizeof(buf), "bpi_write(0x%06x, 0x%04x) TX:", word_addr, data); + std::string msg = buf; + for (int i = 0; i < total_bytes; i++) { + snprintf(buf, sizeof(buf), " %02x", tx[i]); + msg += buf; + } + printInfo(msg); + } + + /* Select USER1 instruction */ + uint8_t user1[] = {0x02}; + _jtag->shiftIR(user1, NULL, _irlen); + + /* Shift data */ + _jtag->shiftDR(tx, NULL, total_bits); + _jtag->flush(); +} + +bool BPIFlash::detect() +{ + printInfo("Detecting BPI flash..."); + + /* Issue Read ID command to flash: write 0x0090 to any address */ + bpi_write(0, FLASH_CMD_READ_ID); + + /* Small delay for command to take effect */ + usleep(1000); + + /* Read manufacturer ID at offset 0x00 */ + _manufacturer_id = bpi_read(0x00); + + /* Read device ID at offset 0x01 */ + _device_id = bpi_read(0x01); + + /* Return to read array mode */ + bpi_write(0, FLASH_CMD_READ_ARRAY); + usleep(1000); + + if (_verbose) { + char buf[64]; + snprintf(buf, sizeof(buf), "Raw Manufacturer ID: 0x%04x", _manufacturer_id); + printInfo(buf); + snprintf(buf, sizeof(buf), "Raw Device ID: 0x%04x", _device_id); + printInfo(buf); + } + + if (_manufacturer_id == 0x0089 || _manufacturer_id == 0x8900) { + printInfo("Intel/Micron flash detected"); + } else if (_manufacturer_id == 0x0020 || _manufacturer_id == 0x2000) { + printInfo("Micron flash detected"); + } else if (_manufacturer_id == 0xFFFF || _manufacturer_id == 0x0000) { + char buf[64]; + snprintf(buf, sizeof(buf), "No BPI flash detected (ID: 0x%04x)", _manufacturer_id); + printError(buf); + return false; + } else { + char buf[64]; + snprintf(buf, sizeof(buf), "Unknown manufacturer: 0x%04x", _manufacturer_id); + printWarn(buf); + } + + { + char buf[64]; + snprintf(buf, sizeof(buf), "Manufacturer ID: 0x%04x", _manufacturer_id); + printInfo(buf); + snprintf(buf, sizeof(buf), "Device ID: 0x%04x", _device_id); + printInfo(buf); + } + + /* MT28GU512AAA = 512Mbit = 64MB */ + _capacity = 64 * 1024 * 1024; + _block_size = 256 * 1024; + printInfo("Flash capacity: 64 MB (512 Mbit)"); + + return true; +} + +bool BPIFlash::wait_ready(uint32_t timeout_ms) +{ + uint32_t elapsed = 0; + const uint32_t poll_interval = 10; + + /* Issue read status command */ + bpi_write(0, FLASH_CMD_READ_STATUS); + usleep(100); + + while (elapsed < timeout_ms) { + uint16_t status = bpi_read(0); + + /* Intel CFI status register is 8 bits, upper byte undefined */ + uint8_t sr = status & 0xFF; + + if (sr & SR_READY) { + if (sr & (SR_ERASE_ERR | SR_PROG_ERR | SR_VPP_ERR)) { + char buf[64]; + snprintf(buf, sizeof(buf), "BPI Flash error: status = 0x%02x", sr); + printError(buf); + bpi_write(0, FLASH_CMD_CLEAR_STATUS); + return false; + } + /* Return to read array mode */ + bpi_write(0, FLASH_CMD_READ_ARRAY); + return true; + } + + usleep(poll_interval * 1000); + elapsed += poll_interval; + } + + printError("BPI Flash timeout"); + return false; +} + +bool BPIFlash::unlock_block(uint32_t word_addr) +{ + bpi_write(word_addr, FLASH_CMD_UNLOCK_BLOCK); + usleep(100); + bpi_write(word_addr, FLASH_CMD_UNLOCK_CONF); + usleep(100); + return true; +} + +bool BPIFlash::erase_block(uint32_t addr) +{ + uint32_t word_addr = addr >> 1; + + if (_verbose) { + char buf[64]; + snprintf(buf, sizeof(buf), "Erasing block at 0x%06x", addr); + printInfo(buf); + } + + unlock_block(word_addr); + + /* Block erase command sequence */ + bpi_write(word_addr, FLASH_CMD_BLOCK_ERASE); + usleep(100); + bpi_write(word_addr, FLASH_CMD_CONFIRM); + + if (!wait_ready(30000)) { + printError("Block erase failed"); + return false; + } + + /* Verify erase by reading first few words */ + if (_verbose) { + bpi_write(0, FLASH_CMD_READ_ARRAY); + usleep(100); + char buf[128]; + snprintf(buf, sizeof(buf), "Verify erase at 0x%06x:", addr); + printInfo(buf); + for (int i = 0; i < 4; i++) { + uint16_t val = bpi_read(word_addr + i); + snprintf(buf, sizeof(buf), " [0x%06x] = 0x%04x %s", + addr + i*2, val, (val == 0xFFFF) ? "(OK)" : "(NOT ERASED!)"); + printInfo(buf); + } + } + + return true; +} + +bool BPIFlash::bulk_erase() +{ + printInfo("Bulk erasing BPI flash..."); + + uint32_t num_blocks = _capacity / _block_size; + ProgressBar progress("Erasing", num_blocks, 50, _verbose > 0); + + for (uint32_t i = 0; i < num_blocks; i++) { + uint32_t block_addr = i * _block_size; + if (!erase_block(block_addr)) { + progress.fail(); + return false; + } + progress.display(i + 1); + } + + progress.done(); + return true; +} + +bool BPIFlash::read(uint8_t *data, uint32_t addr, uint32_t len) +{ + if (_verbose) + printInfo("Reading " + std::to_string(len) + " bytes from 0x" + + std::to_string(addr)); + + /* Ensure read array mode */ + bpi_write(0, FLASH_CMD_READ_ARRAY); + usleep(100); + + ProgressBar progress("Reading", len, 50, _verbose > 0); + + for (uint32_t i = 0; i < len; i += 2) { + uint32_t word_addr = (addr + i) >> 1; + uint16_t word = bpi_read(word_addr); + + data[i] = reverseByte((word >> 8) & 0xFF); + if (i + 1 < len) + data[i + 1] = reverseByte(word & 0xFF); + + if ((i & 0xFFF) == 0) + progress.display(i); + } + + progress.done(); + return true; +} + +bool BPIFlash::write(uint32_t addr, const uint8_t *data, uint32_t len) +{ + char buf[128]; + snprintf(buf, sizeof(buf), "Writing %u bytes to BPI flash at 0x%06x", len, addr); + printInfo(buf); + + /* Calculate blocks to erase */ + uint32_t start_block = addr / _block_size; + uint32_t end_block = (addr + len - 1) / _block_size; + + /* Erase required blocks */ + printInfo("Erasing " + std::to_string(end_block - start_block + 1) + + " blocks..."); + for (uint32_t block = start_block; block <= end_block; block++) { + if (!erase_block(block * _block_size)) { + return false; + } + } + + /* Program data using buffered programming (0x00E9) + * MT28GU512AAA has 512-word buffer, we use 32 words for reliability + * Sequence: Setup(0xE9) -> WordCount(N-1) -> N data words -> Confirm(0xD0) + */ + printInfo("Programming (buffered mode)..."); + ProgressBar progress("Writing", len, 50, _verbose > 0); + + /* MT28GU512AAA has 1KB programming regions - must program entire region at once + * to avoid object mode issues. Buffer size = 512 words = 1KB = one programming region. + */ + const uint32_t BUFFER_WORDS = 512; /* 512 words = 1KB = one programming region */ + const uint32_t BUFFER_BYTES = BUFFER_WORDS * 2; + + uint32_t last_block = 0xFFFFFFFF; /* Track which block is unlocked */ + + uint32_t offset = 0; + while (offset < len) { + uint32_t byte_addr = addr + offset; + uint32_t word_addr = byte_addr >> 1; + + /* Calculate block address (word address of block start) */ + uint32_t block_word_addr = (byte_addr / _block_size) * (_block_size >> 1); + + /* Unlock only when entering a new block */ + uint32_t current_block = byte_addr / _block_size; + if (current_block != last_block) { + unlock_block(block_word_addr); + last_block = current_block; + } + + /* Clear any pending status before new buffered program */ + bpi_write(0, FLASH_CMD_CLEAR_STATUS); + + /* Calculate how many words to write in this buffer */ + uint32_t remaining_bytes = len - offset; + uint32_t chunk_bytes = (remaining_bytes > BUFFER_BYTES) ? BUFFER_BYTES : remaining_bytes; + uint32_t chunk_words = (chunk_bytes + 1) / 2; + + /* Don't cross block boundaries */ + uint32_t bytes_to_block_end = _block_size - (byte_addr % _block_size); + if (chunk_bytes > bytes_to_block_end) + chunk_bytes = bytes_to_block_end; + chunk_words = (chunk_bytes + 1) / 2; + + if (_verbose > 1) { + char buf[128]; + snprintf(buf, sizeof(buf), "Buffered write: addr=0x%06x, words=%u, block=0x%06x", + byte_addr, chunk_words, block_word_addr << 1); + printInfo(buf); + } + + /* Buffered Program Setup - sent to block/colony base address */ + bpi_write(block_word_addr, FLASH_CMD_BUFFERED_PRG); + usleep(10); + + /* Write word count (N-1) - sent to block address per datasheet */ + bpi_write(block_word_addr, chunk_words - 1); + + /* Write data words for BPI x16 boot. + * Two transformations (same as Vivado write_cfgmem -interface BPIx16): + * 1. Bit reversal within each byte: FPGA D00=MSBit, flash DQ[0]=LSBit + * 2. Byte swap: first bitstream byte → upper flash byte D[15:8] + */ + for (uint32_t w = 0; w < chunk_words; w++) { + uint32_t data_offset = offset + w * 2; + uint8_t b0 = data[data_offset]; + uint8_t b1 = 0xFF; /* pad with 0xFF if odd length */ + if (data_offset + 1 < len) + b1 = data[data_offset + 1]; + uint16_t word = (reverseByte(b0) << 8) | reverseByte(b1); + bpi_write(word_addr + w, word); + } + + /* Confirm - sent to block address */ + bpi_write(block_word_addr, FLASH_CMD_CONFIRM); + + /* Wait for program to complete */ + if (!wait_ready(5000)) { + char buf[64]; + snprintf(buf, sizeof(buf), "Buffered program failed at address 0x%06x", byte_addr); + printError(buf); + progress.fail(); + return false; + } + + /* Small delay before next buffer operation */ + usleep(100); + + offset += chunk_words * 2; + + if ((offset & 0xFFF) == 0 || offset >= len) + progress.display(offset); + } + + /* Return to read array mode */ + bpi_write(0, FLASH_CMD_READ_ARRAY); + + progress.done(); + + /* Verify first 32 words */ + printInfo("Verifying first 32 words..."); + usleep(1000); + bpi_write(0, FLASH_CMD_READ_ARRAY); + usleep(100); + + bool verify_ok = true; + for (uint32_t i = 0; i < 64 && i < len; i += 2) { + uint8_t b0 = data[i]; + uint8_t b1 = 0xFF; + if (i + 1 < len) + b1 = data[i + 1]; + uint16_t expected = (reverseByte(b0) << 8) | reverseByte(b1); + + uint16_t actual = bpi_read(i >> 1); + if (actual != expected) { + char buf[128]; + snprintf(buf, sizeof(buf), "Verify FAIL at 0x%04x: expected 0x%04x, got 0x%04x", + i, expected, actual); + printError(buf); + verify_ok = false; + } else if (_verbose) { + char buf[128]; + snprintf(buf, sizeof(buf), "Verify OK at 0x%04x: 0x%04x", i, actual); + printInfo(buf); + } + } + + if (verify_ok) { + printInfo("Verification passed for first 32 words"); + } else { + printError("Verification FAILED!"); + } + + printInfo("BPI flash programming complete"); + return true; +} diff --git a/src/bpiFlash.hpp b/src/bpiFlash.hpp new file mode 100644 index 0000000..858807c --- /dev/null +++ b/src/bpiFlash.hpp @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2024 openFPGALoader contributors + * BPI (Parallel NOR) Flash support via JTAG bridge + */ + +#ifndef SRC_BPIFLASH_HPP_ +#define SRC_BPIFLASH_HPP_ + +#include +#include + +#include "jtag.hpp" + +/*! + * \class BPIFlash + * \brief Intel CFI parallel NOR flash programming via JTAG bridge + */ +class BPIFlash { + public: + BPIFlash(Jtag *jtag, int8_t verbose); + ~BPIFlash(); + + /*! + * \brief Read device ID and manufacturer info + * \return true if device detected + */ + bool detect(); + + /*! + * \brief Read flash content + * \param[out] data: buffer to store read data + * \param[in] addr: start address (word address) + * \param[in] len: number of bytes to read + * \return true on success + */ + bool read(uint8_t *data, uint32_t addr, uint32_t len); + + /*! + * \brief Write data to flash (handles erase internally) + * \param[in] addr: start address (word address) + * \param[in] data: data to write + * \param[in] len: number of bytes to write + * \return true on success + */ + bool write(uint32_t addr, const uint8_t *data, uint32_t len); + + /*! + * \brief Erase a block + * \param[in] addr: address within the block to erase + * \return true on success + */ + bool erase_block(uint32_t addr); + + /*! + * \brief Bulk erase entire flash + * \return true on success + */ + bool bulk_erase(); + + /*! + * \brief Get flash capacity in bytes + */ + uint32_t capacity() const { return _capacity; } + + /*! + * \brief Get block size in bytes + */ + uint32_t block_size() const { return _block_size; } + + private: + /* BPI bridge command codes (match bpiOverJtag_core.v) */ + static const uint8_t CMD_WRITE = 0x1; + static const uint8_t CMD_READ = 0x2; + static const uint8_t CMD_NOP = 0x3; + + /* Intel CFI flash commands */ + static const uint16_t FLASH_CMD_READ_ARRAY = 0x00FF; + static const uint16_t FLASH_CMD_READ_ID = 0x0090; + static const uint16_t FLASH_CMD_READ_CFI = 0x0098; + static const uint16_t FLASH_CMD_READ_STATUS = 0x0070; + static const uint16_t FLASH_CMD_CLEAR_STATUS = 0x0050; + static const uint16_t FLASH_CMD_PROGRAM = 0x0041; /* Single-word program (MT28GU512AAA) */ + static const uint16_t FLASH_CMD_BUFFERED_PRG = 0x00E9; + static const uint16_t FLASH_CMD_CONFIRM = 0x00D0; + static const uint16_t FLASH_CMD_BLOCK_ERASE = 0x0020; + static const uint16_t FLASH_CMD_UNLOCK_BLOCK = 0x0060; + static const uint16_t FLASH_CMD_UNLOCK_CONF = 0x00D0; + + /* Status register bits */ + static const uint16_t SR_READY = 0x0080; + static const uint16_t SR_ERASE_ERR = 0x0020; + static const uint16_t SR_PROG_ERR = 0x0010; + static const uint16_t SR_VPP_ERR = 0x0008; + static const uint16_t SR_BLOCK_LOCK = 0x0002; + + /*! + * \brief Read a 16-bit word from flash at word address + */ + uint16_t bpi_read(uint32_t word_addr); + + /*! + * \brief Write a 16-bit word to flash at word address + */ + void bpi_write(uint32_t word_addr, uint16_t data); + + /*! + * \brief Wait for operation to complete + * \return true if completed successfully + */ + bool wait_ready(uint32_t timeout_ms = 10000); + + /*! + * \brief Unlock a block for programming/erase + */ + bool unlock_block(uint32_t word_addr); + + Jtag *_jtag; + int8_t _verbose; + int _irlen; + uint32_t _capacity; + uint32_t _block_size; + uint16_t _manufacturer_id; + uint16_t _device_id; +}; + +#endif // SRC_BPIFLASH_HPP_ diff --git a/src/xilinx.cpp b/src/xilinx.cpp index a1fe30d..ebdbe44 100644 --- a/src/xilinx.cpp +++ b/src/xilinx.cpp @@ -281,7 +281,7 @@ Xilinx::Xilinx(Jtag *jtag, const std::string &filename, skip_reset), _device_package(device_package), _spiOverJtagPath(spiOverJtagPath), _irlen(6), _secondary_filename(secondary_filename), _soj_is_v2(false), - _jtag_chain_len(1) + _jtag_chain_len(1), _is_bpi_board(false) { if (prg_type == Device::RD_FLASH) { _mode = Device::READ_MODE; @@ -405,6 +405,12 @@ Xilinx::Xilinx(Jtag *jtag, const std::string &filename, _fpga_family = UNKNOWN_FAMILY; } + /* Check for BPI flash boards */ + if (_device_package == "xc7k480tffg1156") { + _is_bpi_board = true; + printInfo("BPI flash board detected (parallel NOR flash)"); + } + if (read_dna) { if (_fpga_family == ARTIX_FAMILY || _fpga_family == KINTEXUS_FAMILY) { uint64_t dna = Xilinx::fuse_dna_read(); @@ -654,17 +660,22 @@ void Xilinx::program(unsigned int offset, bool unprotect_flash) } if (_mode == Device::SPI_MODE) { - if (_flash_chips & PRIMARY_FLASH) { - select_flash_chip(PRIMARY_FLASH); - program_spi(bit, _file_extension, offset, unprotect_flash); - } - if (_flash_chips & SECONDARY_FLASH) { - select_flash_chip(SECONDARY_FLASH); - program_spi(secondary_bit, _secondary_file_extension, offset, unprotect_flash); - } - - reset(); + /* Check for BPI flash boards */ + if (_is_bpi_board) { + program_bpi(bit, offset); + reset(); + } else { + if (_flash_chips & PRIMARY_FLASH) { + select_flash_chip(PRIMARY_FLASH); + program_spi(bit, _file_extension, offset, unprotect_flash); + } + if (_flash_chips & SECONDARY_FLASH) { + select_flash_chip(SECONDARY_FLASH); + program_spi(secondary_bit, _secondary_file_extension, offset, unprotect_flash); + } + reset(); + } } else { if (_fpga_family == SPARTAN3_FAMILY) xc3s_flow_program(bit); @@ -744,6 +755,68 @@ bool Xilinx::load_bridge() return true; } +bool Xilinx::load_bpi_bridge() +{ + std::string bitname; + + if (_device_package.empty()) { + printError("Can't program BPI flash: missing device-package information"); + return false; + } + + bitname = get_shell_env_var("OPENFPGALOADER_SOJ_DIR", DATA_DIR "/openFPGALoader"); + bitname += "/bpiOverJtag_" + _device_package + ".bit.gz"; + +#if defined (_WIN64) || defined (_WIN32) + bitname = PathHelper::absolutePath(bitname); +#endif + + /* Load BPI over JTAG bridge */ + try { + BitParser bridge(bitname, true, _verbose); + printSuccess("Use: " + bridge.getFilename()); + + bridge.parse(); + program_mem(&bridge); + } catch (std::exception &e) { + printError(e.what()); + throw std::runtime_error(e.what()); + } + + /* Initialize BPI flash instance */ + _bpi_flash.reset(new BPIFlash(_jtag, _verbose)); + + return true; +} + +void Xilinx::program_bpi(ConfigBitstreamParser *bit, unsigned int offset) +{ + if (!bit) + throw std::runtime_error("called with null bitstream"); + + /* Load BPI bridge if not already loaded */ + if (!_bpi_flash) { + if (!load_bpi_bridge()) { + throw std::runtime_error("Failed to load BPI bridge"); + } + } + + /* Detect flash */ + if (!_bpi_flash->detect()) { + throw std::runtime_error("BPI flash detection failed"); + } + + /* Program the flash */ + const uint8_t *data = bit->getData(); + int length = bit->getLength() / 8; + + if (!_bpi_flash->write(offset, data, length)) { + throw std::runtime_error("BPI flash programming failed"); + } + + printInfo("BPI flash programming complete"); +} + float Xilinx::get_spiOverJtag_version() { uint8_t jtx[6] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; diff --git a/src/xilinx.hpp b/src/xilinx.hpp index 9b366ac..257af60 100644 --- a/src/xilinx.hpp +++ b/src/xilinx.hpp @@ -7,9 +7,11 @@ #define SRC_XILINX_HPP_ #include +#include #include #include +#include "bpiFlash.hpp" #include "configBitstreamParser.hpp" #include "device.hpp" #include "jedParser.hpp" @@ -33,6 +35,7 @@ class Xilinx: public Device, SPIInterface { void program(unsigned int offset, bool unprotect_flash) override; void program_spi(ConfigBitstreamParser * bit, std::string extention, unsigned int offset, bool unprotect_flash); + void program_bpi(ConfigBitstreamParser * bit, unsigned int offset); void program_mem(ConfigBitstreamParser *bitfile); bool dumpFlash(uint32_t base_addr, uint32_t len) override; @@ -220,6 +223,17 @@ class Xilinx: public Device, SPIInterface { */ bool load_bridge(); + /*! + * \brief load BPI flash bridge for parallel NOR flash access + * \return false if failed, true otherwise + */ + bool load_bpi_bridge(); + + /*! + * \brief check if board uses BPI flash + */ + bool is_bpi_board() const { return _is_bpi_board; } + /*! * \brief read SpiOverJtag version to select between v1 and v2 * \return 2.0 for v2 or 1.0 for v1 @@ -262,6 +276,8 @@ class Xilinx: public Device, SPIInterface { std::string _user_instruction; /* which USER bscan instruction to interface with SPI */ bool _soj_is_v2; /* SpiOverJtag version (1.0 or 2.0) */ uint32_t _jtag_chain_len; /* Jtag Chain Length */ + bool _is_bpi_board; /* true if board uses BPI parallel flash */ + std::unique_ptr _bpi_flash; /* BPI flash instance */ }; #endif // SRC_XILINX_HPP_