verilog基礎語法之數據類型
- 1、 wire類型
- 2、 reg類型
- 3、向量
? ?Verilog最常用的數據類型有兩種:線網(wire)和寄存器(reg)。其中,wire 類型表示硬件單元之間的物理連線,reg用來表示存儲單元。
1、 wire類型
? ?wire類型是信號在不同元器件之間傳遞的媒介,用于表示硬件電路單元之間的物理連線,由其連接的器件的輸出端連續驅動。如果沒有驅動元件連接到wire型變量,或者存在沖突的驅動(即多個驅動源同時驅動同一個wire),那么該變量的值默認為"High-Impedance"(高阻態),通常表示為"Z"。高阻態是一種不驅動信號的狀態,相當于該信號線上沒有電流流動。“驅動”是指信號源對連線施加的電平,這個信號源可以是一個邏輯門、觸發器、緩沖器、驅動元件或者任何其他可以產生信號的硬件實體。
? ?另外,wire型變量的值可以由連續賦值(使用=操作符)或非阻塞賦值(在always塊中使用<=操作符)決定。連續賦值類似于組合邏輯,它描述了輸入變量和輸出變量之間的直接關系,而輸出值會隨著輸入值的變化而立即變化。
示例:
wire signal_wire; // 聲明一個wire類型的信號線
assign signal_wire = some_driver; // some_driver是一個驅動源,可以是門、觸發器等
在這個例子中,assign語句定義了一個連續賦值,some_driver是驅動signal_wire的驅動源。如果some_driver是一個邏輯門的輸出,那么signal_wire將根據該邏輯門的輸入而變化。
wire gnd = 1'b0;//創建一個名為 gnd 的全局地線信號,它被賦予了一個邏輯0(二進制值0)的初始值,地線通常在電路中表示參考點或0電平。
這行代碼定義了一個名為 gnd 的 wire 類型的信號,并初始化為邏輯值 0。在某些情況下,設計者可能會使用 wire 類型來定義一個常量,即使用 wire 來表示一個始終為特定值的信號,但更常見的做法是使用 parameter 或 localparam 關鍵字來定義常量。對于地線,一般不需要在代碼中顯式定義,因為地線通常在FPGA的物理設計中是預定義的,并在布局時連接到相應的電源和地引腳。
? ?需要特別說明的是,wire型變量通常用于表示模塊的端口或者作為信號的傳遞媒介。它們不應當在always塊內部被賦值,因為wire代表著連線,其值由外部信號源決定,而非由always塊內的邏輯產生。如果試圖在always塊中對wire型變量使用<=操作符,這將導致編譯錯誤,因為wire不允許在always塊內部賦值。 然而,wire型變量可以在always塊中被用作非阻塞賦值表達式中的一個源(source),但這只出現在一個模塊的輸出端口被另一個模塊的輸入端口所驅動的情況下。在這種情況下,wire實際上是連接兩個模塊的信號線。
? ?以下是一個簡單的示例,演示了如何在模塊間使用wire型變量進行非阻塞賦值:
module d_flip_flop(input wire clk, // 時鐘信號input wire data_in, // 數據輸入output reg data_out // 數據輸出
);`在這里插入代碼片`// 使用非阻塞賦值在時鐘上升沿捕獲data_in的值
always @(posedge clk) begindata_out <= data_in;endendmodule// 測試模塊,實例化d_flip_flop
module testbench();wire clk;wire data_in;wire data_out;// 實例化d_flip_flop模塊d_flip_flop u_d_flip_flop(.clk(clk),.data_in(data_in),.data_out(data_out));// 驅動測試信號initial beginclk = 0;forever #10 clk = ~clk; // 產生時鐘信號endinitial begindata_in = 0;#20 data_in = 1; // 在時間20ns處改變輸入數據#20 data_in = 0;end// 監視輸出變化initial begin$monitor("Time = %t, clk = %b, data_in = %b, data_out = %b", $time, clk, data_in, data_out);endendmodule
在這個例子中,data_out是一個reg型變量,它在d_flip_flop模塊的always塊中使用非阻塞賦值從data_in獲得值。clk和data_in是wire型變量,它們在測試模塊testbench中被驅動,并且連接到d_flip_flop模塊的相應端口。請注意,盡管data_out是reg類型并且可以使用非阻塞賦值,但data_in和clk作為wire類型,它們的值是由測試模塊生成的信號所驅動的,而不是在d_flip_flop模塊內部的always塊中賦值。在這個示例的always塊中,data_in的值在時鐘邊沿被采樣,并在data_out中保持,直到下一個時鐘邊沿到來。
總結來說,在Verilog中,“驅動”指的是信號源對連線施加的電平,而驅動源是產生這些信號的任何硬件元件。如果沒有驅動源,wire型變量將呈現高阻態"Z"。
2、 reg類型
? ?reg用來表示存儲單元,它是一種可以存儲數據并保持其狀態的硬件元素。在仿真環境中,reg類型的變量行為與實際硬件中的寄存器類似,用于模擬寄存器的行為。在硬件實現中,reg聲明的變量將被映射到FPGA或ASIC中的相應寄存器結構。與wire不同,reg不是用于連接模塊的物理線,而是用于表示可以存儲狀態的寄存器。以下是reg類型的一些關鍵特性和用法:
-
狀態保持:reg類型的變量可以保持它們的值,直到它們被新的賦值語句更新。這種特性使得reg類型非常適合用于建模需要記憶先前值的硬件寄存器和觸發器。
-
非阻塞賦值:reg類型的變量通常與非阻塞賦值(使用 <=操作符)一起使用,這在時序邏輯中非常重要。非阻塞賦值確保了多個寄存器可以在同一時鐘邊沿更新,而不會產生競態條件。
-
時序控制:在always塊中,當指定了時鐘信號(例如 posedge clk 或 negedgeclk),reg變量的賦值將與時鐘信號的邊沿同步。
-
初始化:可以在聲明時初始化reg變量,但這種初始化僅在仿真開始時有效,實際硬件實現中寄存器的初始狀態取決于其物理實現或配置過程。
-
并發與順序:reg變量可以用于順序邏輯(在always塊中)和并發邏輯(在連續賦值語句之外聲明)。
示例一:使用<=操作符在always塊中對reg型變量賦值的示例:
module flip_flop(input wire clk, // 時鐘信號input wire reset, // 復位信號input wire data_in, // 數據輸入output reg data_out // 數據輸出
);// 觸發器行為的always塊
always @(posedge clk or posedge reset) beginif (reset) begin// 異步復位:當reset為高時,data_out被清零data_out <= 1'b0;end else begin// 時鐘邊沿觸發的數據鎖存:data_out在每個clk的上升沿捕獲data_in的值data_out <= data_in;end
endendmodule
在這個例子中,data_out是一個reg型變量,它在always塊中使用<=操作符進行賦值。當reset信號為高時,data_out被清零;否則,在clk的每個上升沿,data_out將捕獲data_in的值。
示例二:使用reg變量進行并發邏輯賦值的示例:
module combinatorial_logic(input wire [3:0] a,input wire [3:0] b,output reg [3:0] c
);// 并發賦值:輸出端口c的值由輸入a和b的當前值決定
// 這個always塊沒有時鐘信號,因此它在仿真期間是并發執行的
always @(a or b) beginc = a + b;
endendmodule
在這個例子中,模塊combinatorial_logic有一個4位寬的輸入a和b,以及一個8位寬的輸出c。輸出c被聲明為reg類型,并且它的值是由輸入a和b的當前值決定的。這里的reg變量可以用于并發賦值,當reg變量在連續賦值語句之外聲明,并且賦值不是發生在時鐘邊沿的always塊中時,它們可以用于描述并發邏輯。這種用法通常在描述組合邏輯時看到,其中reg變量的賦值不是基于時序信號,而是基于其他信號的變化。盡管,使用了always塊,但由于缺少時鐘信號或復位信號,這個always塊實際上定義了并發邏輯。當仿真環境中a或b的值發生變化時,c的值會立即更新,這與連續賦值的行為類似。
? ?請注意,這種使用reg進行并發賦值的方式并不常見,因為它可能會隱藏邏輯的時序特性,使得設計難以驗證和理解。在實際的硬件設計中,我們通常使用assign語句來創建組合邏輯的連續賦值,而使用帶有時鐘信號的always塊來創建順序邏輯。
? ?此外,當設計被綜合到實際的硬件時,沒有時鐘信號的always塊可能會被優化掉,因為綜合工具會認為這是一個恒定的賦值,而不是一個需要時序控制的寄存器賦值。在大多數情況下,如果你發現自己需要使用reg來創建并發邏輯,你應該重新考慮設計,并可能使用assign語句來代替。
3、向量
? ?在Verilog中,向量數據類型是一種復合數據類型,是Verilog中用于表示多位寬信號的一種數據結構,它允許你將多個數據位組合成一個單一的實體。Verilog中的向量可以是單比特的集合,也可以是多位的集合,它們在硬件描述中非常有用,因為它們可以表示多比特的信號、總線或寄存器、內存等。向量的索引是從高位到低位遞減的,即[7:0]表示從位7(最高位)到位0(最低位)。這種索引方式與常見的十進制計數方式相反,需要特別注意以避免混淆。向量在Verilog中非常有用,因為它們允許對多位寬的信號進行統一的操作和管理。以下是Verilog中向量數據類型的一些關鍵點:
-
位寬(Bit-width):向量數據類型具有明確的位寬,定義了向量中包含的位數。位寬在聲明時指定,使用方括號[]表示。
-
線網(Wires):wire類型的向量是最常用的,用于表示多個位的連接,如數據總線或地址總線。
-
寄存器(Registers):reg類型的向量用于表示需要存儲的多位信號,通常在時序邏輯中使用。
-
索引(Indexing):向量可以被索引,允許訪問向量中的單個位或位的子集。索引從0開始,到位寬減一結束。
-
部分選擇(Part Selection):可以基于起始位和結束位選擇向量的一部分,用于創建子向量。
-
拼接(Concatenation):可以使用{a, b}的語法將兩個或多個向量拼接成一個新的向量。
-
重復(Repetition):可以使用大括號和數字的組合,如{n{element}},來創建一個包含n個重復元素的向量。
-
位選擇和部分選擇操作符:位選擇:使用方括號加索引,如vector[3]選擇向量vector的第4位(索引從0開始)。部分選擇:使用方括號加索引范圍,如vector[3:1]選擇從第4位到第2位的子向量。
-
向量賦值:可以對整個向量進行賦值,也可以對向量的一部分進行賦值。
另外,以下是聲明向量的兩種基本方式:
- 使用方括號:在聲明時,使用方括號[]來指定位寬,其中包含兩位數字,分別表示向量的高(MSB)和低(LSB)位索引。
- 單比特聲明:如果向量的所有位都是單獨聲明的,然后通過拼接(concatenation)操作符{}來組合成一個向量。
以下是一些使用向量的示例:
module vector_example(input wire [7:0] data_in, // 8位寬的輸入向量output reg [15:0] data_out, // 16位寬的輸出向量output reg [3:0] flags // 4位寬的輸出向量,用于標志位
);// 向量拼接
always @(data_in) begindata_out = {data_in, data_in}; // 將data_in拼接到自身,形成16位的向量
end// 向量部分選擇
always @(data_out) beginflags = data_out[15:12]; // 選擇data_out的高4位作為標志位
endendmodule
在這個例子中,data_in是一個8位寬的線網向量,可以表示一個字節的數據。data_out是一個16位寬的輸出向量,而flags是一個4位寬的的寄存器向量,可以表示4個控制信號。通過使用向量和相關的操作符,可以方便地對多位信號進行操作。
另外,當位寬大于 1 時,wire 或 reg 即可聲明為向量的形式。例如:
reg [3:0] counter ; //聲明4bit位寬的寄存器counter
wire [32-1:0] gpio_data; //聲明32bit位寬的線型變量gpio_data
wire [8:2] addr ; //聲明7bit位寬的線型變量addr,位寬范圍為8:2
reg [0:31] data ; //聲明32bit位寬的寄存器變量data, 最高有效位為0
對于上面的向量,我們可以指定某一位或若干相鄰位,作為其他邏輯使用。例如:
wire [9:0] data_low = data[0:9] ;
addr_temp[3:2] = addr[8:7] + 1'b1 ;
參考資料:
編碼寶庫:Verilog 數據類型