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 0000000..82af67f Binary files /dev/null and b/bpiOverJtag/bpiOverJtag_xc7k480tffg1156.bit.gz differ diff --git a/bpiOverJtag/build_bpi_xc7k480t.sh b/bpiOverJtag/build_bpi_xc7k480t.sh new file mode 100755 index 0000000..ef98df0 --- /dev/null +++ b/bpiOverJtag/build_bpi_xc7k480t.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# Build bpiOverJtag bitstream for xc7k480tffg1156 + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo "Building bpiOverJtag for xc7k480tffg1156..." + +# Find Vivado +if ! command -v vivado &> /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_