串行外设接口(Serial Peripheral Interface, SPI)逻辑设计部分 - spi_slave
目錄
- 1. (csn == 1'b1) 時
- 1.1. spi_slave 完成 Master讀寫握手 的最短時間
- 1.2. spi_slave 完成 Slave讀寫握手 的最短時間
- 2. (csn == 1'b0) 時
- (CPOL ^ CPHA == 0)時
- (CPOL ^ CPHA == 1)時
- 3. 信號的復位
- 4. 代碼
先設計再寫代碼!先設計再寫代碼!先設計再寫代碼!
設計時候要畫波形!畫波形!還是TMD畫波形!
spi slave最大的難度在于它是一個slave,但是與spi master之間沒有握手信號,這就導致每次與spi master之間作數據交換的時候,spi slave可能還沒準備好,數據傳輸的可靠性不行,這也是SPI本身的缺陷。
片選信號拉低表示數據傳輸開始,可是如果此時spi還沒有把上一次傳輸的數據輸出、或是這次發送給spi master的數據還沒有準備好,就會產生意料不到的情況。
spi slave是否開啟傳輸,這就是spi slave的兩種狀態,奇葩的是這兩種狀態的轉換是spi master控制的,而且開啟傳輸的時刻spi slave未知。對,咱們要基于這樣的狀態機對spi slave進行設計。
當spi slave 檢測到 被片選中后,可以用計數器計算什么時候這次傳輸結束。
1. (csn == 1’b1) 時
此時不發生傳輸,spi slave 可以與用戶端進行數據交換,但是csn隨時都可能拉低,這里咱們計算一下spi slave與用戶端完成讀寫的最快時間
也就是說,如果spi master兩次傳輸的時間間隔小于這個最快時間,就必定會使spi master與spi slave交換的數據無法被用戶那邊讀出、寫入新數據。
所以可以讓spi master在每次完成傳輸之后等待一段時間再開啟下一次傳輸,這就需要spi master與spi slave相互配合。
1.1. spi_slave 完成 Master讀寫握手 的最短時間
即spi_slave與spi_master完成一次傳輸后,將從spi_master收到的新數據寫給用戶、并從用戶那邊讀出下一次發送給spi_master的數據,所使用的最短時間。
無論是spi slave通過異步FIFO與用戶端作跨時鐘域隔離,還是直接與sck域模塊作交互,都一樣。
那么什么樣的時序對應Master讀寫握手最短時間呢?
以與異步FIFO交互為例
在與spi master交互完成之后,得先把移位寄存器中的新數據寫出去,然后才能讓新數據給移位寄存器。
所以在移位寄存器移位結束立刻拉高wr_en,此時最快的情況應該是FIFO那邊的full就為低,所以一拍就將移位寄存器中的數據寫入FIFO。
由于Master讀新數據需要兩拍,所以最快情況應該是在與spi master交互結束之后立刻拉高rd_en,在下一拍就將rdata讀入移位寄存器。
當CPOL ^ CPHA == 0時,移位寄存器除0bit外其他位都要在下降沿讀新數據,但第0bit還是得在上升沿讀取,所以是兩拍。
當CPOL ^ CPHA == 1時,移位寄存器都是在上升沿讀取,所以也是兩拍。
1.2. spi_slave 完成 Slave讀寫握手 的最短時間
如果是Slave讀寫,也是要求spi_slave先把獲取的數據發給用戶端,再從用戶端獲取新的數據,時序圖如下
2. (csn == 1’b0) 時
在csn拉低時表示與spi master進行寄存器數據交換,整個過程與spi master類似,也是根據CPOL和CPHA兩種模式進行移位,也需要移位寄存器來判斷什么時候csn拉高。
(CPOL ^ CPHA == 0)時
即對應{CPOL,CPHA} == 2'b00 || 2'b11;時,此時是sck上升沿采樣mosi至移位寄存器、sck下降沿驅動miso,大端模式
別忘了在sck上升沿處驅動csn的拉低,csn == 1'b0的第一個沿是下降沿,即先驅動、再采樣。
注意移位寄存器不要違背觸發器單沿觸發的設計要求
即移位寄存器賦值data_shift[7:1] <= tx_data[7:1];、移位都是在下降沿進行。而采樣data_shift[0] <= mosi;則是在上升沿進行
那么判斷傳輸結束還是需要計數器,由于驅動miso是在下降沿,所以計數器也是在下降沿,波形圖與spi master一樣。
(CPOL ^ CPHA == 1)時
即對應{CPOL,CPHA} == 2'b10 || 2'b01;時,此時是sck下降沿采樣mosi至移位寄存器、sck上升沿驅動miso,大端模式
同樣是先采樣、再驅動。先采樣的話,data_shift就需要額外1bit存儲這個先采樣來的值,如下圖
注意這種情況下沒有違背單沿觸發要求,因為讀取新數據data_shift[8:1] <= tx_rdata;或是驅動移位data_shift << 1均在posedge sck時刻
傳輸結束的過程也是使用計數器,如下圖
這樣就可以直到什么時候csn拉高,進而與用戶端的某些交互信號就可以按時拉高。
3. 信號的復位
在實際仿真過程中發現spi slave中的信號有時未被復位、一直為x的情況,為什么呢?分析下
按理說,在rstn為低時應該完成了復位,結果波形顯示復位失敗,原因只有一個——sck沒有了。
原因就是,rstn不僅對spi slave中的信號復位,還在sck產生模塊baud_clk_gen中也對sck進行了復位,那么在rstn為低期間sck才由1’bx變為恒定值,sck壓根就沒有上升沿或下降沿,因此spi slave中的信號一直為x。
例如CLK_FREQ/BAUD_RATE = 2、CPOL=1時波形如下
可見posedge sck或negedge sck時刻,從未有rstn為低的時候,故spi slave中的信號就不會被復位
解決方法是分別為sck和spi模塊各設置一個復位信號,這樣的話先對sck時鐘進行復位,保證sck能夠正確產生,再對spi_master和spi_slave進行同步復位,保證spi模塊的信號能夠正確產生。
例如
4. 代碼
module spi_slave#(parameter CHIP_SEL_NUM = 3,parameter DATA_WIDTH = 16,parameter ASYNC_FIFO_WIDTH = 4096,parameter CPOL = 0,parameter CPHA = 1)(input rstn,input clk,input [DATA_WIDTH-1:0] tx_data,input tx_data_val,output tx_fifo_full,input rx_req,output [DATA_WIDTH-1:0] rx_data,output rx_data_val,output rx_fifo_empty,input sck,input mosi,output miso,input csn);localparam BIT_CNT_WIDTH = $clog2(DATA_WIDTH+1);reg tx_fifo_rd_en; wire [DATA_WIDTH-1:0] tx_fifo_rdata; wire tx_fifo_rdata_val; wire tx_fifo_empty; reg miso_r; wire [DATA_WIDTH-1:0] rx_fifo_rdata; reg rx_fifo_wr_en; wire rx_fifo_full; reg [BIT_CNT_WIDTH-1:0] bit_cnt;generate if(CPOL ^ CPHA == 1'b0) beginreg [DATA_WIDTH-1:0] data_shift;always@(negedge sck) beginif(!rstn)data_shift[DATA_WIDTH-1:1] <= 'b0;else if(csn) beginif(tx_fifo_rdata_val)data_shift[DATA_WIDTH-1:1] <= tx_fifo_rdata[DATA_WIDTH-1:1];endelsedata_shift[DATA_WIDTH-1:1] <= data_shift[DATA_WIDTH-2:0];endalways@(posedge sck) beginif(!rstn)data_shift[0] <= 1'b0;else if(csn) beginif(tx_fifo_rdata_val)data_shift[0] <= tx_fifo_rdata[0];endelsedata_shift[0] <= mosi;endassign rx_fifo_rdata = data_shift;always@(negedge sck) beginif(!rstn)miso_r <= 1'b0;else if(!csn) miso_r <= data_shift[DATA_WIDTH-1];endassign miso = miso_r;always@(negedge sck) beginif(!rstn)bit_cnt <= DATA_WIDTH;else if(!csn)bit_cnt <= bit_cnt - 1;elsebit_cnt <= DATA_WIDTH;endalways@(posedge sck) beginif(!rstn)rx_fifo_wr_en <= 1'b0;else if(csn) beginif(rx_fifo_wr_en && !rx_fifo_full)rx_fifo_wr_en <= 1'b0;endelse if(bit_cnt == 0)rx_fifo_wr_en <= 1'b1;endalways@(posedge sck) beginif(!rstn)tx_fifo_rd_en <= 1'b1;else if(csn) beginif(tx_fifo_rd_en && !tx_fifo_empty)tx_fifo_rd_en <= 1'b0;endelse if(bit_cnt == 0) tx_fifo_rd_en <= 1'b1;endend else beginreg [DATA_WIDTH:0] data_shift;always@(posedge sck) beginif(!rstn)data_shift[DATA_WIDTH:1] <= 'b0;else if(csn) beginif(tx_fifo_rdata_val)data_shift[DATA_WIDTH:1] <= tx_fifo_rdata;endelsedata_shift[DATA_WIDTH:1] <= data_shift[DATA_WIDTH-1:0];endassign rx_fifo_rdata = data_shift[DATA_WIDTH:1];always@(negedge sck) beginif(!rstn)data_shift[0] <= 1'b0;else if(!csn) data_shift[0] <= mosi;endalways@(posedge sck) beginif(!rstn)miso_r <= 1'b0;else if(!csn) miso_r <= data_shift[DATA_WIDTH];endassign miso = miso_r;always@(posedge sck) beginif(!rstn)bit_cnt <= DATA_WIDTH;else if(!csn)bit_cnt <= bit_cnt - 1;elsebit_cnt <= DATA_WIDTH;endalways@(posedge sck) beginif(!rstn)rx_fifo_wr_en <= 1'b0;else if(csn) beginif(rx_fifo_wr_en && !rx_fifo_full)rx_fifo_wr_en <= 1'b0;endelse if(bit_cnt == 1)rx_fifo_wr_en <= 1'b1;endalways@(posedge sck) beginif(!rstn)tx_fifo_rd_en <= 1'b1;else if(csn) beginif(tx_fifo_rd_en && !tx_fifo_empty)tx_fifo_rd_en <= 1'b0;endelse if(bit_cnt == 1) tx_fifo_rd_en <= 1'b1;endendendgeneratelocalparam ADDR_WIDTH = $clog2(ASYNC_FIFO_WIDTH/DATA_WIDTH);async_fifo#(.ASYNC_FIFO_DEPTH (ASYNC_FIFO_WIDTH ),.WDATA_WIDTH (DATA_WIDTH ),.RDATA_WIDTH (DATA_WIDTH ),.PROG_DEPTH (ASYNC_FIFO_WIDTH ),.ADDR_WIDTH (ADDR_WIDTH ))tx_async_fifo(.rstn (rstn ),.wclk (clk ),.wdata ( tx_data ),.wr_en ( tx_data_val ),.full ( tx_fifo_full ),.rclk ( sck ),.rd_en ( tx_fifo_rd_en ),.rdata ( tx_fifo_rdata ),.valid ( tx_fifo_rdata_val ),.empty ( tx_fifo_empty ),.pfull ( ));async_fifo#(.ASYNC_FIFO_DEPTH (ASYNC_FIFO_WIDTH ),.WDATA_WIDTH (DATA_WIDTH ),.RDATA_WIDTH (DATA_WIDTH ),.PROG_DEPTH (ASYNC_FIFO_WIDTH ),.ADDR_WIDTH (ADDR_WIDTH ))rx_async_fifo(.rstn (rstn ),.wclk (sck ),.wdata ( rx_fifo_rdata ),.wr_en (rx_fifo_wr_en ),.full (rx_fifo_full ),.rclk ( clk ),.rd_en ( rx_req ),.rdata ( rx_data ),.valid ( rx_data_val ),.empty ( rx_fifo_empty ),.pfull ( ));endmodule總結
以上是生活随笔為你收集整理的串行外设接口(Serial Peripheral Interface, SPI)逻辑设计部分 - spi_slave的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不知名的有DIO、SCK、RCK引脚的四
- 下一篇: 萌新重装系统指南