本论文使用Verilog HDL硬件描述语言,结合野火可以FPGA征途Pro开发板,实现了SPI通信协议的全擦除,扇区擦除,读数据,页写,连续写的驱动设计。在 Altera Cyclone Ⅳ 芯片上采用“自顶向下”的模块化设计思想及 Verilog HDL 硬件描述语言,设计并实现串行外设接口(SPI)。在 Quartus II 13.0 软件开发平台上编译、仿真后下载到 FPGA 芯片上,进行在线编程调试,实现了 SPI 总线通信功能。基于 FPGA 的系统设计调试维护方便、可靠性高,而且设计具有灵活性,可以方便地进行扩展和移植。
关键词:SPI;串口通信;FPGA;Verilog HDL
串行外设接口 (Serial peripheral interface, SPI) 是由Motorola 公司推出的一种同步串行外围设备接口。SPI总线是一种高速、同步、全双工的串行通信总线。SPI 总线接口只有四根外部接口线,结构简单、速度快、可靠性强。典型的 SPI 接口通信由四根信号线实现,分别为:
SCS:从片选信号,逻辑 “0” 为有效状态,表示被选中与主设进行数据传输。
SCLK:串行时钟线,由主设输出,从设按照此时钟进行数据的同步传输。
MOSI:数据传输线,由主设到从设。
MISO:数据传输线,由从设到主设。[1]
SPI 接口是一种全双工、三线通信的系统,是常用的工业标准同步串行接口,它允许主机处理器与各种外围设备之间的通信方式是串行通信。在 SPI 接口中,主/从机之间数据的传输需要 1 个时钟信号和 2 条数据线,所以 SPI 总线区分主机(Master)和从机(Slave)2 部分,结构框图如图 1 所示。
主机和从机之间 SPI 总线由 4 根线构成:①SCK。串行同步时钟信号,用来同步主机和从机的数据传输,
由主机控制输出,从机在 SCK 的边沿接收或发送数据。②MOSI。主机输出/从机输入线,主机在上升沿(或下
降沿)通过该信号线发送数据给从机,从机在下降沿(或上升沿)通过该信号线接收该数据。③MISO。主
机输入/从机输出线,从机在上升沿(或下降沿)通过该信号线发送数据给主机,主机在下降沿(或上升沿)
通过该信号线接收该数据。④SS。从机片选信号线,它同样是由主机控制输出。[2]
(图1-2-1-1:一主一从SPI通讯设备连接图)
(Figure 1-2-1: One master and one slave SPI communication device connection diagram)
(图1-2-1:一主多从SPI通讯设备连接图)
(Figure 1-2-1-2: One master and multiple slave SPI communication device connection diagram)
SPI通信协议采用的是主从通信模式,通信双方有主从之分,根据从机的设备个数,SPI通信设备之间的连接方式可以分为一主一从和一主多从。
SPI通信协议包含1条时钟信号线、2条数据总线和1条片选信号线,时钟信号线为SCK、2条数据总线为MOSI和MISO,片选信号线为CS。它们的作用介绍如下:
1.SCK(Serial Clock) : 时钟信号线,用于同步通信数据。由通信主机产生,决定了通信的速率,不同的设备支持的最高时钟频率不同,两个设备通信时通信速率受限于低速设备。
2.MOSI(MasterOutput ,Slave Input) : 主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机从这条信号线读入主机发送的数据,数据方向由主机到从机。
3.MISO(Master Input , Slave Output) : 主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输入到主机,数据方向由从机到主机。
。CS(Chip Select) : 片选信号线,也称为CS_N。当有多个从设备与主机相连时,设备的其他数据信号线SCK、MOSI、MISO同时并联到相同的SPI总线上,即无论有多少从设备都使用这三条总线,每个从设备都有一条独立的CS_N信号线,本数据线独占主机的一个引脚,既有多少个从设备就有多少个片选信号线。I2C协议中通过设备地址来寻址,选中总线上的某个设备并与其进行通信;而SPI协议中没有设备地址,它使用CS_N信号线来寻址,当主机要选择从设备时,把该设备的CS_N信号线设置为低电平,该设备即被选中,即片选信号有效,接着主机开始与被选中的从设备进行SPI通信。所以SPI通信以CS_N信号线置为低电平为开始信号,以CS_N信号线拉高为结束信号。
SPI通信协议一共有4种通信模式:模式0、模式1、模式2、模式3.这四种模式分由时钟极性(CPLD,Clock Polarity)和时钟相位(CPHA,ClockPhase)来定义,其中CPOL参数规定了空闲状态(CS_N为高电平,设备未被选中时SCK时钟信号的电平状态,CPHA规定了数据采样是在SCK时钟奇数边沿还是偶数边沿。
设计的 SPI 总线接口完成工作有:①将主机收到的 16 位并行数据转换为串行数据,并发送给从机;
②接收来自从机的串行数据,将其转换为并行数据,通过并行端口输出;③输出从机所需要的输入信号、
时钟信号 SCK 和片选信号 SS
SPI 接口最早是由美国的Motorola 公司所定义的,它的中文名称叫做串行外设接口,用于 Motorola 公司自己研发的产品之中。SPI 接口可以作为通信的桥梁,连接 CPU 等控制设备和外围设备。SPI 接口能够以串行、同步、高速的特点来传输通信数据,具有简单易用、节省面积等优点,因此具有越来越广泛的用途,并且逐渐应用到其他场景,比如 SD 卡、液晶显示屏、射频通信卡等。传统的 SPI 接口可以连接控制设备和外围设备进行全双工通信,随着通信场景的需求,会引入设计半双工、单工等通信方式,会根据功能设计更多的寄存器进行控制。在规模一般的芯片设计中,可能仅简单设计 SPI 接口、满足基本通信需求即可,但是在芯片性能不断发展的今天,在设计 SPI 接口时需要考虑到 SPI 能否与其他各 IP 进行高效的通信与交互,能否在芯片 IP 复杂化的同时可以尽量复用之前的设计,能否适应芯片设计中新的功能点和需求点。SPI 接口设计好后,会进行功能点的验证。如果以传统的 Verilog 语言验证SPI,需要编写大量激励文件,以众多定向验证覆盖到功能点。如果以单纯的System Verilog 语言验证SPI,可以实现随机化的激励输入,达到高效的验证,但是验证平台环境不容易得到复用,在项目迭代中具有一定的麻烦。所以需要寻求既可以高效验证 SPI 功能点,又可以高效搭建实现验证平台环境的方式。[3]
module key_filter
#(
parameter CNT_20MS_MAX = 20'd999_999
)
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire key_in ,
output reg key_flag
);
reg [19:0] cnt_20ms;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms <= 20'd0;
else if(key_in == 1'b1)
cnt_20ms <= 20'd0;
else if(cnt_20ms == CNT_20MS_MAX && key_in == 1'b0)
cnt_20ms <= CNT_20MS_MAX;
else if(key_in == 1'b0)
cnt_20ms <= cnt_20ms + 20'd1;
else
cnt_20ms <= cnt_20ms;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_20MS_MAX - 20'd1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
endmodule
module flash_be_ctrl
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire key_flag ,
output reg cs_n ,
output reg sck ,
output reg mosi
);
parameter CNT_CLK_MAX = 5'd31;
parameter CNT_BYTE_MAX = 3'd6;
parameter CNT_SCK_MAX = 2'd3;
parameter CNT_BIT_MAX = 3'd7;
parameter IDLE = 4'b0001,
WREN = 4'b0010,
DELAY = 4'b0100,
BE = 4'b1000;
parameter WREN_IN = 8'b0000_0110;
parameter BE_IN = 8'b1100_0111;
reg [3:0] state;
reg [4:0] cnt_clk;
reg [2:0] cnt_byte;
reg [1:0] cnt_sck;
reg [2:0] cnt_bit;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else
case(state)
IDLE:
if(key_flag == 1'b1)
state <= WREN;
else
state <= IDLE;
WREN:
if(cnt_clk == CNT_CLK_MAX && cnt_byte == 3'd2)
state <= DELAY;
else
state <=WREN;
DELAY:
if(cnt_clk == CNT_CLK_MAX && cnt_byte == 3'd3)
state <= BE;
else
state <=DELAY;
BE :
if(cnt_clk == CNT_CLK_MAX && cnt_byte == CNT_BYTE_MAX)
state <= IDLE;
else
state <=BE;
default:state <= IDLE;
endcase
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 5'd0;
else
case(state)
IDLE :cnt_clk <= 5'd0;
WREN :cnt_clk <= cnt_clk + 5'd1;//溢出清零
DELAY:cnt_clk <= cnt_clk + 5'd1;//溢出清零
BE :cnt_clk <= cnt_clk + 5'd1;//溢出清零
default:cnt_clk <= cnt_clk;
endcase
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_byte <= 3'd0;
else if(cnt_clk == CNT_CLK_MAX && cnt_byte == CNT_BYTE_MAX)
cnt_byte <= 3'd0;
else if(cnt_clk == CNT_CLK_MAX)
cnt_byte <= cnt_byte + 3'd1;
else
cnt_byte <= cnt_byte;
always@(posedge sys_clk or negedge sys_rst_n)//不同
if(sys_rst_n == 1'b0)
cnt_sck <= 2'd0;
else if((cnt_byte == 3'd1 || cnt_byte == 3'd5) && (cnt_sck == CNT_SCK_MAX))
cnt_sck <= 2'd0;
else if(cnt_byte == 3'd1 || cnt_byte == 3'd5)
cnt_sck <= cnt_sck + 2'd1;
else
cnt_sck <= 2'd0;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_bit <= 3'd0;
else if(cnt_sck == 2'd2 && cnt_bit == CNT_BIT_MAX)
cnt_bit <= 3'd0;
else if(cnt_sck == 2'd2)
cnt_bit <= cnt_bit + 3'd1;
else
cnt_bit <= cnt_bit;
/* always@(*)
case(state)
IDLE :cs_n <= 1'b1;
WREN :cs_n <= 1'b0;
DELAY:cs_n <= 1'b1;
BE :cs_n <= 1'b0;
default:cs_n <= 1'b1;
endcase */
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cs_n <= 1'b1;
else if(key_flag == 1'b1)
cs_n <= 1'b0;
else if(state == WREN && cnt_clk == CNT_CLK_MAX && cnt_byte == 3'd2)
cs_n <= 1'b1;
else if(state == DELAY && cnt_clk == CNT_CLK_MAX && cnt_byte == 3'd3)
cs_n <= 1'b0;
else if(state == BE && cnt_clk == CNT_CLK_MAX && cnt_byte == 3'd6)
cs_n <= 1'b1;
else
cs_n <= cs_n;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sck <= 1'b0;
else if(cnt_sck == 2'd0)
sck <= 1'b0;
else if(cnt_sck == 2'd2)
sck <= 1'b1;
else
sck <= sck;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
mosi <= 1'b0;
else if(state == WREN && cnt_byte == 3'd2)
mosi <= 1'b0;
else if(state == BE && cnt_byte == 3'd6)
mosi <= 1'b0;
else if(state == WREN && cnt_byte == 3'd1 && cnt_sck == 2'd0)
mosi <= WREN_IN[7 - cnt_bit];
else if(state == BE && cnt_byte == 3'd5 && cnt_sck == 2'd0)
mosi <= BE_IN[7 - cnt_bit];
else
mosi <= mosi;
endmodule
module spi_flash_be
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire key_in ,
output wire cs_n ,
output wire sck ,
output wire mosi
);
wire key_flag;
key_filter
#(
.CNT_20MS_MAX(20'd999_999)
)
key_filter_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.key_in (key_in ),
.key_flag (key_flag )
);
flash_be_ctrl flash_be_ctrl_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.key_flag (key_flag ),
.cs_n (cs_n ),
.sck (sck ),
.mosi (mosi )
);
endmodule
`timescale 1ns/1ns
module tb_flash_be_ctrl();
reg sys_clk ;
reg sys_rst_n ;
reg key_flag ;
wire cs_n ;
wire sck ;
wire mosi ;
initial
begin
sys_clk <= 1'b1;
sys_rst_n <= 1'b0;
key_flag <= 1'b0;
#20
sys_rst_n <= 1'b1;
#200
key_flag <= 1'b1;
#20
key_flag <= 1'b0;
end
always #10 sys_clk <= ~sys_clk;
defparam memory.mem_access.initfile = "initmemory.txt";
flash_be_ctrl flash_be_ctrl_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.key_flag (key_flag ),
.cs_n (cs_n ),
.sck (sck ),
.mosi (mosi )
);
m25p16 memory
(
.c (sck ),
.data_in (mosi ),
.s (cs_n ),
.w (1'b1 ),
.hold (1'b1 ),
.data_out ( )
);
endmodule
[1]蒋国庆,顾军.基于FPGA的LPC总线转多路SPI总线设计[J].电子质量,2022(10):39-45.
[2]杨梓鹤,彭秋雨,李湛艺,程晓迪.SPI接口仿真设计与实现[J].科技与创新,2022(19):121-123+126.DOI:10.15913/j.cnki.kjycx.2022.19.038.
[3]王大为. 基于UVM的SPI接口IP核的设计与验证[D].北方工业学,2022.DOI:10.26926/d.cnki.gbfgu.2022.000608.