天天看點

FPGA/CPLD數字電路設計經驗分享

 摘要:在數字電路的設計中,時序設計是一個系統性能的主要标志,在高層次設計方法中,對時序控制的抽象度也相應提高,是以在設計中較難把握,但在了解 RTL 電路時序模型的基礎上,采用合理的設計方法在設計複雜數字系統是行之有效的,通過許多設計執行個體證明采用這種方式可以使電路的後仿真通過率大大提高,并且系統的工作頻率可以達到一個較高水準。

關鍵詞:FPGA 數字電路 時序 時延路徑 建立時間 保持時間

1 數字電路設計中的幾個基本概念:

1.1 建立時間和保持時間:

建立時間(setup time)是指在觸發器的時鐘信号上升沿到來以前,資料穩定不變的時間,如果建立時間不夠,資料将不能在這個時鐘上升沿被打入觸發器;保持時間(hold time)是指在觸發器的時鐘信号上升沿到來以後,資料穩定不變的時間, 如果保持時間不夠,資料同樣不能被打入觸發器。 如圖1 。 資料穩定傳輸必須滿足建立和保持時間的要求,當然在一些情況下,建立時間和保持時間的值可以為零。 PLD/FPGA開發軟體可以自動計算兩個相關輸入的建立和保持時間(如圖2)

圖1 建立時間和保持時間關系圖

注:在考慮建立保持時間時,應該考慮時鐘樹向後偏斜的情況,在考慮建立時間時應該考慮時鐘樹向前偏斜的情況。在進行後仿真時,最大延遲用來檢查建立時間,最小延時用來檢查保持時間。

建立時間的限制和時鐘周期有關,當系統在高頻時鐘下無法工作時,降低時鐘頻率就可以使系統完成工作。保持時間是一個和時鐘周期無關的參數,如果設計不合理,使得布局布線工具無法布出高品質的時鐘樹,那麼無論如何調整時鐘頻率也無法達到要求,隻有對所設計系統作較大改動才有可能正常工作,導緻設計效率大大降低。是以合理的設計系統的時序是提高設計品質的關鍵。在可程式設計器件中,時鐘樹的偏斜幾乎可以不考慮,是以保持時間通常都是滿足的。

1.2 FPGA 中的競争和冒險現象

信号在FPGA器件内部通過連線和邏輯單元時,都有一定的延時。延時的大小與連線的長短和邏輯單元的數目有關,同時還受器件的制造技術、工作電壓、溫度等條件的影響。信号的高低電平轉換也需要一定的過渡時間。由于存在這兩方面因素,多路信号的電平值發生變化時,在信号變化的瞬間,組合邏輯的輸出有先後順序,并不是同時變化,往往會出現一些不正确的尖峰信号,這些尖峰信号稱為"毛刺"。如果一個組合邏輯電路中有"毛刺"出現,就說明該電路存在"冒險"。(與分立元件不同,由于PLD内部不存在寄生電容電感,這些毛刺将被完整的保留并向下一級傳遞,是以毛刺現象在PLD、FPGA設計中尤為突出)圖2是一個邏輯冒險的例子,從圖3的仿真波形可以看出,"A、B、C、D"四個輸入信号經過布線延時以後,高低電平變換不是同時發生的,這導緻輸出信号"OUT"出現了毛刺。(我們無法保證所有連線的長度一緻,是以即使四個輸入信号在輸入端同時變化,但經過PLD内部的走線,到達或門的時間也是不一樣的,毛刺必然産生)。可以概括的講,隻要輸入信号同時變化,(經過内部走線)組合邏輯必将産生毛刺。 将它們的輸出直接連接配接到時鐘輸入端、清零或置位端口的設計方法是錯誤的,這可能會導緻嚴重的後果。 是以我們必須檢查設計中所有時鐘、清零和置位等對毛刺敏感的輸入端口,確定輸入不會含有任何毛刺

圖2 存在邏輯冒險的電路示例

圖3 圖2所示電路的仿真波形

冒險往往會影響到邏輯電路的穩定性。時鐘端口、清零和置位端口對毛刺信号十分敏感,任何一點毛刺都可能會使系統出錯,是以判斷邏輯電路中是否存在冒險以及如何避免冒險是設計人員必須要考慮的問題。

如何處理毛刺

我們可以通過改變設計,破壞毛刺産生的條件,來減少毛刺的發生。例如,在數字電路設計中,常常采用格雷碼計數器取代普通的二進制計數器,這是因為格雷碼計數器的輸出每次隻有一位跳變,消除了競争冒險的發生條件,避免了毛刺的産生。

毛刺并不是對所有的輸入都有危害,例如D觸發器的D輸入端,隻要毛刺不出現在時鐘的上升沿并且滿足資料的建立和保持時間,就不會對系統造成危害,我們可以說D觸發器的D輸入端對毛刺不敏感。 根據這個特性,我們應當在系統中盡可能采用同步電路,這是因為同步電路信号的變化都發生在時鐘沿,隻要毛刺不出現在時鐘的沿口并且不滿足資料的建立和保持時間,就不會對系統造成危害。 (由于毛刺很短,多為幾納秒,基本上都不可能滿足資料的建立和保持時間)

去除毛刺的一種常見的方法是利用D觸發器的D輸入端對毛刺信号不敏感的特點,在輸出信号的保持時間内,用觸發器讀取組合邏輯的輸出信号,這種方法類似于将異步電路轉化為同步電路。 圖4給出了這種方法的示範電路,圖5是仿真波形。

如前所述,優秀的設計方案,如采用格雷碼計數器,同步電路等,可以大大減少毛刺,但它并不能完全消除毛刺。 毛刺并不是對所有輸入都有危害,例如D觸發器的D輸入端,隻要毛刺不出現在時鐘的上升沿并且滿足資料的建立和保持時間,就不會對系統造成危害。是以我們可以說D觸發器的D輸入端對毛刺不敏感。但對于D觸發器的時鐘端,置位端,清零端,則都是對毛刺敏感的輸入端,任何一點毛刺就會使系統出錯,但隻要認真處理,我們可以把危害降到最低直至消除。下面我們就對幾種具體的信号進行探讨。

1.3 清除和置位信号在FPGA的設計中,全局的清零和置位信号必須經過全局的清零和置位管腳輸入,因為他們也屬于全局的資源,其扇出能力大,而且在FPGA内部是直接連接配接到所有的觸發器的置位和清零端的,這樣的做法會使晶片的工作可靠、性能穩定,而使用普通的IO腳則不能保證該性能。

在FPGA的設計中,除了從外部管腳引入的全局清除和置位信号外在FPGA内部邏輯的進行中也經常需要産生一些内部的清除或置位信号。清除和置位信号要求象對待時鐘那樣小心地考慮它們,因為這些信号對毛刺也是非常敏感的。

在同步電路設計中,有時候可以用同步置位的辦法來替代異步清0。在用硬體描述語言的設計中可以用如下的方式來描述:

異步清0的描述方法:

process(rst,clk)

begin

if rst=’1’ then

count<=(others=>’0’);

elsif clk’event and clk=’1’ then

count<=count+1;

end if;

end process;

同步清0的描述方法:

process

begin

wait until clk’event and clk=’1’;

if rst=’1’ then

count<=(others=>’0’);

else

count<=count+1;

end if;

end process;

1.4 觸發器和所存器:

我們知道,觸發器是在時鐘的沿進行資料的鎖存的,而所存器是用電平使能來鎖存資料的。是以觸發器的Q輸出端在每一個時鐘沿都會被更新,而所存器隻能在使能電平有效器件才會被更新。在FPGA設計中建議如果不是必須那麼應該盡量使用觸發器而不是所存器。

那麼在使用硬體描述語言進行電路設計的時候如何區分觸發器和所存器的描述方法哪?其實有不少人在使用的過程中可能并沒有特意區分過,是以也忽略了二者在描述方法上的差別。下面是用VHDL語言描述的觸發器和所存器以及綜合器産生的電路邏輯圖。

觸發器的語言描述:

process

begin

wait until clk’event and clk=’1’;

q<=d;

end process;

所存器的語言描述:

process(en,d)

begin

if en=’1’ then

q<=d;

end if;

end process;

由上述對Latch的描述可見,其很容易于選擇器的描述相混淆,用VHDL語言對選擇器的描述方法如下:

process(en,a,b)

begin

if en=’1’ then

q<=a;

else

q<=b;

end if;

end process;

2 FPGA/CPLD 中的一些設計方法

2.1 FPGA 設計中的同步設計

異步設計不是總能滿足(它們所饋送的觸發器的)建立和保持時間的要求。是以,異步輸入常常會把錯誤的資料鎖存到觸發器,或者使觸發器進入亞穩定的狀态,在該狀态下,觸發器的輸出不能識别為l或0。如果沒有正确地處理,亞穩性會導緻嚴重的系統可靠性問題。

另外,在FPGA的内部資源裡最重要的一部分就是其時鐘資源(全局時鐘網絡),它一般是經過FPGA的特定全局時鐘管腳進入FPGA内部,後經過全局時鐘BUF适配到全局時鐘網絡的,這樣的時鐘網絡可以保證相同的時鐘沿到達晶片内部每一個觸發器的延遲時間差異是可以忽略不計的。

在FPGA中上述的全局時鐘網絡被稱為時鐘樹,無論是專業的第三方工具還是器件廠商提供的布局布線器在延時參數提取、分析的時候都是依據全局時鐘網絡作為計算的基準的。如果一個設計沒有使用時鐘樹提供的時鐘,那麼這些設計工具有的會拒絕做延時分析有的延時資料将是不可靠的。

在我們日常的設計中很多情形下會用到需要分頻的情形,好多人的做法是先用高頻時鐘計數,然後使用計數器的某一位輸出作為工作時鐘進行其他的邏輯設計。其實這樣的方法是不規範的。比如下面的描述方法:

process

begin

wait until clk’event and clk=’1’;

if fck=’1’ then

count<=(others=>’0’);

else

count<=count+1;

end if;

end process;

process

begin

wait until count(2)’event and count(2)=’1’ ;

shift_reg<=data;

end process;

在上述的第一個process電路描述中,首先計數器的輸出結果(count(2))相對于全局時鐘clk已經産生了一定的延時(延時的大小取決于計數器的位數和所選擇使用的器件工藝);而在第二個process中使用計數器的bit2作為時鐘,那麼shift_reg相對于全局clk的延時将變得不好控制。布局布線器最終給出的時間分析也是不可靠的。這樣産生的結果波形仿真如下圖所示:

正确的做法可以将第二個process這樣來寫。

process

begin

wait until clk’event and clk=’1’ ;

if count(2 downto 0)=”000” then

shift_reg<=data;

end if;

end process;

或者分成兩步來寫:

process(count)

begin

if count(2 downto 0)=”000” then

en<=’1’;

else

en<=’0’;

end if;

end process;

process

begin

wait until clk’event and clk=’1’ ;

if en=’1’ then

shift_reg<=data;

end if;

end process;

這樣做是相當于産生了一個8分頻的使能信号,在使能信号有效的時候将data資料采樣到shift_reg寄存器中。但此種情形下shift_reg的延時是相對于全局時鐘clk的。下面的圖形更能看得清楚。

2.2 FPGA 設計中的延時電路的産生:

在日常的電路設計中,有時候我們需要對信号進行延時處理來适應對外接口的時序關系,最經常也是最典型的情況是做處理機的接口;因為與處理的接口時序關系是異步的,而一個規範的FPGA設計應該是盡可能采用同步設計。那麼遇到這種情況該如何處理呢?

首先在FPGA中要産生延時,信号必須經過一定的實體資源。在硬體描述語言中有關鍵詞Wait for xx ns,需要說明的是該文法是僅僅用于仿真而不能用于綜合的,可綜合的延時方法有:

使信号經過邏輯門得到延時(如非門); &#61550;

使用器件提供的延時單元(如Altera公司的LCELL,Xilinx公司的); &#61550;

注意:當使用多級非門的時候綜合器往往會将其優化掉,因為綜合器會認為一個信号非兩次還是它自己。

需要說明的是在FPGA/CPLD内部結構是一種标準的宏單元,下圖是Xilinx公司的Spartans II系列器件的一個标準宏單元。雖然不同的廠家的晶片宏單元的結構不同,但概括而言都是由一些組合邏輯外加一或二個觸發器而構成。在實際應用中,當一個子產品内的組合邏輯被使用了那麼與其對應的觸發器也就不能用了;同樣如果觸發器單元被用了那麼組合邏輯單元也就廢了。這就是有時候(特别是使用CPLD)雖然設計使用的資源并不多但布局布線器卻報告資源不夠使用的原因。

現面的一個例子是前一段時間我在公司遇到的一個設計。設計使用Altera公司的EPM7256型号的CPLD。該設計實際使用的寄存器資源隻有109個,占整個器件資源的42%。可是該設計使用了如下圖所示的延時方法來做處理器接口的時序:

在該電路的設計中使用了大量的LCELL來産生100多納秒的延時,這樣做的後果是雖然整個電路的觸發器資源隻使用了42%,可是用MaxplusII進行布局布線已經不能夠通過了。而且我懷疑經過這麼多邏輯的延時後所産生的信号還能保持原來的性能不。

當需要對某一信号作一段延時時,初學者往往在此信号後串接一些非門或其它門電路,此方法在分離電路中是可行的。但在FPGA中,開發軟體在綜合設計時會将這些門當作備援邏輯去掉,達不到延時的效果。用ALTERA公司的MaxplusII開發FPGA時,可以通過插入一些LCELL原語來産生一定的延時,但這樣形成的延時在FPGA晶片中并不穩定,會随溫度等外部環境的改變而改變,是以并不提倡這樣做。在此,可以用高頻時鐘來驅動一移位寄存器,待延時信号作資料輸入,按所需延時正确設定移位寄存器的級數,移位寄存器的輸出即為延時後的信号。此方法産生的延時信号與原信号比有誤差,誤差大小由高頻時鐘的周期來決定。對于資料信号的延時,在輸出端用資料時鐘對延時後信号重新采樣,就可以消除誤差。

對于這樣大的延時我建議的實作方法是采用時鐘鎖存來産生延時的方法,我們知道當一個信号用時鐘鎖存一次,将會占用一個觸發器資源,信号會向後推移一個時鐘周期;該同僚的設計裡CPLD晶片正好連接配接有32MHz的時鐘,那麼每用時鐘鎖存一次ssp信号就會推移31ns,這樣隻需多使用3個觸發器資源就可以達到目的了。電路圖和仿真波形如下圖所示:當然這樣做對原來信号高低電平的寬度會稍有改變,但隻要是在與其接口的晶片的容許範圍之内就不會影響到功能的實作。

2.3 如何提高系統的運作速度

同步電路的速度是指同步時鐘的速度。同步時鐘愈快,電路處理資料的時間間隔越短,電路在機關時間處理的資料量就愈大.我們先來看一看同步電路中資料傳遞的一個基本模型,如下圖:

(Tco是觸發器時鐘到資料輸出的延時;Tdelay是組合邏輯的延時;Tsetup是觸發器的建立時間)

假設資料已經被時鐘的上升沿打入D觸發器,那麼資料到達第一個觸發器的Q端需要Tco,再經過組合邏輯的延時Tdelay到達的第二個觸發器的D端,要想時鐘能在第二個觸發器再次被穩定的鎖入觸發器,則時鐘的延遲不能晚于Tco+Tdelay+Tsetup,(我們可以回顧一下前面講過的建立和保持時間的概念,就可以了解為什麼公式最後要加上一個Tdelay) 由以上分析可知:最小時鐘周期:T=Tco+Tdelay+Tsetup 最快時鐘頻率 F= 1/T PLD開發軟體也正是通過這個公式來計算系統運作速度 Fmax

注:在這個邏輯圖中有個參數:Tpd ,即時鐘的延時參數,我們在剛才做時間分析的時候,沒有提這個參數,(如果使用PLD的全局時鐘型号,Tpd可以為0,如果是普通時鐘,則不為0)。是以如果考慮到時鐘的延時,精确的公式應該是T=Tco+Tdelay+Tsetup-Tpd。當然以上全部分析的都是器件内部的運作速度,如果考慮晶片I/O管腳延時對系統速度的影響,那麼還需要加一些修正。

由于Tco、Tsetup是由具體的器件和工藝決定的,我們設計電路時隻可以改變Tdelay。是以縮短觸發器間組合邏輯的延時是提高同步電路速度的關鍵。由于一般同步電路都不止一級鎖存(如圖3),而要使電路穩定工作,時鐘周期必須滿足最大延時要求,縮短最長延時路徑,才可提高電路的工作頻率。

如圖2所示:我們可以将較大的組合邏輯分解為較小的幾塊,中間插入觸發器,這樣可以提高電路的工作頻率。這也是所謂“流水線”(pipelining)技術的基本原理。

對于圖3的上半部分,它時鐘頻率受制于第二個較大的組合邏輯的延時,通過适當的方法平均配置設定組合邏輯,可以避免在兩個觸發器之間出現過大的延時,消除速度瓶頸。

FPGA/CPLD 開發軟體中也有一些參數設定,通過修改這些設定,可以提高編譯/布局布線後系統速度,但是根據經驗這種速度的提高是很有限的,假如按照要求我們需要設計一個可以工作到50MHz的系統,實際布局布線器報告出來的Fmax隻有40MHz,此時如果我們使用布局布線器的設定選項最多可以提高到45MHz,這還是運氣比較好的情況。而且你必須了解這些選項的含義、使用背景等。

其實在一個設計裡影響速度的瓶頸經常隻會有幾條,我們将延時最大的路徑稱作關鍵路徑。當設計的運作速度不符合系統設計要求的時候我們可以首先找到不能滿足要求的關鍵路徑,按照上述的方法将關鍵路徑上的組合邏輯拆分成多個中間用觸發器隔開,這樣很容易就可以從根本上提升系統的運作速度了。

有的設計在設計開始就知道那部分電路會産生比較大的組合邏輯,導緻速度瓶頸的産生,那麼就應該在開始就想好解決辦法。比如現在設計需要産生一個32位的加法器,并且要求能夠工作在50MHz。根據經驗直接用32位加法器肯定是達不到50MHz的要求的,這時我們可以将其分成3個12位計數器來操作,後面的計數器隻要将前面計數器結果的高位(進位位)相加就可以了。

下面是原來在寬帶接入伺服器設計中的流量統計單元中的32位加法器的描述:

----------------------------------------------------------

---- flow count element

----------------------------------------------------------

-----temporary computing 12 bits adder

process(Count_0_en,count_buffer,Len,Carry_0_0,Carry_0_1)

begin

case Count_0_en is

---1st Step addition (10 downto 0) + (10 downto 0)

when "001" => add_12_a_0 <= ('0' & count_buffer(0)(10 downto 0));

add_12_b_0 <= ('0' & Len(10 downto 0));

---2nd Step addition (21 downto 11) + Carry_0_0

when "010" => add_12_a_0 <= ('0' & count_buffer(0)(21 downto 11));

add_12_b_0 <= ("00000000000" & Carry_0_0);

---3rd Step addition (31 downto 22) + Carry_0_1

when "100" => add_12_a_0 <= ("00" & count_buffer(0)(31 downto 22));

add_12_b_0 <= (" 

繼續閱讀