本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。
系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。后续会陆续更新 Xilinx 的 Vivado、ISE 及相关操作软件的开发的相关内容,学习FPGA设计方法及设计思想的同时,实操结合各类操作软件,会让你在技术学习道路上无比的顺畅,告别技术学习小BUG卡破脑壳,告别目前忽悠性的培训诱导,真正的去学习去实战应用。话不多说,上货。
SPI 协议驱动设计
作者:郝旭帅 校对:陆辉
本篇实现基于叁芯智能科技的SANXIN -B01 FPGA开发板,以下为配套的教程,如有入手开发板,可以登录官方淘宝店购买,还有配套的学习视频。
SANXIN-B01 Verilog教程-郝旭帅团队
SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议。
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,中间靠三线或者四线连接(三线时为单向传输或者数据线双向传输)。所有基于SPI的设备共有的,它们是MISO、MOSI、SCLK、CS。
MISO– Master Input Slave Output,主设备数据输入,从设备数据输出。
MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入。
SCLK – Serial Clock,时钟信号,由主设备产生。
CS – Chip Select,从设备使能信号,由主设备控制。
cs是从芯片是否被主芯片选中的控制信号,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),主芯片对此从芯片的操作才有效。这就使在同一条总线上连接多个spi设备成为可能。
通讯是通过数据交换完成的,由sclk提供时钟脉冲,mosi、miso则基于此脉冲完成数据传输。数据输出通过 mosi线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。因此,至少需要N次时钟信号的改变(上沿和下沿为一次),才能完成N位数据的传输。
spi通信有四种不同的模式,不同的从设备可能在出厂时就已经配置为某种模式。通信的双方必须是工作在同一模式下,所以我们可以对主设备的spi模式进行配置,通过CPOL(时钟极性)和CPHA(时钟相位)来控制我们主设备的通信模式。
mode0:CPOL=0,CPHA=0;
mode1:CPOL=0,CPHA=1;
mode2:CPOL=1,CPHA=0;
mode3:CPOL=1,CPHA=1;
时钟极性CPOL是用来配置SCLK在空闲时,应该处于的状态;时钟相位CPHA用来配置在第几个边沿进行采样。
CPOL=0,表示在空闲状态时,时钟SCLK为低电平。
CPOL=1,表示在空闲状态时,时钟SCLK为高电平。
CPHA=0,表示数据采样是在第1个边沿。
CPHA=1,表示数据采样是在第2个边沿。
即:
CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿。
CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。
CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。
CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。
FLASH闪存 的英文名称是"Flash Memory",一般简称为"Flash",它属于内存器件的一种,是一种非易失性( Non-Volatile )内存。
在开发板上有一块flash(M25P16),用来保存FPGA的硬件配置信息,也可以用来存储用户的应用程序或数据。
M25P16是一款带有写保护机制和高速SPI总线访问的2M字节串行Flash存储器,该存储器主要特点:2M字节的存储空间,分32个扇区,每个扇区256页,每页256字节;能单个扇区擦除和整片擦除;每扇区擦写次数保证10万次、数据保存期限至少20年。
C(serial clock:串行时钟)为D和Q提供了数据输入或者输出的时序。D的数据总是在C的上升沿被采样。Q的数据 在C的下降沿被输出。
(Chip Select:芯片选择端),当输入为低时,该芯片被选中,可以允许进行读写操作。当输入为高时,该芯片被释放,不能够进行操作。
对于Hold和W, 为保持功能和硬件写保护功能,在本设计中不使用此管脚,在硬件设计时,这两个管脚全部被拉高了,即全部失效。
flash采用spi的通信协议,flash当做从机。serial clcok等效于spi中的sclk,chip select等效于spi中的cs,D等效于spi中的mosi,Q等效于spi中的miso。
flash可以支持mode0和mode3,这两种模式中,都是在时钟的上升沿采样,在时钟的下降沿发送数据。
flash的每一页都可以被写入,但是写入只能是把1改变为0。擦除可以把0改变为1。所以在正常写入数据之前,都要将flash进行擦除。
flash的命令表如下:
下面介绍几个常用的命令。
RDID(Read Identification :读ID):发送命令RDID(9F),然后接收第1个字节的memory type(20H),第二个字节的memory capacity(15H)。后续的字节暂不关心。
WREN(Write Enable :写使能):在任何写或者擦除的命令之前,都必须首先打开写使能。打开写使能为发送命令WREN(06h)。
RDSR(Read Status Register:读状态寄存器):发送命令RDSR(05h),然后返回一个字节的状态值。
状态寄存器的格式如下:
WIP(Write In Progress bit)表示flash内部是否正在进行内部操作,写和擦除都会导致flash内部进行一段时间的工作,在内部工作期间,外部的命令会被忽略,所以在进行任何命令之前,都需要查看flash内部是否正在工作。WIP为1时,表示flash内部正在工作;WIP为0时,表示flash内部没有在工作。
READ(Read DATA Bytes:读数据):发送命令READ(03H),后续发送3个字节的地址,然后就可以接收数据,内部的地址会不断递增。一个读命令就可以把整个flash全部读完。
PP(Page Program :页编写):发送命令PP(02H),接着发送3个字节的地址,然后发送数据即可。切记所写的数据不能超过本页的地址范围。
SE(Sector Erase :扇区擦除):发送命令SE(D8H),接着发送3个字节的地址。
BE(Bulk Erase:整片擦除):发送命令BE(C7H)。
关于flash的其他的介绍,可以参考03_芯片手册->FLASH->M25P16.pdf。
设计flash(M25P16)控制器。
根据M25P16的数据手册得知,其接口为spi接口,且支持模式0和模式3,本设计中选择模式0。
输入时序图如下:
输出时序如下:
时序图中所对应的符号说明:
根据输入和输出的时序图以及参数表,将SPI的时钟的频率定为10MHz。
在设计中,FPGA作为主机,M25P16作为从机。
此模块命名为m25p16_drive。
二级模块(分模块)(第一页)
二级模块(分模块)(第二页)
设计中,各个命令单独写出控制器,通过多路选择器选择出对应的命令,然后控制spi_8bit_drive将数据按照spi的协议发送出去。各个命令的脉冲通过ctrl模块进行控制各个命令控制器,写入的数据首先写入到写缓冲区,读出的数据读出后写入到读缓冲区。
暂不分配的端口,在应用时都是由上游模块进行控制,本设计测试时,编写上游模块进行测试。
各个模块的功能,和连接线的功能在各个模块设计中说明。
本模块负责将8bit的并行数据按照spi协议发送出去,以及负责按照spi协议接收数据,将接收的数据(8bit)并行传输给各个模块。
spi_send_en为发送数据使能信号(脉冲信号),spi_send_data为所要发送数据,spi_send_done为发送完成信号(脉冲信号)。
spi_read_en为接收数据使能信号(脉冲信号),spi_read_data为所接收的数据,spi_read_done为接收完成信号(脉冲信号)。
module spi_8bit_drive (
input wire clk,
input wire rst_n,
input wire spi_send_en,
input wire [7:0] spi_send_data,
output reg spi_send_done,
input wire spi_read_en,
output reg [7:0] spi_read_data,
output reg spi_read_done,
output wire spi_sclk,
output wire spi_mosi,
input wire spi_miso
);
reg [8:0] send_data_buf;
reg [3:0] send_cnt;
reg rec_en;
reg rec_en_n;
reg [3:0] rec_cnt;
reg [7:0] rec_data_buf;
always @ (negedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
send_data_buf <= 9'd0;
else
if (spi_send_en == 1'b1)
send_data_buf <= {spi_send_data, 1'b0};
else
send_data_buf <= send_data_buf;
end
always @ (negedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
send_cnt <= 4'd8;
else
if (spi_send_en == 1'b1)
send_cnt <= 4'd0;
else
if (send_cnt < 4'd8)
send_cnt <= send_cnt + 1'b1;
else
send_cnt <= send_cnt;
end
assign spi_mosi = send_data_buf[8 - send_cnt];
assign spi_sclk = (send_cnt < 4'd8 || rec_en_n == 1'b1) ? clk : 1'b0;
always @ (negedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
spi_send_done <= 1'b0;
else
if (send_cnt == 4'd7)
spi_send_done <= 1'b1;
else
spi_send_done <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rec_en <= 1'b0;
else
if (spi_read_en == 1'b1)
rec_en <= 1'b1;
else
if (rec_cnt == 4'd7)
rec_en <= 1'b0;
else
rec_en <= rec_en;
end
always @ (negedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rec_en_n <= 1'b0;
else
rec_en_n <= rec_en;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rec_data_buf <= 8'd0;
else
if (rec_en == 1'b1)
rec_data_buf <= {rec_data_buf[6:0], spi_miso};
else
rec_data_buf <=rec_data_buf;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rec_cnt <= 4'd0;
else
if (rec_en == 1'b1)
rec_cnt <= rec_cnt + 1'b1;
else
rec_cnt <= 4'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
spi_read_done <= 1'b0;
else
if (rec_cnt == 4'd8)
spi_read_done <= 1'b1;
else
spi_read_done <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
spi_read_data <= 8'd0;
else
if (rec_cnt == 4'd8)
spi_read_data <= rec_data_buf;
else
spi_read_data <= spi_read_data;
end
endmodule
在发送逻辑控制中,全部的信号采用下降沿驱动。利用外部给予的spi_send_en作为启动信号,启动send_cnt。send_cnt在不发送数据时为8,发送数据时,从0到7。
在接收逻辑中,全部的信号采用上升沿驱动。利用外部给予的spi_read_en作为启动信号,启动rec_en,经过移位接收数据。
在spi_sclk输出时,采用组合逻辑。由于设计采用spi的模式0,故而spi_sclk不发送或者接收数据时为0,接收数据时为时钟信号。因为要求为模式0,所以在接收数据时,spi_sclk的输出不能够先有下降沿,即要求spi_sclk的控制信号不能由上升沿信号驱动,所以将rec_en同步到下降沿的rec_en_n。
仿真代码为:
1ns/1ps
module spi_8bit_drive_tb;
reg clk;
reg rst_n;
reg spi_send_en;
reg [7:0] spi_send_data;
wire spi_send_done;
reg spi_read_en;
wire [7:0] spi_read_data;
wire spi_read_done;
wire spi_sclk;
wire spi_mosi;
reg spi_miso;
spi_8bit_drive spi_8bit_drive_inst(
(clk),
(rst_n),
(spi_send_en),
(spi_send_data),
(spi_send_done),
(spi_read_en),
(spi_read_data),
(spi_read_done),
(spi_sclk),
(spi_mosi),
(spi_miso)
);
initial clk = 1'b0;
always # 50 clk = ~clk;
initial begin
rst_n = 1'b0;
spi_send_en = 1'b0;
spi_send_data = 8'd0;
spi_read_en = 1'b0;
spi_miso = 1'b0;
# 201
rst_n = 1'b1;
# 200
(posedge clk);
# 2;
spi_send_en = 1'b1;
spi_send_data = {$random} % 256;
(posedge clk);
# 2;
spi_send_en = 1'b0;
spi_send_data = 8'd0;
(posedge spi_send_done);
# 2000
(posedge clk);
# 2;
spi_read_en = 1'b1;
(posedge clk);
# 2;
spi_read_en = 1'b0;
(posedge spi_read_done);
# 200
$stop;
end
always @ (negedge clk) spi_miso <= {$random} % 2;
endmodule
在仿真中,将时钟设置为10MHz。
所有的信号采用上升沿驱动。发送一个8bit的随机数值,接收一个8bit的随机数值。
spi_miso信号为从机下降沿驱动信号。
通过RTL仿真,可以看出发送和接收全部正常。
本模块负责将7个命令模块发出的命令(写使能、写数据和读使能)经过选择发送给spi_8bit_drive模块。
module mux7_1 (
input wire rdsr_send_en,
input wire [7:0] rdsr_send_data,
input wire rdsr_read_en,
input wire pp_send_en,
input wire [7:0] pp_send_data,
input wire wren_send_en,
input wire [7:0] wren_send_data,
input wire be_send_en,
input wire [7:0] be_send_data,
input wire se_send_en,
input wire [7:0] se_send_data,
input wire rdid_send_en,
input wire [7:0] rdid_send_data,
input wire rdid_read_en,
input wire read_send_en,
input wire [7:0] read_send_data,
input wire read_read_en,
input wire [2:0] mux_sel,
output reg spi_send_en,
output reg [7:0] spi_send_data,
output reg spi_read_en
);
always @ * begin
case (mux_sel)
begin :
spi_send_en = rdsr_send_en;
spi_send_data = rdsr_send_data;
spi_read_en = rdsr_read_en;
end
begin :
spi_send_en = pp_send_en;
spi_send_data = pp_send_data;
spi_read_en = 1'b0;
end
begin :
spi_send_en = wren_send_en;
spi_send_data = wren_send_data;
spi_read_en = 1'b0;
end
begin :
spi_send_en = be_send_en;
spi_send_data = be_send_data;
spi_read_en = 1'b0;
end
begin :
spi_send_en = se_send_en;
spi_send_data = se_send_data;
spi_read_en = 1'b0;
end
begin :
spi_send_en = rdid_send_en;
spi_send_data = rdid_send_data;
spi_read_en = rdid_read_en;
end
begin :
spi_send_en = read_send_en;
spi_send_data = read_send_data;
spi_read_en = read_read_en;
end
default : begin
spi_send_en = 1'b0;
spi_send_data = 8'd0;
spi_read_en = 1'b0;
end
endcase
end
endmodule
在设计中,有的命令模块不需要进行读取(pp和se等等),此时将输出的读使能信号输出为低电平。
该模块接收到be_en(整片擦除的脉冲信号)信号后,发送对应的使能和数据,等待发送完成脉冲。发送完成后,输出擦除完成的脉冲。
module be (
input wire clk,
input wire rst_n,
input wire be_en,
output reg be_done,
output reg be_send_en,
output wire [7:0] be_send_data,
input wire spi_send_done
);
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
be_send_en <= 1'b0;
else
be_send_en <= be_en;
end
assign be_send_data = 8'hc7;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
be_done <= 1'b0;
else
be_done <= spi_send_done;
end
endmodule
整片擦除的命令为8’hc7。
该模块接收到wren_en(打开flash内部的写使能的脉冲信号)信号后,发送对应的使能和数据,等待发送完成脉冲。发送完成后,输出擦除完成的脉冲。
module wren (
input wire clk,
input wire rst_n,
input wire wren_en,
output reg wren_done,
output reg wren_send_en,
output wire [7:0] wren_send_data,
input wire spi_send_done
);
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wren_send_en <= 1'b0;
else
wren_send_en <= wren_en;
end
assign wren_send_data = 8'h06;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wren_done <= 1'b0;
else
wren_done <= spi_send_done;
end
endmodule
打开flash内部写使能的命令码为8’h06。
该模块接收到se_en(擦除扇区的写使能的脉冲信号)信号后,发送对应的使能和数据,等待发送完成脉冲。发送完成后,接着发送高八位地址,中间八位地址和低八位地址。全部发送完成后,发送se_done信号。
该模块采用状态机实现。SE_STATE(扇区擦除命令发送)、H_ADDR(高八位地址发送)、M_ADDR(中间八位地址发送)、L_ADDR(低八位地址发送)、SE_DONE(扇区擦除完成)。所有的脉冲信号在未标注的时刻,输出全部为0。
设计代码为:
module se (
input wire clk,
input wire rst_n,
input wire se_en,
input wire [23:0] se_addr,
output reg se_done,
output reg se_send_en,
output reg [7:0] se_send_data,
input wire spi_send_done
);
localparam SE_STATE = 5'b00001;
localparam H_ADDR = 5'b00010;
localparam M_ADDR = 5'b00100;
localparam L_ADDR = 5'b01000;
localparam SE_DONE = 5'b10000;
reg [4:0] c_state;
reg [4:0] n_state;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= SE_STATE;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
SE_STATE : begin
if (se_en == 1'b0)
n_state = SE_STATE;
else
n_state = H_ADDR;
end
H_ADDR : begin
if (spi_send_done == 1'b0)
n_state = H_ADDR;
else
n_state = M_ADDR;
end
M_ADDR : begin
if (spi_send_done == 1'b0)
n_state = M_ADDR;
else
n_state = L_ADDR;
end
L_ADDR : begin
if (spi_send_done == 1'b0)
n_state = L_ADDR;
else
n_state = SE_DONE;
end
SE_DONE : begin
if (spi_send_done == 1'b0)
n_state = SE_DONE;
else
n_state = SE_STATE;
end
default : n_state = SE_STATE;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
se_send_en <= 1'b0;
else
case (c_state)
SE_STATE : begin
if (se_en == 1'b1)
se_send_en <= 1'b1;
else
se_send_en <= 1'b0;
end
H_ADDR : begin
if (spi_send_done == 1'b1)
se_send_en <= 1'b1;
else
se_send_en <= 1'b0;
end
M_ADDR : begin
if (spi_send_done == 1'b1)
se_send_en <= 1'b1;
else
se_send_en <= 1'b0;
end
L_ADDR : begin
if (spi_send_done == 1'b1)
se_send_en <= 1'b1;
else
se_send_en <= 1'b0;
end
SE_DONE : begin
se_send_en <= 1'b0;
end
default : se_send_en <= 1'b0;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
se_send_data <= 8'd0;
else
case (c_state)
SE_STATE : begin
if (se_en == 1'b1)
se_send_data <= 8'hd8;
else
se_send_data <= 8'd0;
end
H_ADDR : begin
if (spi_send_done == 1'b1)
se_send_data <= se_addr[23:16];
else
se_send_data <= 8'd0;
end
M_ADDR : begin
if (spi_send_done == 1'b1)
se_send_data <= se_addr[15:8];
else
se_send_data <= 8'd0;
end
L_ADDR : begin
if (spi_send_done == 1'b1)
se_send_data <= se_addr[7:0];
else
se_send_data <= 8'd0;
end
SE_DONE : begin
se_send_data <= 8'd0;
end
default : se_send_data <= 8'd0;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
se_done <= 1'b0;
else
if (c_state == SE_DONE && spi_send_done == 1'b1)
se_done <= 1'b1;
else
se_done <= 1'b0;
end
endmodule
在发送过程中,由于是每8bit发送一次,所以在时序上将看到发送时,每8个脉冲一组,中间会有明显的间隔。
该模块负责将外部写fifo中的数据写入到flash中。wr_fifo_rd为写fifo的读使能信号,wrdata为从写fifo中读出的数据,wr_len为需要写入flash中数据的长度,wr_addr为写入地址。
该模块采用状态机实现。PP_STATE(发送pp命令),H_ADDR(发送高八位地址)、M_ADDR(发送中间八位地址),L_ADDR(发送低八位地址)、RDFIFO(读写fifo)、FIFO_WAIT(等待读写fifo的数据输出)、SEND(发送8bit数据)、SEND_WAIT(发送等待,发送完成后判断是否发送完成)。对于所有的脉冲信号,没有赋值的位置,全部赋值为0。
cnt为记录已经发送的数据个数。
设计代码为:
module pp (
input wire clk,
input wire rst_n,
input wire pp_en,
output reg pp_done,
output reg wr_fifo_rd,
input wire [7:0] wrdata,
input wire [8:0] wr_len,
input wire [23:0] wr_addr,
output reg pp_send_en,
output reg [7:0] pp_send_data,
input wire spi_send_done
);
localparam PP_STATE = 8'b0000_0001;
localparam H_ADDR = 8'b0000_0010;
localparam M_ADDR = 8'b0000_0100;
localparam L_ADDR = 8'b0000_1000;
localparam RDFIFO = 8'b0001_0000;
localparam FIFO_WAIT = 8'b0010_0000;
localparam SEND = 8'b0100_0000;
localparam SEND_WAIT = 8'b1000_0000;
reg [7:0] c_state;
reg [7:0] n_state;
reg [8:0] cnt;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= PP_STATE;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
PP_STATE : begin
if (pp_en == 1'b0)
n_state = PP_STATE;
else
n_state = H_ADDR;
end
H_ADDR : begin
if (spi_send_done == 1'b1)
n_state = M_ADDR;
else
n_state = H_ADDR;
end
M_ADDR : begin
if (spi_send_done == 1'b1)
n_state = L_ADDR;
else
n_state = M_ADDR;
end
L_ADDR : begin
if (spi_send_done == 1'b1)
n_state = RDFIFO;
else
n_state = L_ADDR;
end
RDFIFO : begin
if (spi_send_done == 1'b1)
n_state = FIFO_WAIT;
else
n_state = RDFIFO;
end
FIFO_WAIT : begin
n_state = SEND;
end
SEND : begin
n_state = SEND_WAIT;
end
SEND_WAIT : begin
if (spi_send_done == 1'b1)
if (cnt == wr_len)
n_state = PP_STATE;
else
n_state = FIFO_WAIT;
else
n_state = SEND_WAIT;
end
default : n_state = PP_STATE;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
pp_send_en <= 1'b0;
else
case (c_state)
PP_STATE : begin
if (pp_en == 1'b1)
pp_send_en <= 1'b1;
else
pp_send_en <= 1'b0;
end
H_ADDR : begin
if (spi_send_done == 1'b1)
pp_send_en <= 1'b1;
else
pp_send_en <= 1'b0;
end
M_ADDR : begin
if (spi_send_done == 1'b1)
pp_send_en <= 1'b1;
else
pp_send_en <= 1'b0;
end
L_ADDR : begin
if (spi_send_done == 1'b1)
pp_send_en <= 1'b1;
else
pp_send_en <= 1'b0;
end
SEND : begin
pp_send_en <= 1'b1;
end
default : pp_send_en <= 1'b0;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
pp_send_data <= 8'd0;
else
case (c_state)
PP_STATE : begin
if (pp_en == 1'b1)
pp_send_data <= 8'h02;
else
pp_send_data <= 8'd0;
end
H_ADDR : begin
if (spi_send_done == 1'b1)
pp_send_data <= wr_addr[23:16];
else
pp_send_data <= 8'd0;
end
M_ADDR : begin
if (spi_send_done == 1'b1)
pp_send_data <= wr_addr[15:8];
else
pp_send_data <= 8'd0;
end
L_ADDR : begin
if (spi_send_done == 1'b1)
pp_send_data <= wr_addr[7:0];
else
pp_send_data <= 8'd0;
end
SEND : begin
pp_send_data <= wrdata;
end
default : pp_send_data <= 8'd0;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
pp_done <= 1'b0;
else
if (c_state == SEND_WAIT && spi_send_done == 1'b1 && cnt == wr_len)
pp_done <= 1'b1;
else
pp_done <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
cnt <= 9'd0;
else
if ((c_state == RDFIFO && spi_send_done == 1'b1) || (c_state == SEND_WAIT && spi_send_done == 1'b1 && cnt < wr_len))
cnt <= cnt + 1'b1;
else
if (c_state == PP_STATE)
cnt <= 9'd0;
else
cnt <= cnt;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wr_fifo_rd <= 1'b1;
else
if ((c_state == RDFIFO && spi_send_done == 1'b1) || (c_state == SEND_WAIT && spi_send_done == 1'b1 && cnt < wr_len))
wr_fifo_rd <= 1'b1;
else
wr_fifo_rd <= 1'b0;
end
endmodule
本模块的功能为读取m25p16的状态寄存器,主要检测状态寄存器的最低位(WIP)。
WIP(write in progress :正在进行写进程),该bie位表示了flash内部是否在进行写进程。如果处于写进程时,flash忽略外部所有的命令,所以建议在执行任何命令前,首先进行检测该位。1表示正在写进程中,0表示不处于写进程。
如果检测到正在写进程中,进行延迟1ms,然后再次读取该位状态。直到写进程结束。
本模块采用状态机设计实现。ILDE(发送读状态寄存器命令)、RDSRSTATE(发送读使能)、WIP(判断wip位)、 DELAY1ms(延迟1ms)。cnt为延迟1ms的计数器。
设计代码为:
module rdsr (
input wire clk,
input wire rst_n,
input wire rdsr_en,
output reg rdsr_done,
output reg rdsr_send_en,
output reg [7:0] rdsr_send_data,
input wire spi_send_done,
output reg rdsr_read_en,
input wire [7:0] spi_read_data,
input wire spi_read_done
);
parameter T_1ms = 50_000;
localparam IDLE = 4'b0001;
localparam RDSRSTATE = 4'b0010;
localparam WIP = 4'b0100;
localparam DELAY1ms = 4'b1000;
reg [3:0] c_state;
reg [3:0] n_state;
reg [15:0] cnt;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= IDLE;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
IDLE : begin
if (rdsr_en == 1'b0)
n_state = IDLE;
else
n_state = RDSRSTATE;
end
RDSRSTATE : begin
if (spi_send_done == 1'b1)
n_state = WIP;
else
n_state = RDSRSTATE;
end
WIP : begin
if (spi_read_done == 1'b0)
n_state = WIP;
else
if (spi_read_data[0] == 1'b0)
n_state = IDLE;
else
n_state = DELAY1ms;
end
DELAY1ms : begin
if (cnt < T_1ms - 1'b1)
n_state = DELAY1ms;
else
n_state = RDSRSTATE;
end
default : n_state = IDLE;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
cnt <= 16'd0;
else
if (c_state == DELAY1ms && cnt < T_1ms - 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 16'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rdsr_done <= 1'b0;
else
if (c_state == WIP && spi_read_done == 1'b1 && spi_read_data[0] == 1'b0)
rdsr_done <= 1'b1;
else
rdsr_done <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rdsr_send_data <= 8'd0;
else
if ((c_state == IDLE && rdsr_en == 1'b1) || (c_state == DELAY1ms && cnt == T_1ms - 1'b1))
rdsr_send_data <= 8'h05;
else
rdsr_send_data <= 8'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rdsr_send_en <= 1'b0;
else
if ((c_state == IDLE && rdsr_en == 1'b1) || (c_state == DELAY1ms && cnt == T_1ms - 1'b1))
rdsr_send_en <= 1'b1;
else
rdsr_send_en <= 1'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rdsr_read_en <= 1'b0;
else
if (c_state == RDSRSTATE && spi_send_done == 1'b1)
rdsr_read_en <= 1'b1;
else
rdsr_read_en <= 1'b0;
end
endmodule
该模块负责读取flash的ID(2015),验证ID的正确性。
该模块采用状态机的方式实现。IDLE(等待读取ID的命令)、IDSTATE1(读取高八位ID)、IDSTATE2(读取中间八位ID)、IDSTATE3(读取低八位ID)、ID_CHECK(检测ID的正确性)。
状态转移图如下:
设计代码为:
module rdid (
input wire clk,
input wire rst_n,
input wire rdid_en,
output reg rdid_done,
output reg rdid_send_en,
output reg [7:0] rdid_send_data,
input wire spi_send_done,
output reg rdid_read_en,
input wire spi_read_done,
input wire [7:0] spi_read_data
);
localparam IDLE = 5'b00001;
localparam IDSTATE1 = 5'b00010;
localparam IDSTATE2 = 5'b00100;
localparam IDSTATE3 = 5'b01000;
localparam ID_CHECK = 5'b10000;
reg [4:0] c_state;
reg [4:0] n_state;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= IDLE;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
IDLE : begin
if (rdid_en == 1'b1)
n_state = IDSTATE1;
else
n_state = IDLE;
end
IDSTATE1 : begin
if (spi_send_done == 1'b1)
n_state = IDSTATE2;
else
n_state = IDSTATE1;
end
IDSTATE2 : begin
if (spi_read_done == 1'b1 && spi_read_data == 8'h20)
n_state = IDSTATE3;
else
n_state = IDSTATE2;
end
IDSTATE3 : begin
if (spi_read_done == 1'b1 && spi_read_data == 8'h20)
n_state = ID_CHECK;
else
n_state = IDSTATE3;
end
ID_CHECK : begin
if (spi_read_done == 1'b1 && spi_read_data == 8'h15)
n_state = IDLE;
else
n_state = ID_CHECK;
end
default : n_state = IDLE;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rdid_send_data <= 8'd0;
else
if (c_state == IDLE && rdid_en == 1'b1)
rdid_send_data <= 8'h9f;
else
rdid_send_data <= 8'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rdid_send_en <= 1'b0;
else
if (c_state == IDLE && rdid_en == 1'b1)
rdid_send_en <= 1'b1;
else
rdid_send_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rdid_read_en <= 1'b0;
else
if ((c_state == IDSTATE1 && spi_send_done == 1'b1) || (c_state == IDSTATE2 && spi_read_done == 1'b1 && spi_read_data == 8'h20) || (c_state == IDSTATE3 && spi_read_done == 1'b1 && spi_read_data == 8'h20))
rdid_read_en <= 1'b1;
else
rdid_read_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rdid_done <= 1'b0;
else
if (c_state == ID_CHECK && spi_read_done == 1'b1 && spi_read_data == 8'h15)
rdid_done <= 1'b1;
else
rdid_done <= 1'b0;
end
endmodule
该模块负责将flash的数据读出,写入到输出缓存中。
该模块采用状态机实现。RD_STATE(等待读命令)、H_ADDR(发送高八位地址)、M_ADDR(发送中间八位地址)、L_ADDR(发送低八位地址)、RDDATA(读取数据)、WRFIFO(将读出的数据写入到FIFO中)、CHECK_LEN(判断读取的长度)。
状态转移图如下:
设计代码为:
module read_ctrl (
input wire clk,
input wire rst_n,
input wire read_en,
input wire [23:0] rd_addr,
input wire [8:0] rd_len,
output reg [7:0] rddata,
output reg rd_fifo_wr,
output reg read_done,
output reg read_send_en,
output reg [7:0] read_send_data,
input wire spi_send_done,
output reg read_read_en,
input wire spi_read_done,
input wire [7:0] spi_read_data
);
localparam RD_STATE = 7'b000_0001;
localparam H_ADDR = 7'b000_0010;
localparam M_ADDR = 7'b000_0100;
localparam L_ADDR = 7'b000_1000;
localparam RDDATA = 7'b001_0000;
localparam WRFIFO = 7'b010_0000;
localparam CHECK_LEN = 7'b100_0000;
reg [6:0] c_state;
reg [6:0] n_state;
reg [8:0] cnt;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= RD_STATE;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
RD_STATE : begin
if (read_en == 1'b1)
n_state = H_ADDR;
else
n_state = RD_STATE;
end
H_ADDR : begin
if (spi_send_done == 1'b1)
n_state = M_ADDR;
else
n_state = H_ADDR;
end
M_ADDR : begin
if (spi_send_done == 1'b1)
n_state = L_ADDR;
else
n_state = M_ADDR;
end
L_ADDR : begin
if (spi_send_done == 1'b1)
n_state = RDDATA;
else
n_state = L_ADDR;
end
RDDATA : begin
if (spi_send_done == 1'b1)
n_state = WRFIFO;
else
n_state = RDDATA;
end
WRFIFO : begin
if (spi_read_done == 1'b1)
n_state = CHECK_LEN;
else
n_state = WRFIFO;
end
CHECK_LEN : begin
if (cnt == rd_len)
n_state = RD_STATE;
else
n_state = WRFIFO;
end
default : n_state = RD_STATE;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
cnt <= 9'd0;
else
if ((c_state == RDDATA && spi_send_done == 1'b1) || (c_state == CHECK_LEN && cnt < rd_len))
cnt <= cnt + 1'b1;
else
if (c_state == RD_STATE)
cnt <= 9'd0;
else
cnt <= cnt;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
read_read_en <= 1'b0;
else
if ((c_state == RDDATA && spi_send_done == 1'b1) || (c_state == CHECK_LEN && cnt < rd_len))
read_read_en <= 1'b1;
else
read_read_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
read_done <= 1'b0;
else
if (c_state == CHECK_LEN && cnt == rd_len)
read_done <= 1'b1;
else
read_done <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rd_fifo_wr <= 1'b0;
else
if (c_state == WRFIFO && spi_read_done == 1'b1)
rd_fifo_wr <= 1'b1;
else
rd_fifo_wr <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rddata <= 8'd0;
else
if (c_state == WRFIFO && spi_read_done == 1'b1)
rddata <= spi_read_data;
else
rddata <= 8'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
read_send_en <= 1'b0;
else
case (c_state)
RD_STATE : begin
if (read_en == 1'b1)
read_send_en <= 1'b1;
else
read_send_en <= 1'b0;
end
H_ADDR : begin
if (spi_send_done == 1'b1)
read_send_en <= 1'b1;
else
read_send_en <= 1'b0;
end
M_ADDR : begin
if (spi_send_done == 1'b1)
read_send_en <= 1'b1;
else
read_send_en <= 1'b0;
end
L_ADDR : begin
if (spi_send_done == 1'b1)
read_send_en <= 1'b1;
else
read_send_en <= 1'b0;
end
default : read_send_en <= 1'b0;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
read_send_data <= 8'd0;
else
case (c_state)
RD_STATE : begin
if (read_en == 1'b1)
read_send_data <= 8'h03;
else
read_send_data <= 8'd0;
end
H_ADDR : begin
if (spi_send_done == 1'b1)
read_send_data <= rd_addr[23:16];
else
read_send_data <= 8'd0;
end
M_ADDR : begin
if (spi_send_done == 1'b1)
read_send_data <= rd_addr[15:8];
else
read_send_data <= 8'd0;
end
L_ADDR : begin
if (spi_send_done == 1'b1)
read_send_data <= rd_addr[7:0];
else
read_send_data <= 8'd0;
end
default : read_send_data <= 8'd0;
endcase
end
endmodule
两个fifo的宽度设置为8,深度设置为256,同步fifo,带有复位。
该模块根据外部的命令,按照m25p16的执行规则,进行控制各个模块的执行。
该模块采用状态机实现。INIT_RDSR(读WIP),INIT_RDID(读ID),INIT_ID(判断ID),WIP(读WIP),WIP_DONE(等待WIP),IDLE(空闲状态),**STATE(执行对应的命令),**WREN(打开flash的写使能)。在进行任何命令前,都检查wip。
状态转移图如下:
在不同的状态,mux_sel选择对应的命令通过。
drive_busy只有在IDLE状态才是低电平。
spi_cs_n信号, DLE状态为高电平、WIP_DONE(INIT_RDID)中spi_read_done信号为高时 (保证能够多次读取状态寄存器)、在其他状态发生切换时,spi_cs_n 为高电平,否则为低电平。
设计代码为:
module ctrl (
input wire clk,
input wire rst_n,
input wire flag_be,
input wire flag_se,
input wire flag_wr,
input wire flag_rd,
input wire [23:0] addr,
input wire [8:0] len,
output wire drive_busy,
output reg spi_cs_n,
input wire spi_read_done,
output reg rdsr_en,
input wire rdsr_done,
output reg wren_en,
input wire wren_done,
output reg pp_en,
output reg [23:0] wr_addr,
output reg [8:0] wr_len,
input wire pp_done,
output reg be_en,
input wire be_done,
output reg se_en,
output reg [23:0] se_addr,
input wire se_done,
output reg rdid_en,
input wire rdid_done,
output reg read_en,
output reg [23:0] rd_addr,
output reg [8:0] rd_len,
input wire read_done,
output reg [2:0] mux_sel
);
localparam INIT_RDSR = 13'b0000_0000_00001;
localparam INIT_RDID = 13'b0000_0000_00010;
localparam INIT_ID = 13'b0000_0000_00100;
localparam WIP = 13'b0000_0000_01000;
localparam WIP_DONE = 13'b0000_0000_10000;
localparam IDLE = 13'b0000_0001_00000;
localparam RDSTATE = 13'b0000_0010_00000;
localparam PPWREN = 13'b0000_0100_00000;
localparam PPSTATE = 13'b0000_1000_00000;
localparam SEWREN = 13'b0001_0000_00000;
localparam SESTATE = 13'b0010_0000_00000;
localparam BEWREN = 13'b0100_0000_00000;
localparam BESTATE = 13'b1000_0000_00000;
reg [12:0] c_state;
reg [12:0] n_state;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= INIT_RDSR;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
INIT_RDSR : begin
n_state = INIT_RDID;
end
INIT_RDID : begin
if (rdsr_done == 1'b1)
n_state = INIT_ID;
else
n_state = INIT_RDID;
end
INIT_ID : begin
if (rdid_done == 1'b1)
n_state = WIP;
else
n_state = INIT_ID;
end
WIP : begin
n_state = WIP_DONE;
end
WIP_DONE : begin
if (rdsr_done == 1'b1)
n_state = IDLE;
else
n_state = WIP_DONE;
end
IDLE : begin
if (flag_wr == 1'b1)
n_state = PPWREN;
else
if (flag_rd == 1'b1)
n_state = RDSTATE;
else
if (flag_be == 1'b1)
n_state = BEWREN;
else
if (flag_se == 1'b1)
n_state = SEWREN;
else
n_state = IDLE;
end
RDSTATE : begin
if (read_done == 1'b1)
n_state = WIP;
else
n_state = RDSTATE;
end
PPWREN : begin
if (wren_done == 1'b1)
n_state = PPSTATE;
else
n_state = PPWREN;
end
PPSTATE : begin
if (pp_done == 1'b1)
n_state = WIP;
else
n_state = PPSTATE;
end
SEWREN : begin
if (wren_done == 1'b1)
n_state = SESTATE;
else
n_state = SEWREN;
end
SESTATE : begin
if (se_done == 1'b1)
n_state = WIP;
else
n_state = SESTATE;
end
BEWREN : begin
if (wren_done == 1'b1)
n_state = BESTATE;
else
n_state = BEWREN;
end
BESTATE : begin
if (be_done == 1'b1)
n_state = WIP;
else
n_state = BESTATE;
end
default : n_state = INIT_RDSR;
endcase
end
assign drive_busy = (c_state != IDLE || flag_be == 1'b1 || flag_rd == 1'b1 || flag_se == 1'b1 || flag_wr == 1'b1);
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
spi_cs_n <= 1'b1;
else
case (c_state)
INIT_RDSR : spi_cs_n <= 1'b1;
INIT_RDID : if (spi_read_done == 1'b1)
spi_cs_n <= 1'b1;
else
spi_cs_n <= 1'b0;
INIT_ID : if (rdid_done == 1'b1)
spi_cs_n <= 1'b1;
else
spi_cs_n <= 1'b0;
WIP : spi_cs_n <= 1'b1;
WIP_DONE : if (spi_read_done == 1'b1)
spi_cs_n <= 1'b1;
else
spi_cs_n <= 1'b0;
IDLE : spi_cs_n <= 1'b1;
RDSTATE : if (read_done == 1'b1)
spi_cs_n <= 1'b1;
else
spi_cs_n <= 1'b0;
PPWREN : if (wren_done == 1'b1)
spi_cs_n <= 1'b1;
else
spi_cs_n <= 1'b0;
PPSTATE : if (pp_done == 1'b1)
spi_cs_n <= 1'b1;
else
spi_cs_n <= 1'b0;
SEWREN : if (wren_done == 1'b1)
spi_cs_n <= 1'b1;
else
spi_cs_n <= 1'b0;
SESTATE : if (se_done == 1'b1)
spi_cs_n <= 1'b1;
else
spi_cs_n <= 1'b0;
BEWREN : if (wren_done == 1'b1)
spi_cs_n <= 1'b1;
else
spi_cs_n <= 1'b0;
BESTATE : if (be_done == 1'b1)
spi_cs_n <= 1'b1;
else
spi_cs_n <= 1'b0;
default : spi_cs_n <= 1'b1;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rdsr_en <= 1'b0;
else
if (c_state == INIT_RDSR || c_state == WIP)
rdsr_en <= 1'b1;
else
rdsr_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wren_en <= 1'b0;
else
if (c_state == IDLE && (flag_be == 1'b1 || flag_se == 1'b1 || flag_wr == 1'b1))
wren_en <= 1'b1;
else
wren_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
pp_en <= 1'b0;
else
if (c_state == PPWREN && wren_done == 1'b1)
pp_en <= 1'b1;
else
pp_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wr_len <= 9'd0;
else
if (c_state == IDLE && flag_wr == 1'b1)
wr_len <= len;
else
wr_len <= wr_len;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wr_addr <= 24'd0;
else
if (c_state == IDLE && flag_wr == 1'b1)
wr_addr <= addr;
else
wr_addr <= wr_addr;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
be_en <= 1'b0;
else
if (c_state == BEWREN && wren_done == 1'b1)
be_en <= 1'b1;
else
be_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
se_en <= 1'b0;
else
if (c_state == SEWREN && wren_done == 1'b1)
se_en <= 1'b1;
else
se_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
se_addr <= 24'd0;
else
if (c_state == IDLE && flag_se == 1'b1)
se_addr <= addr;
else
se_addr <= se_addr;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rdid_en <= 1'b0;
else
if (c_state == INIT_RDID && rdsr_done == 1'b1)
rdid_en <= 1'b1;
else
rdid_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rd_len <= 9'd0;
else
if (c_state == IDLE && flag_rd == 1'b1)
rd_len <= len;
else
rd_len <= rd_len;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rd_addr <= 24'd0;
else
if (c_state == IDLE && flag_rd == 1'b1)
rd_addr <= addr;
else
rd_addr <= rd_addr;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
read_en <= 1'b0;
else
if (c_state == IDLE && flag_rd == 1'b1)
read_en <= 1'b1;
else
read_en <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
mux_sel <= 3'd0;
else
case (c_state)
INIT_RDSR : mux_sel <= 3'd0;
INIT_RDID : mux_sel <= 3'd0;
INIT_ID : mux_sel <= 3'd5;
WIP : mux_sel <= 3'd0;
WIP_DONE : mux_sel <= 3'd0;
IDLE : mux_sel <= 3'd0;
RDSTATE : mux_sel <= 3'd6;
PPWREN : mux_sel <= 3'd2;
PPSTATE : mux_sel <= 3'd1;
SEWREN : mux_sel <= 3'd2;
SESTATE : mux_sel <= 3'd4;
BEWREN : mux_sel <= 3'd2;
BESTATE : mux_sel <= 3'd3;
default : mux_sel <= 3'd0;
endcase
end
endmodule
本模块负责连接所有二级模块,实现所有的功能。
module m25p16_drive (
input wire clk,
input wire rst_n,
input wire wrfifo_wr,
input wire [7:0] wrfifo_data,
input wire flag_be,
input wire flag_se,
input wire flag_wr,
input wire flag_rd,
input wire [23:0] addr,
input wire [8:0] len,
input wire rdfifo_rd,
output wire [7:0] rdfifo_rdata,
output wire rdfifo_rdempty,
output wire drive_busy,
output wire spi_cs_n,
output wire spi_sclk,
output wire spi_mosi,
input wire spi_miso
);
wire spi_send_en;
wire [7:0] spi_send_data;
wire spi_send_done;
wire spi_read_en;
wire [7:0] spi_read_data;
wire spi_read_done;
wire rdsr_send_en;
wire [7:0] rdsr_send_data;
wire rdsr_read_en;
wire pp_send_en;
wire [7:0] pp_send_data;
wire wren_send_en;
wire [7:0] wren_send_data;
wire be_send_en;
wire [7:0] be_send_data;
wire se_send_en;
wire [7:0] se_send_data;
wire rdid_send_en;
wire [7:0] rdid_send_data;
wire rdid_read_en;
wire read_send_en;
wire [7:0] read_send_data;
wire read_read_en;
wire [2:0] mux_sel;
wire be_en;
wire be_done;
wire wren_en;
wire wren_done;
wire se_en;
wire [23:0] se_addr;
wire se_done;
wire pp_en;
wire pp_done;
wire wr_fifo_rd;
wire [7:0] wrdata;
wire [8:0] wr_len;
wire [23:0] wr_addr;
wire rdsr_en;
wire rdsr_done;
wire rdid_en;
wire rdid_done;
wire read_en;
wire [23:0] rd_addr;
wire [8:0] rd_len;
wire [7:0] rddata;
wire rd_fifo_wr;
wire read_done;
ctrl ctrl_inst(
.clk (clk),
.rst_n (rst_n),
.flag_be (flag_be),
.flag_se (flag_se),
.flag_wr (flag_wr),
.flag_rd (flag_rd),
.addr (addr),
.len (len),
.drive_busy (drive_busy),
.spi_cs_n (spi_cs_n),
.spi_read_done (spi_read_done),
.rdsr_en (rdsr_en),
.rdsr_done (rdsr_done),
.wren_en (wren_en),
.wren_done (wren_done),
.pp_en (pp_en),
.wr_addr (wr_addr),
.wr_len (wr_len),
.pp_done (pp_done),
.be_en (be_en),
.be_done (be_done),
.se_en (se_en),
.se_addr (se_addr),
.se_done (se_done),
.rdid_en (rdid_en),
.rdid_done (rdid_done),
.read_en (read_en),
.rd_addr (rd_addr),
.rd_len (rd_len),
.read_done (read_done),
.mux_sel (mux_sel)
);
rd_fifo rd_fifo_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( rddata ),
.rdreq ( rdfifo_rd ),
.wrreq ( rd_fifo_wr ),
.empty ( rdfifo_rdempty ),
.q ( rdfifo_rdata )
);
wr_fifo wr_fifo_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( wrfifo_data ),
.rdreq ( wr_fifo_rd ),
.wrreq ( wrfifo_wr ),
.q ( wrdata )
);
read_ctrl read_ctrl_inst(
.clk (clk),
.rst_n (rst_n),
.read_en (read_en),
.rd_addr (rd_addr),
.rd_len (rd_len),
.rddata (rddata),
.rd_fifo_wr (rd_fifo_wr),
.read_done (read_done),
.read_send_en (read_send_en),
.read_send_data (read_send_data),
.spi_send_done (spi_send_done),
.read_read_en (read_read_en),
.spi_read_done (spi_read_done),
.spi_read_data (spi_read_data)
);
rdid rdid_inst(
.clk (clk),
.rst_n (rst_n),
.rdid_en (rdid_en),
.rdid_done (rdid_done),
.rdid_send_en (rdid_send_en),
.rdid_send_data (rdid_send_data),
.spi_send_done (spi_send_done),
.rdid_read_en (rdid_read_en),
.spi_read_done (spi_read_done),
.spi_read_data (spi_read_data)
);
rdsr rdsr_inst(
.clk (clk),
.rst_n (rst_n),
.rdsr_en (rdsr_en),
.rdsr_done (rdsr_done),
.rdsr_send_en (rdsr_send_en),
.rdsr_send_data (rdsr_send_data),
.spi_send_done (spi_send_done),
.rdsr_read_en (rdsr_read_en),
.spi_read_data (spi_read_data),
.spi_read_done (spi_read_done)
);
pp pp_inst(
.clk (clk),
.rst_n (rst_n),
.pp_en (pp_en),
.pp_done (pp_done),
.wr_fifo_rd (wr_fifo_rd),
.wrdata (wrdata),
.wr_len (wr_len),
.wr_addr (wr_addr),
.pp_send_en (pp_send_en),
.pp_send_data (pp_send_data),
.spi_send_done (spi_send_done)
);
se se_inst(
.clk (clk),
.rst_n (rst_n),
.se_en (se_en),
.se_addr (se_addr),
.se_done (se_done),
.se_send_en (se_send_en),
.se_send_data (se_send_data),
.spi_send_done (spi_send_done)
);
wren wren_inst(
.clk (clk),
.rst_n (rst_n),
.wren_en (wren_en),
.wren_done (wren_done),
.wren_send_en (wren_send_en),
.wren_send_data (wren_send_data),
.spi_send_done (spi_send_done)
);
be be_inst(
.clk (clk),
.rst_n (rst_n),
.be_en (be_en),
.be_done (be_done),
.be_send_en (be_send_en),
.be_send_data (be_send_data),
.spi_send_done (spi_send_done)
);
mux7_1 mux7_1_inst(
.rdsr_send_en (rdsr_send_en),
.rdsr_send_data (rdsr_send_data),
.rdsr_read_en (rdsr_read_en),
.pp_send_en (pp_send_en),
.pp_send_data (pp_send_data),
.wren_send_en (wren_send_en),
.wren_send_data (wren_send_data),
.be_send_en (be_send_en),
.be_send_data (be_send_data),
.se_send_en (se_send_en),
.se_send_data (se_send_data),
.rdid_send_en (rdid_send_en),
.rdid_send_data (rdid_send_data),
.rdid_read_en (rdid_read_en),
.read_send_en (read_send_en),
.read_send_data (read_send_data),
.read_read_en (read_read_en),
.mux_sel (mux_sel),
.spi_send_en (spi_send_en),
.spi_send_data (spi_send_data),
.spi_read_en (spi_read_en)
);
spi_8bit_drive spi_8bit_drive_inst(
.clk (clk),
.rst_n (rst_n),
.spi_send_en (spi_send_en),
.spi_send_data (spi_send_data),
.spi_send_done (spi_send_done),
.spi_read_en (spi_read_en),
.spi_read_data (spi_read_data),
.spi_read_done (spi_read_done),
.spi_sclk (spi_sclk),
.spi_mosi (spi_mosi),
.spi_miso (spi_miso)
);
endmodule
本次设计涉及到读取flash的id以及状态寄存器,所以在仿真时需要加入仿真模型。仿真模型放在msim的m25p16_sim_module中。m25p16为仿真模型的顶层文件。
由于读写和擦除的时间较长,RTL仿真中,将只仿真RDSR和RDID,其他的功能测试在板级测试时进行。
仿真代码如下:
1ns/1ps
module m25p16_drive_tb;
reg clk;
reg rst_n;
wire drive_busy;
wire spi_cs_n;
wire spi_sclk;
wire spi_mosi;
wire spi_miso;
m25p16_drive m25p16_drive_inst(
(clk),
(rst_n),
(1'b0),
(8'd0),
(1'b0),
(1'b0),
(1'b0),
(1'b0),
(24'd0),
(9'd0),
(1'b0),
(),
(),
(drive_busy),
(spi_cs_n),
(spi_sclk),
(spi_mosi),
(spi_miso)
);
m25p16 m25p16_inst(
(spi_sclk),
(spi_mosi),
(spi_cs_n),
(1'b1),
(1'b1),
(spi_miso)
);
initial clk = 1'b0;
always # 50 clk = ~clk;
initial begin
rst_n = 1'b0;
# 201
rst_n = 1'b1;
(negedge drive_busy);
# 2000
$stop;
end
endmodule
在设置testbench时,注意将所有文件全部添加到文件中。
选择testbench时,注意选中设置的m25p16_drive_tb。
利用modelsim仿真,可以得出如下RTL仿真波形。
读到ID,以及检测WIP都是正确的。
由于m25p16的时序原因,整个设计工作在10MHz(利用PLL产生)。
在进行测试控制时,对最后一个扇区进行擦除;对最后一个扇区的第一页进行写入数据100个(1至100);对最后一个扇区的第一个进行读取,验证数据是否为1至100。
测试的控制模块命名为test_ctrl。
此模块采用状态机实现。WRFIFO(将1至100写入wrfifo中)、SE(扇区擦除)、PP(写入flash)、RD(读出flash)、WAIT_RD(等待读取)、CHECK( 检测读出的数据的正确性)。
设计代码为:
module test_ctrl (
input wire clk,
input wire rst_n,
output reg wrfifo_wr,
output reg [7:0] wrfifo_data,
output reg flag_se,
output reg flag_wr,
output reg flag_rd,
input wire drive_busy,
output reg rdfifo_rd,
input wire [7:0] rdfifo_rdata
);
localparam WRFIFO = 6'b000_001;
localparam SE = 6'b000_010;
localparam PP = 6'b000_100;
localparam RD = 6'b001_000;
localparam WAIT_RD = 6'b010_000;
localparam CHECK = 6'b100_000;
reg [5:0] c_state;
reg [5:0] n_state;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= WRFIFO;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
WRFIFO : begin
if (wrfifo_data == 8'd100)
n_state = SE;
else
n_state = WRFIFO;
end
SE : begin
if (drive_busy == 1'b0)
n_state = PP;
else
n_state = SE;
end
PP : begin
if (drive_busy == 1'b0)
n_state = RD;
else
n_state = PP;
end
RD : begin
if (drive_busy == 1'b0)
n_state = WAIT_RD;
else
n_state = RD;
end
WAIT_RD : begin
if (drive_busy == 1'b0)
n_state = CHECK;
else
n_state = WAIT_RD;
end
CHECK : begin
n_state = CHECK;
end
default : n_state = WRFIFO;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wrfifo_data <= 8'd0;
else
if (c_state == WRFIFO && wrfifo_data < 8'd100)
wrfifo_data <= wrfifo_data + 1'b1;
else
wrfifo_data <= 8'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wrfifo_wr <= 1'd0;
else
if (c_state == WRFIFO && wrfifo_data < 8'd100)
wrfifo_wr <= 1'd1;
else
wrfifo_wr <= 1'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
flag_se <= 1'b0;
else
if (c_state == SE && drive_busy == 1'b0)
flag_se <= 1'b1;
else
flag_se <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
flag_wr <= 1'b0;
else
if (c_state == PP && drive_busy == 1'b0)
flag_wr <= 1'b1;
else
flag_wr <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
flag_rd <= 1'b0;
else
if (c_state == RD && drive_busy == 1'b0)
flag_rd <= 1'b1;
else
flag_rd <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rdfifo_rd <= 1'b0;
else
if (c_state == WAIT_RD && drive_busy == 1'b0)
rdfifo_rd <= 1'b1;
else
if (c_state == CHECK && rdfifo_rdata == 8'd99)
rdfifo_rd <= 1'b0;
else
rdfifo_rd <= rdfifo_rd;
end
endmodule
将test模块设置为顶层。在test模块中,m25p16_drive例化中,对于整片擦除不做控制,对于addr直接指向最后一个扇区的第一页,len指定为100。
代码为:
module test (
input wire clk,
input wire rst_n,
output wire spi_cs_n,
output wire spi_sclk,
output wire spi_mosi,
input wire spi_miso
);
wire wrfifo_wr;
wire [7:0] wrfifo_data;
wire flag_rd;
wire flag_se;
wire flag_wr;
wire drive_busy;
wire rdfifo_rd;
wire [7:0] rdfifo_rdata;
wire clk_10m;
wire pll_locked;
pll_test pll_test_inst (
( ~rst_n ),
( clk ),
( clk_10m ),
( pll_locked )
);
test_ctrl test_ctrl_inst(
(clk_10m),
(pll_locked),
(wrfifo_wr),
(wrfifo_data),
(flag_se),
(flag_wr),
(flag_rd),
(drive_busy),
(rdfifo_rd),
(rdfifo_rdata)
);
m25p16_drive m25p16_drive_inst(
(clk_10m),
(pll_locked),
(wrfifo_wr),
(wrfifo_data),
(1'b0),
(flag_se),
(flag_wr),
(flag_rd),
(24'hff0000),
(9'd100),
(rdfifo_rd),
(rdfifo_rdata),
(),
(drive_busy),
(spi_cs_n),
(spi_sclk),
(spi_mosi),
(spi_miso)
);
endmodule
由于开发板上的flash是为FPGA进行保存配置信息的,所以管脚都连接在专用管脚上,本次实验需要将这专用管脚配置为普通io。
右击器件型号,选择device。
点击device and pin options。
选择Dual-purpose pins,将其中所有的功能改为普通IO。
点击ok后,即可进行综合分析。
连接开发板和PC,打开逻辑分析仪。
采样时钟选择10MHz(PLL 的c0),采样深度设置为2K。
观测信号如下图所示。
首先将wrfifo_wr的触发条件设置为上升沿。点击触发后,按下复位按键。触发后,可以看到写入数据1至100后,然后进行SE命令。
将rdfifo_rd的触发条件设置为上升沿(将wrfifo_wr触发条件修改为donot care)。点击触发后,按下复位按键。
通过仿真和下板实测,验证控制器设计正确。
往期精选
FPGA技术江湖广发江湖帖
无广告纯净模式,给技术交流一片净土,从初学小白到行业精英业界大佬等,从军工领域到民用企业等,从通信、图像处理到人工智能等各个方向应有尽有,QQ微信双选,FPGA技术江湖打造最纯净最专业的技术交流学习平台。
FPGA技术江湖微信交流群
加群主微信,备注姓名+学校/公司+专业/岗位进群
FPGA技术江湖QQ交流群
备注姓名+学校/公司+专业/岗位进群