协议简介
https://www.cnblogs.com/liujinggang/p/9609739.html
https://www.cnblogs.com/deng-tao/p/6004280.html
https://blog.csdn.net/weiqifa0/article/details/82765892
Verilog代码
SPI总线协议是一种全双工的串行通信协议,数据传输时高位在前,低位在后。SPI协议规定一个SPI设备不能在数据通信过程中仅仅充当一个发送者(Transmitter)或者接受者(Receiver)。在片选信号CS为0的情况下,每个clock周期内,SPI设备都会发送并接收1 bit数据,相当于有1 bit数据被交换了。数据传输高位在前,低位在后(MSB first)。SPI主从结构内部数据传输示意图如下图所示
用三段式状态机实现
代码
`timescale 1ns / 1ps
module spi_master2(
input clk,
input rstn,
input en,
input [7:0] tx_data, // 要发送给slave的数据
output reg [7:0] rx_data, // 从slave接收的数据
output trans_done, // 发送完成信号
output rec_done, // 接收完成信号
// spi interface
input miso,
output reg sclk,
output mosi,
output cs
);
reg [1:0] spi_state;
reg [1:0] spi_state_next;
reg [2:0] spi_count;
localparam [1:0] IDLE = 2'd0,TRANS = 2'd1,FINISH = 2'd2;
always @(posedge clk or negedge rstn) begin
if(!rstn) spi_state <= IDLE;
else spi_state <= spi_state_next;
end
always @(*) begin
case(spi_state)
IDLE:
begin
if(en) spi_state_next <= TRANS;
else spi_state_next <= IDLE;
end
TRANS:
begin
if(spi_count == 3'd0 && sclk) spi_state_next <= FINISH; // 跟sclk相与是为了spi_count==0持续1个sclk周期
else spi_state_next <= TRANS;
end
FINISH:
begin
spi_state_next <= IDLE;
end
default: spi_state_next <= IDLE;
endcase
end
// sclk 为系统时钟的二分频时钟
always @(posedge clk or negedge rstn) begin
if(!rstn) sclk <= 1'b0;
else begin
if(spi_state==TRANS) sclk <= ~sclk;
else sclk <= 1'b0;
end
end
// spi_count
always @(posedge clk or negedge rstn) begin
if(!rstn) spi_count <= 3'd7;
else begin
if(spi_state==FINISH) spi_count <= 3'd7;
else if(spi_state==TRANS && sclk) spi_count <= spi_count - 1'b1;
else spi_count <= spi_count;
end
end
// 接收数据
always @(posedge clk or negedge rstn) begin
if(!rstn) rx_data <= 8'd0;
else begin
if(spi_state==TRANS && (~sclk)) rx_data[spi_count] <= miso;
else rx_data <= rx_data;
end
end
// 发送数据
assign mosi = (spi_state == TRANS)? tx_data[spi_count] : 1'bz; // 设为z态方便调试
assign trans_done = (spi_state == FINISH)? 1'b1 : 1'b0;
assign rec_done = trans_done;
assign cs = (spi_state == TRANS)? 1'b1 : 1'b0;
endmodule
测试激励
`timescale 1ns / 1ps
module spi_master2_tb;
// Inputs
reg clk;
reg rstn;
reg en;
reg [7:0] tx_data;
wire miso;
// Outputs
wire [7:0] rx_data;
wire trans_done;
wire rec_done;
wire sclk;
wire mosi;
wire cs;
// Instantiate the Unit Under Test (UUT)
spi_master2 uut (
.clk(clk),
.rstn(rstn),
.en(en),
.tx_data(tx_data),
.rx_data(rx_data),
.trans_done(trans_done),
.rec_done(rec_done),
.miso(miso),
.sclk(sclk),
.mosi(mosi),
.cs(cs)
);
initial begin
// Initialize Inputs
clk = 0;
rstn = 0;
en = 0;
tx_data = 0;
//miso = 0;
// Wait 100 ns for global reset to finish
#100;
@(negedge clk);
rstn = 1;
@(negedge clk);
en = 1;
tx_data = 8'b1010_1010;
@(negedge trans_done);
//miso = 1;
tx_data = 8'b0101_0101;
@(negedge trans_done);
en = 0;
// Add stimulus here
end
assign miso = mosi;
always #20 clk = ~clk;
endmodule
测试波形
用计数实现
代码
`timescale 1ns / 1ps
module spi_master(
input clk,
input rstn,
input en,
input [7:0] tx_data, // 要发送给slave的数据
output reg [7:0] rx_data, // 从slave接收的数据
output reg trans_done, // 发送完成信号
output reg rec_done, // 接收完成信号
// spi interface
input miso,
output reg sclk,
output reg mosi,
output reg cs
);
reg [3:0] spi_state;
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
spi_state <= 4'd0;
cs <= 1'b1;
mosi <= 1'b0;
trans_done <= 1'b0;
sclk <= 1'b0;
rx_data <= 8'd0;
rec_done <= 1'b0;
end
else begin
if(en) begin
spi_state <= spi_state + 1'b1;
cs <= 1'b0;
case(spi_state)
4'd0:
begin
sclk <= 1'b0;
mosi <= tx_data[7];
trans_done <= 1'b0;
rec_done <= 1'b0;
end
4'd1:
begin
sclk <= 1'b1;
mosi <= mosi;
trans_done <= 1'b0;
rx_data[7] <= miso;
rec_done <= 1'b0;
end
4'd2:
begin
sclk <= 1'b0;
mosi <= tx_data[6];
trans_done <= 1'b0;
rec_done <= 1'b0;
end
4'd3:
begin
sclk <= 1'b1;
mosi <= mosi;
trans_done <= 1'b0;
rx_data[6] <= miso;
rec_done <= 1'b0;
end
4'd4:
begin
sclk <= 1'b0;
mosi <= tx_data[5];
trans_done <= 1'b0;
rec_done <= 1'b0;
end
4'd5:
begin
sclk <= 1'b1;
mosi <= mosi;
trans_done <= 1'b0;
rx_data[5] <= miso;
rec_done <= 1'b0;
end
4'd6:
begin
sclk <= 1'b0;
mosi <= tx_data[4];
trans_done <= 1'b0;
rec_done <= 1'b0;
end
4'd7:
begin
sclk <= 1'b1;
mosi <= mosi;
trans_done <= 1'b0;
rx_data[4] <= miso;
rec_done <= 1'b0;
end
4'd8:
begin
sclk <= 1'b0;
mosi <= tx_data[3];
trans_done <= 1'b0;
rec_done <= 1'b0;
end
4'd9:
begin
sclk <= 1'b1;
mosi <= mosi;
trans_done <= 1'b0;
rx_data[3] <= miso;
rec_done <= 1'b0;
end
4'd10:
begin
sclk <= 1'b0;
mosi <= tx_data[2];
trans_done <= 1'b0;
rec_done <= 1'b0;
end
4'd11:
begin
sclk <= 1'b1;
mosi <= mosi;
trans_done <= 1'b0;
rx_data[2] <= miso;
rec_done <= 1'b0;
end
4'd12:
begin
sclk <= 1'b0;
mosi <= tx_data[1];
trans_done <= 1'b0;
rec_done <= 1'b0;
end
4'd13:
begin
sclk <= 1'b1;
mosi <= mosi;
trans_done <= 1'b0;
rx_data[1] <= miso;
rec_done <= 1'b0;
end
4'd14:
begin
sclk <= 1'b0;
mosi <= tx_data[0];
trans_done <= 1'b1;
rec_done <= 1'b0;
end
4'd15:
begin
sclk <= 1'b1;
mosi <= mosi;
trans_done <= 1'b0;
rx_data[0] <= miso;
rec_done <= 1'b1;
end
endcase
end
else begin
spi_state <= 4'd0;
cs <= 1'b1;
mosi <= 1'b0;
trans_done <= 1'b0;
sclk <= 1'b0;
rx_data <= 8'd0;
rec_done <= 1'b0;
end
end
end
endmodule
测试激励
`timescale 1ns / 1ps
module spi_master_tb;
// Inputs
reg clk;
reg rstn;
reg en;
reg [7:0] tx_data;
wire miso;
// Outputs
wire [7:0] rx_data;
wire trans_done;
wire rec_done;
wire sclk;
wire mosi;
wire cs;
// Instantiate the Unit Under Test (UUT)
spi_master uut (
.clk(clk),
.rstn(rstn),
.en(en),
.tx_data(tx_data),
.rx_data(rx_data),
.trans_done(trans_done),
.rec_done(rec_done),
.miso(miso),
.sclk(sclk),
.mosi(mosi),
.cs(cs)
);
initial begin
// Initialize Inputs
clk = 0;
rstn = 0;
en = 0;
tx_data = 0;
//miso = 0;
// Wait 100 ns for global reset to finish
#100;
@(negedge clk);
rstn = 1;
@(negedge clk);
en = 1;
tx_data = 8'b1010_1010;
@(negedge trans_done);
//miso = 1;
tx_data = 8'b0101_0101;
@(negedge trans_done);
en = 0;
// Add stimulus here
end
assign miso = mosi;
always #20 clk = ~clk;
endmodule