SPI protocol introduction
SPI is the abbreviation of serial peripheral interface, which is the serial expansion bus. SPI is a single-master device communication. There is only one master device in the bus that initiates communication. The device that can initiate communication is called the master device. When the SPI master device wants to read and write from the slave device, it first pulls down the ss line of the corresponding slave device (low level is active). Then send the working wheat seed to the clock line. At the corresponding pulse time, the master device sends the signal to MOSI to realize reading and writing, and at the same time, it can sample MISO to realize reading. Generally, SPI communication involves the following terms:
SCLK | serial clock (from master device) |
---|---|
MOSI | Master Output Slave Input (from master device) |
MISO | Master Input Slave Output (from slave device) |
SS | Slave Select (active low, from master device) |
Two link methods of master device and slave device
One master and one slave
A master-slave mode indicates that there is a master and a slave, as shown below: (there is also an SCK signal, from the master to the slave)
One master, many slaves
One master and multiple slaves means that there is one master and one slave. The only difference is that each slave should be equipped with a selection signal.
Working mode of SPI protocol
SPI has four working modes, which are mainly determined by the combination of clock polarity CPOL (Clock Polarity) and clock phase CPHA (Clock Phase).
- If CPOL is 0, it means that SCK is 0 in the idle state. If it is 1, it means SCK is 1 in the idle state.
- When CPHA is 0, it means that the output data is valid on the first edge of SCK, and when CPHA is 1, it means that the input and output data are valid on the second edge of SCK
CPOL = 0, CPHA = 0
CPOL = 0, CPHA = 1
CPOL = 1. CPHA = 0
CPOL = 1, CPHA = 1
Verilog design ideas of SPI MASTER
Design pin description:
Signal name | directions + explanation |
---|---|
clk | input, clock signal |
rst_n | input, reset signal |
miso | Input, slave input to host |
data_i | Input, the master sends data from the slave (certain bit width) |
start | Input, enable signal to start |
mosi | Output, master to slave |
sclk | output, clock signal |
ss_n | Output, slave selection signal |
finish | One transfer complete signal |
First, include the required macro definitions in a file defines.v, the code in which is as follows:
`define CPOL 0 //clock polarity
`define CPHA 0 //clock phase
`define CLK_FREQ 50_000_000 // input clk frequency
`define SCLK_FREQ 5_000_000 // sclk frequency
`define DATA_WIDTH 8 // a word width
`define CLK_CYCLE 20
The second is the design idea of SPI MASTER. The general idea is to use a state machine. First, the state machine is in the IDLE state. Then after receiving the start signal, the data in the register data_I will be sent out one by one. When the specified wide-spaced bits are sent after finishing. There are two options at this time, one is to jump to the FINISH state, the other is to jump to the EXTRA state, and then jump to the FINISH state. Mainly because after the data transmission is completed, it is necessary to determine whether the SCLK state at this time is the default state in idle mode. If not, it needs to jump to the EXTRA state. After the end state, the next state returns to the IDLE state and the start command is obtained.
IDLE | idle state |
---|---|
DATA | Send data status |
EXTRA | extra status |
FINISH | end state |
The verilog code is as follows:
`include "defines.v"
module SPI_MASTER(
input wire clk ,
input wire rst_n ,
input wire miso ,
input wire [`DATA_WIDTH-1:0] data_i ,
input wire start ,
output wire mosi ,
output reg sclk ,
output reg ss_n ,
output wire finish
);
parameter IDLE = 5'b00001 ,
//CHOOSE = 5'b00010 ,
DATA = 5'b00100 ,
EXTRA = 5'b01000 ,
FINISH = 5'b10000 ;
parameter CNT_MAX = `CLK_FREQ / `SCLK_FREQ - 1;
reg [31:0] cnt ; //sclk的时钟周期的计数器
reg [4:0] state ;
reg [4:0] nx_state ;
wire [3:0] cnt_data ; //输出的数据计数器
reg sclk_dly ; //sclk的打一拍信号
reg [3:0] cnt_sclk_pos ; //sclk的上升沿计数器信号
reg [3:0] cnt_sclk_neg ; //sclk的下降沿计数器信号
reg start_dly ; //start的打一拍信号
reg [3:0] cnt_data_dly ;
wire cnt_max_flag ; //计数器cnt达到最大值的信号
wire dec_pos_or_neg_sample ; //1 posedge sample, 0 negedge sample
wire sclk_posedge ; //sclk的上升沿
wire sclk_negedge ; //sclk的下降沿
assign dec_pos_or_neg_sample = (`CPOL == `CPHA) ? 1'b1 : 1'b0;
assign cnt_max_flag = (cnt == CNT_MAX ) ? 1'b1 : 1'b0;
assign sclk_posedge = ((sclk == 1'b1) && (sclk_dly == 1'b0)) ? 1'b1 : 1'b0;
assign sclk_negedge = ((sclk == 1'b0) && (sclk_dly == 1'b1)) ? 1'b1 : 1'b0;
assign cnt_data = dec_pos_or_neg_sample ? cnt_sclk_pos : cnt_sclk_neg;
always @(posedge clk or negedge rst_n) begin
sclk_dly <= sclk;
start_dly <= start;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_sclk_pos <= 4'd0;
end
else if(state == FINISH) begin
cnt_sclk_pos <= 4'd0;
end
//else if((sclk_posedge) && (cnt_sclk_pos == `DATA_WIDTH - 1)) begin
// cnt_sclk_pos <= `DATA_WIDTH - 1;
//end
else if(sclk_posedge) begin
cnt_sclk_pos <= cnt_sclk_pos + 1'b1;
end
else begin
cnt_sclk_pos <= cnt_sclk_pos;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_sclk_neg <= 4'd0;
end
else if(state == FINISH) begin
cnt_sclk_neg <= 4'd0;
end
//else if((sclk_negedge) && (cnt_sclk_neg == `DATA_WIDTH - 1)) begin
// cnt_sclk_neg <= `DATA_WIDTH - 1;
//end
else if(sclk_negedge) begin
cnt_sclk_neg <= cnt_sclk_neg + 1'b1;
end
else begin
cnt_sclk_neg <= cnt_sclk_neg;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state <= IDLE;
end
else begin
state <= nx_state;
end
end
always @(*) begin
nx_state <= IDLE;
case(state)
IDLE: nx_state <= start_dly ? DATA : IDLE;
DATA: nx_state <= (cnt_data == `DATA_WIDTH) ? (`CPHA == 0) ? EXTRA : FINISH : DATA;
EXTRA: nx_state <= cnt_max_flag ? FINISH : EXTRA ;
FINISH: nx_state <= IDLE;
default:nx_state <= IDLE;
endcase
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt <= 'd0;
end
else if((state == DATA) && (nx_state == FINISH) && (cnt == CNT_MAX)) begin
cnt <= 'd0;
end
else if((state == DATA) && (nx_state == EXTRA) && (cnt == CNT_MAX)) begin
cnt <= 'd0;
end
else if((state == DATA) && (cnt == CNT_MAX)) begin
cnt <= 'd0;
end
else if((state == EXTRA) && (cnt == CNT_MAX)) begin
cnt <= 'd0;
end
else if(state == DATA) begin
cnt <= cnt + 1'b1;
end
else if(state == EXTRA) begin
cnt <= cnt + 1'b1;
end
else begin
cnt <= 'd0;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
sclk <= (`CPOL) ? 1'b1 : 1'b0;
end
else if(start_dly) begin
sclk <= ~sclk;
end
else if((state == DATA) && (cnt_max_flag) && (cnt_data < `DATA_WIDTH) ) begin
sclk <= ~sclk;
end
else if((state == DATA) && (cnt_max_flag) && (cnt_data == `DATA_WIDTH) && (nx_state == EXTRA)) begin
sclk <= ~sclk;
end
else if((state == EXTRA) && (cnt_max_flag) && (nx_state == FINISH)) begin
sclk <= ~sclk;
end
else begin
sclk <= sclk;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
ss_n <= 1'b1;
end
else if(start) begin
ss_n <= 1'b0;
end
else if(state == FINISH) begin
ss_n <= 1'b1;
end
else begin
ss_n <= ss_n;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_data_dly <= 'd0;
end
else begin
cnt_data_dly <= cnt_data;
end
end
assign finish = (state == FINISH) ? 1'b1 : 1'b0;
assign mosi = (state == DATA) ? ((cnt_data_dly < `DATA_WIDTH) ? data_i[cnt_data_dly] : data_i[`DATA_WIDTH-1]) : data_i[0];
endmodule
The simulated testbench is as follows:
`timescale 1ns/1ns
`include "defines.v"
module tb_master_slave();
reg clk ;
reg rst_n ;
//reg mosi ;
reg [`DATA_WIDTH-1:0] data_i ;
reg start ;
reg miso ;
wire mosi ;
wire sclk ;
wire finish ;
wire ss_n ;
wire [`DATA_WIDTH-1:0] data_o ;
wire r_finish ;
SPI_MASTER u_spi_master (
.clk (clk) ,
.rst_n (rst_n) ,
.miso (miso) ,
.data_i (data_i) ,
.start (start) ,
.mosi (mosi) ,
.sclk (sclk) ,
.finish (finish) ,
.ss_n (ss_n)
);
SPI_SLAVE u_spi_slave(
.clk (clk) ,
.rst_n (rst_n) ,
.mosi (mosi) ,
.sclk (sclk) ,
.tx_finish(finish),
.start (start) ,
.ss_n (ss_n) ,
.data_o (data_o) ,
.r_finish(r_finish)
);
initial begin
clk = 1'b0;
rst_n = 1'b0;
start = 1'b0;
data_i = 8'h35;
miso = 1'b0;
#30
rst_n = 1'b1;
#10;
@(posedge clk);
start <= 1'b1;
@(posedge clk);
start <= 1'b0;
@(negedge finish);
data_i = 8'h44;
repeat(2) @(posedge clk);
start = 1'b1;
@(posedge clk);
start = 1'b0;
end
always #(`CLK_CYCLE / 2) clk = ~clk;
endmodule
The simulation waveform is as follows, and the data can be successfully sent through mosi.
SPI SLAVE design ideas
The design idea of SPI SPLAVE is generally similar to that of master. The port description is as follows:
Signal name | directions + explanation |
---|---|
clk | input, clock signal |
rst_n | input, reset signal |
miso | Input, slave input to host |
data_i | Input, the master sends data from the slave (certain bit width) |
start | Input, enable signal to start |
mosi | Output, master to slave |
sclk | input, clock signal |
ss_n | Input, slave selection signal |
r_finish | One transfer complete signal |
date_o | Output, collected data |
The state machine idea is still adopted. First, in the IDLE state, when the start signal is enabled, it will jump to the RV_DATA state of receiving data. The RV_DATA data reception is completed, and then jumps back to the FINISH state, indicating that the reading is complete.
IDLE | idle state |
---|---|
RV_ DATA | Receive data status |
FINISH | end state |
The code of spi slave is as follows: |
`include "defines.v"
module SPI_SLAVE(
input wire clk ,
input wire rst_n ,
input wire mosi ,
input wire sclk ,
input wire tx_finish ,
input wire start ,
input wire ss_n ,
output wire [`DATA_WIDTH-1:0] data_o ,
//output wire miso ,
output wire r_finish
);
parameter IDLE = 4'b0001 ,
RV_DATA = 4'b0010 ,
FINISH = 4'b0100 ;
wire sclk_posedge ;
wire sclk_negedge ;
wire dec_pos_or_neg_sample;
//wire sclk_posedge ;
//wire sclk_negedge ;
reg sclk_dly ;
reg [`DATA_WIDTH-1:0] data_shift_pos ;
reg [`DATA_WIDTH-1:0] data_shift_neg ;
reg [3:0] state ;
reg [3:0] nx_state ;
reg [3:0] cnt_sclk_pos ;
reg [3:0] cnt_sclk_neg ;
wire [3:0] num_sample_data ;
assign sclk_posedge = ((sclk == 1'b1) && (sclk_dly == 1'b0)) ? 1'b1 : 1'b0;
assign sclk_negedge = ((sclk == 1'b0) && (sclk_dly == 1'b1)) ? 1'b1 : 1'b0;
assign dec_pos_or_neg_sample = (`CPOL == `CPHA) ? 1'b1 : 1'b0;
//assign sclk_posedge = ((sclk == 1'b1) && (sclk_dly == 1'b0)) ? 1'b1 : 1'b0;
//assign sclk_negedge = ((sclk == 1'b0) && (sclk_dly == 1'b1)) ? 1'b1 : 1'b0;
assign num_sample_data = (dec_pos_or_neg_sample) ? cnt_sclk_pos : cnt_sclk_neg;
always @(posedge clk or negedge rst_n) begin
sclk_dly <= sclk;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state <= IDLE;
end
else begin
state <= nx_state;
end
end
always @(*) begin
nx_state <= IDLE;
case(state)
IDLE: nx_state <= start ? RV_DATA :IDLE;
RV_DATA: begin
if((num_sample_data == 7) && (dec_pos_or_neg_sample) && (sclk_posedge) && (!ss_n)) begin
nx_state <= FINISH;
end
else if((num_sample_data == 7) && (~dec_pos_or_neg_sample) && (sclk_negedge) && (!ss_n)) begin
nx_state <= FINISH;
end
else begin
nx_state <= RV_DATA;
end
end
FINISH: nx_state <= IDLE;
endcase
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_sclk_pos <= 4'd0;
end
else if((state == FINISH)) begin
cnt_sclk_pos <= 4'd0;
end
else if(sclk_posedge) begin
cnt_sclk_pos <= cnt_sclk_pos + 1'b1;
end
else begin
cnt_sclk_pos <= cnt_sclk_pos;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_sclk_neg <= 4'd0;
end
else if (state == FINISH) begin
cnt_sclk_neg <= 4'd0;
end
else if (sclk_negedge) begin
cnt_sclk_neg <= cnt_sclk_neg + 1'b1;
end
else begin
cnt_sclk_neg <= cnt_sclk_neg;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
data_shift_pos <= {
`DATA_WIDTH{
1'b0}};
end
else if((state == RV_DATA) && (sclk_posedge)) begin
data_shift_pos <= {
mosi, data_shift_pos[`DATA_WIDTH-1:1]};
end
else if (state == FINISH) begin
data_shift_pos <= {
`DATA_WIDTH{
1'b0}};
end
else begin
data_shift_pos <= data_shift_pos;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
data_shift_neg <= {
`DATA_WIDTH{
1'b0}};
end
else if((state == RV_DATA) && (sclk_negedge)) begin
data_shift_neg <= {
mosi, data_shift_neg[`DATA_WIDTH-1:1]};
end
else if(state == FINISH) begin
data_shift_neg <= {
`DATA_WIDTH{
1'b0}};
end
else begin
data_shift_neg <= data_shift_neg;
end
end
//assign data_o = dec_pos_or_neg_sample ? data_shift_pos : data_shift_neg;
assign data_o = (state == FINISH) ? (dec_pos_or_neg_sample ? data_shift_pos : data_shift_neg): {
`DATA_WIDTH{
1'b0}};
assign r_finish = (state == FINISH);
endmodule
SPI MASTRT and SLAVE joint simulation
The testbench looks like this:
`timescale 1ns/1ns
`include "defines.v"
module tb_master_slave();
reg clk ;
reg rst_n ;
//reg mosi ;
reg [`DATA_WIDTH-1:0] data_i ;
reg start ;
reg miso ;
wire mosi ;
wire sclk ;
wire finish ;
wire ss_n ;
wire [`DATA_WIDTH-1:0] data_o ;
wire r_finish ;
SPI_MASTER u_spi_master (
.clk (clk) ,
.rst_n (rst_n) ,
.miso (miso) ,
.data_i (data_i) ,
.start (start) ,
.mosi (mosi) ,
.sclk (sclk) ,
.finish (finish) ,
.ss_n (ss_n)
);
SPI_SLAVE u_spi_slave(
.clk (clk) ,
.rst_n (rst_n) ,
.mosi (mosi) ,
.sclk (sclk) ,
.tx_finish(finish),
.start (start) ,
.ss_n (ss_n) ,
.data_o (data_o) ,
.r_finish(r_finish)
);
initial begin
clk = 1'b0;
rst_n = 1'b0;
start = 1'b0;
data_i = 8'h35;
miso = 1'b0;
#30
rst_n = 1'b1;
#10;
@(posedge clk);
start <= 1'b1;
@(posedge clk);
start <= 1'b0;
@(negedge finish);
data_i = 8'h44;
repeat(2) @(posedge clk);
start = 1'b1;
@(posedge clk);
start = 1'b0;
end
always #(`CLK_CYCLE / 2) clk = ~clk;
endmodule
The simulated waveform is as follows, it can be seen that 8'h35 and 8'h44 are successfully read. Verification successful.
Summarize
Through the writing of the spi protocol this time, I feel that I have mastered the state machine, and the waveform can emerge in my mind. come on! Come on! ! !