學習目的:
(1) 熟悉SPI接口和它的讀寫時序;
(2) 復習Verilog仿真語句中的$readmemb命令和$display命令;
(3) 掌握SPI接口寫時序操作的硬件語言描述流程(本例僅以寫時序為例),為以后描述更復雜的時序邏輯電路奠定基礎。
學習過程:
【SPI的相關知識】
① SPI的速度比串口的快,采用源同步傳輸的方式,且為串行傳輸,應用場景不同則時序和接口名稱會有不同;
② 串行flash的讀寫擦除命令可通過SPI接口進行通信,CPU芯片與FPGA可通過SPI接口進行通信,某些芯片的參數寄存器也可通過SPI的方式配置;
③ SPI接口說明
圖1 SPI接口
SCLK:主機給從機的系統時鐘信號;
SDI:主機輸出給從機的數據信號;
SDO:從機輸出給主機的數據信號;
CS:片選信號(此處為高電平有效);
SDIO(三線模式):主機與從機之間的雙向數據總線。
【DAC3283芯片與SPI有關內容】
圖1 DAC3283芯片的寄存器映射圖
從圖1可知,此DAC芯片共有32個寄存器需要配置(CONFIG0~CONFIG31),且每個寄存器均為8bit。
【關于這些寄存器的配置方法】對于寄存器數量少的情況,可以直接定義幾個reg型變量,然后用這些reg變量初始化那些待配置的寄存器;而對于像本例這樣有如此多的寄存器需要配置,則需通過FPGA的ROM或RAM完成寄存器的初始化,即:先將配置參數寫到FPGA的ROM或者RAM中,然后再通過FPGA把這些參數從ROM或者RAM中讀出并寫入到外部芯片的寄存器中去。在本例中,是通過SPI接口寫入到DAC3283芯片的。
【DAC3283芯片的SPI接口時序】
圖2 SPI的寫時序圖
圖3 SPI的讀時序圖
首先,對圖2、圖3中的幾個信號名稱作介紹。SCLK是由FPGA送給DAC芯片的時鐘信號;SDENB是串行接口的使能信號(相當于片選信號),只有當它為低電平時,SPI的讀寫才有效;對于“三線模式”的SPI,SDIO是一個雙向的數據線,負責數據的讀寫數據傳輸;對于“四線模式”的SPI,SDIO也是一個雙向的數據線,但ALARM_SDO是一個只讀數據線,僅負責DAC芯片輸出數據的傳輸。數據在SCLK的上升沿時刻寫入DAC芯片,在SCLK的下降沿時刻從DAC芯片中讀出。
然后,介紹一下SPI每次傳輸的數據內容。本例中,SPI采用的是先傳高位后傳低位的串行傳輸方式,每次傳輸16bit,其中,前8bit構成SPI的指令周期,后8bit構成SPI的數據周期。在前8bit中,rwb(R/W)是讀/寫控制信號,即:若rwb為高電平時,此條指令為讀指令;若rwb為低電平時,此條指令為寫指令。N1、N0指明每幀傳輸的數據字節數(范圍為1~4字節,不包含前面的那個指令字節),A3…A0是DAC芯片各寄存器的地址。
【SPI接口的狀態機】
? | |
? | ? |
圖4 DAC芯片寄存器初始化操作狀態機(概念模型)
IDLE是起始狀態(默認態),work_en是從外部輸入的啟動使能信號(高電平有效);WAIT是等待狀態,在本例中,等待8個時鐘周期后(wait_cnt[3] == 1'b1)即進入到READ_MEM狀態(讀存儲器狀態),直接讀取RAM中的數據;WRITE_REG是寫寄存器狀態,將32個16bit位寬的數據通過串行移位的方式送給DAC芯片的寄存器,移位完1個16bit位寬的數據后(shift_cnt == 4'd15 && pose_flag == 1'b1 && data_end != 1'b1)就回到WAIT狀態,然后重新等待8個時鐘周期,重新執行READ_MEM狀態和WRITE_REG狀態,直到32個數據全都讀寫完畢后才進入STOP狀態(shift_cnt == 4'd15 && pose_flag == 1'b1 && data_end == 1'b1),拉高conf_end信號。
【設計步驟】
(1) 寫spi_ctrl.v文件,首先寫分頻計數器,將50MHz的系統時鐘分頻為1MHz作為SPI的時鐘spi_clk(一般是50~60MHz,但此處僅是演示實驗,用1MHz即可),同時,產生clk_p和clk_n兩個相位相反、頻率為1MHz的reg信號,clk_p是正相時鐘信號,用以觸發脈沖標志信號pose_flag(標志SPI時鐘信號上升沿的到來),clk_n是反相的時鐘信號,用作spi_clk,送給SPI當作時鐘使用,以上共需寫4個always語句塊。
(2) 建立一個16bit x 32的單口RAM IP核(僅讀不寫),并例化到spi_ctrl.v中,完成mif文件的編輯和拷貝。
(3) 聲明狀態機變量,寫狀態機(兩段式),包括等待狀態下的計數器、讀RAM的地址產生模塊(1個系統時鐘周期讀出1個數據)、串行移位寄存器(左移,移出最高位)、移位操作的計數器、data_end信號控制模塊、數據輸出模塊、片選信號控制模塊、conf_end信號控制模塊以及狀態跳轉模塊等部分。
(4) 寫testbench文件和run.do文件,運行仿真并分析仿真結果(跑300us)。對testbench文件的要求如下:①能使用$readmemb命令讀出dac_ini_16x32.mif文件中的數據;②能在rec_spi任務塊中實現對SPI傳輸數據的回收,并與dac_ini_16x32.mif文件中的數據進行校對,然后用$display命令顯示校對結果和數據信息。
(1)設計一個計數分頻模塊,用50MHz的系統時鐘產生1MHz的時鐘信號給SPI讀/寫操作使用,同時產生clk_p和clk_n兩個相位相反、頻率均為1MHz的信號:
同時,產生一個標志clk_p上升沿的標志信號pose_flag,用它統一系統的全局時鐘:
【注意事項】盡量不要使用分頻產生的clk_p或clk_n當作always塊的觸發條件!原因:時鐘信號在很大程度上決定了整個設計的性能和可靠性,應盡量避免使用FPGA內部邏輯產生的時鐘,因為它很容易導致功能或時序出現問題,內部邏輯產生的時鐘信號容易出現毛刺(布線用線的質量較差),影響信號質量,同時,組合邏輯電路固有的延時也容易導致時序問題。詳細的解說請見特權的《深入玩轉FPGA》一書的P59。
(2)RAM IP核的創建和例化:
圖5 RAM IP核的參數配置
圖6 去掉q端寄存器
圖7 新建和添加mif文件
圖8 勾選例化文件
例化到spi_ctrl.v文件中:
(3)聲明狀態變量和狀態參數,寫狀態機:
狀態跳轉控制:
① 寫狀態機寫到WAIT狀態時,發現需要產生wait_end信號,而wait_end信號是由計數等待8個時鐘周期后產生的,故先需要設計WAIT狀態等待計數模塊:
然后,用設計wait_end信號產生模塊:
② 寫狀態機寫到READ_MEM狀態時,發現需要先將RAM中的數據讀出來,故先需設計產生讀RAM地址的模塊:
③ 寫狀態機寫到WRITE_REG狀態時,發現先要將從RAM讀出來的數據緩存起來(通過shift_buf緩存),然后逐比特地通過sdi線送給DAC芯片(串行移位寄存器):
然后,設計模塊產生移位完成的標志信號shift_end:
然后,設計SPI接口的數據輸出模塊、片選信號控制模塊以及時鐘信號產生模塊:
數據逐比特地送給spi_sdi接口,每幀傳輸16bit的數據,當傳輸完32個16bit的數據后,需要產生一個data_end標志信號,標志所有數據已經傳輸完畢:
當傳輸完32個16bit的數據時,也即完成了DAC芯片32個寄存器的配置工作,故需要產生一個conf_end標志信號,標志配置操作的完成:
Testbench文件:
① 用文件控制任務$readmemb讀出dac_ini_16x32.mif文件中的數據,并用這些數據初始化存儲器mif_data(reg [15:0] mif_data[0:31]):
② 計一個名為spi_check的任務,用它回收spi通信的數據,并將其與dac_ini_16x32.mif文件中的數據進行校對,如果數據一致則輸出數據的索引和具體內容,否則提示“SPI write is error!”:
run.do文件:
【仿真結果及分析】
圖10 transcript窗口的信息2
如圖9、圖10所示,Testbench從SPI接口回收的數據與mif文件中的數據一致,由此可見SPI數據傳輸的正確性。
圖11 狀態機視圖
如圖12所示,是SPI仿真波形的整體圖:
圖12 仿真波形圖1
如圖13所示,spi_clk的上升沿對準spi_sdi數據的中心,滿足DAC芯片SPI寫時序的要求,即:數據在時鐘信號上升沿到來的時候寫入DAC芯片。
圖13 仿真波形圖2
如圖14所示,展示了狀態機跳轉到STOP狀態(10000)及data_end上升沿的出現。
圖14 仿真波形圖3
圖15 仿真波形圖4
由圖15可知,在WRITE_REG狀態下,片選信號spi_csn低電平狀態的持續時間約為16us,而spi_clk的周期是1us,故由此可知SPI每次移位傳輸占用了16個SPI時鐘周期,這與代碼中設計的每幀傳輸16bit數據的設想是一致的。
轉載:https://www.cnblogs.com/huangsanye/p/5256494.html