spi协议--Verilog及仿真
学习文章:https://www.cnblogs.com/liujinggang/p/9609739.html
1、协议原理:
spi协议采用的是主从模式控制,支持一个master和多个slave。如果fpga作为主机,那么SCLK和CS必须由fpga产生。
SPI(Serial Peripheral Interface)串行外设接口,spi接口有四根信号线。
MOSI(SDO):主机数据输出,从机数据输入。在片选信号有效时,数据由高位到低位,在时钟的上升沿依次发送给从设备。
MISO(SDI):主机数据输入,从机数据输出。在片选信号有效时,数据由高位到低位,在时钟的上升沿依次发送给主设备。
SCLK:时钟信号,由主设备产生。
CS:从设备的片选线,由主设备控制。
spi传输有四种模式:由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来决定,其中CPOL决定SCLK在空闲状态是高电平还是低电平,CPHA决定数据是在SCLK的上升沿被采样还是下降沿被采样。
模式0:CPOL=0,CPHA=0。低电平、上升沿采样、下降沿跳变。
模式1:CPOL=0,CPHA=1。低电平、下降沿采样、上升沿跳变。
模式2:CPOL=1,CPHA=0。高电平、下降沿采样、上升沿跳变。
模式3:CPOL=1,CPHA=1。高电平、上升沿采样、下降沿跳变。
这里代码是按照模式0逻辑设计:
这里通过设计一个有16个状态的状态机实现spi中模式0的时序图。
2、协议代码:spi实现数据发送和接收,所以模块用到输入的引脚要有发送的数据i_data_in和接收的MISO。对应要有输出的MOSI和数据输出o_data_out。
综合代码:
module spi( input sys_clk, input sys_rst_n, input i_tx_en, input i_rx_en, input [7:0]i_data_in, output reg[7:0]o_data_out, output reg o_tx_done, output reg o_rx_done, //spi的四根信号线 input MISO, output reg SCLK, output reg CS, output reg MOSI ); reg [3:0]tx_state; reg [3:0]rx_state; always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin tx_state<=4'd0; rx_state<=4'd0; o_data_out<=8'd0; o_tx_done<=1'b0; o_rx_done<=1'b0; SCLK<=1'b0; CS<=1'b0; MOSI<=1'b0; end else if(i_tx_en)begin CS<=1'b0;//片选信号为0,表示开始发送 case(tx_state) 4'd1,4'd3,4'd5,4'd7,4'd9,4'd11,4'd13,4'd15:begin SCLK<=1'b1; tx_state<=tx_state+1'b1; o_tx_done<=1'b0; end 4'd0:begin SCLK<=1'b0; MOSI<=i_data_in[7]; o_tx_done<=1'b0; tx_state<=tx_state+1'b1; end 4'd2:begin SCLK<=1'b0; MOSI<=i_data_in[6]; o_tx_done<=1'b0; tx_state<=tx_state+1'b1; end 4'd4:begin SCLK<=1'b0; MOSI<=i_data_in[5]; o_tx_done<=1'b0; tx_state<=tx_state+1'b1; end 4'd6:begin SCLK<=1'b0; MOSI<=i_data_in[4]; o_tx_done<=1'b0; tx_state<=tx_state+1'b1; end 4'd8:begin SCLK<=1'b0; MOSI<=i_data_in[3]; o_tx_done<=1'b0; tx_state<=tx_state+1'b1; end 4'd10:begin SCLK<=1'b0; MOSI<=i_data_in[2]; o_tx_done<=1'b0; tx_state<=tx_state+1'b1; end 4'd12:begin SCLK<=1'b0; MOSI<=i_data_in[1]; o_tx_done<=1'b0; tx_state<=tx_state+1'b1; end 4'd14:begin SCLK<=1'b0; MOSI<=i_data_in[0]; o_tx_done<=1'b1; tx_state<=tx_state+1'b1; end default:tx_state<=4'd0; endcase end else if(i_rx_en)begin CS<=1'b0;//片选信号为0,表示开始接收 case(rx_state) 4'd0,4'd2,4'd4,4'd6,4'd8,4'd10,4'd12,4'd14:begin SCLK<=1'b0; rx_state<=rx_state+1'b1; o_rx_done<=1'b0; end 4'd1:begin SCLK<=1'b1; o_data_out[7]<=MISO; rx_state<=rx_state+1'b1; o_rx_done<=1'b0; end 4'd3:begin SCLK<=1'b1; o_data_out[6]<=MISO; rx_state<=rx_state+1'b1; o_rx_done<=1'b0; end 4'd5:begin SCLK<=1'b1; o_data_out[5]<=MISO; rx_state<=rx_state+1'b1; o_rx_done<=1'b0; end 4'd7:begin SCLK<=1'b1; o_data_out[4]<=MISO; rx_state<=rx_state+1'b1; o_rx_done<=1'b0; end 4'd9:begin SCLK<=1'b1; o_data_out[3]<=MISO; rx_state<=rx_state+1'b1; o_rx_done<=1'b0; end 4'd11:begin SCLK<=1'b1; o_data_out[2]<=MISO; rx_state<=rx_state+1'b1; o_rx_done<=1'b0; end 4'd13:begin SCLK<=1'b1; o_data_out[1]<=MISO; rx_state<=rx_state+1'b1; o_rx_done<=1'b0; end 4'd15:begin SCLK<=1'b1; o_data_out[0]<=MISO; rx_state<=rx_state+1'b1; o_rx_done<=1'b1; end default:rx_state<=4'd0; endcase end else begin tx_state<=4'd0; rx_state<=4'd0; o_data_out<=8'd0; o_tx_done<=1'b0; o_rx_done<=1'b0; SCLK<=1'b0; CS<=1'b1;//片选信号为1,表示停止数据的接收 MOSI<=1'b0; end end endmodule
仿真代码:这个仿真只是对spi的发送数据的仿真,接收数据没有。
`timescale 1ns/1ns module spi_tb; reg sys_clk; reg sys_rst_n; reg i_tx_en; reg i_rx_en; reg [7:0]i_data_in; reg MISO; wire [7:0]o_data_out; wire o_tx_done; wire o_rx_done; wire SCLK; wire CS; wire MOSI; spi u_spi( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n), .i_tx_en (i_tx_en), .i_rx_en (i_rx_en), .i_data_in (i_data_in), .o_data_out (o_data_out), .o_tx_done (o_tx_done), .o_rx_done (o_rx_done) ); initial begin sys_clk=0; sys_rst_n=0; #10 sys_rst_n=1; i_tx_en=1; i_rx_en=0; i_data_in=8'b0; MISO=0; end always #10 sys_clk=~sys_clk; always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) i_data_in=8'b0000_0000; else if(i_data_in==8'b1111_1111)begin i_tx_en=0; i_data_in=8'b0000_0000; end else if(o_tx_done) i_data_in=i_data_in+1'b1; end endmodule
仿真结果:
这里可以看到仿真时候i_data_in有8'b0000_0000、8'b0000_0001、8'b0000_0010等等,这里仿真时间是1us,所以未能把所有的情况都显示出波形。然后MOSI能够成功发送这三个数据,说明spi发送数据的功能成功实现!
这里明明状态4'd1时,SCLK是1'b1的,还有状态4'd2时,SCLK是1'b0的,这里好像不一样。是因为SCLK的状态要等到sys_clk的上升沿,才能变化!
所以这里能够解释,在发送8'b0000_0001,明明状态4'd14时,o_tx_done应该是1和MOSI应该是在发送最后位1,然后要推到下一个状态4'd15那里,因为其实那里算4'd14的,只是上升沿在那里所以跳变就慢一拍。