N25Q128是一个128 Mbit(16Mb x 8)串行闪存,具有先进的写保护机制。它可以通过高速spi兼容的总线访问,并可以在XIP模式下工作。
引脚功能如下所示:
该芯片支持两种模式运行:
CPOL=0, CPHA=0
CPOL=1, CPHA=1

N25Q128内存可以使用3种不同的串行协议,该设备可以在三种不同的协议中工作:扩展SPI、DIO-SPI和QIO-SPI。每个协议都有一个专用的指令集,并且每个指令集都具有相同的功能:

标准SPI协议通过新的四线和双线指令得到了增强(扩展SPI协议)。对于双线输入输出SPI(DIO-SPI),所有的指令代码、地址和数据总是通过两条数据线传输。对于四线输入输出SPI(QIO-SPI),指令代码、地址和数据总是通过四条数据线传输,从而实现了随机访问时间和数据吞吐量的巨大提升。
芯片可以工作在“XIP模式”下,这意味着设备只需要地址而不需要指令就能输出数据。这种模式显著减少了随机访问时间,从而使得许多需要快速代码执行的应用程序能够在不将内存内容影射到RAM的情况下运行。

对Flash进行操作的时候需要对参数有所了解
1、4KBytes为1个Sector(扇区);
2、16个Sector(扇区)是1个Block(块)64KBytes;
3、容量为16M=128Mbit字节,共有256个Block,4096个Sector
2、16个Sector(扇区)是1个Block(块)64KBytes;
3、容量为16M=128Mbit字节,共有256个Block,4096个Sector


这里主要介绍Extendend SPI模式下的单线操作,并且只使用了以下几个操作:
1、RDID:读取Flash的识别码;
2、WREN:写使能;
3、SSE:Sector擦除
4、PP:页编程;
5、READ:读取数据;
擦除前要设置一次写使能,写入数据前也需要设置一次写使能;


模块接口,这里主要是验证时序是否正确,所以没有留下完整的数据接口,只保留了几个可以用vio来验证的接口:
module spi_driver(
input clk_i ,//50MHz
input rst_i ,
input start_i ,//开始信号,上升沿触发
input [7:0] cmd_i ,//指令
input [23:0] addr_i ,//读写地址
output spi_clk_o ,
output spi_cs_o ,
output spi_mosi_o ,
input spi_miso_i
);
常用的几个指令
// 基础读写指令
localparam READ_DATA = 8'h03; // 标准读取[1,3,6](@ref)
localparam FAST_READ = 8'h0B; // 单线快速读取[2,6](@ref)
localparam PAGE_PROGRAM = 8'h02; // 单线页编程[1,3,6](@ref)
localparam WRITE_ENABLE = 8'h06; // 写使能[1,3,6](@ref)
localparam WRITE_DISABLE = 8'h04; // 写禁用
// 擦除指令
localparam SUBSECTOR_ERASE = 8'h20; // 4KB子扇区擦除[1,5,7](@ref)
localparam SECTOR_ERASE = 8'hD8; // 64KB主扇区擦除[5,6](@ref)
localparam CHIP_ERASE = 8'hC7; // 全片擦除[5,7](@ref)
// 状态与配置指令
localparam READ_STATUS_REG = 8'h05; // 读取状态寄存器[1,6](@ref)
localparam WRITE_STATUS_REG = 8'h01; // 写入状态寄存器[6](@ref)
// 多线模式指令
localparam QUAD_OUTPUT_FAST_READ = 8'h6B; // 四线输出快速读取(QOFR)[2](@ref)
localparam QUAD_IO_FAST_READ = 8'hEB; // 四线I/O快速读取(QIOFR)[2](@ref)
localparam QUAD_INPUT_FAST_PROGRAM = 8'h32; // 四线输入快速编程(QIFP)[2](@ref)
// 标识指令
localparam READ_JEDEC_ID = 8'h9F; // 读取JEDEC ID[6,7](@ref)
localparam READ_UNIQUE_ID = 8'h4B; // 读取唯一标识符[6](@ref)
测试逻辑spi_driver.v:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2025/03/27 22:27:22
// Design Name:
// Module Name: spi_driver
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module spi_driver(
input clk_i ,//50MHz
input rst_i ,
input start_i ,//开始信号,上升沿触发
input [7:0] cmd_i ,//指令
input [23:0] addr_i ,//读写地址
output spi_clk_o ,
output spi_cs_o ,
output spi_mosi_o ,
input spi_miso_i
);
parameter DIV_NUM = 4;
parameter DIV_HALF = DIV_NUM/2;
// 基础读写指令
localparam READ_DATA = 8'h03; // 标准读取[1,3,6](@ref)
localparam FAST_READ = 8'h0B; // 单线快速读取[2,6](@ref)
localparam PAGE_PROGRAM = 8'h02; // 单线页编程[1,3,6](@ref)
localparam WRITE_ENABLE = 8'h06; // 写使能[1,3,6](@ref)
localparam WRITE_DISABLE = 8'h04; // 写禁用
// 擦除指令
localparam SUBSECTOR_ERASE = 8'h20; // 4KB子扇区擦除[1,5,7](@ref)
localparam SECTOR_ERASE = 8'hD8; // 64KB主扇区擦除[5,6](@ref)
localparam CHIP_ERASE = 8'hC7; // 全片擦除[5,7](@ref)
// 状态与配置指令
localparam READ_STATUS_REG = 8'h05; // 读取状态寄存器[1,6](@ref)
localparam WRITE_STATUS_REG = 8'h01; // 写入状态寄存器[6](@ref)
// 多线模式指令
localparam QUAD_OUTPUT_FAST_READ = 8'h6B; // 四线输出快速读取(QOFR)[2](@ref)
localparam QUAD_IO_FAST_READ = 8'hEB; // 四线I/O快速读取(QIOFR)[2](@ref)
localparam QUAD_INPUT_FAST_PROGRAM = 8'h32; // 四线输入快速编程(QIFP)[2](@ref)
// 标识指令
localparam READ_JEDEC_ID = 8'h9F; // 读取JEDEC ID[6,7](@ref)
localparam READ_UNIQUE_ID = 8'h4B; // 读取唯一标识符[6](@ref)
reg start_d0;
reg start_d1;
wire start_pdge;
reg [7:0]rec_data;
always@(posedge clk_i)begin
start_d0 <= start_i;
start_d1 <= start_d0;
end
assign start_pdge = start_d0 & (!start_d1);
reg work_en;
always@(posedge clk_i)begin
if(rst_i)begin
work_en <= 1'b0;
end
else if(start_pdge)begin
work_en <= 1'b1;
end
end
reg [3:0]div_cnt;
reg spi_clk;
reg [3:0]state;
localparam IDLE = 4'd0;
localparam WRITE_CMD = 4'd1;
localparam READ_ID = 4'd2;
localparam WRITE_ERASER_ADDR = 4'd3;
localparam WRITE_DATA_ADDR = 4'd4;
localparam WRITE_DATA = 4'd5;
localparam READ_Flash_DATA_ADDR = 4'd6;
localparam READ_Flash_DATA = 4'd7;
localparam DONE = 4'd15;
reg [15:0]byte_cnt;
reg [7:0]rec_byte;
reg spi_cs;
reg spi_mosi;
wire spi_miso;
reg [3:0]bit_cnt;
always@(posedge clk_i)begin
if(rst_i)begin
div_cnt <= 4'd0;
end
else if(spi_cs == 1'b0)begin
if(div_cnt >= (DIV_NUM-1'b1))begin
div_cnt <= 4'd0;
end
else begin
div_cnt <= div_cnt + 1'b1;
end
end
else begin
div_cnt <= 4'd0;
end
end
always@(posedge clk_i)begin
if(rst_i)begin
spi_clk <= 4'd1;
end
else if(start_pdge)begin
spi_clk <= 4'd0;
end
else if(spi_cs==1'b0)begin
if(div_cnt == (DIV_HALF-1'b1))begin
spi_clk <= 4'd1;
end
else if(div_cnt == (DIV_NUM-1'b1))begin
spi_clk <= 4'd0;
end
end
else begin
spi_clk <= 4'd1;
end
end
always@(posedge clk_i)begin
if(rst_i)begin
bit_cnt <= 4'd0;
end
else begin
if(div_cnt == (DIV_NUM-1'b1))begin
if(bit_cnt >= 4'd7)begin
bit_cnt <= 4'd0;
end
else begin
bit_cnt <= bit_cnt + 1'b1;
end
end
end
end
reg [7:0]test_data;
always@(posedge clk_i)begin
if(rst_i)begin
state <= 4'd0;
spi_cs <= 1'b1;
byte_cnt <= 16'd0;
test_data <= 8'd240;
end
else begin
case(state)
IDLE:begin
if(start_pdge)begin
spi_cs <= 1'b0;
state <= WRITE_CMD;
byte_cnt <= 16'd0;
test_data <= 8'd240;
end
else begin
state <= IDLE;
end
end
WRITE_CMD:begin
if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
case(cmd_i)
READ_JEDEC_ID:state <= READ_ID;
WRITE_ENABLE: state <= DONE;
WRITE_DISABLE:state <= DONE;
SUBSECTOR_ERASE:state <= WRITE_ERASER_ADDR;
PAGE_PROGRAM : state <= WRITE_DATA_ADDR;
READ_DATA:state <= READ_Flash_DATA_ADDR;
endcase
end
else begin
state <= WRITE_CMD;
end
end
READ_ID:begin
if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1))&&(byte_cnt>=16'd16))begin
byte_cnt <= 16'd0;
state <= DONE;
spi_cs <= 1'b1;
end
else if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
byte_cnt <= byte_cnt + 1'b1;
end
end
WRITE_ERASER_ADDR:begin
if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1))&&(byte_cnt>=16'd2))begin
state <= DONE;
byte_cnt <= 16'd0;
spi_cs <= 1'b1;
end
else if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
byte_cnt <= byte_cnt + 1'b1;
end
end
WRITE_DATA_ADDR:begin
if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1))&&(byte_cnt>=16'd2))begin
state <= WRITE_DATA;
byte_cnt <= 16'd0;
end
else if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
byte_cnt <= byte_cnt + 1'b1;
end
end
WRITE_DATA:begin
if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1))&&(byte_cnt>=16'd255))begin
state <= DONE;
byte_cnt <= 16'd0;
spi_cs <= 1'b1;
end
else if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
byte_cnt <= byte_cnt + 1'b1;
test_data <= test_data+1'b1;
end
end
READ_Flash_DATA_ADDR:begin
if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1))&&(byte_cnt>=16'd2))begin
state <= READ_Flash_DATA;
byte_cnt <= 16'd0;
end
else if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
byte_cnt <= byte_cnt + 1'b1;
end
end
READ_Flash_DATA:begin
if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1))&&(byte_cnt>=16'd255))begin
state <= DONE;
spi_cs <= 1'b1;
byte_cnt <= 16'd0;
end
else if((bit_cnt>=4'd7)&&(div_cnt>=(DIV_NUM-1'b1)))begin
byte_cnt <= byte_cnt + 1'b1;
end
end
DONE:begin
spi_cs <= 1'b1;
byte_cnt <= 16'd0;
state <= IDLE;
byte_cnt <= 16'd0;
end
default:begin
end
endcase
end
end
reg [23:0]cnt;
always@(*)begin
case(state)
WRITE_CMD:begin
spi_mosi = cmd_i[7-bit_cnt];
end
WRITE_ERASER_ADDR:begin
cnt = 23-(byte_cnt*8+bit_cnt);
spi_mosi = addr_i[23-(byte_cnt*8+bit_cnt)];
end
WRITE_DATA_ADDR:begin
spi_mosi = addr_i[23-(byte_cnt*8+bit_cnt)];
end
WRITE_DATA:begin
spi_mosi = test_data[7-bit_cnt];
end
READ_Flash_DATA_ADDR:begin
spi_mosi = addr_i[23-(byte_cnt*8+bit_cnt)];
end
endcase
end
assign spi_mosi_o = (spi_cs==1'b0) ? spi_mosi : 1'b1;
always@(posedge clk_i)begin
if(rst_i)begin
rec_byte <= 8'd0;
end
else begin
case(state)
READ_ID:begin
if(div_cnt == DIV_HALF)begin
rec_byte[7-bit_cnt] <= spi_miso_i;
end
end
READ_Flash_DATA:begin
if(div_cnt == DIV_HALF)begin
rec_byte[7-bit_cnt] <= spi_miso_i;
end
end
endcase
end
end
always@(posedge clk_i)begin
if(rst_i)begin
rec_data <= 8'd0;
end
else begin
case(state)
READ_ID:begin
if((bit_cnt == 4'd0)&&(div_cnt==4'd0))begin
rec_data <= rec_byte;
end
end
READ_Flash_DATA:begin
if((bit_cnt == 4'd0)&&(div_cnt==4'd0))begin
rec_data <= rec_byte;
end
end
endcase
end
end
assign spi_cs_o = spi_cs;
assign spi_clk_o = spi_clk;
ila_0 your_instance_name (
.clk(clk_i), // input wire clk
.probe0({
data_req_o ,
data_i ,
data_vld_o ,
data_o ,
spi_clk_o ,
spi_cs ,
spi_mosi_o ,
spi_miso_i ,
state ,
spi_clk,
rec_byte ,
byte_cnt ,
bit_cnt ,
rec_data
}) // input wire [127:0] probe0
);
endmodule
顶层文件SPI_TOP.v
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2025/03/29 14:25:52
// Design Name:
// Module Name: SPI_TOP
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module SPI_TOP(
input wire clk_sys , //OXCO_10M
input wire sys_rst_n ,
output spi_cs ,
output spi_mosi_o ,
input spi_miso_i
);
wire spi_clk ;
wire [7:0] flash_instruction ;
wire [23:0] flash_addr ;
vio_0 u_vio_0 (
.clk(clk_sys), // input wire clk
.probe_out0(i_start), // output wire [0 : 0] probe_out0
.probe_out1(flash_instruction), // output wire [7 : 0] probe_out1
.probe_out2(flash_addr) // output wire [23 : 0] probe_out2
);
//原语
STARTUPE2 #(
.PROG_USR("FALSE"), // Activate program event security feature. Requires encrypted bitstreams.
.SIM_CCLK_FREQ(0.0) // Set the Configuration Clock Frequency(ns) for simulation
)
STARTUPE2_inst(
.CFGCLK(), // 1-bit output: Configuration main clock output
.CFGMCLK(), // 1-bit output: Configuration internal oscillator clock output
.EOS(), // 1-bit output: Active high output signal indicating the End Of Startup.
.PREQ(), // 1-bit output: PROGRAM request to fabric output
.CLK(0), // 1-bit input: User start-up clock input
.GSR(0), // 1-bit input: Global Set/Reset input (GSR cannot be used for the port name)
.GTS(0), // 1-bit input: Global 3-state input (GTS cannot be used for the port name)
.KEYCLEARB(1), // 1-bit input: Clear AES Decrypter Key input from Battery-Backed RAM (BBRAM)
.PACK(1), // 1-bit input: PROGRAM acknowledge input
.USRCCLKO(spi_clk), // 1-bit input: User CCLK input**SPI CLK**
.USRCCLKTS(0), // 1-bit input: User CCLK 3-state enable input
.USRDONEO(1), // 1-bit input: User DONE pin output control
.USRDONETS(1) // 1-bit input: User DONE 3-state enable outpu
);
spi_driver u_spi_driver(
.clk_i (clk_sys ),
.rst_i (!sys_rst_n ),
.start_i (i_start ),
.cmd_i (flash_instruction ),
.addr_i (flash_addr ),
.spi_clk_o (spi_clk ),
.spi_cs_o (spi_cs ),
.spi_mosi_o (spi_mosi_o ),
.spi_miso_i (spi_miso_i )
);
endmodule
时序仿真,读取和写入的一致,满足要求: