$unit编译单元声明
$unit編譯單元聲明
SystemVerilog含有編譯單元。
相比Verilog,SystemVerilog增加了編譯單元的概念。編譯單元是同時編譯的所有源文件。編譯單元為軟件工具提供了一種對整個設計子塊單獨編譯的方法。一個子塊可能包含第一個或多個模塊(module)。這下模塊可能包含在一個或多個文件中。設計的子塊可能還包含接口塊和測試程序塊。
編譯單元域包含外部聲明。
SystemVerilog允許在包、模塊、接口和程序塊的外部進行聲明擴展Verilog的聲明域。這些外部聲明在“編譯單元域”中,并且對所有同時編譯的模塊都是可見的。
編譯單元域可以包含:
(1)時間單元和精度聲明
(2)變量聲明
(3)net聲明
(4)常量聲明
(5)用戶定義數據類型,使用typedef、enum、或class
(6)任務和函數定義
編譯單元域中的外部聲明(不可綜合)
///外部聲明/ parameter VERSION = "1.2a"; //外部常量 reg resetN = 1; //外部變量(低有效) typedef struct packes{ //外部用戶定義類型reg [31:0] address;reg [31:0] data;reg [31:0] opcode; }instruction_word_t;function automatic int log2 (input int n);//外部函數if (n <= 1) return (1);log2 = 0;while( n > 1)beginn = n/2;log2++;endreturn(log2); endfunction/模塊定義 //用外部聲明定義端口類型 module register(output instruction_word_t q,input instruction_word_t d,input wire clock );always @(posedge clock ,negedge resetN)if(!resetN) q <= 0;//使用外部復位else q <= d; endmodule 注意:外部編譯單元域聲明不是全局的編譯單元域中的聲明與全局聲明不一樣。真正的全局聲明,如全局變量或函數,不管源文件是單獨還是同時編譯,都會被設計的所有模塊共享。
SystemVerilog的編譯單元域只作用于同時編譯的源文件。每次編譯源文件,就創建一個唯一僅針對此次編譯的編譯單元域。例如,如果模塊CPU和模塊controller都引用外部聲明的變量regset,那么就可能存才兩種假設:
(1)如果兩個模塊同時編譯,將只要一個編譯單元域。外部聲明的reset變量將被這兩個模塊共用。
(2)如果兩個模塊分別編譯,將會有兩個編譯單元域,可能有兩個不同的reset變量。
在后一種假設下,包含外部reset聲明的編譯看上去沒有問題。另一個文件在單獨編譯時會有自己唯一的$unit編譯域,不會看到前一個編譯中的reset聲明。取決于reset使用的環境,第二個編譯可能會由于未聲明變量而失敗,也可能使reset成為隱式net而編譯成功。這種可能性是很危險的。如果第二個編譯通過使reset成為隱式net而成功,那么現在就會有兩個叫做reset的信號,每個編譯中一個。這兩個不同的reset信號用什么方式都不能連在一起.
$unit只能用于導入包
(1)不要在 $unit空間進行任何聲明!所有的聲明都要在命名包內進行。
(2)必要時可以將包導入到 $unit中。這在模塊或接口的多個端口使用用戶自定義類型,而這個類型定義又在包中時非常有用。
在 $unit編譯單元域中直接聲明對象會在文件單獨編譯時導致設計出錯。如果聲明分散在多個文件中,還會產生spaghetti代碼,難以維護、重用或調試聲明錯位。(spaghetti代碼指結構混亂、邏輯性差、難于調試的代碼)
SystemVerilog標識符搜索規則
編譯單元域中的聲明可以在組成編譯單元的模塊的任何層次引用。
SystemVerilog定義了簡單的搜索規則來引用標識符:
(1)搜索那些按IEEE 1364Verilog標準定義的局部聲明
(2)搜索統配符導入到當前作用域的包中的聲明
(3)搜索編譯單元域中是聲明
(4)搜索設計層次中的聲明,遵循IEEE1364Verilog搜索規則
編譯單元域中的變量和net
當使用外部聲明時有一點需要著重考慮。Verilog支持隱式聲明,即在特定環境下,為聲明的標識符假定為net類型(通常為wire類型)。當從上下文不能推斷出隱式類型或者希望使用默認net類型以外的類型時,Verilog要求在標識符引用之前要顯式聲明標識符類型。
隱式類型聲明規則影響編譯單元域中的變量和net聲明。軟件工具必須在標識符引用之前找到外部聲明。否則,這個名稱將被看做未聲明的標識符并遵循Verilog隱式類型規則。
將包導入$unit的編碼原則
SystemVerilog允許將模塊端口聲明為用戶定義類型。
當許多模塊端口都是用戶自定義類型時,像上面那樣顯式地引用包中就會顯得繁瑣了。一種可選擇的風格是在模塊聲明之前將包導入到$unit編譯單元域中。
//將包中特定子項導入到$unit中 import definitions::instruction_t;module ALU ( input definitions::instruction_t IW,input logic clock,output logic[31:0] result );包還可以通過通配符導入到$unit域中。注意通配符導入實際上不能導入包中所有子項。它只是將包加到SystemVerilog源路徑中。
//將包中特定子項導入到$unit中 import definitions::*;module ALU ( input definitions::instruction_t IW,input logic clock,output logic[31:0] result );使用單獨編譯將包導入到$unit中
文件順序編譯依賴性
當子項從包中導入(包中特定子項導入或者是通配符導入)時,import語句必須出現在包中子項被引用之前。如果包導入語句與模塊或接口不在同一文件中,而模塊和接口文件又要引用包中子項,那么包含import文件必須在文件比編譯順序中首先列出來。如果文件順序不正確,模塊或接口的編譯要么失敗,要么因看不到包中子項而錯誤地指定為隱式net。
多文件編譯與單文件編譯
可以讀入Verilog和SystemVerilog源代碼的綜合編譯器、代碼檢測器、一些仿真器和其他可能的工具通常可以一次編譯一個或多個文件。當多個文件同時進行編譯時,只要一個$unit域。包導入(不管是包中特定子項導入或者是通配符導入)到 $unit域中使包中子項對導入語之后讀入的所有模塊和接口都可見。但是,如果這些文件單獨編譯,將會產生多個不同的 $unit編譯單元。一個 $unit中導入的包對另一個 $unit是不可見的。
在每個文件中使用導入語句
對于將包中子項導入到 $unit編譯單元域中出現的這兩個問題,解決辦法就是將import語句放到每個文件模塊或接口定義之前。這個解決方法在每個文件單獨編譯時很奏效。但是,當多個文件同時進行編譯時,還要當心。將同樣的包中子項多次導入到同一 $unit域中是非法的。(這與在同一名稱空間中兩次聲明同一變量一樣是非法的)
用 $unit包導入的條件編譯
可以使用C語言常用的編程技巧,使我們在單個文件編譯和多個文件編譯時都可以將包中子項導入到 $unit域中。這個技巧是利用條件編譯,使第一次遇到導入語句時將其編譯到 $unit中,而再次出現則不編譯這些語句。為了說明導入語句在當前 $unit域中是否已經編譯完,在導入語句第一次編譯時設置`define標志。
帶條件編譯的包
`ifndef DFFS_DONE //如果已編譯標志沒設置`define DFFS_DONE //設置該標志package definitions;paramter VERSION = "1.1";typedef enum{ADD,SUB,MUL} opcodes_t;typedef struct{logic [31:0] a,b;opcodes_t opcode;}instruction_t;function automatic [31:0] multiplier(input [31:0] a,b);//用戶定義的32位乘法return a*b; //抽象乘法(無錯誤檢測)endfunction endpackage import definitions::*; //將包導入到 $unit中`endif每個需要包中定義的設計或測試平臺都有應該將
`include "definitions.pkg"放在文件的開始。當設計或測試平臺文件被編譯時,都會將包和導入語包含進去。definitions.pkg文件中的條件編譯將保證如果包還沒被編譯和導入,就去完成這兩個任務。如果包已經編譯并導入到當前 $unit域中,該文件的編譯將被忽略。
注意:對于這種編碼風格,包文件應該用`include編譯命令間接傳遞給軟件工具編譯器。這種條件編譯風格使用Verilog的`include命令把definitions.pkg文件當作其他文件的一部分進行編譯。這樣做時為了保證definitions.pkg文件末尾的導入語將包導入到設計或測試文件,編譯正在使用的同一 $unit域中。如果definitions.pkg文件直接出現在軟件工具編譯器的命令行中,包和導入語句就會被編譯到另一個 $unit空間,而不是設計或測試平臺塊正在使用 $unit域中。
//包含條件編譯包文件的設計文件 `include "definitions.pkg" //編譯包文件module ALU ( input definitions::instruction_t IW,input logic clock,output logic[31:0] result ); always_ff @(posedge clock) begincase(IW.opcode)definitions::ADD : result = IW.a + IW.b;definitions::SUB : result = IW.a - IW.b;definitions::MUL : result = definitions::multiplier(IW.a,IW.b);endcase end endmodule //包含條件編譯包文件的測試平臺文件 `include "definitions.pkg" //編譯包文件module test;instruction_t test_word;logic [31:0] alu_out;logic clock = 0;ALU dut (.IW(test_word),.clock(clock ),. result(alu_out));always #10 clock = ~clock;initial begin@(posedge clock)test_word.a = 5;test_word.b =7;test_word.opcode = ADD;@(negedge clock)$display ("alu_out = %d (expected 12)", alu_out);$finish; end endmodule`include對單文件和多文件編譯
進行單文件編譯時,包將被編譯并導入到每個 $unit編譯單元中。這就保證了每個 $unit都能看到相同的包中子項。由于每個 $unit都是唯一的,不會在多次編譯包的過程中出現名稱沖突。
進行多文件編譯時,條件保證包只進行一次編譯并導入到所有模塊的公共 $unit編譯域中。不管設計或測試平臺文件那個先編譯,都會導入包,保證包中子項對所有文件都是可見的
包中變量是共享變量(不可綜合)
包中可以包含變量聲明。包中變量為所有導入變量的設計塊(和測試塊)共享。包中變量的行為在單文件編譯和多文件編譯中截然不同。在多文件編譯中,包被導入到同一 $unit編譯域中。每個設計塊或測試塊將看到相同的包中變量。一個塊寫到包中變量的值將對其他所有塊可見。在單文件編譯中,每個 $unit域都有一個唯一的變量,它恰好與另一個不同的 $unit域中的變量同名。一個設計塊或測試塊對包中變量寫入的值對其他設計或測試塊不可見。
包中的靜態任務和函數是不可綜合
靜態任務和函數或者含靜態儲存的自動任務和函數,也具有同樣的潛在問題。在多文件編譯中,只有一個 $unit域,它將導入任務和函數的一個實例。任務或函數中的靜態儲存對所有設計和驗證塊都是可見的。在單文件編譯中,每一個獨立的 $unit導入的任務或函數實例不同。任務或函數的靜態儲存不會在設計和測試塊中共享。
這個關于條件編譯導入語句到 $unit中的限制在可綜合的模型中沒有問題,因為綜合不支持包中的變量聲明、靜態任務和函數。
使用包而不用 $unit是更好的編碼風格
可在編譯單元域(所有模塊和接口定義的外部)中聲明的可綜合結構有
(1)typedef用戶定義類型定義
(2)自動函數
(3)自動任務
(4)parameter和localparam常量
(5)包導入
雖然在編譯單元域定義用戶定義類型不是一種好的風格,但它可綜合的。更好的風格是吧用戶定義類型的定義放在有名稱的包中,這可以降低出現spaghetti代碼和文件順序依懶性的風險。
外部任務和函數必須是自動的
在 $unit編譯單元域中聲明任務和函數也不是一種好的編碼風格。但是,在 $unit中定義的任務和函數是可綜合的。當模塊引用編譯單元域中定義的任務和函數時,綜合將復制該任務或函數代碼并把它看成在模塊中定義的一樣。為了綜合,編譯單元域中定義的任務和函數必須聲明為automatic,并且不能包含靜態變量。這還是因為自動任何和函數的儲存區在每次調用時才會實際分配。因此,每個模塊引用的自動任務或函數的引用的綜合前仿真行為與綜合后行為相同(綜合后任務或函數的功能已經在模塊內實現了)。
編譯單元域中定義的parameter常量不能重新定義,因為它不是模塊實例的一部分。綜合會將編譯單元域中聲明的常量看作文本值。在 $unit域中聲明參數不是一種好的建模風格,因為常量聲明文件與模板文件分開編譯時,這些常量對模塊是不可見的。
總結
以上是生活随笔為你收集整理的$unit编译单元声明的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SystemVerilog声明的位置
- 下一篇: 未命名语句块中的声明