可程式設計邏輯/Verilog RTL 編碼規範
Verilog RTL 是 Verilog 語言的子集,用於描述實際硬體。RTL 程式碼可以綜合成 ASIC 門或 FPGA 單元。因此,它必須符合非常嚴格的規則。
當 RTL 設計人員檢視 Verilog RTL 程式碼行時,他們看到的不是程式碼行,而是諸如門、多路複用器、加法器和觸發器之類的結構,以及它們是如何連線在一起的。例如
always @(posedge clk) begin
if(cond)
res <= a;
else
res <= b;
end
在上面的程式碼中,RTL 設計人員會立即看到一個觸發器(res),其輸入由一個多路複用器驅動(因為存在 if 語句)。多路複用器的選擇引腳是訊號 cond,多路複用器的輸入是 a 和 b。
要無縫地在 Verilog RTL 和門結構之間轉換需要練習,但這是成為一名優秀 RTL 設計人員的關鍵技能。
如果在 always 塊內對訊號進行賦值,則應為該 always 塊的每個可能路徑對訊號進行賦值。如果情況並非如此,當 always 塊執行時,模擬將愉快地保留訊號的先前值。這種行為在實際門中是不可能的,除非使用鎖存器來保留值,這通常不是預期效果。因此,請確保在所有分支中對所有訊號進行賦值。在早期對訊號賦予預設值(使用非阻塞賦值)並稍後覆蓋它們是可以的。Verilog 清楚地規定,只有後面的賦值會生效。
以下是不正確程式碼的示例
always @(b or c or e or f or cond)
if(cond)
a <= b + c; // forgot to assign d in that case
else
d <= e + f; // forgot to assign a in that case
end
雖然不是嚴格要求,但良好的編碼風格是僅對在 always 塊之外使用的所有訊號使用阻塞賦值,而僅對在單個 always 塊內完全評估並且不在任何其他地方使用的臨時訊號使用非阻塞賦值。
考慮以下程式碼,我們希望根據條件對 a+1 或 b+1 的總和進行賦值。我們不想推斷出兩個加法器,而是推斷出一個多路複用器和一個加法器。
reg [N-1:0] sum;
always @(posedge clk)
if(cond)
sum <= a + 1'b1;
else
sum <= b + 1'b1;
end
使用在 always 塊之外的賦值語句的臨時訊號(tmp)可以實現我們的目標
reg [N-1:0] sum;
wire [N-1:0] tmp;
assign tmp = cond ? a : b;
always @(posedge clk)
begin
sum <= tmp + 1'b1;
end
在 RTL 設計實踐中,最好不要在單個 always 塊內混合阻塞和非阻塞語句。臨時訊號可以在 always 塊之外或使用阻塞賦值的單獨 always 塊中計算。應該不存在它們沒有被賦值但被使用的路徑。請注意,在替代 RTL 程式碼中,tmp 需要宣告為一個 reg,因為它是在過程中(在 'always' 塊內)賦值的。
reg [N-1:0] sum;
reg [N-1:0] tmp;
always @*
tmp = cond ? a : b;
always @(posedge clk)
begin
sum <= tmp + 1'b1;
end
您不能從多個 always 塊驅動同一個訊號,這會導致多個驅動器嘗試寫入同一個訊號,這是不可能的(除非您顯式地生成一個三態匯流排)。因此,訊號只能由一個 always 塊驅動。以下程式碼是錯誤的,因為訊號 foo 由兩個不同的 always 塊驅動
always @(posedge clk) begin
if(reset)
foo <= 1'b0;
end
always @(posedge clk) begin
if(count)
foo <= a;
end
Initial 語句在綜合過程中會被忽略,它們不會轉化成任何邏輯結構。因此,RTL 程式碼不應包含 initial 語句。
通常,所有晶片都有一些復位訊號或序列。使用晶片的復位序列來複位您想要的值,而不是在 initial 塊內復位值。
always @(posedge clk) begin
if(reset) begin
// Reset all state
sig1 <= 1'b0;
sig2 <= 1'b0;
end else begin
// Design behavior
end
end
組合 always 塊的敏感列表必須包含該塊中使用的所有輸入。如果您忘記了敏感列表中的某些訊號,綜合將靜默地假定它們已包含在內並生成門。但是,Verilog 模擬器將尊重您不完整的敏感列表,並且模擬行為將不正確。這意味著,根據透過模擬您認為有效的行為實際上是錯誤的(一旦您綜合了您的設計)。
這是一個不正確程式碼的示例:您能發現敏感列表中缺少哪個訊號嗎?
always @(b or c or e or f)
if(cond)
a = b + c;
else
a = e + f;
end
答案:cond。
最佳的 RTL 方法是使用新的 Verilog 2001 選項 @*,因為這在編寫方面會快得多,並且會自動包含所有輸入。
always @*
if(cond)
a = b + c;
else
a = e + f;
end
為了在綜合過程中生成實際的門,所有迴圈都將被展開。這也意味著迭代可以一勞永逸地確定,它們不能依賴於動態值。迴圈範圍可以是常量、引數或 `define,但不能基於訊號的表示式。
編寫一個推斷出太多邏輯的非常大的迴圈是一個常見的錯誤。只需幾行 Verilog 程式碼,就可以意外地生成數千個門甚至更多。
命名規範有助於理解設計。它們不是 Verilog 作為 RTL 子集的一部分的嚴格要求,但強烈建議使用它們。
對於流水線設計,習慣上將訊號名稱的字尾新增它們所屬的流水線階段。例如,在一個典型的 4 階段 CPU 流水線(F、D、E、W)中,valid_W 是階段 W 的有效訊號。它比 valid_E 延遲一個週期。這在檢視波形時很有幫助:要麼確保將所有屬於同一流水線階段的訊號對齊,要麼根據名稱中指示的流水線階段在心理上進行週期偏移。它還有助於確保計算 W 階段訊號的表示式都來自前面的流水線階段(E)。
當您使用常量時,Verilog 會將常量的寬度擴充套件到 32 位。在以下示例中,當編譯器生成邏輯時,它將首先將常量 1 擴充套件到 32 位的值 1。然後,它將該值截斷為 6 位,以新增到 bar。這通常會生成一個截斷警告。
wire [5:0] foo;
wire [5:0] bar;
assign foo = bar + 1;
另一種方法是使常量的大小等於 bar 的大小。這將消除截斷警告,但您仍然會收到一個大小警告,因為加法結果是 7 位,而不是 6 位(如 foo 所宣告的),因此儲存在 foo 中的結果被截斷。根據實際設計,這可能是完全可以接受的,但編譯器正在提醒設計人員確保如果結果大於其被賦值到的位置,您的設計可以正常工作。
wire [5:0] foo;
wire [5:0] bar;
assign foo = bar + 6'd1;