FPGA零基础学习精选|SPI协议驱动设计

原创 FPGA技术江湖 2023-12-27 07:30

大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。


本系列将带来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:芯片选择端),当输入为低时,该芯片被选中,可以允许进行读写操作。当输入为高时,该芯片被释放,不能够进行操作。

对于H——o——l——d——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模块进行控制各个命令控制器,写入的数据首先写入到写缓冲区,读出的数据读出后写入到读缓冲区。

暂不分配的端口,在应用时都是由上游模块进行控制,本设计测试时,编写上游模块进行测试。

各个模块的功能,和连接线的功能在各个模块设计中说明。


  • spi_8bit_drive设计实现

本模块负责将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。

仿真代码为:

`timescale 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 (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) ); 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仿真,可以看出发送和接收全部正常。


  • mux7_1设计实现

本模块负责将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) 3'd0 : begin spi_send_en = rdsr_send_en; spi_send_data = rdsr_send_data; spi_read_en = rdsr_read_en; end 3'd1 : begin spi_send_en = pp_send_en; spi_send_data = pp_send_data; spi_read_en = 1'b0; end 3'd2 : begin spi_send_en = wren_send_en; spi_send_data = wren_send_data; spi_read_en = 1'b0; end 3'd3 : begin spi_send_en = be_send_en; spi_send_data = be_send_data; spi_read_en = 1'b0; end 3'd4 : begin spi_send_en = se_send_en; spi_send_data = se_send_data; spi_read_en = 1'b0; end 3'd5 : begin spi_send_en = rdid_send_en; spi_send_data = rdid_send_data; spi_read_en = rdid_read_en; end 3'd6 : 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设计实现

该模块接收到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设计实现

该模块接收到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设计实现

该模块接收到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个脉冲一组,中间会有明显的间隔。


  • pp设计实现

该模块负责将外部写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


  • rdsr设计实现

本模块的功能为读取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


  • rdid设计实现

该模块负责读取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


  • read_ctrl设计实现

该模块负责将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


  • wr_fifo和rd_fifo调用

两个fifo的宽度设置为8,深度设置为256,同步fifo,带有复位。


  • ctrl设计实现

该模块根据外部的命令,按照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


  • m25p16_drive设计实现

本模块负责连接所有二级模块,实现所有的功能。

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


  • RTL仿真

本次设计涉及到读取flash的id以及状态寄存器,所以在仿真时需要加入仿真模型。仿真模型放在msim的m25p16_sim_module中。m25p16为仿真模型的顶层文件。

由于读写和擦除的时间较长,RTL仿真中,将只仿真RDSR和RDID,其他的功能测试在板级测试时进行。

仿真代码如下:

`timescale 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 (clk), .rst_n (rst_n), .wrfifo_wr (1'b0), .wrfifo_data (8'd0), .flag_be (1'b0), .flag_se (1'b0), .flag_wr (1'b0), .flag_rd (1'b0), .addr (24'd0), .len (9'd0), .rdfifo_rd (1'b0), .rdfifo_rdata (), .rdfifo_rdempty (), .drive_busy (drive_busy), .spi_cs_n (spi_cs_n), .spi_sclk (spi_sclk), .spi_mosi (spi_mosi), .spi_miso (spi_miso) );
m25p16 m25p16_inst( .c (spi_sclk), .data_in (spi_mosi), .s (spi_cs_n), .w (1'b1), .hold (1'b1), .data_out (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 ( .areset ( ~rst_n ), .inclk0 ( clk ), .c0 ( clk_10m ), .locked ( pll_locked ) );
test_ctrl test_ctrl_inst(
.clk (clk_10m), .rst_n (pll_locked), .wrfifo_wr (wrfifo_wr), .wrfifo_data (wrfifo_data), .flag_se (flag_se), .flag_wr (flag_wr), .flag_rd (flag_rd), .drive_busy (drive_busy), .rdfifo_rd (rdfifo_rd), .rdfifo_rdata (rdfifo_rdata) ); m25p16_drive m25p16_drive_inst(
.clk (clk_10m), .rst_n (pll_locked), .wrfifo_wr (wrfifo_wr), .wrfifo_data (wrfifo_data), .flag_be (1'b0), .flag_se (flag_se), .flag_wr (flag_wr), .flag_rd (flag_rd), .addr (24'hff0000), .len (9'd100), .rdfifo_rd (rdfifo_rd), .rdfifo_rdata (rdfifo_rdata), .rdfifo_rdempty (), .drive_busy (drive_busy), .spi_cs_n (spi_cs_n), .spi_sclk (spi_sclk), .spi_mosi (spi_mosi), .spi_miso (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工程师人才招聘平台

FPGA人才招聘,企业HR,看过来!

系统设计精选 | 基于FPGA的实时图像边缘检测系统设计(附代码)

基于原语的千兆以太网RGMII接口设计

时序分析理论和timequest使用_中文电子版

求职面试 | FPGA或IC面试题最新汇总篇

FPGA图像处理专题课新增Vivado部分内容,线上线下均可报名

FPGA时序分析及约束专题课新增Vivado部分内容,线上线下均可报名

资料汇总|FPGA软件安装包、书籍、源码、技术文档…(2023.07.09更新)

FPGA就业班,2023.12.18开班,加量不加价,系统性学习FPGA,高薪就业,线上线下同步!

FPGA技术江湖广发江湖帖

无广告纯净模式,给技术交流一片净土,从初学小白到行业精英业界大佬等,从军工领域到民用企业等,从通信、图像处理到人工智能等各个方向应有尽有,QQ微信双选,FPGA技术江湖打造最纯净最专业的技术交流学习平台。


FPGA技术江湖微信交流群

加群主微信,备注姓名+学校/公司+专业/岗位进群


FPGA技术江湖QQ交流群

备注姓名+学校/公司+专业/岗位进群


FPGA技术江湖 任何技术的学习就好比一个江湖,对于每一位侠客都需要不断的历练,从初入江湖的小白到归隐山林的隐世高人,需要不断的自我感悟自己修炼,让我们一起仗剑闯FPGA乃至更大的江湖。
评论
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 98浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 108浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 119浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-30 14:30 78浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 122浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 101浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 100浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 84浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 71浏览
  • TOF多区传感器: ND06   ND06是一款微型多区高集成度ToF测距传感器,其支持24个区域(6 x 4)同步测距,测距范围远达5m,具有测距范围广、精度高、测距稳定等特点。适用于投影仪的无感自动对焦和梯形校正、AIoT、手势识别、智能面板和智能灯具等多种场景。                 如果用ND06进行手势识别,只需要经过三个步骤: 第一步&
    esad0 2024-12-04 11:20 50浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 68浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 120浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 106浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦