Menu Close

SPI 通讯协议(7)SPI FLASH (verilog) 工程解析 (TOP.v)及仿真

top.v 模块为整个工程的最顶层模块, 这个模块包括pll (负责系统时钟, spi phy 时钟处理);reset 复位逻辑; 开发版上的按键滤波;spi_IF.v  (控制spi );uart 串口通讯模块; fpga 心跳led逻辑。目前我们使用的是 FII-PRX100T-DDR 开发版,当然,这个工程可以非常方便的移植到其他的fpga 开发班上。

参考文章:

SPI 通讯协议 及 SPI 相关工程 详解

FII_PRX100T_DDR_V1.2 硬件原理图

FII-PRX100-D(ARTIX 100T,XC7A100T)硬件参考指南

TOP.v 结构图

%title插图%num

 

参考代码

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Fraser Innovation Inc
// Engineer: WilliamG
// 
// Create Date: 2020/06/22 09:12:51
// Design Name: 
// Module Name: top.v
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////
module TOP#
(
    SIM_MODE = "FALSE"
)
(
    input  IN_CLK_50M, //外部时钟
    input  [7:1] PB,   //外部按键 消抖后选择需要的使用

    input  RXD,        //串口接收
    output TXD,        //串口发送

    output led,        //fpga心跳

    output FLASH_CLK,  //SPI时钟
    output FLASH_CS,   //SPI片选
    output FLASH_MOSI, //SPI主器件输出从器件输入
    input  FLASH_MISO, //SPI主器件输入从器件输出

    output FLASH_WP_N  //SPI主器件输入从器件输出
);

//=======================================================================================
//时钟模块 复位电路
wire clk_50m;
wire clk_25m;
wire locked;

pll SYS_PLL
(
    .clk_in1 (IN_CLK_50M),

    .locked (locked), 
    .clk_out1 (clk_50m), 
    .clk_out2 (clk_25m), 
    .reset (1'b0)
);

wire pb_rst; //按键复位脉冲 高电平有效
wire rst_r_n; //复位寄存

assign rst_r_n = locked & (~pb_rst); //低有效 locked 或按键触发
//=======================================================================================
reg [31:0] rst_cnt = 0; //复位延时计数
reg sys_rst_n = 0; //系统时钟 locked 系统复位

always @ (posedge clk_50m)
if(!rst_r_n)
    sys_rst_n <= 0;
else
begin
    if(rst_cnt >= 500) //delay 500ms
        sys_rst_n <= 1;
    else
    begin
        sys_rst_n <= 0; //计数不到 保持复位状态
        rst_cnt <= rst_cnt +1;
    end
end
//=======================================================================================
//按键消抖模块
wire [7:1] pb_flag; //按键脉冲

key_filter # (.SIM_MODE(SIM_MODE))
key_filter_inst
(
    .in_clk (clk_50m),  //系统时钟50MHz
    .rst_n (sys_rst_n), //复位信号,低电平有效
    .pb (PB),           //开发板输入按键信号
    .pb_flag(pb_flag)   //消抖后脉冲信号
);
//=======================================================================================
//按键分配

wire pb_rd_id, pb_wr_addr, pb_wr, pb_rd, pb_erase;

assign pb_erase = pb_flag[1]; //PB1作为擦除扇区指令
assign pb_wr = pb_flag[2]; //PB2作为写数据 256Byte
assign pb_rd = pb_flag[3]; //PB3作为读数据
assign pb_rd_id = pb_flag[5]; //PB5作为读id
//assign pb_wr_addr = pb_flag[5]; //PB5作为写地址

assign pb_rst = pb_flag[7]; //PB7作为复位按键

//=======================================================================================
//flash模块 
wire phy_clk = clk_25m;
wire sys_clk = clk_50m;

wire uart_fifo_rd;
wire [7:0] uart_fifo_dout;
wire uart_fifo_empty;
wire uart_done;

wire spi_rd;
wire [7:0] spi_dout;
wire spi_empty;

(* mark_debug = "true" *)wire spi_cmd_done; 
spi_IF #( .SIM_MODE (SIM_MODE) )
spi_IF_inst
(
    .sys_clk         (sys_clk),      //系统时钟

    .pb_rd_id        (pb_rd_id),     //PB7作为读id
    .pb_wr           (pb_wr),        //PB4作为写数据 256Byte
    .pb_rd           (pb_rd),        //PB6作为读数据
    .pb_erase        (pb_erase),     //PB2作为擦除扇区指令
    .spi_cmd_done    (spi_cmd_done),

    .spi_rd          (spi_rd),
    .spi_dout        (spi_dout),
    .spi_empty       (spi_empty),

    .uart_fifo_rd    (uart_fifo_rd),
    .uart_fifo_dout  (uart_fifo_dout),
    .uart_fifo_empty (uart_fifo_empty),
    .uart_done       (uart_done),

    .phy_clk         (phy_clk),    //spi时钟

    .FLASH_CS        (FLASH_CS),   //SPI片选 
    .FLASH_CLK       (FLASH_CLK),  //SPI时钟 
    .FLASH_MOSI      (FLASH_MOSI), //SPI主器件输出从器件输入
    .FLASH_MISO      (FLASH_MISO), //SPI主器件输入从器件输出

    .rst_n           (sys_rst_n)

);

//=======================================================================================
pc_uart
#(
    .MAIN_CLK (50)
)
pc_uart_inst
(
    .clk             (sys_clk),

    .txd             (TXD),
    .rxd             (RXD),

    .spi_rd          (spi_rd),
    .spi_dout        (spi_dout),
    .spi_empty       (spi_empty),
    .spi_done        (spi_cmd_done),

    .uart_fifo_rd    (uart_fifo_rd),
    .uart_fifo_dout  (uart_fifo_dout),
    .uart_fifo_empty (uart_fifo_empty),

    .rxd_done        (uart_done),

    .reset           (!sys_rst_n)
);
//=======================================================================================

reg [23:0] led_cnt = 0;
always @ (posedge sys_clk)
led_cnt <= led_cnt + 1;

assign led = led_cnt[23];

assign FLASH_WP_N = 1'b0;
//=======================================================================================

endmodule

 

顶层文件主要是将各个模块进行连接,同时提供相关的时钟;reset 复位信号等;对外部pin (按键)进行滤波,提取系统时钟单沿信号;以及提供一个fpga 的心跳led。

相关的仿真工程

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Fraser Innovation Inc
// Engineer: WilliamG
// 
// Create Date: 03/02/2021 03:51:26 PM
// Design Name: 
// Module Name: tb_sim
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module tb_sim(

);

reg clk = 0;
always clk = #10 ~clk;

wire led;

wire rx = 1;
wire tx;

integer i;

reg [7:1] PB = 7'h7f;
/*
assign pb_erase = PB[1]; //PB1作为擦除扇区指令
assign pb_wr = PB[2];    //PB2作为写数据 256Byte
assign pb_rd = PB[3];    //PB3作为读数据
assign pb_rd_id = PB[5]; //PB5作为读id

assign pb_rst = PB[1];   //PB1作为复位按键

*/
//`define SIM_TEST
`ifdef SIM_TEST
wire wp_n = 1'b0;

reg sck = 0;
reg cs = 1;
reg mosi = 1;
wire miso;

initial
begin

    sck = 0;
    cs = 1;
    mosi = 1;
    #20000;
    @(posedge clk);

    cs = 0;
    #100;
 // 发送05 命令
    mosi = 0; #20; sck = 1; #20; sck = 0; // 0
    mosi = 0; #20; sck = 1; #20; sck = 0; // 1
    mosi = 0; #20; sck = 1; #20; sck = 0; // 2
    mosi = 0; #20; sck = 1; #20; sck = 0; // 3
    mosi = 0; #20; sck = 1; #20; sck = 0; // 4
    mosi = 1; #20; sck = 1; #20; sck = 0; // 5
    mosi = 0; #20; sck = 1; #20; sck = 0; // 6
    mosi = 1; #20; sck = 1; #20; sck = 0; // 7
    #100;
// 发送 8 clock , 等待miso 返回数据
    mosi = 1; #20; sck = 1; #20; sck = 0; // 0
    mosi = 1; #20; sck = 1; #20; sck = 0; // 1
    mosi = 1; #20; sck = 1; #20; sck = 0; // 2
    mosi = 1; #20; sck = 1; #20; sck = 0; // 3
    mosi = 1; #20; sck = 1; #20; sck = 0; // 4
    mosi = 1; #20; sck = 1; #20; sck = 0; // 5
    mosi = 1; #20; sck = 1; #20; sck = 0; // 6
    mosi = 1; #20; sck = 1; #20; sck = 0; // 7
    #100;
    cs = 1; 
    #800;

// 发送02 写命令
    cs = 0;
    #100;

    mosi = 0; #20; sck = 1; #20; sck = 0; // 0
    mosi = 0; #20; sck = 1; #20; sck = 0; // 1
    mosi = 0; #20; sck = 1; #20; sck = 0; // 2
    mosi = 0; #20; sck = 1; #20; sck = 0; // 3
    mosi = 0; #20; sck = 1; #20; sck = 0; // 4
    mosi = 0; #20; sck = 1; #20; sck = 0; // 5
    mosi = 1; #20; sck = 1; #20; sck = 0; // 6
    mosi = 0; #20; sck = 1; #20; sck = 0; // 7
    #100;

// 发送 add[23:16] == 0x10
    mosi = 0; #20; sck = 1; #20; sck = 0; // 0
    mosi = 0; #20; sck = 1; #20; sck = 0; // 1
    mosi = 0; #20; sck = 1; #20; sck = 0; // 2
    mosi = 1; #20; sck = 1; #20; sck = 0; // 3
    mosi = 0; #20; sck = 1; #20; sck = 0; // 4
    mosi = 0; #20; sck = 1; #20; sck = 0; // 5
    mosi = 0; #20; sck = 1; #20; sck = 0; // 6
    mosi = 0; #20; sck = 1; #20; sck = 0; // 7
    #100;
// 发送 add[15:08] == 0x00
    mosi = 0; #20; sck = 1; #20; sck = 0; // 0
    mosi = 0; #20; sck = 1; #20; sck = 0; // 1
    mosi = 0; #20; sck = 1; #20; sck = 0; // 2
    mosi = 0; #20; sck = 1; #20; sck = 0; // 3
    mosi = 0; #20; sck = 1; #20; sck = 0; // 4
    mosi = 0; #20; sck = 1; #20; sck = 0; // 5
    mosi = 0; #20; sck = 1; #20; sck = 0; // 6
    mosi = 0; #20; sck = 1; #20; sck = 0; // 7
    #100;
// 发送 add[07:00] == 0x00
    mosi = 0; #20; sck = 1; #20; sck = 0; // 0
    mosi = 0; #20; sck = 1; #20; sck = 0; // 1
    mosi = 0; #20; sck = 1; #20; sck = 0; // 2
    mosi = 0; #20; sck = 1; #20; sck = 0; // 3
    mosi = 0; #20; sck = 1; #20; sck = 0; // 4
    mosi = 0; #20; sck = 1; #20; sck = 0; // 5
    mosi = 0; #20; sck = 1; #20; sck = 0; // 6
    mosi = 0; #20; sck = 1; #20; sck = 0; // 7
    #100;
// 发送 2 bytes 数据 0xb7
    for (i = 0; i < 2; i = i + 1)
    begin
        mosi = 1; #20; sck = 1; #20; sck = 0; // 0
        mosi = 0; #20; sck = 1; #20; sck = 0; // 1
        mosi = 1; #20; sck = 1; #20; sck = 0; // 2
        mosi = 1; #20; sck = 1; #20; sck = 0; // 3
        mosi = 0; #20; sck = 1; #20; sck = 0; // 4
        mosi = 1; #20; sck = 1; #20; sck = 0; // 5
        mosi = 1; #20; sck = 1; #20; sck = 0; // 6
        mosi = 1; #20; sck = 1; #20; sck = 0; // 7
        #100;
    end

    cs = 1;
    #800;

    cs = 0; 
    #100;
 // 发送05 命令
    mosi = 0; #20; sck = 1; #20; sck = 0; // 0
    mosi = 0; #20; sck = 1; #20; sck = 0; // 1
    mosi = 0; #20; sck = 1; #20; sck = 0; // 2
    mosi = 0; #20; sck = 1; #20; sck = 0; // 3
    mosi = 0; #20; sck = 1; #20; sck = 0; // 4
    mosi = 1; #20; sck = 1; #20; sck = 0; // 5
    mosi = 0; #20; sck = 1; #20; sck = 0; // 6
    mosi = 1; #20; sck = 1; #20; sck = 0; // 7
    #100;
// 发送 8 clock , 等待miso 返回数据
    mosi = 1; #20; sck = 1; #20; sck = 0; // 0
    mosi = 1; #20; sck = 1; #20; sck = 0; // 1
    mosi = 1; #20; sck = 1; #20; sck = 0; // 2
    mosi = 1; #20; sck = 1; #20; sck = 0; // 3
    mosi = 1; #20; sck = 1; #20; sck = 0; // 4
    mosi = 1; #20; sck = 1; #20; sck = 0; // 5
    mosi = 1; #20; sck = 1; #20; sck = 0; // 6
    mosi = 1; #20; sck = 1; #20; sck = 0; // 7
    #100;
    cs = 1; 
    #800;


    #200000;
    $stop;
end
`else
wire sck;
wire cs;
wire mosi;
wire miso;

wire wp_n;
initial
begin
    PB = 7'h7f;
    #20000;
/*
     // erase sector 
    PB[1] = 1'b0;
    #2000;
    PB[1] = 1'b1;
    while (spi_top_inst.spi_cmd_done == 0) @(posedge clk);
*/

// read id
    PB[5] = 1'b0;
    #2000;
    PB[5] = 1'b1;
    while (spi_top_inst.spi_cmd_done == 0) @(posedge clk);


// write 128 data 
    PB[2] = 1'b0;
    #2000;
    PB[2] = 1'b1;
    while (spi_top_inst.spi_cmd_done == 0) @(posedge clk);

// read 128 data 
    PB[3] = 1'b0;
    #2000;
    PB[3] = 1'b1;
    while (spi_top_inst.spi_cmd_done == 0) @(posedge clk);

#200000;
$stop;
end


TOP #( .SIM_MODE("TRUE"))
spi_top_inst
(
    .IN_CLK_50M (clk), //50M

    .PB (PB),

//SPI的4根线
    .FLASH_CLK  (sck),
    .FLASH_CS   (cs),
    .FLASH_MOSI (mosi),
    .FLASH_MISO (miso),
    .FLASH_WP_N (wp_n),

//串口
    .RXD (rx),
    .TXD (tx),

    .led (led)
);
`endif

SST25WF080B SST25WF080B_inst 
( 
    .SCK (sck),
    .CEB (cs),

    .SI (mosi),
    .SO (miso),
    .WPB (wp_n),
    .HOLDB (1)
);

//==================================================================
endmodule

 

当定义 `define SIM_TEST , 仿真工程模拟 mosi 发送 时钟, 数据,片选等,这时仿真并不需要top.v ,但可以帮助大家理解spi 通讯。

当没有定义 `define SIM_TEST , 仿真工程模拟按键发送 读flash ID , 写数据,读数据操作。没有模拟扇区擦除操作, 因为需要很长时间等待05 寄存器 ok。但在开发部上运行,擦除按键是没有问题的。

 

%title插图%num

Posted in FPGA, FPGA, IP开发, RISC-V, RISC-V 外设, 教材与教案, 文章

发表评论

相关链接