大家好,最近试了一些spi通信在verilog中的实现方案,最终写了一个比较简单的方案。分享给大家,代码需要可自取。
一、上代码:
`timescale 1ns / 1ps
//
module spi
#(parameter T_bit = 15)
(
input sys_rst,
input sys_clk_10M,//sys_clk=10M,作为主频
input [T_bit-1:0] spi_dataIn,
output reg spi_dataOut = 1'b0,
output reg spi_enOut = 1'b0 ,
output reg spi_clkOut =1'b0 ,
output flag,
output [15:0] cnt_bit,
output [15:0] cnt,
output clk_1M,
output cnt_clk
);
reg [3:0] cnt_clk =4'b0; //对时钟进行分频
always@(posedge sys_clk_10M)
begin
cnt_clk <= cnt_clk + 1'b1;
end
reg clk_1M = 1'b0;
always@(posedge sys_clk_10M)
begin
if(cnt_clk == 3'd5) //
clk_1M <= ~clk_1M;
end
reg [15:0] cnt_bit = 16'd0;
always@(posedge clk_1M)
begin
if(sys_rst ||(cnt_bit == (T_bit-1)) || (spi_enOut == 1)) //系统复位 高有效
cnt_bit <= 16'd0;
else
cnt_bit <= cnt_bit + 1'b1;
end
reg [15:0] cnt = 16'd0;
always@(posedge clk_1M)
begin
if(sys_rst) //系统复位 高有效
begin
cnt <= 16'd0;
end
else if(cnt == 16'd100)
cnt <= 16'd0;
else
cnt <= cnt + 1'b1;
end
reg flag =1'b0 ;
always@(posedge clk_1M)
begin
if(sys_rst) //系统复位 高有效
begin
flag <= 1'd0;
end
else if( (cnt >= 16'd10) && (cnt <= 16'd10 + 16'd14 )) //flag在10us-30us之间有效
flag <= 1'd1;
else
flag <= 1'd0 ;
end
always@(posedge clk_1M)
begin
if(sys_rst || (spi_enOut ==1)) //系统复位 高有效
begin
spi_dataOut <= 1'd0;
end
else if( flag)
spi_dataOut <= spi_dataIn[T_bit - 1 - cnt_bit]; //从高位开始传
else
spi_dataOut <= spi_dataOut ;
end
reg flag_r1 =1'b0,flag_r2=1'b0,flag_r3=1'b0,flag_r4=1'b0,flag_r5=1'b0,flag_r6=1'b0;
reg clk_1M_r1=1'b0,clk_1M_r2=1'b0,clk_1M_r3=1'b0;
always@(posedge sys_clk_10M)
begin
if(sys_rst) //系统复位 高有效
begin
spi_enOut <= 1'd0;
end
else
begin
flag_r1 <= flag; //
flag_r2 <= flag_r1;
flag_r3 <= flag_r2;
flag_r4 <= flag_r3;
flag_r5 <= flag_r4;
flag_r6 <= flag_r5;
spi_enOut <= ~flag_r6;
end
end
always@(posedge sys_clk_10M)
begin
if(sys_rst) //系统复位 高有效
begin
spi_clkOut <= 1'd0;
end
else if( (cnt >= 16'd12) && (cnt <= 16'd10 + 16'd16 )) //flag在??us-??us之间有效
spi_clkOut <= clk_1M;
else
spi_clkOut <= 1'd0 ;
end
endmodule
二、上仿真代码:
`timescale 1ns / 1ps
//
module spi_test;
// Inputs
reg sys_rst;
reg sys_clk_10M;
reg [14:0] spi_dataIn;
// Outputs
wire spi_dataOut;
wire spi_enOut;
wire spi_clkOut;
wire flag;
wire [15:0] cnt_bit;
wire [15:0] cnt;
wire clk_1M;
wire [3:0] cnt_clk;
// Instantiate the Unit Under Test (UUT)
spi uut (
.sys_rst(sys_rst),
.sys_clk_10M(sys_clk_10M),
.spi_dataIn(spi_dataIn),
.spi_dataOut(spi_dataOut),
.spi_enOut(spi_enOut),
.spi_clkOut(spi_clkOut),
.flag(flag),
.cnt_bit(cnt_bit),
.cnt(cnt),
.clk_1M(clk_1M),
.cnt_clk(cnt_clk)
);
initial begin
// Initialize Inputs
sys_rst = 1;
sys_clk_10M = 0;
spi_dataIn = 15'b1111_1010_0101_001;
// Wait 100 ns for global reset to finish
#100;
sys_rst = 0;
forever #50 sys_clk_10M = ~sys_clk_10M;
// Add stimulus here
end
endmodule
三、上仿真结果:
图1:仿真结果
四、对上述仿真结果进行一个简单的解释:
从图中可以看出,当flag信号来临后,一定延时后spi_enOut拉低,spi_clkOut开始在clk_1M上升沿传输数据(一共15个数据)。传输结束后,spi_enOut拉高,spi_clkOut为零。
①clk_1M是spi通信协议的时钟,sys_clk_10M是系统的时钟。其中clk_1M是由sys_clk_10M进行分频得到,分频计数器是cnt_clk。
②flag是spi通信模块接受到的外部使能信号,当flag是1的时候,spi开始通信。本代码中flag信号利用cnt实现了固定位置的触发。
③spi_enOut是spi模块内部使能信号(即片选),低为有效值。本代码中spi_enOut相对于flag具有一定的延时(通过打拍实现,增加打拍数量可以改变延时量)。
④spi_clkOut是spi模块的输出时钟。在spi_clkOut上升沿对spi_dataOut进行读取。其中spi_clkOut相对于clk_1M具有一定的延时。
⑤spi_dataIn是spi模块的输入数据。本代码中是15'b1111_1010_0101_001,在仿真代码中给出。
⑥cnt_bit是传输数据数量,本代码中cnt_bit实现了0-14的循环。
感谢关注!