`timescale 1ns / 1ps
//spi
module spi#(
parameter SYSCLK = 50_000_000,
parameter SPICLK = 20_000_000
)(
input sysclk,
input rst_n,
//用户接口
input spi_req,//spi的开始信号
input spi_valid,//数据有效信号
input [7:0] data_din,//数据通过MOSI 发送给从机的数据
input [7:0] spi_byte_number,//字节数,本次通信能够发送的字节数
output reg spi_byte_done,//每发送一个字节的完成信号
output reg spi_done, //spi通信结束发送数据完成的信号
output reg [7:0] spi_dout, //从机给主机的数据(采集MISO的)
//标准四线spi接口
output reg CS, //片选信号 用来选择从机拉低有效
output reg SCK, //时钟线,用于同步数据收发
output reg MOSI , //主机发送 ,从机接收
input MISO //主机接收,从机发送
);
localparam delay = SYSCLK / SPICLK; //SCK的分频系数,用于对SCK分频
localparam MID = delay >> 1; //二分之一的SCK的位置 (>> 代表右移一位 ,等效于 /2)
localparam Q_MID = delay >> 2; //四分之一的SCK的位置 (>> 代表右移两位 ,等效于 /4)
localparam TQ_MID = MID + Q_MID; //四分之三的SCK的位置
reg [31:0] cnt_sck;//用于对SCK计数
reg [31:0] cnt_bit;//对数据进行计数
reg [31:0] cnt_byte;//对发送字节进行计数
reg [7:0] data;//对输入进来的数据打拍目的:将数据同步到当前时钟域下
// 对输入进来的数据打拍
always@(posedge sysclk)
if(!rst_n)
data<=0;
else if(spi_valid)
data<=data_din;
else
data<=data;
//cnt_sck对分频计数器赋值
always@(posedge sysclk)
if(!rst_n)
cnt_sck<=0;
else if(CS==0)begin
if(cnt_sck==delay-1)
cnt_sck<=0;
else
cnt_sck<=cnt_sck+1;
end
else
cnt_sck<=0;
//cnt_bit
always@(posedge sysclk)
if(!rst_n)
cnt_bit<=0;
else if(CS==0)begin
if(cnt_sck==delay-1)begin
if(cnt_bit==7)
cnt_bit<=0;
else
cnt_bit<=cnt_bit+1;
end
else
cnt_bit<=cnt_bit ;
end
else
cnt_bit<=0;
//----cnt_byte字节计数
always @(posedge sysclk ) begin
if(!rst_n)
cnt_byte <= 0;
else if(CS==0)begin
if(cnt_bit == 7 && cnt_sck == delay - 1)begin //发生完成一个字节
if(cnt_byte == spi_byte_number - 1) //将所有的字节发生完毕
cnt_byte <= 0;
else
cnt_byte <= cnt_byte + 1;
end
else
cnt_byte <= cnt_byte;
end
else
cnt_byte <= 0;
end
//CS
always@(posedge sysclk)
if(!rst_n||spi_done) //当复位或者spi通信结束的时候,CS拉高
CS<=1;
else if(spi_req)
CS<=0;
else
CS<=CS;
//spi_byte_done
always@(posedge sysclk)
if(!rst_n)
spi_byte_done<=0;
else if(cnt_sck==delay-1&&cnt_bit==7)
spi_byte_done<=1;
else
spi_byte_done<=0;
//spi_done
always@(posedge sysclk)
if(!rst_n)
spi_done<=0;
else if(cnt_sck==delay-1&&cnt_bit==7&&cnt_byte==spi_byte_number-1)//所有的字节发生完毕
spi_done<=1;
else
spi_done<=0;
//spi_dout采集从机返回(MISO)的数据
always@(posedge sysclk)
if(!rst_n)
spi_dout<=0;
else if(CS==0)begin
if(cnt_sck==MID-1)//数据采样
spi_dout<={spi_dout[6:0],MISO};//采集进来的数据是高位
else
spi_dout<=spi_dout;
end
else
spi_dout<=0;
//SCK 0模式
always@(posedge sysclk)
if(!rst_n)
SCK<=0;//空闲时刻 SCK为低电平
else if(CS==0)begin
if(cnt_sck>=Q_MID&&cnt_sck<=TQ_MID-1) //四分之一 到 四分之三的时候拉高
SCK<=1;
else
SCK<=0;
end
else
SCK<=SCK;
//MOSI
always@(posedge sysclk)
if(!rst_n)
MOSI<=0;//空闲时刻 SPI时序没有明确规定是高电平 还是 低电平
else if(CS==0)begin
if(cnt_sck==MID-1)//数据跟新
MOSI<=data_din[7-cnt_bit];
else
MOSI<=MOSI;
end
else
MOSI<=0;
endmodule
仿真
`timescale 1ns / 1ps
module tb_spi(
);
reg sysclk ;
reg rst_n ;
reg spi_req ;
reg [7:0] data_din ;
reg [7:0] spi_byte_number;
reg MISO;
reg spi_valid;
wire spi_byte_done ;
wire spi_done ;
wire [7:0] spi_dout ;
wire CS ;
wire SCK ;
wire MOSI ;
initial begin
sysclk =0;
rst_n =0;
spi_req =0;
spi_valid =1;
MISO =1;
data_din =8'b1010_1010;
spi_byte_number =5;
#101
rst_n=1;
spi_req=1;
#500 spi_req = 1; //开始信号
#20 spi_req = 0;
end
always #10 sysclk=~sysclk;
spi#(
. SYSCLK ( 50_0),
. SPICLK ( 20_0)
)spi_u(
. sysclk (sysclk ) ,
. rst_n (rst_n ) ,
. spi_req ( spi_req ) ,
. spi_valid (spi_valid ) ,//spi的开始信号
. data_din ( data_din ) ,//数据通过MOSI 发送给从机的数据
. spi_byte_number ( spi_byte_number) ,//字节数,本次通信能够发送的字节数
. spi_byte_done ( spi_byte_done ) ,//每发送一个字节的完成信号
. spi_done ( spi_done ) , //spi通信结束发送数据完成的信号
. spi_dout ( spi_dout ) , //从机给主机的数据(采集MISO的)
. CS ( CS ) , //片选信号 用来选择从机拉低有效
. SCK ( SCK ) , //时钟线,用于同步数据收发
. MOSI ( MOSI ) , //主机发送 ,从机接收
. MISO ( MISO ) //主机接收,从机发送
);
endmodule