From 98fea265076226e56d884137228ba5745a762455 Mon Sep 17 00:00:00 2001 From: blurbdust Date: Mon, 9 Feb 2026 09:28:42 -0600 Subject: [PATCH] Adding BPI flash support and YPCB board support --- CMakeLists.txt | 4 + bpiOverJtag/.gitignore | 6 + bpiOverJtag/Makefile | 14 + bpiOverJtag/bpiOverJtag_core.v | 331 ++++++++++ .../bpiOverJtag_xc7k480tffg1156.bit.gz | Bin 0 -> 132631 bytes bpiOverJtag/constr_xc7k_ffg1156.xdc | 58 ++ bpiOverJtag/xilinx_bpiOverJtag.v | 84 +++ doc/boards.yml | 7 + spiOverJtag/Makefile | 4 +- spiOverJtag/build.py | 31 +- src/board.hpp | 295 ++++----- src/bpiFlash.cpp | 583 ++++++++++++++++++ src/bpiFlash.hpp | 140 +++++ src/main.cpp | 8 +- src/xilinx.cpp | 97 ++- src/xilinx.hpp | 17 + 16 files changed, 1504 insertions(+), 175 deletions(-) create mode 100644 bpiOverJtag/.gitignore create mode 100644 bpiOverJtag/Makefile create mode 100644 bpiOverJtag/bpiOverJtag_core.v create mode 100644 bpiOverJtag/bpiOverJtag_xc7k480tffg1156.bit.gz create mode 100644 bpiOverJtag/constr_xc7k_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 b60fd58..53a9c1c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -303,6 +303,7 @@ endif() # To be sorted # =========================== list(APPEND OPENFPGALOADER_SOURCE + src/bpiFlash.cpp src/spiFlash.cpp src/spiInterface.cpp src/epcq.cpp @@ -310,6 +311,7 @@ list(APPEND OPENFPGALOADER_SOURCE ) list(APPEND OPENFPGALOADER_HEADERS + src/bpiFlash.hpp src/jtag.hpp src/jtagInterface.hpp src/spiFlash.hpp @@ -781,6 +783,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/.gitignore b/bpiOverJtag/.gitignore new file mode 100644 index 0000000..69629ee --- /dev/null +++ b/bpiOverJtag/.gitignore @@ -0,0 +1,6 @@ +tmp_* +*.bit +*.rbf +vivado*.jou +vivado*.log +.Xil diff --git a/bpiOverJtag/Makefile b/bpiOverJtag/Makefile new file mode 100644 index 0000000..74fb39b --- /dev/null +++ b/bpiOverJtag/Makefile @@ -0,0 +1,14 @@ +BPI_XILINX_PARTS := xc7k480tffg1156 +BPI_XILINX_BIT_FILES := $(addsuffix .bit.gz,$(addprefix bpiOverJtag_, $(BPI_XILINX_PARTS))) + +BIT_FILES := $(BPI_XILINX_BIT_FILES) + +all: $(BIT_FILES) + +$(BPI_XILINX_BIT_FILES) : bpiOverJtag_%.bit.gz : tmp_%/bpiOverJtag.bit + +tmp_%/bpiOverJtag.bit : xilinx_bpiOverJtag.v bpiOverJtag_core.v + ../spiOverJtag/build.py $* bpi + +clean: + -rm -rf tmp_* *.jou *.log .Xil diff --git a/bpiOverJtag/bpiOverJtag_core.v b/bpiOverJtag/bpiOverJtag_core.v new file mode 100644 index 0000000..074c5d3 --- /dev/null +++ b/bpiOverJtag/bpiOverJtag_core.v @@ -0,0 +1,331 @@ +`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 + * 0x4 = Burst write (addr + count + N×data words) + */ + +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 = 4'd0, + RECV_CMD = 4'd1, + RECV_ADDR = 4'd2, + RECV_DATA = 4'd3, + EXEC = 4'd4, + SEND_DATA = 4'd5, + DONE = 4'd6, + BURST_RECV_CNT = 4'd7, + BURST_DATA = 4'd8, + BURST_EXEC = 4'd9; + +reg [3: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; +reg [15:0] burst_cnt, burst_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, + CMD_BURST_WRITE = 4'h4; + +/* 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; + burst_cnt_d = burst_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 if (cmd_reg == CMD_BURST_WRITE) begin + bit_cnt_d = 15; /* 16 bits for burst count */ + state_d = BURST_RECV_CNT; + 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 + + BURST_RECV_CNT: begin + burst_cnt_d = {tdi, burst_cnt[15:1]}; + bit_cnt_d = bit_cnt - 1'b1; + if (bit_cnt == 0) begin + bit_cnt_d = 15; + state_d = BURST_DATA; + end + end + + BURST_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; + state_d = BURST_EXEC; + end + end + + BURST_EXEC: begin + wait_cnt_d = wait_cnt - 1'b1; + if (wait_cnt == 0) begin + burst_cnt_d = burst_cnt - 1'b1; + if (burst_cnt == 16'd1) begin + state_d = DONE; + end else begin + addr_reg_d = addr_reg + 1'b1; + bit_cnt_d = 15; + state_d = BURST_DATA; + end + end + 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; + burst_cnt <= burst_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]}; + else if (state == BURST_DATA && bit_cnt == 0) + bpi_addr <= addr_reg; +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 + BURST_EXEC: begin + bpi_ce_n <= 1'b0; + bpi_adv_n <= 1'b0; + 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 + 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_32_2E_30_30; // "02.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..5e8c91fe6de60c81e0cd23f8e07be79cbdf2b06a GIT binary patch literal 132631 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

1iSY`gD;U%Jc?-6bwnXN>pr! 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 literal 0 HcmV?d00001 diff --git a/bpiOverJtag/constr_xc7k_ffg1156.xdc b/bpiOverJtag/constr_xc7k_ffg1156.xdc new file mode 100644 index 0000000..3dd8bc4 --- /dev/null +++ b/bpiOverJtag/constr_xc7k_ffg1156.xdc @@ -0,0 +1,58 @@ +## 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] +set_property BITSTREAM.CONFIG.UNUSEDPIN PULLNONE [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..113f980 --- /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..dd2a50e 100644 --- a/doc/boards.yml +++ b/doc/boards.yml @@ -987,6 +987,13 @@ 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. 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/spiOverJtag/Makefile b/spiOverJtag/Makefile index 91fd1de..1daca52 100644 --- a/spiOverJtag/Makefile +++ b/spiOverJtag/Makefile @@ -36,7 +36,7 @@ tmp_efinix_%/efinix_spiOverJtag.bit : efinix_spiOverJtag.v $(XILINX_BIT_FILES) : spiOverJtag_%.bit.gz : tmp_%/spiOverJtag.bit tmp_%/spiOverJtag.bit : xilinx_spiOverJtag.v spiOverJtag_core.v - ./build.py $* + ./build.py $* spi $(ALTERA_BIT_FILES): spiOverJtag_%.rbf.gz: tmp_%/spiOverJtag.rbf gzip -9 -c $< > $@ @@ -44,7 +44,7 @@ tmp_%/spiOverJtag.rbf: tmp_%/spiOverJtag.sof quartus_cpf --option=bitstream_compression=off -c $< $@ tmp_%/spiOverJtag.sof: altera_spiOverJtag.v - ./build.py $* + ./build.py $* spi clean: -rm -rf tmp_* *.jou *.log .Xil diff --git a/spiOverJtag/build.py b/spiOverJtag/build.py index 8ae4ec3..5445f1a 100755 --- a/spiOverJtag/build.py +++ b/spiOverJtag/build.py @@ -37,10 +37,14 @@ packages = { }, } -if len(os.sys.argv) != 2: - print("missing board param") +if len(os.sys.argv) != 3 : + print("missing board flash type params") os.sys.exit() -part = os.sys.argv[1] +part = os.sys.argv[1] +flash_type = os.sys.argv[2] + +# Check file type keyword +assert flash_type in ["spi", "bpi"] build_dir="tmp_" + part if not os.path.isdir(build_dir): @@ -84,7 +88,7 @@ elif subpart == "xc7v": tool = "vivado" elif subpart == "xc7k": device_size = int(part.split('k')[1].split('t')[0]) - if device_size <= 160: + if flash_type == "bpi" or device_size <= 160: family = "Kintex 7" tool = "vivado" else: @@ -214,10 +218,13 @@ if tool in ["ise", "vivado"]: tool_options = {'part': part + '-1'} cst_file = currDir + "constr_" + pkg_name + "." + cst_type.lower() - files.append({'name': currDir + 'xilinx_spiOverJtag.v', + files.append({'name': os.path.join(currDir, f"xilinx_{flash_type}OverJtag.v"), 'file_type': 'verilogSource'}) files.append({'name': cst_file, 'file_type': cst_type}) else: + # Altera only support SPI mode. + assert flash_type in ["spi"] + full_part = { "10cl016484" : "10CL016YU484C8G", "10cl025256" : "10CL025YU256C8G", @@ -245,7 +252,7 @@ else: 'file_type': 'SDC'}) tool_options = {'device': full_part, 'family':family} -files.append({'name': currDir + 'spiOverJtag_core.v', +files.append({'name': os.path.join(currDir, f"{flash_type}OverJtag_core.v"), 'file_type': 'verilogSource'}) parameters[family.lower().replace(' ', '')]= { @@ -254,11 +261,11 @@ parameters[family.lower().replace(' ', '')]= { 'description': 'fpga family', 'default': 1} -edam = {'name' : "spiOverJtag", +edam = {'name' : f"{flash_type}OverJtag", 'files': files, 'tool_options': {tool: tool_options}, 'parameters': parameters, - 'toplevel' : 'spiOverJtag', + 'toplevel' : f"{flash_type}OverJtag", } backend = get_edatool(tool)(edam=edam, work_root=build_dir) @@ -271,14 +278,14 @@ if tool in ["vivado", "ise"]: import gzip # Compress bitstream. - with open(f"tmp_{part}/spiOverJtag.bit", 'rb') as bit: - with gzip.open(f"spiOverJtag_{part}.bit.gz", 'wb', compresslevel=9) as bit_gz: + with open(f"tmp_{part}/{flash_type}OverJtag.bit", 'rb') as bit: + with gzip.open(f"{flash_type}OverJtag_{part}.bit.gz", 'wb', compresslevel=9) as bit_gz: shutil.copyfileobj(bit, bit_gz) # Create Symbolic links for all supported packages. if family in ["Artix", "Spartan 7"]: - in_file = f"spiOverJtag_{part}.bit.gz" + in_file = f"{flash_type}OverJtag_{part}.bit.gz" for pkg in packages[family][part]: - out_file = f"spiOverJtag_{part}{pkg}.bit.gz" + out_file = f"{flash_type}OverJtag_{part}{pkg}.bit.gz" if not os.path.exists(out_file): subprocess.run(["ln", "-s", in_file, out_file]) diff --git a/src/board.hpp b/src/board.hpp index 502d268..dd02953 100644 --- a/src/board.hpp +++ b/src/board.hpp @@ -69,6 +69,11 @@ enum { COMM_DFU = (1 << 2), }; +enum { + SPI_FLASH = 0, + BPI_FLASH = 1, +}; + /*! * \brief a board has a target cable and optionally a pin configuration * (bitbang mode) @@ -81,6 +86,7 @@ typedef struct { uint16_t done_pin; /*! done pin value */ uint16_t oe_pin; /*! output enable pin value */ uint16_t mode; /*! communication type (JTAG or SPI) */ + uint8_t spi_bpi; /*! SPI Only: Flash type (SPI or BPI) */ jtag_pins_conf_t jtag_pins_config; /*! for bitbang, provide struct with pins value */ spi_pins_conf_t spi_pins_config; /*! for SPI, provide struct with pins value */ uint32_t default_freq; /* Default clock speed: 0 = use cable default */ @@ -92,195 +98,196 @@ typedef struct { #define CABLE_DEFAULT 0 #define CABLE_MHZ(_m) ((_m) * 1000000) -#define JTAG_BOARD(_name, _fpga_part, _cable, _rst, _done, _freq) \ - {_name, {"", _cable, _fpga_part, _rst, _done, 0, COMM_JTAG, {}, {}, _freq, 0, 0, -1}} -#define JTAG_BITBANG_BOARD(_name, _fpga_part, _cable, _rst, _done, _tms, _tck, _tdi, _tdo, _freq) \ - {_name, {"", _cable, _fpga_part, _rst, _done, 0, COMM_JTAG, { _tms, _tck, _tdi, _tdo, 0, 0 }, {}, \ +#define JTAG_BOARD(_name, _fpga_part, _cable, _spi_bpi, _rst, _done, _freq) \ + {_name, {"", _cable, _fpga_part, _rst, _done, 0, COMM_JTAG, _spi_bpi, {}, {}, _freq, 0, 0, -1}} +#define JTAG_BITBANG_BOARD(_name, _fpga_part, _cable, _spi_bpi, _rst, _done, _tms, _tck, _tdi, _tdo, _freq) \ + {_name, {"", _cable, _fpga_part, _rst, _done, 0, COMM_JTAG, _spi_bpi, { _tms, _tck, _tdi, _tdo, 0, 0 }, {}, \ _freq, 0, 0, -1}} #define SPI_BOARD(_name, _manufacturer, _fpga_part, _cable, _rst, _done, _oe, _cs, _sck, _si, _so, _holdn, _wpn, _freq) \ - {_name, {_manufacturer, _cable, _fpga_part, _rst, _done, _oe, COMM_SPI, {}, \ + {_name, {_manufacturer, _cable, _fpga_part, _rst, _done, _oe, COMM_SPI, SPI_FLASH, {}, \ {_cs, _sck, _so, _si, _holdn, _wpn}, _freq, 0, 0, -1}} #define DFU_BOARD(_name, _fpga_part, _cable, _vid, _pid, _alt) \ - {_name, {"", _cable, _fpga_part, 0, 0, 0, COMM_DFU, {}, {}, 0, _vid, _pid, _alt}} + {_name, {"", _cable, _fpga_part, 0, 0, 0, COMM_DFU, SPI_FLASH, {}, {}, 0, _vid, _pid, _alt}} static std::map board_list = { - JTAG_BOARD("ac701", "xc7a200tfbg676", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("acornCle215", "xc7a200tsbg484", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("analogMax", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("litex-acorn-baseboard-mini", "xc7a200tsbg484", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("alchitry_au", "xc7a35tftg256", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("alchitry_au_plus","xc7a100tftg256", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("alinx_ax516", "xc6slx16csg324", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("alinx_ax7101", "xc7a100tfgg484", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("alinx_ax7102", "xc7a100tfgg484", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("alinx_ax7201", "xc7a200tfbg484", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("alinx_ax7203", "xc7a200tfbg484", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("antmicro_ddr4_tester", "xc7k160tffg676", "ft4232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("antmicro_ddr5_tester", "xc7k160tffg676", "ft4232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("antmicro_lpddr4_tester", "xc7k70tfbg484", "ft4232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("anvyl", "xc6slx45csg484", "digilent", 0, 0, CABLE_DEFAULT), + JTAG_BOARD("ac701", "xc7a200tfbg676", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("acornCle215", "xc7a200tsbg484", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("analogMax", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("litex-acorn-baseboard-mini", "xc7a200tsbg484", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("alchitry_au", "xc7a35tftg256", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("alchitry_au_plus","xc7a100tftg256", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("alinx_ax516", "xc6slx16csg324", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("alinx_ax7101", "xc7a100tfgg484", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("alinx_ax7102", "xc7a100tfgg484", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("alinx_ax7201", "xc7a200tfbg484", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("alinx_ax7203", "xc7a200tfbg484", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("antmicro_ddr4_tester", "xc7k160tffg676", "ft4232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("antmicro_ddr5_tester", "xc7k160tffg676", "ft4232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("antmicro_lpddr4_tester", "xc7k70tfbg484", "ft4232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("anvyl", "xc6slx45csg484", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), /* left for backward compatibility, use right name instead */ - JTAG_BOARD("arty", "xc7a35tcsg324", "digilent", 0, 0, CABLE_MHZ(10)), - JTAG_BOARD("arty_a7_35t", "xc7a35tcsg324", "digilent", 0, 0, CABLE_MHZ(10)), - JTAG_BOARD("arty_a7_100t", "xc7a100tcsg324", "digilent", 0, 0, CABLE_MHZ(10)), - JTAG_BOARD("arty_s7_25", "xc7s25csga324", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("arty_s7_50", "xc7s50csga324", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("arty_z7_10", "xc7z010clg400", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("arty_z7_20", "xc7z020clg400", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("axu2cga", "xczu2cg", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("basys3", "xc7a35tcpg236", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("c5g", "", "usb-blaster",0, 0, CABLE_DEFAULT), - JTAG_BOARD("cmod_s7", "xc7s25csga225", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("cmoda7_15t", "xc7a15tcpg236", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("cmoda7_35t", "xc7a35tcpg236", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("colorlight", "", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("colorlight-i5", "", "cmsisdap", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("colorlight-i9", "", "cmsisdap", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("colorlight-i9+", "xc7a50tfgg484", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("crosslinknx_evn", "", "ft2232", 0, 0, CABLE_MHZ(1)), - JTAG_BOARD("certusnx_versa_evn", "", "ft2232", 0, 0, CABLE_MHZ(1)), - JTAG_BOARD("certuspronx_evn", "", "ft2232", 0, 0, CABLE_MHZ(1)), - JTAG_BOARD("certuspronx_versa_evn", "", "ft2232", 0, 0, CABLE_MHZ(1)), - JTAG_BOARD("cyc1000", "10cl025256", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("cyc5000", "5ce215", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("c10lp-refkit", "10cl055484", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("de0", "", "usb-blaster",0, 0, CABLE_DEFAULT), - JTAG_BOARD("de0nano", "ep4ce2217", "usb-blaster",0, 0, CABLE_DEFAULT), - JTAG_BOARD("de0nanoSoc", "", "usb-blasterII",0, 0, CABLE_DEFAULT), - JTAG_BOARD("de10lite", "", "usb-blaster",0, 0, CABLE_DEFAULT), - JTAG_BOARD("de10nano", "", "usb-blasterII",0, 0, CABLE_DEFAULT), - JTAG_BOARD("de1Soc", "5CSEMA5", "usb-blasterII",0, 0, CABLE_DEFAULT), - JTAG_BOARD("deca", "10M50DA", "usb-blasterII",0, 0, CABLE_DEFAULT), - JTAG_BOARD("dragonL", "xc6slx25tcsg324", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("ecp5_evn", "", "ft2232", 0, 0, CABLE_DEFAULT), + JTAG_BOARD("arty", "xc7a35tcsg324", "digilent", SPI_FLASH, 0, 0, CABLE_MHZ(10)), + JTAG_BOARD("arty_a7_35t", "xc7a35tcsg324", "digilent", SPI_FLASH, 0, 0, CABLE_MHZ(10)), + JTAG_BOARD("arty_a7_100t", "xc7a100tcsg324", "digilent", SPI_FLASH, 0, 0, CABLE_MHZ(10)), + JTAG_BOARD("arty_s7_25", "xc7s25csga324", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("arty_s7_50", "xc7s50csga324", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("arty_z7_10", "xc7z010clg400", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("arty_z7_20", "xc7z020clg400", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("axu2cga", "xczu2cg", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("basys3", "xc7a35tcpg236", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("c5g", "", "usb-blaster", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("cmod_s7", "xc7s25csga225", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("cmoda7_15t", "xc7a15tcpg236", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("cmoda7_35t", "xc7a35tcpg236", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("colorlight", "", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("colorlight-i5", "", "cmsisdap", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("colorlight-i9", "", "cmsisdap", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("colorlight-i9+", "xc7a50tfgg484", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("crosslinknx_evn", "", "ft2232", SPI_FLASH, 0, 0, CABLE_MHZ(1)), + JTAG_BOARD("certusnx_versa_evn", "", "ft2232", SPI_FLASH, 0, 0, CABLE_MHZ(1)), + JTAG_BOARD("certuspronx_evn", "", "ft2232", SPI_FLASH, 0, 0, CABLE_MHZ(1)), + JTAG_BOARD("certuspronx_versa_evn", "", "ft2232", SPI_FLASH, 0, 0, CABLE_MHZ(1)), + JTAG_BOARD("cyc1000", "10cl025256", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("cyc5000", "5ce215", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("c10lp-refkit", "10cl055484", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("de0", "", "usb-blaster", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("de0nano", "ep4ce2217", "usb-blaster", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("de0nanoSoc", "", "usb-blasterII", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("de10lite", "", "usb-blaster", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("de10nano", "", "usb-blasterII", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("de1Soc", "5CSEMA5", "usb-blasterII", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("deca", "10M50DA", "usb-blasterII", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("dragonL", "xc6slx25tcsg324", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("ecp5_evn", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), SPI_BOARD("ecp5_generic", "lattice", "ecp5", "", 0, 0, 0, DBUS3, DBUS0, DBUS1, DBUS2, 0, 0, CABLE_DEFAULT), - JTAG_BOARD("ecpix5", "", "ecpix5-debug", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("ecpix5_r03", "", "ft4232", 0, 0, CABLE_DEFAULT), - SPI_BOARD("fireant", "efinix", "trion", "ft232", + JTAG_BOARD("ecpix5", "", "ecpix5-debug", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("ecpix5_r03", "", "ft4232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + SPI_BOARD("fireant", "efinix", "trion", "ft232", DBUS4, DBUS5, 0, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT), DFU_BOARD("fomu", "", "dfu", 0x1209, 0x5bf0, 0), SPI_BOARD("ft2232_spi", "none", "none", "ft2232", DBUS7, DBUS6, 0, DBUS4, DBUS0, DBUS1, DBUS2, 0, 0, CABLE_DEFAULT), - JTAG_BOARD("gcm_jtag", "none", "ft4232", 0, 0, CABLE_DEFAULT), + JTAG_BOARD("gcm_jtag", "none", "ft4232", SPI_FLASH, 0, 0, CABLE_DEFAULT), SPI_BOARD("gcm_bootflash", "none", "none", "ft4232_b", 0, 0, 0, DBUS3, DBUS0, DBUS1, DBUS2, 0, 0, CABLE_DEFAULT), - SPI_BOARD("gatemate_pgm_spi", "colognechip", "GM1Ax", "gatemate_pgm", + SPI_BOARD("gatemate_pgm_spi", "colognechip", "GM1Ax", "gatemate_pgm", DBUS4, DBUS5, CBUS0, DBUS3, DBUS0, DBUS1, DBUS2, 0, 0, CABLE_DEFAULT), - JTAG_BOARD("gatemate_evb_jtag", "", "gatemate_evb_jtag", 0, 0, CABLE_DEFAULT), - SPI_BOARD("gatemate_evb_spi", "colognechip", "GM1Ax", "gatemate_evb_spi", + JTAG_BOARD("gatemate_evb_jtag", "", "gatemate_evb_jtag", SPI_FLASH, 0, 0, CABLE_DEFAULT), + SPI_BOARD("gatemate_evb_spi", "colognechip", "GM1Ax", "gatemate_evb_spi", DBUS4, DBUS5, CBUS0, DBUS3, DBUS0, DBUS1, DBUS2, 0, 0, CABLE_DEFAULT), - JTAG_BOARD("genesys2", "xc7k325tffg900", "digilent_b", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("gr740-mini", "", "ft4232hp_b", 0, 0, CABLE_MHZ(1)), - JTAG_BOARD("hseda-xc6slx16", "xc6slx16ftg256", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("hyvision_opt01", "xc7k70tfbg676", "", 0, 0, CABLE_DEFAULT), + JTAG_BOARD("genesys2", "xc7k325tffg900", "digilent_b", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("gr740-mini", "", "ft4232hp_b", SPI_FLASH, 0, 0, CABLE_MHZ(1)), + JTAG_BOARD("hseda-xc6slx16", "xc6slx16ftg256", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("hyvision_opt01", "xc7k70tfbg676", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), /* most ice40 boards uses the same pinout */ SPI_BOARD("ice40_generic", "lattice", "ice40", "ft2232", DBUS7, DBUS6, 0, DBUS4, DBUS0, DBUS1, DBUS2, 0, 0, CABLE_DEFAULT), DFU_BOARD("icebreaker-bitsy", "", "dfu", 0x1d50, 0x6146, 0), - JTAG_BITBANG_BOARD("icepi-zero", "", "ft231X", 0, 0, + JTAG_BITBANG_BOARD("icepi-zero", "", "ft231X", SPI_FLASH, 0, 0, FT232RL_DCD, FT232RL_DSR, FT232RL_RI, FT232RL_CTS, CABLE_DEFAULT), - JTAG_BOARD("kc705", "", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("kcu105", "xcku040-ffva1156", "jtag-smt2-nc", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("kcu116", "xcku5p-ffvb676", "jtag-smt2-nc", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("LD-SCHOKO", "LFE5U-45F-6CABGA256", "", 0, 0, CABLE_MHZ(6)), - DFU_BOARD("LD-SCHOKO-DFU", "", "dfu", 0x16d0, 0x116d, 0), - JTAG_BOARD("LD-KONFEKT", "LFE5U-12F-6BG256C", "", 0, 0, CABLE_MHZ(6)), - DFU_BOARD("LD-KONFEKT-DFU", "", "dfu", 0x16d0, 0x116d, 0), - JTAG_BOARD("licheeTang", "", "anlogicCable", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("lilygo-t-fpga", "", "gwu2x", 0, 0, CABLE_DEFAULT), + JTAG_BOARD("kc705", "", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("kcu105", "xcku040-ffva1156", "jtag-smt2-nc", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("kcu116", "xcku5p-ffvb676", "jtag-smt2-nc", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("LD-SCHOKO", "LFE5U-45F-6CABGA256", "", SPI_FLASH, 0, 0, CABLE_MHZ(6)), + DFU_BOARD("LD-SCHOKO-DFU", "", "dfu", 0x16d0, 0x116d, 0), + JTAG_BOARD("LD-KONFEKT", "LFE5U-12F-6BG256C", "", SPI_FLASH, 0, 0, CABLE_MHZ(6)), + DFU_BOARD("LD-KONFEKT-DFU", "", "dfu", 0x16d0, 0x116d, 0), + JTAG_BOARD("licheeTang", "", "anlogicCable", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("lilygo-t-fpga", "", "gwu2x", SPI_FLASH, 0, 0, CABLE_DEFAULT), /* left for backward compatibility, use tec0117 instead */ - JTAG_BOARD("littleBee", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("machXO2EVN", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("machXO3SK", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("machXO3EVN", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("mlk-s200-eg4d20", "", "anlogicCable", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("mimas_a7", "xc7a50tfgg484", "numato", 0, 0, CABLE_MHZ(30)), - JTAG_BOARD("neso_a7", "xc7a100tcsg324", "numato-neso", 0, 0, CABLE_MHZ(30)), - JTAG_BOARD("minispartan6", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("nexys_a7_50", "xc7a50tcsg324", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("nexys_a7_100", "xc7a100tcsg324", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("nexysVideo", "xc7a200tsbg484", "digilent_b", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("olimex_gatemateevb", "GM1A1", "dirtyJtag", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("opensourceSDRLabKintex7", "xc7k325tffg676", "ft232", 0, 0, CABLE_DEFAULT), + JTAG_BOARD("littleBee", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("machXO2EVN", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("machXO3SK", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("machXO3EVN", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("mlk-s200-eg4d20", "", "anlogicCable", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("mimas_a7", "xc7a50tfgg484", "numato", SPI_FLASH, 0, 0, CABLE_MHZ(30)), + JTAG_BOARD("neso_a7", "xc7a100tcsg324", "numato-neso", SPI_FLASH, 0, 0, CABLE_MHZ(30)), + JTAG_BOARD("minispartan6", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("nexys_a7_50", "xc7a50tcsg324", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("nexys_a7_100", "xc7a100tcsg324", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("nexysVideo", "xc7a200tsbg484", "digilent_b", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("olimex_gatemateevb", "GM1A1", "dirtyJtag", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("opensourceSDRLabKintex7", "xc7k325tffg676", "ft232", SPI_FLASH, 0, 0, CABLE_DEFAULT), DFU_BOARD("orangeCrab", "", "dfu", 0x1209, 0x5af0, 0), DFU_BOARD("orbtrace_dfu", "", "dfu", 0x1209, 0x3442, 1), - JTAG_BOARD("papilio_one", "xc3s500evq100", "papilio", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("pipistrello", "xc6slx45csg324", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("pynq_z1", "xc7z020clg400", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("pynq_z2", "xc7z020clg400", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("qmtechCyclone10", "10cl016484", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("qmtechCycloneIVGX", "ep4cgx15027", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("qmtechCycloneIV", "ep4ce1523", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("qmtechCycloneV", "5ce223", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("qmtechCycloneV_5ce523", "5ce523", "", 0,0, CABLE_DEFAULT), - JTAG_BOARD("qmtechKintex7", "xc7k325tffg676", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("redpitaya14", "xc7z010clg400", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("runber", "", "ft232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("spartanEdgeAccelBoard", "", "",0, 0, CABLE_DEFAULT), - JTAG_BOARD("spec45", "xc6slx45tfgg484", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("spec150", "xc6slx150tfgg484", "", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("stlv7325", "xc7k325tffg676", "ft4232", 0, 0, CABLE_MHZ(3)), - JTAG_BOARD("tangconsole", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("tangnano", "", "ch552_jtag", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("tangnano1k", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("tangnano4k", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("tangnano9k", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("tangnano20k", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("tangprimer20k", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("tangprimer25k", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("tangmega138k", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("te0712_8", "xc7a200tfbg484", "", 0, 0, CABLE_MHZ(15)), - JTAG_BOARD("tec0117", "", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("tec0330", "xc7vx330tffg1157", "", 0, 0, CABLE_MHZ(15)), + JTAG_BOARD("papilio_one", "xc3s500evq100", "papilio", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("pipistrello", "xc6slx45csg324", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("pynq_z1", "xc7z020clg400", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("pynq_z2", "xc7z020clg400", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("qmtechCyclone10", "10cl016484", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("qmtechCycloneIVGX", "ep4cgx15027", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("qmtechCycloneIV", "ep4ce1523", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("qmtechCycloneV", "5ce223", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("qmtechCycloneV_5ce523", "5ce523", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("qmtechKintex7", "xc7k325tffg676", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("redpitaya14", "xc7z010clg400", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("runber", "", "ft232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("spartanEdgeAccelBoard", "", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("spec45", "xc6slx45tfgg484", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("spec150", "xc6slx150tfgg484", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("stlv7325", "xc7k325tffg676", "ft4232", SPI_FLASH, 0, 0, CABLE_MHZ(3)), + JTAG_BOARD("tangconsole", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("tangnano", "", "ch552_jtag", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("tangnano1k", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("tangnano4k", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("tangnano9k", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("tangnano20k", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("tangprimer20k", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("tangprimer25k", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("tangmega138k", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("te0712_8", "xc7a200tfbg484", "", SPI_FLASH, 0, 0, CABLE_MHZ(15)), + JTAG_BOARD("tec0117", "", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("tec0330", "xc7vx330tffg1157", "", SPI_FLASH, 0, 0, CABLE_MHZ(15)), SPI_BOARD("titanium_ti60_f225","efinix", "titanium", "efinix_spi_ft4232", DBUS4, DBUS5, DBUS7, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT), - JTAG_BOARD("titanium_ti60_f225_jtag", "ti60f225","efinix_jtag_ft4232", 0, 0, CABLE_DEFAULT), + JTAG_BOARD("titanium_ti60_f225_jtag", "ti60f225","efinix_jtag_ft4232", SPI_FLASH, 0, 0, CABLE_DEFAULT), SPI_BOARD("trion_t20_bga256", "efinix", "trion", "efinix_spi_ft2232", DBUS4, DBUS5, 0, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT), - JTAG_BOARD("trion_t20_bga256_jtag", "t20f256", "efinix_jtag_ft2232", 0, 0, CABLE_DEFAULT), + JTAG_BOARD("trion_t20_bga256_jtag", "t20f256", "efinix_jtag_ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), SPI_BOARD("trion_t120_bga576", "efinix", "trion", "efinix_spi_ft2232", DBUS4, DBUS5, DBUS7, DBUS3, DBUS0, DBUS1, DBUS2, DBUS6, 0, CABLE_DEFAULT), - JTAG_BOARD("trion_t120_bga576_jtag", "", "ft2232_b", 0, 0, CABLE_DEFAULT), - JTAG_BITBANG_BOARD("ulx2s", "", "ft232RL", 0, 0, + JTAG_BOARD("trion_t120_bga576_jtag", "", "ft2232_b", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BITBANG_BOARD("ulx2s", "", "ft232RL", SPI_FLASH, 0, 0, FT232RL_RI, FT232RL_DSR, FT232RL_CTS, FT232RL_DCD, CABLE_DEFAULT), - JTAG_BITBANG_BOARD("ulx3s", "", "ft231X", 0, 0, + JTAG_BITBANG_BOARD("ulx3s", "", "ft231X", SPI_FLASH, 0, 0, FT232RL_DCD, FT232RL_DSR, FT232RL_RI, FT232RL_CTS, CABLE_DEFAULT), DFU_BOARD("ulx3s_dfu", "", "dfu", 0x1d50, 0x614b, 0), - JTAG_BOARD("ulx3s_esp", "", "esp32s3", 0, 0, CABLE_DEFAULT), + JTAG_BOARD("ulx3s_esp", "", "esp32s3", SPI_FLASH, 0, 0, CABLE_DEFAULT), DFU_BOARD("ulx4m_dfu", "", "dfu", 0x1d50, 0x614b, 0), - JTAG_BOARD("usrpx300", "xc7k325tffg900", "digilent", 0, 0, CABLE_MHZ(15)), - JTAG_BOARD("usrpx310", "xc7k410tffg900", "digilent", 0, 0, CABLE_MHZ(15)), - JTAG_BOARD("vec_v6", "xc6vlx130tff784", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("vc709", "xc7vx690tffg1761", "digilent", 0, 0, CABLE_MHZ(15)), - JTAG_BOARD("vcu108", "xcvu095-ffva2104", "jtag-smt2-nc", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("vcu118", "xcvu9p-flga2104", "jtag-smt2-nc", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("vcu128", "xcvu37p-fsvh2892", "ft4232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("vcu1525", "xcvu9p-fsgd2104", "ft4232", 0, 0, CABLE_MHZ(15)), - JTAG_BOARD("xem8320", "xcau25p-2ffvb676", "" , 0, 0, CABLE_DEFAULT), - JTAG_BOARD("xyloni_jtag", "t8f81", "efinix_jtag_ft4232", 0, 0, CABLE_DEFAULT), + JTAG_BOARD("usrpx300", "xc7k325tffg900", "digilent", SPI_FLASH, 0, 0, CABLE_MHZ(15)), + JTAG_BOARD("usrpx310", "xc7k410tffg900", "digilent", SPI_FLASH, 0, 0, CABLE_MHZ(15)), + JTAG_BOARD("vec_v6", "xc6vlx130tff784", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("vc709", "xc7vx690tffg1761", "digilent", SPI_FLASH, 0, 0, CABLE_MHZ(15)), + JTAG_BOARD("vcu108", "xcvu095-ffva2104", "jtag-smt2-nc", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("vcu118", "xcvu9p-flga2104", "jtag-smt2-nc", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("vcu128", "xcvu37p-fsvh2892", "ft4232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("vcu1525", "xcvu9p-fsgd2104", "ft4232", SPI_FLASH, 0, 0, CABLE_MHZ(15)), + JTAG_BOARD("xem8320", "xcau25p-2ffvb676", "" , SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("xyloni_jtag", "t8f81", "efinix_jtag_ft4232", SPI_FLASH, 0, 0, CABLE_DEFAULT), 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("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), - JTAG_BOARD("zcu106", "xczu7evffvc1156", "jtag-smt2-nc", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("zedboard", "xc7z020clg484", "digilent_hs2", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("zybo_z7_10", "xc7z010clg400", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("zybo_z7_20", "xc7z020clg400", "digilent", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("mini_itx", "xc7z100ffg900", "jtag-smt2-nc", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("vmm3", "xc7s50csga324", "ft2232", 0, 0, CABLE_DEFAULT), - JTAG_BOARD("step-max10_v1", "10m02scm153c8g", "usb-blaster",0, 0, CABLE_DEFAULT), - JTAG_BOARD("step-mxo2_v2", "lcmxo2-4000hc-4mg132cc", "ft232",0, 0, CABLE_DEFAULT) + JTAG_BOARD("xtrx", "xc7a50tcpg236", "", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("ypcb003381p1", "xc7k480tffg1156", "", BPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("zc702", "xc7z020clg484", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("zc706", "xc7z045ffg900", "jtag-smt2-nc", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("zcu102", "xczu9egffvb1156", "jtag-smt2-nc", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("zcu106", "xczu7evffvc1156", "jtag-smt2-nc", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("zedboard", "xc7z020clg484", "digilent_hs2", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("zybo_z7_10", "xc7z010clg400", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("zybo_z7_20", "xc7z020clg400", "digilent", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("mini_itx", "xc7z100ffg900", "jtag-smt2-nc", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("vmm3", "xc7s50csga324", "ft2232", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("step-max10_v1", "10m02scm153c8g", "usb-blaster", SPI_FLASH, 0, 0, CABLE_DEFAULT), + JTAG_BOARD("step-mxo2_v2", "lcmxo2-4000hc-4mg132cc", "ft232", SPI_FLASH, 0, 0, CABLE_DEFAULT) }; #endif diff --git a/src/bpiFlash.cpp b/src/bpiFlash.cpp new file mode 100644 index 0000000..95926c0 --- /dev/null +++ b/src/bpiFlash.cpp @@ -0,0 +1,583 @@ +// 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 + +#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), + _has_burst(false) +{ +} + +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 + * 0x4 = Burst write (addr + count + N×data words) + */ + +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(); +} + +void BPIFlash::bpi_write_no_flush(uint32_t word_addr, uint16_t data) +{ + /* Same packet as bpi_write() but no shiftIR or flush — + * caller sets IR once before the loop and flushes once after. + */ + 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); + + 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] */ + + for (int i = 0; i < 8; i++) { + tx[i] = (packet >> (i * 8)) & 0xFF; + } + + _jtag->shiftDR(tx, NULL, total_bits); +} + +void BPIFlash::bpi_burst_write(uint32_t word_addr, const uint16_t *data, + uint32_t count) +{ + if (count == 0) + return; + + /* Burst packet: start(1) + cmd(4) + addr(25) + count(16) + N×(data(16) + pad(21)) + * Header: 46 bits. Per word: 37 bits. + */ + const uint32_t header_bits = 1 + 4 + 25 + 16; /* 46 */ + const uint32_t per_word_bits = 16 + 21; /* 37: 20 exec cycles + 1 transition */ + const uint32_t total_bits = header_bits + count * per_word_bits; + const uint32_t total_bytes = (total_bits + 7) / 8; + + std::vector tx(total_bytes, 0); + + /* Helper to set a single bit in the tx buffer */ + auto set_bit = [&](uint32_t bit_pos) { + tx[bit_pos / 8] |= (1 << (bit_pos % 8)); + }; + + /* Pack header LSB-first */ + uint32_t pos = 0; + + /* start bit = 1 */ + set_bit(pos); + pos++; + + /* cmd = CMD_BURST_WRITE (4 bits) */ + for (int i = 0; i < 4; i++) { + if (CMD_BURST_WRITE & (1 << i)) + set_bit(pos); + pos++; + } + + /* addr (25 bits) */ + for (int i = 0; i < 25; i++) { + if (word_addr & (1u << i)) + set_bit(pos); + pos++; + } + + /* count (16 bits) */ + for (int i = 0; i < 16; i++) { + if (count & (1u << i)) + set_bit(pos); + pos++; + } + + /* Pack each data word: 16 data bits + 21 padding bits */ + for (uint32_t w = 0; w < count; w++) { + for (int i = 0; i < 16; i++) { + if (data[w] & (1 << i)) + set_bit(pos); + pos++; + } + pos += 21; /* 20 exec cycles + 1 transition cycle */ + } + + uint8_t user1[] = {0x02}; + _jtag->shiftIR(user1, NULL, _irlen); + _jtag->shiftDR(tx.data(), 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)"); + + /* Enable burst write — assumes v02.00+ JTAG bitstream is loaded. + * Future: could auto-detect via USER4 version readback. + */ + _has_burst = true; + + 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) { + /* Send READ_ARRAY to the erased block's address (not addr 0), + * because multi-bank flash requires per-bank mode commands */ + bpi_write(word_addr, 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) + * 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; + } + + /* 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); + } + + /* 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] + */ + std::vector word_buf(chunk_words); + 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]; + word_buf[w] = (reverseByte(b0) << 8) | reverseByte(b1); + } + + /* Buffered Program Setup - sent to block/colony base address */ + bpi_write(0, FLASH_CMD_CLEAR_STATUS); + bpi_write(block_word_addr, FLASH_CMD_BUFFERED_PRG); + bpi_write(block_word_addr, chunk_words - 1); + + if (_has_burst) { + bpi_burst_write(word_addr, word_buf.data(), chunk_words); + } else { + /* Software-only fallback: one IR, no per-word flush */ + uint8_t user1[] = {0x02}; + _jtag->shiftIR(user1, NULL, _irlen); + for (uint32_t w = 0; w < chunk_words; w++) { + bpi_write_no_flush(word_addr + w, word_buf[w]); + } + _jtag->flush(); + } + + /* 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; + } + + 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..0b03f4c --- /dev/null +++ b/src/bpiFlash.hpp @@ -0,0 +1,140 @@ +// 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; + static const uint8_t CMD_BURST_WRITE = 0x4; + + /* 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 Write a 16-bit word without IR shift or flush (for batched writes) + */ + void bpi_write_no_flush(uint32_t word_addr, uint16_t data); + + /*! + * \brief Burst write multiple 16-bit words in a single DR shift + */ + void bpi_burst_write(uint32_t word_addr, const uint16_t *data, + uint32_t count); + + /*! + * \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; + bool _has_burst; +}; + +#endif // SRC_BPIFLASH_HPP_ diff --git a/src/main.cpp b/src/main.cpp index 15b0152..aae6978 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -100,6 +100,7 @@ struct arguments { unsigned int file_size; string target_flash; bool external_flash; + bool spi_flash_type; int16_t altsetting; uint16_t vid; uint16_t pid; @@ -150,8 +151,8 @@ int main(int argc, char **argv) -1, 0, false, "-", false, false, false, false, Device::PRG_NONE, false, /* spi dfu file_type fpga_part bridge_path probe_firmware */ false, false, "", "", "", "", - /* index_chain file_size target_flash external_flash altsetting */ - -1, 0, "primary", false, -1, + /* index_chain file_size target_flash external_flash spi_flash_type altsetting */ + -1, 0, "primary", false, true, -1, /* vid, pid, index bus_addr, device_addr */ 0, 0, -1, 0, 0, "127.0.0.1", 0, false, false, false, false, "", false, false, @@ -223,6 +224,7 @@ int main(int argc, char **argv) */ if (args.freq == 0) args.freq = board->default_freq; + args.spi_flash_type = board->spi_bpi == SPI_FLASH; } if (args.cable[0] == '-') { /* if no board and no cable */ @@ -656,7 +658,7 @@ int main(int argc, char **argv) if (fab == "xilinx") { #ifdef ENABLE_XILINX_SUPPORT fpga = new Xilinx(jtag, args.bit_file, args.secondary_bit_file, - args.file_type, args.prg_type, args.fpga_part, args.bridge_path, + args.file_type, args.prg_type, args.fpga_part, args.spi_flash_type, args.bridge_path, args.target_flash, args.verify, args.verbose, args.skip_load_bridge, args.skip_reset, args.read_dna, args.read_xadc); #else diff --git a/src/xilinx.cpp b/src/xilinx.cpp index 43f9fad..35e8d38 100644 --- a/src/xilinx.cpp +++ b/src/xilinx.cpp @@ -272,7 +272,9 @@ Xilinx::Xilinx(Jtag *jtag, const std::string &filename, const std::string &secondary_filename, const std::string &file_type, Device::prog_type_t prg_type, - const std::string &device_package, const std::string &spiOverJtagPath, + const std::string &device_package, + const bool spi_flash_type, + const std::string &spiOverJtagPath, const std::string &target_flash, bool verify, int8_t verbose, bool skip_load_bridge, bool skip_reset, bool read_dna, bool read_xadc): @@ -281,7 +283,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(~spi_flash_type) { if (prg_type == Device::RD_FLASH) { _mode = Device::READ_MODE; @@ -654,17 +656,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 +751,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}; @@ -807,11 +876,11 @@ void Xilinx::program_mem(ConfigBitstreamParser *bitfile) * the TLR (Test-Logic-Reset) state. */ _jtag->shiftIR(get_ircode(_ircode_map, "JPROGRAM"), NULL, _irlen); - /* test */ + /* Poll INIT_B (bit 4 of IR capture) until config memory is cleared */ tx_buf = get_ircode(_ircode_map, "BYPASS"); do { _jtag->shiftIR(tx_buf, rx_buf, _irlen); - } while (!(rx_buf[0] &0x01)); + } while (!(rx_buf[0] & 0x10)); /* * 8: Move into the RTI state. X 0 10,000(1) */ diff --git a/src/xilinx.hpp b/src/xilinx.hpp index 9b366ac..bac4978 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" @@ -23,6 +25,7 @@ class Xilinx: public Device, SPIInterface { const std::string &file_type, Device::prog_type_t prg_type, const std::string &device_package, + const bool spi_flash_type, const std::string &spiOverJtagPath, const std::string &target_flash, bool verify, int8_t verbose, @@ -33,6 +36,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 +224,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 +277,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_