Adding first pass at YPCB flash

This commit is contained in:
blurbdust 2026-02-09 09:28:42 -06:00
parent e77af309e1
commit aa32464942
13 changed files with 1226 additions and 11 deletions

View File

@ -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

View File

@ -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

Binary file not shown.

View File

@ -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

View File

@ -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"

View File

@ -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}]

View File

@ -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

View File

@ -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

View File

@ -270,6 +270,7 @@ static std::map <std::string, target_board_t> 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),

482
src/bpiFlash.cpp Normal file
View File

@ -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 <unistd.h>
#include <cstring>
#include <stdexcept>
#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;
}

127
src/bpiFlash.hpp Normal file
View File

@ -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 <cstdint>
#include <string>
#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_

View File

@ -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};

View File

@ -7,9 +7,11 @@
#define SRC_XILINX_HPP_
#include <map>
#include <memory>
#include <string>
#include <vector>
#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<BPIFlash> _bpi_flash; /* BPI flash instance */
};
#endif // SRC_XILINX_HPP_