1. 從“黑盒”到“藍(lán)圖”為什么硬件工程師必須懂VHDL如果你剛開始接觸數(shù)字電路設(shè)計(jì)可能會(huì)覺得用一堆邏輯門、觸發(fā)器和連線來搭建一個(gè)復(fù)雜系統(tǒng)就像用樂高積木搭建一座摩天大樓——理論上可行但實(shí)際操作起來繁瑣得讓人頭皮發(fā)麻。早期的硬件工程師確實(shí)就是這么干的他們用原理圖編輯器一個(gè)門一個(gè)門地畫一根線一根線地連。這種方法的弊端顯而易見設(shè)計(jì)規(guī)模稍大圖紙就變得極其復(fù)雜難以閱讀、修改和調(diào)試更重要的是它嚴(yán)重依賴于工程師個(gè)人的經(jīng)驗(yàn)和直覺設(shè)計(jì)復(fù)用性幾乎為零換個(gè)項(xiàng)目就得重畫一遍。VHDL的出現(xiàn)徹底改變了這場(chǎng)游戲。它的全稱是“VHSIC Hardware Description Language”翻譯過來是“超高速集成電路硬件描述語言”。這個(gè)名字聽起來很學(xué)術(shù)但它的核心思想非常樸素用寫代碼的方式來描述硬件的結(jié)構(gòu)和行為。你可以把它理解為給硬件畫“藍(lán)圖”而不是直接去“搬磚”。我剛開始學(xué)的時(shí)候一位老工程師告訴我“原理圖是告訴機(jī)器‘做什么’VHDL是告訴機(jī)器‘你想讓它成為什么’?!边@句話點(diǎn)明了硬件描述語言HDL的本質(zhì)——它是一種更高層次的設(shè)計(jì)抽象。那么為什么是VHDL而不是其他在工業(yè)界尤其是航空航天、國(guó)防、通信和醫(yī)療等對(duì)可靠性和確定性要求極高的領(lǐng)域VHDL因其嚴(yán)謹(jǐn)、強(qiáng)類型和接近于Ada語言的風(fēng)格而備受青睞。它強(qiáng)制工程師進(jìn)行更嚴(yán)格的設(shè)計(jì)編譯器會(huì)幫你檢查很多潛在的錯(cuò)誤比如信號(hào)多驅(qū)動(dòng)、類型不匹配等這能在設(shè)計(jì)早期就避免很多災(zāi)難性的硬件bug。相比之下另一種主流語言Verilog更偏向于C語言風(fēng)格靈活但有時(shí)也意味著松散。很多大型公司特別是歐洲和國(guó)內(nèi)的一些研究所、國(guó)企其設(shè)計(jì)流程和代碼規(guī)范都是基于VHDL建立的。掌握VHDL不僅僅是學(xué)會(huì)一門語言更是拿到了進(jìn)入這些高門檻、高可靠性設(shè)計(jì)領(lǐng)域的敲門磚。從網(wǎng)絡(luò)上的熱門搜索詞如“VHDL交通燈”、“電子鐘VHDL”、“4x4矩陣按鍵VHDL”我們可以清晰地看到VHDL最經(jīng)典和實(shí)用的應(yīng)用場(chǎng)景中小規(guī)模的數(shù)字系統(tǒng)設(shè)計(jì)與FPGA實(shí)現(xiàn)。這些項(xiàng)目完美契合了VHDL的教學(xué)與應(yīng)用它們有明確的功能定義計(jì)時(shí)、控制、輸入檢測(cè)結(jié)構(gòu)層次清晰可以分解為分頻器、計(jì)數(shù)器、譯碼器、狀態(tài)機(jī)等模塊并且非常適合在FPGA開發(fā)板上進(jìn)行實(shí)物驗(yàn)證。學(xué)習(xí)VHDL絕不是為了學(xué)習(xí)一堆抽象的語法最終目的就是為了能獨(dú)立完成從需求分析、代碼編寫、仿真測(cè)試到硬件實(shí)現(xiàn)的完整流程做出一個(gè)能跑起來的、實(shí)實(shí)在在的數(shù)字系統(tǒng)。2. VHDL程序的核心骨架與“并行世界”觀第一次打開一個(gè)VHDL文件你可能會(huì)被它類似程序代碼的結(jié)構(gòu)嚇到但它的組織方式有著嚴(yán)格的硬件邏輯。一個(gè)最基本、最完整的VHDL設(shè)計(jì)文件通常保存為.vhd后綴包含三個(gè)主要部分庫聲明LIBRARY、實(shí)體ENTITY和結(jié)構(gòu)體ARCHITECTURE。這三部分共同定義了一個(gè)硬件模塊。2.1 實(shí)體ENTITY定義芯片的“引腳”實(shí)體相當(dāng)于一塊芯片的數(shù)據(jù)手冊(cè)首頁它只描述這塊芯片對(duì)外暴露的接口而不關(guān)心內(nèi)部是如何實(shí)現(xiàn)的。這體現(xiàn)了“模塊化”和“黑盒”思想。ENTITY traffic_light IS PORT ( clk : IN STD_LOGIC; -- 系統(tǒng)時(shí)鐘輸入如50MHz rst_n : IN STD_LOGIC; -- 低電平有效的復(fù)位信號(hào) key : IN STD_LOGIC; -- 按鍵輸入用于手動(dòng)控制 red : OUT STD_LOGIC; -- 紅燈控制輸出 yellow : OUT STD_LOGIC; -- 黃燈控制輸出 green : OUT STD_LOGIC -- 綠燈控制輸出 ); END ENTITY traffic_light;這段代碼定義了一個(gè)名為traffic_light交通燈的實(shí)體。PORT關(guān)鍵字后面括號(hào)內(nèi)就是它的所有“引腳”。IN表示輸入引腳信號(hào)從外部流向模塊內(nèi)部。例如clk時(shí)鐘和rst_n復(fù)位。OUT表示輸出引腳信號(hào)從模塊內(nèi)部流向外部。例如red,yellow,green。STD_LOGIC是VHDL中最常用的數(shù)據(jù)類型它表示一個(gè)標(biāo)準(zhǔn)的邏輯位可以取‘0’低電平、‘1’高電平還包括‘Z’高阻態(tài)、‘U’未初始化等共9種值比簡(jiǎn)單的BIT類型只有0和1更能真實(shí)模擬硬件行為。注意rst_n這種命名是一種常見的約定_n后綴代表低電平有效Active Low。當(dāng)這個(gè)引腳為‘0’時(shí)系統(tǒng)復(fù)位。這是一種重要的抗干擾設(shè)計(jì)因?yàn)榫€路上的干擾脈沖多為高電平尖峰。2.2 結(jié)構(gòu)體ARCHITECTURE描述芯片內(nèi)部的“電路”實(shí)體定義了外殼結(jié)構(gòu)體則負(fù)責(zé)填充內(nèi)部的靈魂。一個(gè)實(shí)體可以有多個(gè)結(jié)構(gòu)體對(duì)應(yīng)不同的實(shí)現(xiàn)方案但通常我們只用一個(gè)。ARCHITECTURE rtl OF traffic_light IS -- 在這里聲明內(nèi)部使用的信號(hào)、常量、組件等 SIGNAL counter : INTEGER RANGE 0 TO 50000000; -- 內(nèi)部計(jì)數(shù)器 TYPE state_type IS (RED_STATE, GREEN_STATE, YELLOW_STATE); -- 自定義狀態(tài)類型 SIGNAL current_state : state_type; -- 當(dāng)前狀態(tài)寄存器 BEGIN -- 在這里用并行語句描述硬件電路的行為 PROCESS (clk, rst_n) BEGIN IF rst_n 0 THEN -- 復(fù)位邏輯 counter 0; current_state RED_STATE; ELSIF rising_edge(clk) THEN -- 時(shí)鐘上升沿觸發(fā)的邏輯 -- 這里是順序語句區(qū)域但描述的是硬件時(shí)序行為 END IF; END PROCESS; END ARCHITECTURE rtl;結(jié)構(gòu)體以ARCHITECTURE rtl OF traffic_light IS開始rtl是你給這個(gè)結(jié)構(gòu)體起的名字通常用rtl代表寄存器傳輸級(jí)描述。在BEGIN之前是聲明區(qū)用于定義該結(jié)構(gòu)體內(nèi)部使用的“局部變量”——在硬件中它們對(duì)應(yīng)著實(shí)際的連線SIGNAL或寄存器。最關(guān)鍵的概念來了VHDL是并行語言。這與C、Python等軟件編程語言有根本性區(qū)別。在BEGIN之后的所有語句在仿真時(shí)是同時(shí)執(zhí)行的因?yàn)樗鼈儗?duì)應(yīng)的是硬件電路中并行的邏輯門和連線。例如你描述了兩個(gè)互不相關(guān)的賦值語句a b c;和x y and z;在硬件中這兩個(gè)加法器和與門是同時(shí)工作的。唯一引入“順序”執(zhí)行概念的是PROCESS進(jìn)程語句塊。但請(qǐng)注意進(jìn)程塊本身也是并行執(zhí)行的多個(gè)進(jìn)程之間是并行的關(guān)系。進(jìn)程內(nèi)部的語句在仿真時(shí)按書寫順序執(zhí)行但這本質(zhì)上描述的是一個(gè)由觸發(fā)器和組合邏輯構(gòu)成的時(shí)序電路的行為。2.3 庫LIBRARY與程序包PACKAGE你的“工具箱”在實(shí)體聲明之前我們通常需要聲明使用的庫和程序包。LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL;LIBRARY IEEE;聲明使用IEEE這個(gè)庫。IEEE庫是VHDL的標(biāo)準(zhǔn)庫包含了最核心的程序包。USE IEEE.STD_LOGIC_1164.ALL;使用STD_LOGIC_1164程序包中的所有定義。這是我們能使用STD_LOGIC數(shù)據(jù)類型的根本原因。USE IEEE.NUMERIC_STD.ALL;使用NUMERIC_STD程序包。它定義了用于數(shù)值運(yùn)算如無符號(hào)數(shù)UNSIGNED、有符號(hào)數(shù)SIGNED的類型和運(yùn)算符強(qiáng)烈建議用它來代替舊的STD_LOGIC_ARITH等非標(biāo)準(zhǔn)包以保證代碼的可移植性和嚴(yán)謹(jǐn)性。3. 構(gòu)建數(shù)字系統(tǒng)的核心武器進(jìn)程、信號(hào)與數(shù)據(jù)類型理解了基本框架我們需要深入VHDL描述硬件的幾個(gè)核心機(jī)制這是將想法轉(zhuǎn)化為電路的關(guān)鍵。3.1 進(jìn)程PROCESS描述時(shí)序邏輯的容器進(jìn)程是VHDL中用于描述時(shí)序邏輯和復(fù)雜組合邏輯的主要結(jié)構(gòu)。其基本語法是PROCESS (敏感信號(hào)列表) -- 變量聲明區(qū)可選 BEGIN -- 順序執(zhí)行語句 END PROCESS;進(jìn)程的執(zhí)行由敏感信號(hào)列表觸發(fā)。當(dāng)列表中的任何一個(gè)信號(hào)發(fā)生變化時(shí)進(jìn)程內(nèi)部的代碼就會(huì)從頭到尾執(zhí)行一遍。對(duì)于描述時(shí)序電路如計(jì)數(shù)器、狀態(tài)機(jī)的進(jìn)程其敏感列表通常只有時(shí)鐘clk和復(fù)位rst信號(hào)并且在內(nèi)部使用IF rising_edge(clk)或IF clk‘EVENT AND clk ’1‘來檢測(cè)時(shí)鐘上升沿。這就是描述D觸發(fā)器行為的標(biāo)準(zhǔn)模式。讓我們用一個(gè)具體的計(jì)數(shù)器例子來說明這是實(shí)現(xiàn)“電子鐘”或“交通燈定時(shí)”的基礎(chǔ)PROCESS (clk, rst_n) BEGIN IF rst_n 0 THEN sec_counter 0; -- 異步復(fù)位立即生效 ELSIF rising_edge(clk) THEN IF sec_counter 49999999 THEN -- 假設(shè)50MHz時(shí)鐘計(jì)數(shù)5000萬次為1秒 sec_counter 0; sec_pulse 1; -- 產(chǎn)生一個(gè)1時(shí)鐘周期寬的秒脈沖 ELSE sec_counter sec_counter 1; sec_pulse 0; END IF; END IF; END PROCESS;這個(gè)進(jìn)程描述了一個(gè)秒計(jì)數(shù)器。當(dāng)復(fù)位有效時(shí)計(jì)數(shù)器清零。當(dāng)時(shí)鐘上升沿到來且復(fù)位無效時(shí)檢查計(jì)數(shù)器是否達(dá)到滿值。如果達(dá)到則歸零并產(chǎn)生一個(gè)秒脈沖否則計(jì)數(shù)器加1。這里所有的賦值操作信號(hào)賦值都是在時(shí)鐘邊沿瞬間“計(jì)劃”好的實(shí)際硬件中sec_counter和sec_pulse都是在下一個(gè)時(shí)鐘上升沿才會(huì)更新為新的值這正是寄存器觸發(fā)器的工作方式。3.2 信號(hào)SIGNAL與變量VARIABLE硬件連線的抽象這是VHDL初學(xué)者最容易混淆的地方之一。信號(hào)SIGNAL代表硬件中的一條物理連線或一個(gè)寄存器的輸出。它的賦值不是立即生效的。在進(jìn)程內(nèi)信號(hào)賦值使用且其值的更新會(huì)有δ延遲一個(gè)無窮小的仿真延遲在同一個(gè)進(jìn)程周期內(nèi)讀取到的信號(hào)值是其舊值。信號(hào)通常在結(jié)構(gòu)體的聲明區(qū)或進(jìn)程的敏感列表中使用。變量VARIABLE是軟件意義上的臨時(shí)變量?jī)H在進(jìn)程、函數(shù)或過程內(nèi)部存在。它的賦值使用:并且是立即生效的。變量常用于算法中間結(jié)果的臨時(shí)存儲(chǔ)。一個(gè)經(jīng)典的例子能清晰展示區(qū)別PROCESS (clk) VARIABLE var_a : INTEGER : 0; SIGNAL sig_b : INTEGER : 0; BEGIN IF rising_edge(clk) THEN var_a : var_a 1; -- 立即生效var_a 變?yōu)?1 sig_b sig_b 1; -- 計(jì)劃更新sig_b 仍為 0 report Var_a INTEGERIMAGE(var_a) , Sig_b INTEGERIMAGE(sig_b); -- 此時(shí)打印Var_a 1, Sig_b 0 some_signal var_a sig_b; -- 這里計(jì)算用的是 var_a1 和 sig_b0結(jié)果是1 END IF; END PROCESS; -- 在進(jìn)程結(jié)束后sig_b的值才會(huì)更新為1。下一個(gè)時(shí)鐘上升沿時(shí)進(jìn)程內(nèi)讀取到的sig_b才是1。實(shí)操心得在絕大多數(shù)RTL描述中我強(qiáng)烈建議優(yōu)先使用信號(hào)。變量只在某些特定的、需要立即反饋的算法描述中比如循環(huán)索引使用。濫用變量會(huì)導(dǎo)致代碼行為與綜合后的電路嚴(yán)重不符產(chǎn)生難以調(diào)試的仿真與硬件不一致問題。3.3 關(guān)鍵數(shù)據(jù)類型不僅僅是0和1除了基礎(chǔ)的STD_LOGIC和STD_LOGIC_VECTOR多位總線為了設(shè)計(jì)“數(shù)字時(shí)鐘”或處理“矩陣按鍵”我們需要更豐富的數(shù)據(jù)類型。整數(shù)INTEGER與范圍限定用于計(jì)數(shù)器非常方便。但必須用RANGE關(guān)鍵字限定其范圍以便綜合器推斷出需要多少位寬的寄存器。SIGNAL hour : INTEGER RANGE 0 TO 23; -- 綜合器會(huì)使用5位寄存器2^53223 SIGNAL count : INTEGER RANGE 0 TO 49999999; -- 需要26位寄存器枚舉類型Enumeration定義狀態(tài)機(jī)的狀態(tài)集合這是描述復(fù)雜控制邏輯的神器也是“交通燈”控制的核心。TYPE light_state IS (RED, RED_YELLOW, GREEN, YELLOW); SIGNAL state, next_state : light_state;綜合器會(huì)自動(dòng)為每個(gè)狀態(tài)分配二進(jìn)制編碼如00 01 10 11這使得代碼極其易讀和維護(hù)。數(shù)組ARRAY用于定義寄存器組或存儲(chǔ)器。例如可以用于定義數(shù)碼管的段選碼表。TYPE seg_table IS ARRAY (0 TO 9) OF STD_LOGIC_VECTOR(6 DOWNTO 0); CONSTANT SEG7 : seg_table : ( 0111111, -- 0 0000110, -- 1 1011011, -- 2 -- ... 其他數(shù)字 );4. 實(shí)戰(zhàn)拆解從狀態(tài)機(jī)到模塊化設(shè)計(jì)掌握了核心語法我們通過兩個(gè)典型場(chǎng)景——“交通燈狀態(tài)機(jī)”和“矩陣按鍵掃描”——來串聯(lián)這些知識(shí)展示如何用VHDL構(gòu)建一個(gè)可工作的系統(tǒng)。4.1 交通燈控制器的狀態(tài)機(jī)設(shè)計(jì)交通燈是一個(gè)典型的狀態(tài)機(jī)應(yīng)用。我們假設(shè)一個(gè)簡(jiǎn)單的路口紅燈亮30秒 - 綠燈亮25秒 - 黃燈亮5秒如此循環(huán)。第一步定義狀態(tài)和接口ENTITY traffic_light_fsm IS PORT ( clk_1Hz : IN STD_LOGIC; -- 1Hz時(shí)鐘每秒一個(gè)脈沖 rst : IN STD_LOGIC; red : OUT STD_LOGIC; yellow : OUT STD_LOGIC; green : OUT STD_LOGIC ); END ENTITY; ARCHITECTURE behave OF traffic_light_fsm IS TYPE state_type IS (S_RED, S_GREEN, S_YELLOW); SIGNAL current_state, next_state : state_type; SIGNAL timer_cnt : INTEGER RANGE 0 TO 31; -- 計(jì)時(shí)器最大計(jì)到31秒 SIGNAL timer_done : STD_LOGIC; -- 計(jì)時(shí)完成標(biāo)志 BEGIN第二步設(shè)計(jì)狀態(tài)轉(zhuǎn)移和計(jì)時(shí)邏輯這是核心部分通常用兩個(gè)進(jìn)程來實(shí)現(xiàn)一個(gè)時(shí)序進(jìn)程管理當(dāng)前狀態(tài)和計(jì)數(shù)器一個(gè)組合進(jìn)程計(jì)算下一個(gè)狀態(tài)和輸出。-- 進(jìn)程1時(shí)序邏輯在時(shí)鐘沿更新狀態(tài)和計(jì)數(shù)器 STATE_REG: PROCESS (clk_1Hz, rst) BEGIN IF rst 1 THEN current_state S_RED; timer_cnt 0; ELSIF rising_edge(clk_1Hz) THEN current_state next_state; -- 狀態(tài)轉(zhuǎn)移 -- 計(jì)數(shù)器邏輯 IF timer_done 1 THEN timer_cnt 0; -- 計(jì)時(shí)到清零 ELSE timer_cnt timer_cnt 1; -- 否則加1 END IF; END IF; END PROCESS STATE_REG; -- 進(jìn)程2組合邏輯計(jì)算下一個(gè)狀態(tài)和計(jì)時(shí)條件 STATE_TRANS: PROCESS (current_state, timer_cnt) BEGIN -- 默認(rèn)值避免產(chǎn)生鎖存器Latch next_state current_state; timer_done 0; red 0; yellow 0; green 0; CASE current_state IS WHEN S_RED red 1; IF timer_cnt 29 THEN -- 紅燈亮30秒計(jì)數(shù)0-29 timer_done 1; next_state S_GREEN; END IF; WHEN S_GREEN green 1; IF timer_cnt 24 THEN -- 綠燈亮25秒 timer_done 1; next_state S_YELLOW; END IF; WHEN S_YELLOW yellow 1; IF timer_cnt 4 THEN -- 黃燈亮5秒 timer_done 1; next_state S_RED; END IF; END CASE; END PROCESS STATE_TRANS;關(guān)鍵技巧避免鎖存器Latch。在描述組合邏輯的進(jìn)程如STATE_TRANS中必須確保在IF或CASE語句的所有可能分支下每一個(gè)被賦值的信號(hào)都有明確的賦值。否則綜合工具會(huì)推斷出鎖存器這通常不是設(shè)計(jì)者的本意且會(huì)帶來時(shí)序和可靠性問題。上面代碼中在進(jìn)程開頭給所有輸出信號(hào)賦默認(rèn)值是避免鎖存器最有效的方法之一。4.2 4x4矩陣按鍵掃描模塊設(shè)計(jì)矩陣按鍵是為了節(jié)省IO口將按鍵排列成矩陣通過行掃描和列檢測(cè)來識(shí)別哪個(gè)鍵被按下。這是一個(gè)經(jīng)典的“掃描-檢測(cè)”電路。設(shè)計(jì)思路讓FPGA的4個(gè)IO口循環(huán)輸出低電平掃描行其余置高。同時(shí)讀取4個(gè)列IO口的電平。當(dāng)某一行被拉低時(shí)如果某一列也被檢測(cè)到低電平則說明該行該列交叉點(diǎn)的按鍵被按下。ENTITY keypad_scanner IS PORT ( clk : IN STD_LOGIC; -- 系統(tǒng)高速時(shí)鐘 rst_n : IN STD_LOGIC; row_pins : OUT STD_LOGIC_VECTOR(3 DOWNTO 0); -- 行驅(qū)動(dòng)低有效 col_pins : IN STD_LOGIC_VECTOR(3 DOWNTO 0); -- 列檢測(cè)低有效 key_value : OUT STD_LOGIC_VECTOR(3 DOWNTO 0); -- 鍵值編碼如“0001”代表1號(hào)鍵 key_pressed : OUT STD_LOGIC -- 按鍵有效脈沖 ); END ENTITY; ARCHITECTURE rtl OF keypad_scanner IS SIGNAL scan_cnt : INTEGER RANGE 0 TO 3 : 0; -- 行掃描計(jì)數(shù)器 SIGNAL debounce_cnt : INTEGER RANGE 0 TO 49999; -- 去抖計(jì)數(shù)器假設(shè)時(shí)鐘50MHz計(jì)數(shù)5ms SIGNAL col_sampled : STD_LOGIC_VECTOR(3 DOWNTO 0); -- 采樣后的列值 SIGNAL key_detected : STD_LOGIC; -- 內(nèi)部按鍵檢測(cè)標(biāo)志 BEGIN -- 進(jìn)程行掃描計(jì)數(shù)器和去抖定時(shí)器 PROCESS (clk, rst_n) BEGIN IF rst_n 0 THEN scan_cnt 0; debounce_cnt 0; ELSIF rising_edge(clk) THEN -- 行掃描計(jì)數(shù)器慢速循環(huán)例如每1ms換一行 IF debounce_cnt 49999 THEN debounce_cnt 0; IF scan_cnt 3 THEN scan_cnt 0; ELSE scan_cnt scan_cnt 1; END IF; ELSE debounce_cnt debounce_cnt 1; END IF; END IF; END PROCESS; -- 組合邏輯根據(jù)當(dāng)前掃描行驅(qū)動(dòng)row_pins并生成鍵值 PROCESS (scan_cnt, col_sampled) BEGIN -- 默認(rèn)行輸出全高無效 row_pins (OTHERS 1); -- 根據(jù)掃描計(jì)數(shù)器將對(duì)應(yīng)行拉低 CASE scan_cnt IS WHEN 0 row_pins(0) 0; WHEN 1 row_pins(1) 0; WHEN 2 row_pins(2) 0; WHEN 3 row_pins(3) 0; WHEN OTHERS NULL; END CASE; -- 鍵值編碼邏輯示例簡(jiǎn)單將行號(hào)和列號(hào)拼接 key_detected 0; key_value (OTHERS 0); IF row_pins(0) 0 AND col_sampled(0) 0 THEN key_value 0000; key_detected 1; -- 第0行第0列按鍵 ELSIF row_pins(0) 0 AND col_sampled(1) 0 THEN key_value 0001; key_detected 1; -- 第0行第1列按鍵 -- ... 其他14個(gè)按鍵的判斷邏輯以此類推 END IF; END PROCESS; -- 對(duì)列輸入進(jìn)行同步采樣避免亞穩(wěn)態(tài) PROCESS (clk) BEGIN IF rising_edge(clk) THEN col_sampled col_pins; END IF; END PROCESS; -- 生成一個(gè)時(shí)鐘周期寬的按鍵有效脈沖 PROCESS (clk) VARIABLE key_detected_d : STD_LOGIC : 0; BEGIN IF rising_edge(clk) THEN key_pressed key_detected AND (NOT key_detected_d); key_detected_d : key_detected; END IF; END PROCESS; END ARCHITECTURE rtl;這個(gè)設(shè)計(jì)包含了幾個(gè)重要的工程實(shí)踐去抖動(dòng)Debounce機(jī)械按鍵按下時(shí)會(huì)產(chǎn)生毛刺通過一個(gè)計(jì)數(shù)器debounce_cnt延時(shí)一段時(shí)間如5ms再采樣可以穩(wěn)定讀取鍵值。同步化Synchronization外部異步信號(hào)col_pins直接進(jìn)入時(shí)鐘域可能引發(fā)亞穩(wěn)態(tài)。用一個(gè)觸發(fā)器進(jìn)行采樣col_sampled col_pins;是簡(jiǎn)單的同步化處理。邊沿檢測(cè)最后一個(gè)進(jìn)程通過一個(gè)變量key_detected_d鎖存上一個(gè)時(shí)鐘周期的檢測(cè)標(biāo)志與當(dāng)前標(biāo)志比較產(chǎn)生一個(gè)時(shí)鐘周期寬度的脈沖key_pressed。這確保了后續(xù)電路每次只響應(yīng)一次按鍵動(dòng)作無論按鍵按下多久。5. 數(shù)字時(shí)鐘項(xiàng)目的層次化設(shè)計(jì)與整合一個(gè)完整的“VHDL設(shè)計(jì)數(shù)字時(shí)鐘”項(xiàng)目是模塊化設(shè)計(jì)思想的絕佳演練。它不再是單個(gè)文件而是由多個(gè)功能模塊分頻、計(jì)時(shí)、顯示驅(qū)動(dòng)等組成的一個(gè)系統(tǒng)。5.1 系統(tǒng)頂層框圖與模塊劃分一個(gè)典型的數(shù)字時(shí)鐘可以分解為以下模塊分頻模塊clk_gen將系統(tǒng)高頻時(shí)鐘如50MHz分頻產(chǎn)生1Hz的秒時(shí)鐘基準(zhǔn)和其他所需頻率如掃描時(shí)鐘。計(jì)時(shí)模塊time_counter包含秒、分、時(shí)的計(jì)數(shù)器并處理進(jìn)位60秒進(jìn)1分60分進(jìn)1時(shí)24時(shí)歸零。按鍵處理模塊key_debounce負(fù)責(zé)對(duì)校時(shí)、校分等按鍵進(jìn)行去抖和同步輸出干凈的按鍵脈沖。顯示控制模塊display_ctrl將計(jì)時(shí)模塊輸出的BCD碼二進(jìn)制編碼的十進(jìn)制數(shù)轉(zhuǎn)換為數(shù)碼管的段選信號(hào)并實(shí)現(xiàn)動(dòng)態(tài)掃描如果是多位數(shù)碼管。頂層模塊top將以上所有模塊像搭積木一樣連接起來。5.2 模塊實(shí)例化把“芯片”連接起來在VHDL中使用COMPONENT聲明和PORT MAP來實(shí)例化子模塊?,F(xiàn)代開發(fā)工具如Quartus, Vivado通常更推薦直接使用ENTITY WORK.實(shí)體名的方式無需顯式聲明COMPONENT。頂層文件top.vhd示例LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY digital_clock_top IS PORT ( sys_clk : IN STD_LOGIC; -- 50MHz系統(tǒng)時(shí)鐘 rst_n : IN STD_LOGIC; -- 復(fù)位鍵 adj_min : IN STD_LOGIC; -- 調(diào)分按鍵 adj_hour : IN STD_LOGIC; -- 調(diào)時(shí)按鍵 seg_data : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); -- 段選數(shù)據(jù)含小數(shù)點(diǎn) seg_sel : OUT STD_LOGIC_VECTOR(5 DOWNTO 0) -- 位選信號(hào)選擇6位數(shù)碼管中的哪一位 ); END ENTITY; ARCHITECTURE struct OF digital_clock_top IS -- 內(nèi)部連接信號(hào)聲明 SIGNAL clk_1Hz : STD_LOGIC; SIGNAL clk_1kHz : STD_LOGIC; -- 用于數(shù)碼管動(dòng)態(tài)掃描 SIGNAL sec_val, min_val, hour_val : INTEGER RANGE 0 TO 59; -- 計(jì)時(shí)值 SIGNAL key_min_pulse, key_hour_pulse : STD_LOGIC; -- 按鍵脈沖 BEGIN -- 實(shí)例化分頻模塊 u_clk_gen : ENTITY WORK.clk_gen PORT MAP ( clk_in sys_clk, rst_n rst_n, clk_1Hz_out clk_1Hz, clk_1k_out clk_1kHz ); -- 實(shí)例化按鍵消抖模塊調(diào)分 u_key_min : ENTITY WORK.key_debounce PORT MAP ( clk clk_1kHz, -- 用掃描時(shí)鐘做去抖時(shí)鐘 key_in adj_min, key_pulse_out key_min_pulse ); -- 實(shí)例化按鍵消抖模塊調(diào)時(shí) u_key_hour : ENTITY WORK.key_debounce GENERIC MAP ( -- 使用Generic傳遞參數(shù)例如去抖時(shí)間 DEBOUNCE_MS 20 ) PORT MAP ( clk clk_1kHz, key_in adj_hour, key_pulse_out key_hour_pulse ); -- 實(shí)例化核心計(jì)時(shí)模塊 u_time_counter : ENTITY WORK.time_counter PORT MAP ( clk_1Hz clk_1Hz, rst_n rst_n, adj_min_pulse key_min_pulse, adj_hour_pulse key_hour_pulse, sec_out sec_val, min_out min_val, hour_out hour_val ); -- 實(shí)例化顯示控制模塊 u_display_ctrl : ENTITY WORK.display_ctrl PORT MAP ( clk_scan clk_1kHz, rst_n rst_n, sec_in sec_val, min_in min_val, hour_in hour_val, seg_data_out seg_data, seg_sel_out seg_sel ); END ARCHITECTURE struct;層次化設(shè)計(jì)的優(yōu)勢(shì)可讀性與可維護(hù)性每個(gè)模塊功能單一接口清晰。修改計(jì)時(shí)邏輯不會(huì)影響顯示驅(qū)動(dòng)??蓮?fù)用性key_debounce模塊可以復(fù)用到任何需要按鍵輸入的項(xiàng)目中。clk_gen模塊也可以復(fù)用。并行開發(fā)團(tuán)隊(duì)成員可以分別負(fù)責(zé)不同模塊的設(shè)計(jì)和調(diào)試。5.3 測(cè)試平臺(tái)Testbench編寫入門代碼寫完了怎么知道它對(duì)不對(duì)不能每次都燒錄到FPGA上看。這時(shí)就需要編寫測(cè)試平臺(tái)Testbench。Testbench也是一個(gè)VHDL實(shí)體但它不需要綜合成硬件只用于仿真。它的作用是模擬外部輸入時(shí)鐘、復(fù)位、按鍵并觀察內(nèi)部信號(hào)和輸出驗(yàn)證設(shè)計(jì)功能。一個(gè)最簡(jiǎn)單的時(shí)鐘和復(fù)位激勵(lì)生成如下LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY tb_digital_clock IS -- Testbench實(shí)體通常沒有端口 END ENTITY; ARCHITECTURE sim OF tb_digital_clock IS -- 聲明與被測(cè)模塊DUT連接的信號(hào) SIGNAL sys_clk : STD_LOGIC : 0; SIGNAL rst_n : STD_LOGIC : 1; SIGNAL adj_min : STD_LOGIC : 0; -- ... 其他信號(hào) -- 聲明時(shí)鐘周期常數(shù) CONSTANT CLK_PERIOD : TIME : 20 ns; -- 對(duì)應(yīng)50MHz BEGIN -- 實(shí)例化被測(cè)設(shè)計(jì)DUT u_dut : ENTITY WORK.digital_clock_top PORT MAP ( sys_clk sys_clk, rst_n rst_n, adj_min adj_min, -- ... 其他端口連接 ); -- 生成時(shí)鐘激勵(lì) sys_clk NOT sys_clk AFTER CLK_PERIOD / 2; -- 生成測(cè)試序列 PROCESS BEGIN -- 初始復(fù)位 rst_n 0; WAIT FOR 100 ns; rst_n 1; WAIT FOR 10 us; -- 模擬按下調(diào)分按鍵 adj_min 1; WAIT FOR 50 ms; -- 模擬按下50ms adj_min 0; WAIT FOR 1 ms; -- 再按一次 adj_min 1; WAIT FOR 50 ms; adj_min 0; -- 讓仿真運(yùn)行一段時(shí)間觀察 WAIT FOR 100 ms; std.env.stop; -- 停止仿真VHDL-2008語法 WAIT; END PROCESS; END ARCHITECTURE sim;在ModelSim或QuestaSim等仿真工具中運(yùn)行這個(gè)Testbench你可以看到所有信號(hào)的波形圖觀察計(jì)數(shù)器是否在1Hz時(shí)鐘下正常遞增按鍵按下后分鐘是否加1進(jìn)位邏輯是否正確。仿真是數(shù)字設(shè)計(jì)中最重要、最經(jīng)濟(jì)的調(diào)試手段絕大部分邏輯錯(cuò)誤都應(yīng)該在仿真階段發(fā)現(xiàn)和解決。6. 從代碼到芯片綜合、實(shí)現(xiàn)與調(diào)試當(dāng)你完成代碼編寫和仿真驗(yàn)證后下一步就是將其變成真正的硬件電路。這個(gè)過程主要由FPGA開發(fā)工具鏈完成主要包括以下步驟6.1 綜合Synthesis綜合器如Quartus中的Analysis Synthesis Vivado中的Synthesis將你的RTL級(jí)VHDL代碼翻譯成門級(jí)網(wǎng)表Netlist。這個(gè)網(wǎng)表由目標(biāo)器件如FPGA的基本邏輯單元查找表LUT、觸發(fā)器FF、進(jìn)位鏈等以及它們之間的連接關(guān)系構(gòu)成。綜合過程會(huì)做很多優(yōu)化比如常數(shù)傳播a b 0;會(huì)被優(yōu)化為a b;。消除冗余邏輯。根據(jù)你的時(shí)序約束嘗試不同的電路結(jié)構(gòu)以達(dá)到時(shí)序要求。注意事項(xiàng)綜合器是“聽話的傻瓜”。你寫的代碼風(fēng)格會(huì)直接影響綜合出的電路面積和速度。例如使用IF rising_edge(clk)會(huì)綜合出觸發(fā)器而純組合邏輯語句會(huì)綜合出查找表。不完整的IF或CASE語句會(huì)導(dǎo)致鎖存器。6.2 布局布線Place Route布局布線器將綜合后的網(wǎng)表映射到FPGA芯片的具體物理資源上并連接它們。這個(gè)過程需要考慮芯片上邏輯單元的位置、布線資源的擁塞、時(shí)鐘網(wǎng)絡(luò)的分布等。這個(gè)階段最容易出現(xiàn)時(shí)序問題。工具會(huì)計(jì)算信號(hào)從源觸發(fā)器到目的觸發(fā)器之間的延遲包括邏輯延遲和布線延遲并與你設(shè)定的時(shí)鐘約束進(jìn)行比較。如果延遲大于時(shí)鐘周期就會(huì)建立時(shí)間Setup Time違例電路無法在指定頻率下穩(wěn)定工作。6.3 時(shí)序約束與靜態(tài)時(shí)序分析STA你必須告訴工具你的設(shè)計(jì)需要跑多快這就是時(shí)序約束。最基本的約束是創(chuàng)建時(shí)鐘。# 在Quartus的SDC文件或Vivado的XDC文件中 create_clock -name sys_clk -period 20.000 [get_ports sys_clk] # 定義50MHz時(shí)鐘周期20ns工具會(huì)根據(jù)約束進(jìn)行靜態(tài)時(shí)序分析STA報(bào)告所有時(shí)序路徑是否滿足要求。如果出現(xiàn)違例你需要檢查代碼是否存在過長(zhǎng)的組合邏輯路徑可以插入寄存器流水線來打拍。優(yōu)化約束是否有些路徑是虛假路徑false path或多周期路徑multicycle path可以用set_false_path或set_multicycle_path來放松約束。使用工具優(yōu)化策略在工具設(shè)置中嘗試不同的布局布線努力程度Place Route Effort或物理優(yōu)化選項(xiàng)。6.4 在線調(diào)試SignalTap II與ILA當(dāng)設(shè)計(jì)下載到FPGA后行為異常但仿真又是對(duì)的這時(shí)就需要在線邏輯分析儀。Intel FPGA的SignalTap II和Xilinx的ILAIntegrated Logic Analyzer是嵌入在FPGA邏輯中的調(diào)試工具可以實(shí)時(shí)捕獲內(nèi)部信號(hào)的波形。使用流程大致如下在工程中插入調(diào)試IP核選擇要觀察的信號(hào)和觸發(fā)條件如某個(gè)信號(hào)上升沿。重新綜合、布局布線并生成配置文件。下載配置文件到FPGA。通過JTAG連接在軟件中設(shè)置觸發(fā)捕獲數(shù)據(jù)。實(shí)操心得在線調(diào)試非常消耗FPGA的存儲(chǔ)塊BRAM資源不宜添加過多、過寬、采樣深度過大的信號(hào)。通常只抓取最可疑的幾組信號(hào)并設(shè)置精準(zhǔn)的觸發(fā)條件如“當(dāng)狀態(tài)機(jī)進(jìn)入錯(cuò)誤狀態(tài)時(shí)”或“當(dāng)計(jì)數(shù)器溢出時(shí)”。這是定位硬件與仿真不一致問題的終極手段。學(xué)習(xí)VHDL就像學(xué)習(xí)任何一門手藝從看懂一個(gè)簡(jiǎn)單的計(jì)數(shù)器開始到搭建出一個(gè)完整的數(shù)字時(shí)鐘或交通燈系統(tǒng)每一步都充滿了從抽象代碼到具體電路的奇妙映射。最重要的不是記住所有語法而是建立起“硬件思維”——時(shí)刻思考你寫的每一行代碼對(duì)應(yīng)著什么樣的電路結(jié)構(gòu)在時(shí)鐘的節(jié)拍下如何工作。多寫多仿真多上板調(diào)試遇到問題去深究背后的硬件原理這才是從入門到精通的唯一路徑。我個(gè)人的習(xí)慣是每完成一個(gè)小模塊都會(huì)用仿真波形反復(fù)驗(yàn)證邊界情況并用最簡(jiǎn)單的開發(fā)板外設(shè)比如一個(gè)LED來實(shí)際驗(yàn)證一個(gè)核心功能點(diǎn)這種“代碼-仿真-硬件”的快速反饋循環(huán)能極大地鞏固理解和提升信心。