作者:小墨同學 記得在上幾篇博客中,有幾名網友提出要加進去錯誤分析這一部分,那我們就從今天這篇文章開始加進去我在消化這段代碼的過程中遇到的迷惑,與大家分享。 今天要寫的是一段基于FIFO的串口發送機設計,之前也寫過串口發送的電路,這次寫的與上次的有幾分類似。這段代碼也是我看過別人寫過的之后,消化一下再根據自己的理解寫出來的,下面是我寫這段代碼的全部流程和思路,希望對剛開始接觸的朋友來說有一點點的幫助,也希望有經驗的朋友給予寶貴的建議。 首先來解釋一下FIFO的含義,FIFO就是First Input First Output的縮寫,就是先入先出的意思,按照我的理解就是,先進去的數據先出,例如一個數組的高位先進,那么讀出來的時候也就高位先出。下面是百度百科的解釋。 FIFO一般用于不同時鐘域之間的數據傳輸,比如FIFO的一端是AD數據采集,另一端是計算機的PCI總線,假設其AD采集的速率為16位 100K SPS,那么每秒的數據量為100K×16bit=1.6Mbps,而PCI總線的速度為33MHz,總線寬度32bit,其最大傳輸速率為1056Mbps,在兩個不同的時鐘域間就可以采用FIFO來作為數據緩沖。另外對于不同寬度的數據接口也可以用FIFO,例如單片機位8位數據輸出,而DSP可能是16位數據輸入,在單片機與DSP連接時就可以使用FIFO來達到數據匹配的目的。 下面我們開始設計。 這次設計我們要設計一個串口發送機,想一下的話,我們要發送數據,總得有一個數據產生模塊和數據發送模塊吧。好,那么在我們的腦海里就出現了這兩個模塊。由于我們這次是借用Altera公司提供的IP核FIFO來完成,所以要加入這個模塊,這個模塊作為一個數據緩沖器,需要我們例化,等會我們按照思路來例化它。 好,模塊出來了,我們將這三個模塊分別定義為dataoutput塊,fifo_ctrl塊和uart_ctrl塊。現在考慮連線。我個人感覺在設計之前,把要設計的東西在草稿紙上將大體框圖畫出來,具體到每一根連線,這樣根據圖來寫代碼要比直接用腦子構圖要方便的多。三個模塊,先考慮時鐘和復位信號線,三個模塊都有,然后,數據產生模塊要將產生的數據發給FIFO模塊,所以要有數據寫入線,我們定義它為wr-datain,數據寫入FIFO塊后總要輸出,這些數據就是我們要發送的數據,所以定義輸出數據線tx_data,先不管FIFO,我們再來定義數據發送模塊的連線,數據發送總要有個啟動信號,所以我們定義變量tx_start,之后,還要有一個輸出端給PC機,我們定義這個輸出端位rs232,對于FIFO模塊的例化過程很簡單就不做過多的說明,只把接口說一下,FIFO模塊除了時鐘,復位信號外,還有數據輸入端口,這個端口要和之前的數據產生模塊的數據輸出端口相連,還有寫請求端口,高電平有效,數據發送模塊每隔1秒鐘產生一個16位的數據,并發送寫請求命令給FIFO,還有讀請求命令,高電平有效數據發送模塊在發送數據時要發送一個讀請求給FIFO,從中讀取數據后再發送給PC機,還有空信號empty,只要檢測到FIFO中有數據,empty就為低電平,我們可用這個信號來啟動數據發送模塊。這樣一來,我們的整體框架就出來了有了這個整體框架,再寫代碼就容易多了。 下面是RTL視圖 ![]() 下面我們來寫代碼 按照這個框架,先把接口定義出來,中間的連線用wire型 ![]() 設計完端口之后我們就來設計底層模塊,先設計數據產生模塊dataoutput 這個部分主要是產生數據,可用一個分頻電路實現每1s發送一次的數據,產生這16位數據的時候,需要16個時鐘,每個時鐘數據自加1,總體來說比較簡單 ![]() 寫完一個模塊之后養成好習慣,馬上把端口例化 ![]() 數據產生以后就要進入緩沖器FIFO,由于這段代碼我們是調用的,所以只要例化接口就好了,只需要將產生的fifo_ctrl_inst文件中例化好的代碼拷貝粘貼就好 ![]() 最后我們要寫數據發送部分 之前已經講過,數據發送部分還要包括兩個子模塊,一個是波特率匹配模塊,一個是發送模塊,既然又包括兩個子模塊,那么我們還要構建一個框圖 按照之前的例子,當FIFO當中有數據時empty就會拉低,我們把它取反后送給發送模塊,告訴發送模塊準備發送,這樣,發送模塊就會產生一個波特率計數器啟動信號bps_start給波特率匹配模塊,波特率匹配模塊收到信號后立馬開始匹配計數,并產生采集信號,將采集信號傳給發送模塊,發送模塊根據采集信號,將數據一位一位發送出去。知道了這個原理之后,我們構建起這樣一個框架 ![]() 根據這個框圖,我們定義端口和線 ![]() 定義完端口之后,開始寫發送模塊,用邊沿脈沖檢測法檢測啟動信號tx_start信號的上升沿來啟動發送部分,波特率配置模塊具體代碼在前面也文章中有給出,就不在說明,寫完之后例化端口,這兩個模塊作為數據發送模塊的子模塊,要在數據發送模塊下例化 ![]() ![]() 這樣一來,我們整個設計就完成了,看上去很簡單,但是從我自己實踐的角度來說還是有點挑戰的,包括中間出現的各種問題,下面就來分享一下我在做這個設計時遇到的問題 1.例化問題 在例化端口時,要注意括號里面的才是本層模塊的端口,也就是說在本層模塊上面已經定義過的變量,括號外面的才是被調用模塊的端口,也在下層模塊的頂部被聲明,我在寫這段程序的時候將二者顛倒了,導致連線不成功,最終是通過查看RTL視圖知道了哪根線有問題才修改成功的 ![]() 2.同一個變量不能在多個always語句中被賦值 我們可能習慣這么寫 always @ (posedge clk or negedge rst_n) if(!rst_n) num <= 1'b0; else num <= num+1'b1; 那么,num的值在其他always語句中就不允許再被賦值或者清零,我在寫的時候在其他always語句中將num 清零了,導致編譯不成功 3. 定義變量之前不要出現該變量,即使后面又定義了 例如,我先進性num的運算,之后再定義num,reg [3:0] num,這樣寫的話雖然編譯沒有錯誤,但是在調用modelsim仿真的時候它會出現編譯錯誤,所以為了規范,不要這樣寫 4. 在邊沿脈沖檢測的時候,我習慣于檢測下降沿,而這里是檢測tx_en 的上升沿,所以我在復位清零的時候錯誤的將兩級寄存器賦值為0,實際上在檢測上升沿時要對兩級寄存器復位時置一,再把最后一級寄存器取反后與上一級相與。 ![]() 5. 在發送數據部分,由于受到上次寫接收部分程序的影響,沒有將起始位發送出去,因為在接收部分,是不需要接收起始位的,是從第一位開始,而在發送部分只有先發送起始位才能和上位機握手通信,還有在發送完數據后要發送停止位,其他情況下都發送高電平來阻止通信的進行 ![]() 6.最后一個問題是最棘手的問題,我找了好大一半天也沒發現,最后還是根據源代碼找出來的,不過我還是不知道將這兩條語句顛倒了對程序有什么影響,只知道顛倒后數據會一直在發送,不會像預設一樣,每隔一秒發送一次,至今還是搞不清楚,希望大神指點迷津 ![]() 總之我覺得語法上的錯誤到不至于太難,寫的多了就不會出錯了,關鍵是邏輯上的錯誤很隱蔽,也很難發現,可以通過RTL視圖來檢測連線上是否正確,還可以借助仿真工具,但是我現在仿真工具用的還不熟,就比如這段代碼,在板子上運行時正確的,但是我用軟件仿真時輸出端一直是高電平不變,也不知道為什么。反正要學的東西還很多,這點水平還是遠遠不夠,繼續加油吧! 今天就到此為止了,謝謝大家! |