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 结构图
参考代码
`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。但在开发部上运行,擦除按键是没有问题的。

