一個VHDL程序代碼包含實體(entity)、結構體(architecture)、配置(configuration)、程序包(package)、庫(library)等。
一、
1.用戶自定義數據類型
使用關鍵字TYPE,例如:
TYPE my_integer IS RANGE -32 TO 32;
–用戶自定義的整數類型的子集
TYPE student_grade IS RANGE 0 TO 100;
–用戶自定義的自然數類型的子集
TYPE state IS (idle, forward, backward, stop);
–枚舉數據類型,常用於有限狀態機的狀態定義
一般來說,枚舉類型的數據自動按順序依次編碼。
2.子類型
在原有已定義數據類型上加一些約束條件,可以定義該數據類型的子類型。VHDL不允許不同類型的數據直接進行操作運算,而某個數據類型的子類型則可以和原有類型數據直接進行操作運算。
子類型定義使用SUBTYPE關鍵字。
3.數組(ARRAY)
ARRAY是將相同數據類型的數據集合在一起形成的一種新的數據類型。
TYPE type_name IS ARRAY (specification) OF data_type;
–定義新的數組類型語法結構
SIGNAL signal_name: type_name [:= initial_value];
–使用新的數組類型對SIGNAL,CONSTANT, VARIABLE進行聲明
例如:
TYPE delay_lines IS ARRAY (L-2 DOWNTO 0) OF SIGNED (W_IN-1 DOWNTO 0);
–濾波器輸入延遲鏈類型定義
TYPE coeffs IS ARRAY (L-1 DOWNTO 0) OF SIGNED (W_COEF-1 DOWNTO 0);
–濾波器係數類型定義
SIGNAL delay_regs: delay_lines;
CONSTANT coef: coeffs := (
4.端口數組
在定義電路的輸入/輸出端口時,有時需把端口定義爲矢量陣列,而在ENTITY中不允許使用TYPE進行類型定義,所以必須在包集(PACKAGE)中根據端口的具體信號特徵建立用戶自定義的數據類型,該數據類型可以供包括ENTITY在內的整個設計使用。
—————————————PACKAGE———————————-
library ieee;
use ieee.std_logic_1164.all;
——————————————
PACKAGE my_data_types IS
END my_data_types;
———————————–Main Code—————————————
library ieee;
use ieee.std_logic_1164.all;
use work.my_data_types.all; –用戶自定義包集
——————————————————————
ENTITY mux IS
PORT (inp: IN vector_array(0 to 3);
END mux;
——————————————————————————-
5.有符號數和無符號數
要使用SIGNED和UNSIGNED類型數據,必須在代碼開始部分聲明ieee庫中的包集std_logic_arith。它們支持算術運算但不支持邏輯運算。
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
……
SIGNAL a: IN SIGNED (7 DOWNTO 0);
SIGNAL b: IN SIGNED (7 DOWNTO 0);
SIGNAL x: IN SIGNED (7 DOWNTO 0);
……
v <= a + b;
w <= a AND b;
——————————————————————————-
STD_LOGIC_VECTOR類型的數據不能直接進行算術運算,只有聲明瞭std_logic_signed和std_logic_unsigned兩個包集後纔可以像SIGNED和UNSIGNED類型的數據一樣進行算術運算。
6.數據類型轉換
在ieee庫的std_logic_arith包集中提供了許多數據類型轉換函數:
1. conv_integer(p): 將數據類型爲INTEGER,UNSIGNED,SIGNED,STD_ULOGIC或STD_LOGIC的操作數p轉換成INTEGER類型。不包含STD_LOGIC_VECTOR。
2. conv_unsigned(p,b):將數據類型爲INTEGER,UNSIGNED,SIGNED或STD_ULOGIC的操作數p轉換成位寬爲b的UNSIGNED類型數據。
3. conv_signed(p,b):將數據類型爲INTEGER, UNSIGNED, SIGNED或STD_ULOGIC的操作數p轉換成位寬爲b的SIGNED類型的數據。
4. conv_std_logic_vector(p, b):將數據類型爲INTEGER, UNSIGNED, SIGNED或STD_LOGIC的操作數p轉換成位寬爲b的STD_LOGIC_VECTOR類型的數據。
二、
1.
l
賦值運算符用來給信號、變量和常數賦值。
<=
:=
=>
例:
SIGNAL x: STD_LOGIC;
VARIABLE y: STD_LOGIC_VECTOR(3 DOWNTO 0);
SIGNAL w: STD_LOGIC_VECTOR(0 TO 7);
x <= ‘1’;
y := “0000”;
w <= “1000_0000”;
w <= (0 => ‘1’, OTHERS => ‘0’);
l
操作數必須是BIT, STD_LOGIC或STD_ULOGIC類型的數據或者是這些數據類型的擴展,即BIT_VECTOR, STD_LOGIC_VECTOR,STD_ULOGIC_VECTOR。
VHDL的邏輯運算符有以下幾種:(優先級遞減)
Ÿ
Ÿ
Ÿ
Ÿ
Ÿ
Ÿ
l
操作數可以是INTEGER, SIGNED, UNSIGNED, 如果聲明瞭std_logic_signed或std_logic_unsigned,可對STD_LOGIC_VECTOR類型的數據進行加法或減法運算。
+ —— 加
-
* —— 乘
/ —— 除
** —— 指數運算
MOD —— 取模
REM —— 取餘
ABS —— 取絕對值
加,減,乘是可以綜合成邏輯電路的;除法運算只在除數爲2的n次冪時才能綜合,此時相當於對被除數右移n位;對於指數運算,只有當底數和指數都是靜態數值(常量或GENERIC參數)時纔是可綜合的;對於MOD運算,結果的符號同第二個參數的符號相同,對於REM運算,結果的符號同第一個參數符號相同。
l
=, /=, <, >, <=, >=
左右兩邊操作數的類型必須相同。
l
<左操作數> <移位操作符> <右操作數>
其中左操作數必須是BIT_VECTOR類型的,右操作數必須是INTEGER類型的(可以爲正數或負數)。
VHDL中移位操作符有以下幾種:
u
u
u
u
u
u
例:x <= “01001”,那麼:
y <= x sll 2;
y <= x sla 2;
y <= x srl 3;
y <= x sra 3;
y <= x rol 2;
y <= x srl -2;
l
用於位的拼接,操作數可以是支持邏輯運算的任何數據類型。有以下兩種:
²
²
與Verilog中{}的功能一樣。
2.
l
數值類屬性用來得到數組、塊或一般數據的相關信息,例如可用來獲取數組的長度和數值範圍等。
以下是VHDL中預定義的可綜合的數值類屬性:
d’LOW
d’HIGH
d’LEFT
d’RIGHT
d’LENGTH
d’RANGE
d’REVERSE_RANGE
例:定義信號 SIGNAL d: STD_LOGIC_VECTOR(7 DOWNTO 0);
則有:d’LOW = 0, d’HIGH = 7, d’LEFT = 7, d’RIGHT = 0, d’LENGTH = 8, d’RANGE = (7 DOWNTO 0), d’REVERSE_RANGE = (0 TO 7).
l
對於信號s,有以下預定義的屬性(可綜合的):
s’EVENT
s’STABLE
例:clk的上升沿判斷
IF (clk’EVENT AND clk = ‘1’)
IF (NOT clk’STABLE AND clk = ‘1’)
WAIT UNTIL (clk’EVENT AND clk = ‘1’)
3.
GENERIC語句提供了一種指定常規參數的方法,所指定的參數是靜態的,增加了代碼的可重用性,類似於Verilog中的parameter與defparam。GENERIC語句必須在ENTITY中進行聲明,由GENERIC語句指定的參數是全局的,不僅可在ENTITY內部使用,也可在後面的整個設計中使用。語法結構如下:
GENERIC (parameter_name: parameter_type := parameter_value);
用GENERIC語句指定多個參數:
GENERIC (n: INTEGER := 8; vector: BIT_VECTOR := “0000_1111”);
三、
VHDL中併發描述語句有WHEN和GENERATE。除此之外,僅包含AND, NOT, +, *和sll等邏輯、算術運算操作符的賦值語句也是併發執行的。在BLOCK中的代碼也是併發執行的。
從本質上講,VHDL代碼是並行執行的。只有PROCESS, FUNCTION, PROCEDURE內部的代碼纔是順序執行的。但是當它們作爲一個整體時,與其他模塊之間又是並行執行的。併發代碼稱爲“數據流”代碼。
通常我們只能用併發描述語句來實現組合邏輯電路,爲了實現時序邏輯電路,必須使用順序描述語句。事實上,使用順序描述語句可以同時實現組合邏輯電路和時序邏輯電路。
在併發代碼中可以使用以下各項:
Ø
Ø
Ø
Ø
使用運算操作符
運算類型 |
運算操作符 |
操作數類型 |
邏輯運算 |
NOT, AND, NAND,OR NOR, XOR, XNOR |
BIT, BIT_VECTOR, STD_LOGIC, STD_LOGIC_VECTOR STD_ULOGIC, STD_ULOGIC_VECTOR |
算術運算符 |
+, —, *, /, ** |
INTEGER, SIGNED, UNSIGNED |
比較運算符 |
=, /=, <, >, <=, >= |
任意數據類型 |
移位運算符 |
sll, srl, sla, sra, rol, ror |
BIT_VECTOR |
並置運算符 |
&,(, , ,) |
STD_LOGIC, STD_LOGIC_VECTOR, STD_ULOGIC STD_ULOGIC_VECTOR, SIGNED, UNSIGNED |
WHEN語句
WHEN語句是一種基本的併發描述語句,有兩種形式:WHEN/ELSE和WITH/SELECT/WHEN。
WHEN/ELSE語法結構:
assignment WHEN condition ELSE
assignment WHEN condition ELSE
…;
WITH/SELECT/WHEN語法結構
WITH identifier SELECT
assignment WHEN value,
assignemnt WHEN value,
…;
當使用WITH/SELECT/WHEN時,必須對所有可能出現的條件給予考慮,使用關鍵字OTHERS,如果在某些條件出現時不需要進行任何操作,那應該使用UNAFFECTED。
例:
————————————-with WHEN/ELSE——————————————-
Output <= “000” WHEN (inp = ‘0’ OR reset = ‘1’) ELSE
———————————–with WITH/SELECT/WHEN——————————–
WITH control SELECT
對於WHEN語句,WHEN value的描述方式有以下幾種:
WHEN value
WHEN value1 to value2
WHEN value1 | value2 | …
GENERATE語句
GENERATE語句和順序描述語句中的LOOP語句一樣用於循環執行某項操作,通常與FOR一起使用。語法結構如下:
label: FOR identifier IN range GENERATE
END GENERATE
GENERATE語句還有另一種形式:IF/GENERATE,此處不允許使用ELSE。IF/GENERATE可以嵌套在FOR/GENERATE內部使用。反之亦然。
Label1: FOR identifier IN range GENERATE
……
……
END GENERATE;
例:
SIGNAL x: BIT_VECTOR(7 DOWNTO 0);
SIGNAL y: BIT_VECTOR(15 DOWNTO 0);
SIGNAL z: BIT_VECTOR(7 DOWNTO 0);
……
G1: FOR i IN x’RANGE GENERATE
z(i) <= x(i) AND y(i+8);
END GENERATE;
GENERATE中循環操作的上界和下界必須是靜態的,在使用過程中還要注意多值驅動問題。
例:
OK: FOR i IN 0 TO 7 GENERATE
END GENERATE;
—————————————————————————
NotOK: FOR i IN 0 TO 7 GENERATE
END GENERATE;
—————————————————————-
NotOK: FOR i IN 0 TO 7 GENERATE
END GENERATE;
—————————————————————-
塊語句(BLOCK)
VHDL中有兩種BLOCK:simple BLOCK和guarded BLOCK。
n
Simple BLOCK僅僅是對原有代碼進行區域分割,增強整個代碼的可讀性和可維護性。語法結構如下:
label:BLOCK
BEGIN
END BLOCK label;
—————————————————————————————————-
ARCHITETURE example…
BEGIN
END BLOCK block2;
…
END example;
—————————————————————————————–
例:
b1: BLOCK
BEGIN
END BLOCK b1;
———————————————————————————————————————-
無論是simple BLOCK還是guarded BLOCK,其內部都可以嵌套其他的BLOCK語句,相應的語法結構如下:
label1: BLOCK
BEGIN
label2: BLOCK
BEGIN
[頂層BLOCK其他併發描述語句]
END BLOCK label1;
———————————————————————————————————
n
多了一個衛式表達式,只有當衛式表達式爲真時才能執行。語法結構如下:
Label: BLOCK(衛式表達式)
BEGIN
END BLOCK label;
四、
在PROCESS, FUNCTION, PROCEDURE內部的代碼都是順序執行的,這樣的語句包括IF,WAIT,CASE和LOOP。變量只能在順序代碼中使用,相對於信號而言,變量是局部的,所以它的值不能傳遞到PROCESS,FUNCTION和PROCEDURE的外部。
1.
進程內部經常使用IF,WAIT,CASE或LOOP語句。PROCESS具有敏感信號列表(sensitivity list),或者使用WAIT語句進行執行條件的判斷。PROCESS必須包含在主代碼段中,當敏感信號列表中的某個信號發生變化時(或者當WAIT語句的條件得到滿足時),PROCESS內部的代碼就順序執行一次。語法結構如下:
[label: ] PROCESS (sensitivity list)
BEGIN
END PROCESS [label];
如果要在PROCESS內部使用變量,則必須在關鍵字BEGIN之前的變量聲明部分對其進行定義。變量的初始值是不可綜合的,只用於仿真。在設計同步電路時,要對某些信號邊沿的跳變進行監視(時鐘的上升沿或下降沿)。通常使用EVENT屬性來監視一個信號是否發生了變化。
2.
信號可在PACKAGE,ENTITY和ARCHITECTURE中聲明,而變量只能在一段順序描述代碼的內部聲明。因此,信號通常是全局的,變量通常是局部的。賦予變量的值是立刻生效的,在後續的代碼中,此變量將使用新的變量值,而信號的值通常只有在整個PROCESS執行完畢後纔開始生效。
3.
IF/ELSE語句在綜合時可能會產生不必要的優先級解碼電路。IF語句語法結構如下:
IF conditions THEN assignments;
ELSIF conditions THEN assignments;
ELSE assignments;
END IF;
————————————————————————————————
例:
IF (x < y) temp := “1111_1111”;
ELSIF (x = y AND w = ‘0’) THEN temp := “1111_0000”;
ELSE temp := (OTHERS => ‘0’);
4.
如果在process中使用了WAIT語句,就不能使用敏感信號列表了。WAIT語句使用以下3種形式的語法結構:
WAIT UNTIL signal_condition;
WAIT ON signal1 [, signal2, ...];
WAIT FOR time;
WAIT UNTIL 後面只有一個信號條件表達式,更適合於實現同步電路(將時鐘的上升沿或下降沿作爲條件),由於沒有敏感信號列表,所以它必須是process的第一條語句。當WAIT UNTIL語句的條件滿足是,process內部的代碼就執行一遍。
–帶有同步復位的8bit寄存器
process –沒有敏感信號列表
begin
end process;
WAIT ON 語句中可以出現多個信號,只要信號列表中的任何一個發生變化,process內的代碼就開始執行。
–帶異步復位的8bit寄存器
process
begin
end process;
WAIT FOR 語句只能用於仿真。
5.
CASE語句的語法結構如下:
CASE 表達式 IS
END CASE
例:
case control is
end case;
關鍵詞OTHERS代表了所有未列出的可能情況,與Verilog中default相當。關鍵詞NULL表示沒有操作發生,如WHEN OTHERS => NULL.
CASE語句允許在每個測試條件下執行多個賦值操作,WHEN語句只允許執行一個賦值操作。
6.
LOOP語句用在需要多次重複執行時。語法結構有以下幾種:
FOR/LOOP: 循環固定次數
[label: ] FOR 循環變量 IN 範圍 LOOP
END LOOP [label];
WHILE/LOOP: 循環執行直到某個條件不再滿足
[label: ] WHILE condition LOOP
END LOOP [label];
EXIT: 結束整個循環操作
[label: ] EXIT
NEXT: 跳出本次循環
[label: ] NEXT [loop_label] [WHEN condition];
Example: FOR/LOOP
for i in 0 to 5 loop
end loop
Example: WHILE/LOOP
while (i < 10) loop — 0~9
end loop;
for i in 0 to data’range loop
end loop;
7.
IF語句和CASE語句編寫的代碼在綜合、優化後最終生成的電路結構是一樣的。
例:下面兩段代碼綜合後可以得到結構相同的多路複用器
————with IF————–
if
elsif (sel = “01″)
elsif (sel = “10″)
else x <= d;
end if;
————-with case———–
case sel is
end case;
8.
case語句和when語句的不同之處在於,when語句是併發執行的,case語句是順序實行的。
–下面兩段代碼的功能等效
——-with when——————
with sel select
——-with case——————
case sel is
end case;
9.
原則1:確保在process中用到的所有輸入信號都出現在敏感信號列表中;
原則2:電路的真值表必須在代碼中完整的反映出來。(否則會生成鎖存器)
五、
常量和信號是全局的,既可以用在順序執行的代碼中,也可用在併發執行的代碼中。變量是局部的,只能用在順序代碼中,並且它們的值是不能直接向外傳遞的。
1.
CONSTANT name: type := value;
2.
VHDL中的signal代表的是邏輯電路中的“硬”連線,既可用於電路的輸入/輸出端口,也可用於電路內部各單元之間的連接。Entity的所有端口默認爲signal。格式如下:
SIGNAL name: type [range] [:= initial value];
當信號用在順序描述語句中時,其值不是立刻更新的,信號值是在相應的進程、函數或過程完成之後才進行更新的。對信號賦初值的操作時不可綜合的。
3.
變量僅用於局部電路的描述,只能在順序執行的代碼中使用,而且對它的賦值是立即生效的,所以新的值可在下一行代碼中立即使用。格式:
VARIABLE name: type [range] [:= initial value];
對變量的賦初值操作也是不可綜合的。
4.
當一個信號的賦值是以另一個信號的跳變爲條件時,或者說當發生同步賦值時,該信號經過編譯後就會生成寄存器。如果一個變量是在一個信號跳變時被賦值的,並且該值最終又被賦給了另外的信號,則綜合後就會生成寄存器。如果一個信號在還沒有進行賦值操作時已被使用,那麼也會在綜合時生成寄存器。
process (clk)
begin
end process;
process (clk)
begin
end process;
process (clk)
begin
end process;
六、
1.
經常使用的代碼通常以component,function或procedure的形式編寫。這些代碼被添加到package中,並在最後編譯到目標library中。Package中還可以包含TYPE和CONSTANT的定義。語法格式如下:
package package_name is
end package_name;
package body package_name is
end package_name;]
Example6.1 簡單的程序包
library ieee;
use ieee.std_logic_1164.all;
———————————————————————-
package my_package is
end my_package;
Example6.2 內部包含函數的package
library ieee;
use ieee.std_logic_1164.all;
———————————————————————-
package my_package is
end my_package;
———————————————————————-
package body my_package is
end my_package;
爲了在QUARTUS II中使用這些package,要在當前project目錄下新建一個文件夾,不妨起名爲user_lib,把要編譯的package放進此文件夾中,然後在AssignmentsàSettingàLibrary中設置相應的目錄即可。在VHDL代碼中要使用這些package,要在主程序中加入如下代碼:
use work.package_name.all;
2.
一個元件是一段結構完整的常用代碼,包括聲明,實體和結構體,使用component可以使代碼具有層次化的結構。
元件聲明:
component comp_name is
end component;
元件實例化:
label: comp_name port map (port_list);
元件的聲明可以放在主代碼中,即調用該元件的代碼;或者將元件的聲明放到package中,使用時在主代碼中增加一條USE語句即可,這樣避免了主代碼中每實例化一個元件就要聲明一次的麻煩。
3.
在元件實例化過程中,有兩種方法實現元件端口的映射:位置映射和名稱映射。
component inverter is
end component;
…
U1: inverter port map(x, y);
此處採用的是位置映射法,x對應a,y對應b。
U1: inverter port map(a => x, b=> y);
此處採用的是名稱映射法。對於不需要使用的端口可以斷開,只需使用關鍵字open即可,但是輸入端口不能指定爲空連接。比如:
U2: my_circuit port map(x => a, y => b, w => open, z => b);
4.
元件實例化時如果要通過GENERIC傳遞參數,則需進行GENERIC參數的映射。元件實例化的格式如下:
label: comp_name generic map(param_list) port map(port_list);
七、
Function和procedure統稱爲子程序,內部包含的都是順序描述的VHDL語言.
八、
狀態機的設計包含兩個主要過程:狀態機建模和狀態的編碼。
1.有限狀態機的建模
有限狀態機通常使用CASE語句來建模,一般的模型由兩個進程組成,一個進程用來實現時序邏輯電路,另一個進程用來實現組合邏輯電路。
模型的構建:
(1)
(2)
(3)
TYPE STATE IS (STATE0, STATE1, STATE2, …);
SIGNAL CR_STATE, NEXT_STATE: STATE;
(4)
PROCESS (CLK,RESET)
BEGIN
END IF;
END PROCESS;
(5)
PROCESS(CR_STATE,INPUT)
BEGIN
END PROCESS;
2.狀態編碼
狀態編碼包括二進制編碼、枚舉類型的編碼和一位有效編碼。利用一位有效編碼(One-hot encoding)可以創建更有效地在FPGA結構中實現的有限狀態機。每個狀態可以使用一個觸發器來創建狀態機,並且可以降低組合邏輯的寬度。
有限狀態機的可能狀態由枚舉類型所定義,即:
TYPE type_name IS(枚舉元素1, 枚舉元素2, …., 枚舉元素n);
這個定義是通用的格式,時必須的。在該枚舉類型定義語句之後,就可以聲明信號爲所定義的枚舉類型:
TYPE STATE_TYPE IS(S1, S2, S3, S4, S5, S6, S7);
SIGNAL CS,NS: STATE_TYPE;
爲了選擇有限狀態機的狀態編碼方式,需要指定狀態矢量。也可以通過綜合工具指定編碼方式。當在程序中指定編碼方式時,可以在枚舉類型定義語句後指定狀態矢量,例如,
定義二進制編碼的狀態矢量的語句是:
ATTRIBUTE ENUM_ENCODING: STRING;
ATTRIBUTE ENUM_ENCODING OF STATE_TYPE:TYPE IS “001 010 011 100 101 110 111”;
定義一位有效編碼的狀態矢量的語句爲:
ATTRIBUTE ENUM_ENCODING:STRING;
ATTRIBUTE ENUM_ENCODING OF STATE_TYPE:TYPE IS ”0000001 0000010 0000100 0001000 0010000 0100000 1000000”;