FPGA程序的調(diào)試,尤其是大型程序,一直都是耗時耗力的工作。首先是因為HDL語言沿空間并發(fā)擴展的特性不同于一般基于按時間線性敘事的計算機語言,各個元素之間的邏輯關(guān)系更加緊密,不易理解和思考,debug手段也不多。同時,F(xiàn)PGA程序的綜合布線過程都比較緩慢,經(jīng)常一個中等大小的程序會需要等待數(shù)十分鐘才能得到輸出文件,大型設(shè)計run隔夜是常見的事情,隨便一個筆誤都會帶來很高的沉沒成本。每次說到這里,都不由得回憶起多年以前某次出差做實驗,邂逅過一位研究所大哥玩笑說最愛領(lǐng)導指派FPGA任務(wù),因為只要按下綜合按鈕,便能安心歇息半個午后。那時候,天地初開,摸魚這個詞還沒有發(fā)明,也沒有想到即便是當下最強最fancy的CPU也沒能拯救這種等待。
言歸正傳。在邏輯構(gòu)思基本正確,程序編寫大致規(guī)范的前提下,大多數(shù)的問題,都只是寫代碼時的一些小疏忽。通過實施功能仿真,其實可以把多數(shù)邏輯問題都找出來。但是,實際工作中,經(jīng)常沒有時間或耐心做仿真,而是直接編碼后下硬件調(diào)試了。此時,通過仔細閱讀綜合器給出的警告信息,就可以在最耗時的布局布線之前把各種小問題找出來并修正,從而有效地提高開發(fā)效率。在調(diào)試階段,如果遇到了難以理解的現(xiàn)象,回過頭去分析警告信息也是一種有效的辦法。在本文中,我們以vivado自帶綜合器為例、以verilog為編程語言,看看如何理解和利用警告信息排除代碼中的小bug。
(以上圖片來自網(wǎng)絡(luò):?My?digital?designing?diary?by?Mandapati) 為了方便敘述,我們先建一個樣例工程,包含模塊top和adder。
為了避免像很多經(jīng)典教科書中的?“a=(b++)+(++c)”?那樣被指為“例子代碼不注重軟件工程”,特意把這個程序?qū)懙谋M量貼近工程實際一些(除了沒有注釋)。首先它具有特定的功能,對兩路并發(fā)輸入的數(shù)據(jù)流先相加再累加。其次,數(shù)據(jù)端口定義采用時下常用的AXI-Stream風格。
模塊top是頂層模塊,其源碼Top.v如下圖。該模塊具備時鐘信號clk和異步復位信號rst。輸入數(shù)據(jù)端口din_tdata[31:0],配套流控握手信號din_tvalid和din_tready。在模塊內(nèi)部,輸入數(shù)據(jù)首先被劈成2個16bit數(shù)據(jù),代表要相加的兩路數(shù)據(jù)流,并注入加法器模塊adder。加法器的輸出數(shù)據(jù)是adder_out[15:0]。第31行開始的always語句則完成對加法結(jié)果的累加操作。累加結(jié)果從端口acc_tdata輸出,配套數(shù)據(jù)有效標志acc_tvalid。
再看看加法器的源碼Adder.v,如下圖。兩路數(shù)據(jù)din1和din2,共享同一組流控握手信號din_tvalid和din_tready。加法結(jié)果從端口dout輸出。在模塊內(nèi)部,第14行的always語句負責完成所有邏輯處理。核心語句在第21行:當輸入數(shù)據(jù)有效而且后級設(shè)備準備好時,進行一次加法操作。寫到這里作者也是十分感慨,三十多行代碼就是為了伺候第23行的這個“+”號。
至此,例子程序搭建好了。程序比較小,按下Run Synthesis按鈕等待半分鐘左右就能看到綜合結(jié)果。下面我們一起一邊修改代碼一邊看看常見的警告信息都有哪些。 1.?常數(shù)驅(qū)動 警告之所以是警告,是因為綜合器分不清它是否真的有問題。很多警告是可以忽略的。例如上述例程,看上去很完美,但綜合完成后仍然會得到如下的警告信息:
這里,綜合器提醒我們,top模塊的端口din_tready被驅(qū)動為常數(shù)1,可能是一個潛在的問題。在top的邏輯中,din_tready的功能是提醒模塊外部的前級數(shù)據(jù)源“是否準備好接收數(shù)據(jù)”。被驅(qū)動為1則表示“永遠都準備好接收數(shù)據(jù)”。用順藤摸瓜的方法分析代碼,可以看到din_tready是被加法器實例adder1驅(qū)動的,而在加法器內(nèi)部(adder.v的第31行),該信號來自于加法器輸出端的tready。
再看回top.v的第17行,果然加法器的tready被置為常數(shù)1,根源在此。具體到這個例子,此邏輯本身沒有大問題,因為top模塊的輸入端口只有數(shù)據(jù)有效信號acc_tvalid,并沒有配套的tready,說明該端口是強行輸出的,并不考慮后級沒有準備好的情況。 所以,在此例中,這個常數(shù)驅(qū)動警告“基本上”可以被忽略。但是,問題禁不住細琢磨,比如此例程并沒有考慮在復位信號rst有效期間din_tready應(yīng)當拉低來禁止數(shù)據(jù)輸入,不太周全。
進一步的,我們還能聯(lián)想到,在真實的系統(tǒng)中,這個接口定義是否存在隱患? 即,后級模塊是否真的可以無條件接收數(shù)據(jù)? 這些都是警告信息帶來的福利。 2.?無用信號 下面開始折騰代碼。首先把top.v line27處的端口連接去掉,只留下空括號。這樣,adder1實例的dout_tvalid輸出就懸空了。
綜合之后,得到如下圖的警告。綜合器告知adder1中的dout_tvalid所對應(yīng)的寄存器資源被移除。
顯然,這是由于在top中斷開了信號連接,于是dout_tvalid信號在adder內(nèi)部雖然被賦值,但是在整個邏輯中沒有被任何其它地方使用,也沒有輸出,于是綜合器在給出警告后就將其刪除了。從這個例子可以看到,如果一個信號被自動移除了,應(yīng)當首先應(yīng)當考慮它是否沒有在別處被用到。不過,在下一個例子里馬上可以看到這并不是信號被優(yōu)化掉的唯一的原因。 ? 3.?無源信號 首先,先把源碼復原,然后試著把Top.v第17行注釋掉: ?
綜合之后,得到如下警告信息:
第一條信息直奔主題:adder_tready信號沒有被驅(qū)動。這顯然是前述修改帶來的,源程序里缺乏對adder_tready的賦值操作。第二條以及隨后更多的信息則會讓人困惑:adder1/dout[15:0]被從邏輯中移除了。這些信號明明都有被后續(xù)的累加操作用到,為什么還會被優(yōu)化掉? 通過分析adder中的邏輯關(guān)系可以知道,這仍然是因為adder_tready沒有被驅(qū)動,于是綜合器認為凡是依賴于adder_tready的后續(xù)信號都已經(jīng)沒有存在的意義,于是一股腦全拿掉了。這就提示我們,如果發(fā)現(xiàn)有大片的邏輯消失了,不但要往后尋找看是否缺乏最終的輸出,而且要往前尋找看是否存在不確定或者無驅(qū)動的輸入。
當然,對于各種異常情況,不同的綜合器以及同一個綜合器的不同的參數(shù),會表現(xiàn)很大的差異。比如作者也見過有的綜合器會直接給無驅(qū)動信號賦值為0,這種好心好意的掩飾反而導致有時候問題很難查找。 4.?多重驅(qū)動 在top.v的第16行,把原先的adder_d2改成adder_d1,形成一個典型的筆誤。本來是要分別給信號addr_d1和addr_d2賦值,一不小心變成了給信號adder_d1賦值兩次。
? 對于上述情況,綜合器明確指出了有信號被multi-driven了,如下圖。
但是,它指出的對象卻并不是addr_d2,而是我數(shù)據(jù)源din_tdata。這是因為,在綜合器看來,din_tdata[15:0]和din_tdata[31:16]都連接到了addr_d2[15:0],其實就是din_tdata[15:0]與din_tdata[31:16]直接點對點短接了,所以它們本身就面臨多驅(qū)動問題,addr_d2此時只是一個“別名”而已。好比你出門忘記戴帽子,而綜合器告訴你:請注意冷風已經(jīng)接觸頭皮。這種機器式的敘述風格,有時候著實會帶來一些小麻煩,不過習慣了就好了。 5.?復位缺失 top.v第31行的always語句采用了異步復位。復位信號rst與時鐘clk一起作為語句觸發(fā)條件,在語句內(nèi)部先按判斷rst是否為真來選擇執(zhí)行復位操作。這是verilog典型的異步復位語句寫法。這里,嘗試把第36行注釋掉,如下圖。
? 綜合器會給出如下的警告。
字面背后的意思可以理解為:語句中存在復位語段,但是并沒有對acc_tvalid信號做復位操作,導致邏輯缺失,或者說綜合器分不清應(yīng)該set還是reset,于是擔心綜合結(jié)果會與仿真結(jié)果不符。 這類警告可以幫助我們找出因為忘了寫復位而初始值不確定的寄存器,這往往是很多重大bug的來源。如果存在某些寄存器的確不需要復位操作,則應(yīng)當單獨寫一個只有clk做觸發(fā)的always句段,就能避免上述警告。 那么,此時綜合結(jié)果有沒有生成期望的邏輯呢? 打開綜合輸出的邏輯圖(如下),可以看到acc_tvalid由一個沒有復位和置位的D觸發(fā)器驅(qū)動,符合修改后的語句原意。然而,我們?nèi)匀粦?yīng)該設(shè)法避免這類不太規(guī)范的寫法。尤其是對于新手,務(wù)必要了解語言與真實邏輯的映射關(guān)系,謹記verilog就那么幾種常見的語句套路。新奇的寫法,可能導致完全不可預(yù)期的綜合結(jié)果。
6.?位寬失配
修改adder模塊的端口聲明,如下圖,把din1和din2的位寬從16bit分別改為17和15。
如下圖,綜合器會明確指出在top.v中實現(xiàn)adder模塊時遇到了端口寬度不匹配的問題。
需要指出的是,至少對于vivado + verilog,位寬失配警告只對模塊端口連接有用。如果是兩個位寬不同的信號賦值,綜合器將會直接做高位截斷或者高位補零,而不給任何警告,除非截斷操作觸發(fā)了無用信號警告。所以,不論是wire類型還是reg類型,賦值時的位寬對齊問題,完全需要編程者自行關(guān)注。例如下面的語段,16bit的src被賦值給16bit的dst1和15bit的dst2,顯然賦值給dst2時最高位會丟失,但是此時綜合器不會給出警告,這是verilog語言本身的特點,改不了。而且,因為dst1用到了src的所有bit位,所以在綜合器看來src里也不存在無用的bit位,也不會觸發(fā)無用信號警告。最終結(jié)果就是,可能你就是筆誤給dst2少寫了1位,但這個錯誤要到后期調(diào)試時通過各種故障才被發(fā)現(xiàn)。這里并不是綜合器犯懶,而是verilog語言本身就是這樣設(shè)計的,相比之下VHDL就要嚴格的多,不同位寬信號互相賦值不給警告,而是直接報錯。
7.?不應(yīng)有的鎖存器 把top.v中對adder_d1和adder_d2的直接賦值語句改為always句段,如下圖:
上述修改將產(chǎn)生如下圖的警告信息:adder_d1和adder_d2變量引入了鎖存器(latch)。
分析上述語句,可以看到din_tvalid的確相當于鎖存使能信號,當它為1時din_tdata可穿透到adder_d1和adder_d2。如果打開schematic觀察綜合結(jié)果,會發(fā)現(xiàn)此處使用了一個名為LDCE的鎖存器元件。 我們知道,F(xiàn)PGA公認的基礎(chǔ)邏輯資源是查找表和D觸發(fā)器,是否具備鎖存器要看具體的FPGA型號和綜合器的算法,所以在HDL語言中書寫鎖存器風格的語句并不是好辦法,也是這條警告存在的意義。 至此,我們簡單介紹了在綜合階段常見的一些警告問題。當然,在后續(xù)的implementation操作中,還會有很多更難理解的提示和警告出現(xiàn),它們更加地與具體器件的內(nèi)部結(jié)構(gòu)和元素有關(guān)。到了這些階段,更加需要去關(guān)注XDC文件、關(guān)注物理和時序約束,而不是HDL語言本身。
審核編輯:黃飛
評論
查看更多