一、設計思路
發送數據計數器
接收數據計數器
從機的時鍾SCK是由主機支持的,所以不是一個時鍾域,接收時鍾SCK需要防止亞穩態接兩級觸發器
因為邊沿檢測接兩級觸發器延后一拍,所以接收的數據要再接一級觸發器,與接收數據的邊沿對齊
二、參數化設計
從機代碼參數說明
DATA_W:為接收、發送數據的個數
工作方式設置:
- 模式0:spi_sync復位時為0,接收計數器加一條件為上升沿(pedge),發送計數器加一條件為下降沿(nedge)
- 模式1:spi_sync復位時為0,接收計數器加一條件為下降沿(nedge),發送計數器加一條件為上升沿(pedge)
- 模式2:spi_sync復位時為1,接收計數器加一條件為下降沿(nedge),發送計數器加一條件為上升沿(pedge)
- 模式3:spi_sync復位時為0,接收計數器加一條件為上升沿(pedge),發送計數器加一條件為下降沿(nedge)
參考資料:https://www.cnblogs.com/liujinggang/p/9609739.html
模式0:CPOL= 0,CPHA=0。SCK串行時鍾線空閑是為低電平,數據在SCK時鍾的上升沿被采樣,數據在SCK時鍾的下降沿切換
模式1:CPOL= 0,CPHA=1。SCK串行時鍾線空閑是為低電平,數據在SCK時鍾的下降沿被采樣,數據在SCK時鍾的上升沿切換
模式2:CPOL= 1,CPHA=0。SCK串行時鍾線空閑是為高電平,數據在SCK時鍾的下降沿被采樣,數據在SCK時鍾的上升沿切換
模式3:CPOL= 1,CPHA=1。SCK串行時鍾線空閑是為高電平,數據在SCK時鍾的上升沿被采樣,數據在SCK時鍾的下降沿切換
三、SPI從機代碼(工作模式3)
module spi_slave(
clk , //50MHz時鍾
rst_n , //復位
data_in , //要發送的數據
data_out , //接收到的數據
spi_sck , //主機時鍾
spi_miso , //主收從發(從機)
spi_mosi , //主發從收(從機)
spi_cs , //主機片選,低有效(從機)
tx_en , //發送使能
tx_done , //發送完成標志位
rx_done //接收完成標志位
);
//改DATA_W的參數即可實現任意字節的發送和接收,現在是兩字節發送和接收
parameter DATA_W = 16;
parameter SYNC_W = 2;
//計數器參數
parameter CNT_W = 4;
parameter CNT_N = DATA_W;
input clk;
input rst_n;
input [DATA_W-1:0] data_in;
input spi_sck;
input spi_mosi;
input spi_cs;
input tx_en;
output [DATA_W-1:0] data_out;
output spi_miso;
output tx_done;
output rx_done;
reg [DATA_W-1:0] data_out;
reg spi_miso;
reg tx_done;
reg rx_done;
//中間變量
reg [SYNC_W-1:0] spi_sync;
wire nedge;
wire pedge;
reg spi_mosi_reg;
//計數器變量
reg [CNT_W-1:0] cnt_rxbit;
wire add_cnt_rxbit;
wire end_cnt_rxbit;
reg [CNT_W-1:0] cnt_txbit;
wire add_cnt_txbit;
wire end_cnt_txbit;
reg tx_flag;
//邊沿檢測
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
//SCK時鍾空閑狀態位高電平,工作模式3
spi_sync <= 2'b11;
else
spi_sync <= {spi_sync[0],spi_sck};
end
assign nedge = spi_sync[1:0] == 2'b10;
assign pedge = spi_sync[1:0] == 2'b01;
//上升沿接收,工作模式三
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_rxbit <= 0;
else if(add_cnt_rxbit)begin
if(end_cnt_rxbit)
cnt_rxbit <= 0;
else
cnt_rxbit <= cnt_rxbit + 1'b1;
end
end
assign add_cnt_rxbit = pedge;
assign end_cnt_rxbit = add_cnt_rxbit && cnt_rxbit == CNT_N - 1;
//下降沿發送,工作模式三
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_txbit <= 0;
else if(add_cnt_txbit)begin
if(end_cnt_txbit)
cnt_txbit <= 0;
else
cnt_txbit <= cnt_txbit + 1'b1;
end
end
assign add_cnt_txbit = nedge && tx_flag;
assign end_cnt_txbit = add_cnt_txbit && cnt_txbit == CNT_N - 1;
//因為異步信號同步化的原因,為了與延后的下降沿對齊,多打一拍
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
spi_mosi_reg <= 0;
else
spi_mosi_reg <= spi_mosi;
end
//下降沿接收
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
data_out <= 0;
else if(!spi_cs)
data_out[CNT_N - 1 - cnt_rxbit ] <= spi_mosi_reg;
end
//上升沿發送數據
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
spi_miso <= 0;
else if(!spi_cs && tx_flag)
spi_miso <= data_in[CNT_N - 1 - cnt_txbit];
else
spi_miso <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
rx_done <= 0;
else if(end_cnt_rxbit)
rx_done <= 1;
else
rx_done <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_done <= 0;
else if(end_cnt_txbit)
tx_done <= 1;
else
tx_done <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_flag <= 0;
else if(tx_en)
tx_flag <= 1;
else if(end_cnt_txbit)
tx_flag <= 0;
end
endmodule
四、SPI從機 2字節仿真驗證代碼
`timescale 1ns / 1ns
module spi_slave_tb();
parameter DATA_W = 16;
parameter CYCLE = 20;
parameter CYCLE_SPI = 40;
reg clk;
reg rst_n;
reg [DATA_W-1:0] data_in;
reg spi_sck;
reg spi_mosi;
reg spi_cs;
wire [DATA_W-1:0] data_out;
wire spi_miso;
wire rx_done;
wire tx_done;
reg [DATA_W-1:0] data;
reg tx_en;
spi_slave spi_slave(
.clk (clk), //50MHz時鍾
.rst_n (rst_n), //復位
.data_in (data_in), //要發送的數據
.data_out (data_out), //接收到的數據
.spi_sck (spi_sck), //主機時鍾
.spi_miso (spi_miso), //主收從發(從機)
.spi_mosi (spi_mosi), //主發從收(從機)
.spi_cs (1'b0), //主機片選,低有效(從機)
.tx_done (tx_done), //發送完成標志位
.tx_en (tx_en),
.rx_done (rx_done) //接收完成標志位
);
reg [3:0]state;
initial begin
rst_n = 1;
#3;
rst_n = 0;
#(3*CYCLE)
rst_n = 1;
end
initial clk = 1;
always #(CYCLE/2) clk = ~clk;
//SCK空閑時為1
initial spi_sck = 1;
always #(CYCLE_SPI/2) spi_sck = ~spi_sck;
initial begin
data <= 16'hAAA1;
repeat(5)begin
@ (posedge rx_done)
data <= data + 1'b1;
end
#1000;
$stop;
end
initial begin
tx_en = 0;
data_in = 0;
@ (posedge rst_n)
#(10*CYCLE)
data_in = 16'hAAA9;
tx_en = 1;
#(CYCLE)
tx_en = 0;
end
//下降沿發送數據
always @(negedge spi_sck,negedge rst_n)
begin
if(!rst_n)begin
spi_mosi <= 0;
state <= 4'b0000;
end
else begin
case(state)
4'd0:
begin
state <= state + 1;
spi_mosi <= data[15];
end
4'd1:
begin
state <= state + 1;
spi_mosi <= data[14];
end
4'd2:
begin
state <= state + 1;
spi_mosi <= data[13];
end
4'd3:
begin
state <= state + 1;
spi_mosi <= data[12];
end
4'd4:
begin
state <= state + 1;
spi_mosi <= data[11];
end
4'd5:
begin
state <= state + 1;
spi_mosi <= data[10];
end
4'd6:
begin
state <= state + 1;
spi_mosi <= data[9];
end
4'd7:
begin
state <= state + 1;
spi_mosi <= data[8];
end
4'd8:
begin
state <= state + 1;
spi_mosi <= data[7];
end
4'd9:
begin
state <= state + 1;
spi_mosi <= data[6];
end
4'd10:
begin
state <= state + 1;
spi_mosi <= data[5];
end
4'd11:
begin
state <= state + 1;
spi_mosi <= data[4];
end
4'd12:
begin
state <= state + 1;
spi_mosi <= data[3];
end
4'd13:
begin
state <= state + 1;
spi_mosi <= data[2];
end
4'd14:
begin
state <= state + 1;
spi_mosi <= data[1];
end
4'd15:
begin
state <= state + 1;
spi_mosi <= data[0];
end
default:state <= 4'b0000;
endcase
end
end
endmodule