在Vivado中進(jìn)行HDL代碼設(shè)計(jì),不僅需要描述數(shù)字邏輯電路中的常用功能,還要考慮如何發(fā)揮Xilinx器件的架構(gòu)優(yōu)勢。目前常用的HDL語言有三種。
(1)VHDL語言的優(yōu)勢有:
語法規(guī)則更加嚴(yán)格;
在HDL源代碼中初始化RAM組件更容易;
支持package;
自定義類型;
枚舉類型;
沒有reg和wire之間的混淆。
(2)Verilog語言的優(yōu)勢有:
與C語言類似的語法;
代碼結(jié)構(gòu)更緊湊;
支持塊注釋(老版VHDL不支持);
沒有像VHDL一樣的重組件實(shí)例化。
(3)SystemVerilog語言的優(yōu)勢有:
與Verilog相比代碼結(jié)構(gòu)更加緊湊;
結(jié)構(gòu)體和枚舉類型有更好的擴(kuò)展性;
更高抽象級別的接口;
Vivado綜合支持SystemVerilog 2012
以博主接觸的情況看,目前使用最廣泛的應(yīng)該是Verilog語言,替代VHDL成為國內(nèi)大學(xué)教學(xué)的主流。SystemVerilog其實(shí)有更高級別的描述能力,無論是設(shè)計(jì)還是仿真性能也更強(qiáng)大,目前很多國外大學(xué)都使用SystemVerilog作為教學(xué)語言。本文以Verilog語言為基礎(chǔ)講述HDL代碼編寫技巧。
1.觸發(fā)器、寄存器和鎖存器
Vivado綜合可以識別出帶有如下控制信號的觸發(fā)器(Flip-Flop)和寄存器(register):上升沿或下降沿時鐘、異步置位或復(fù)位信號、同步置位或復(fù)位信號、時鐘使能信號。Verilog中對應(yīng)著always塊,其敏感列表中應(yīng)該包含時鐘信號和所有異步控制信號。
使用HDL代碼設(shè)計(jì)觸發(fā)器、寄存器時注意如下基本規(guī)則:
寄存器不要異步置位/復(fù)位,否則在FPGA內(nèi)找不到對應(yīng)的資源來實(shí)現(xiàn)此功能,會被優(yōu)化為其它方式實(shí)現(xiàn)。
觸發(fā)器不要同時置位和復(fù)位。Xilinx的觸發(fā)器原語不會同時帶有置位和復(fù)位信號,這樣做會對面積和性能產(chǎn)生不利影響。
盡量避免使用置位/復(fù)位邏輯。可以采用其它方法以更少的代價達(dá)到同樣 的效果,比如利用電路全局復(fù)位來定義初始化內(nèi)容。
觸發(fā)器控制信號的輸入應(yīng)總是高電平有效。如果設(shè)置為低電平有效,會插入一個反相器,對電路性能會產(chǎn)生不利影響。
Vivado綜合工具根據(jù)HDL代碼會選擇4種寄存器原語:
FDCE:帶有時鐘使能和異步清0的D觸發(fā)器;
FDPE:帶有時鐘使能和異步預(yù)置(Preset)的D觸發(fā)器;
FDSE:帶有時鐘使能和同步置位的D觸發(fā)器;
FDRE:帶有時鐘使能和同步復(fù)位的D觸發(fā)器;
寄存器的內(nèi)容會在電路上電時初始化,因此在申明信號時最好設(shè)定一個默認(rèn)值。綜合后,報告中可以看到寄存器的使用情況。下面給出一個寄存器的代碼實(shí)例:
//上升沿時鐘、高電平有效同步清0、高電平有效時鐘使能8Bits寄存器
module registers_1(
input [7:0] d_in,
input ce, clk, clr,
output [7:0] dout
);
reg [7:0] d_reg;
always @ (posedge clk)
if(clr) d_reg <= 8'b0;
else if(ce) d_reg <= d_in;
assign dout = d_reg;
endmodule
Vivado綜合還會報告檢測出的鎖存器(Latches),通常這些鎖存器是由HDL代碼設(shè)計(jì)錯誤引起的,比如if或case狀態(tài)不完整。綜合會為檢測出的鎖存器報告一個WARNING(Synth 8-327)。下面給出一個鎖存器的代碼示例:
//帶有Postive Gate和異步復(fù)位的鎖存器
module latches (
input G,
input D,
input CLR,
output reg Q
);
always @ *
if(CLR) Q = 0;
else if(G) Q = D;
endmodule
2.三態(tài)緩沖器
三態(tài)緩沖器(Three-state buffer),又稱為三態(tài)門、三態(tài)驅(qū)動器,其三態(tài)輸出受到使能輸出端的控制,當(dāng)使能輸出有效時,器件實(shí)現(xiàn)正常邏輯狀態(tài)輸出(邏輯0、邏輯1),當(dāng)使能輸出無效時,輸出處于高阻狀態(tài),即等效于與所連的電路斷開。
三態(tài)緩沖器(Tristate buffer)通常由一個信號或一個if-else結(jié)構(gòu)來建模,緩沖器可以用來驅(qū)動內(nèi)部總線,也可以驅(qū)動外部板子上的總線。如果使用if-else結(jié)構(gòu),其中一個分支需要給信號賦值為高阻狀態(tài)
當(dāng)三態(tài)緩沖器通過管腳驅(qū)動外部總線時,使用OBUFT原語實(shí)現(xiàn);當(dāng)驅(qū)動內(nèi)部總線時,使用BUFT原語,綜合工具會將其轉(zhuǎn)換為用LUT實(shí)現(xiàn)的邏輯電路。下面給出處理三態(tài)緩沖器的代碼示例:
//使用always塊描述三態(tài)
module tristates_1 (
input T, I,
output reg O
);
always @(T or I)
if (~T) O = I;
else O = 1'bZ;
endmodule
//使用并行賦值描述三態(tài)
module tristates_2 (
input T, I,
output O);
assign O = (~T) ? I: 1'bZ;
endmodule
3.移位寄存器
一個移位寄存器(Shift Register)就是一個觸發(fā)器鏈,允許數(shù)據(jù)在一個固定的延遲段之間傳遞,也叫靜態(tài)移位寄存器。其通常包括:時鐘信號、可選的時鐘使能信號、串行數(shù)據(jù)輸入和輸出。
Vivado綜合可以用SRL類型的資源實(shí)現(xiàn)移位寄存器,如SRL16E、SRLC32E。根據(jù)移位寄存器的長度,綜合時會選擇采用一個SRL類型原語實(shí)現(xiàn),或采用級聯(lián) 的SRLC類型原語實(shí)現(xiàn)。下面給出移位寄存器的代碼示例:
// 32Bits移位寄存器,上升沿時鐘,高電平有效時鐘使能信號
// 使用連接運(yùn)算符{}實(shí)現(xiàn)
module shift_registers_0 (
input clk, clken, SI,
output SO
);
parameter WIDTH = 32;
reg [WIDTH-1:0] shreg;
always @(posedge clk)
if (clken) shreg = {shreg[WIDTH-2:0], SI};
assign SO = shreg[WIDTH-1];
endmodule
// 使用for循環(huán)實(shí)現(xiàn)
module shift_registers_0 (
input clk, clken, SI,
output SO
);
parameter WIDTH = 32;
reg [WIDTH-1:0] shreg;
integer i;
always @(posedge clk)
if (clken) begin
for (i = 0; i < WIDTH-1; i = i+1)
shreg[i+1] <= shreg[i];
shreg[0] <= SI;
end
assign SO = shreg[WIDTH-1];
endmodule
4.動態(tài)移位寄存器
動態(tài)移位寄存器(Dynamic Shift register)是指電路操作期間移位寄存器的長度可以改變。可以采用如下兩種結(jié)構(gòu)實(shí)現(xiàn):
Verilog示例代碼如下所示:
// 32-bit 動態(tài)移位寄存器
module dynamic_shift_register_1 (CLK, CE, SEL, SI, DO);
parameter SELWIDTH = 5;
input CLK, CE, SI;
input [SELWIDTH-1:0] SEL;
output DO;
localparam DATAWIDTH = 2**SELWIDTH;
reg [DATAWIDTH-1:0] data;
always @(posedge CLK)
if (CE == 1'b1) data <= {data[DATAWIDTH-2:0], SI};
assign DO = data[SEL];
endmodule
5.乘法器
綜合工具從源代碼中的乘法運(yùn)算符來推測是否使用乘法器。乘法運(yùn)算結(jié)果的位寬為兩個操作數(shù)位寬之和。比如16Bits的信號乘以8Bits的信號,結(jié)果為24Bits。乘法器可以用Slice單元或DSP塊實(shí)現(xiàn),選擇依據(jù)有兩點(diǎn):(1).操作數(shù)的大小;(2).是否需要最佳性能。通過第25篇介紹過的USE_DSP屬性可以強(qiáng)制設(shè)定乘法器的實(shí)現(xiàn)方式,設(shè)置為no用slice實(shí)現(xiàn);設(shè)置為yes用DSP塊實(shí)現(xiàn)。
當(dāng)使用DSP塊實(shí)現(xiàn)乘法器時,Vivado綜合可以發(fā)揮DSP塊流水線能力的最大優(yōu)勢,綜合時會在乘法操作數(shù)和乘法器后插入兩級寄存器。當(dāng)乘法器無法用一個DSP塊實(shí)現(xiàn)時,綜合時會拆分乘法運(yùn)算,采用幾個DSP塊或DSP塊加slice的方案實(shí)現(xiàn)。下面給出代碼示例:
// 16*24-bit乘法器,操作數(shù)一級延遲,輸出三級延遲
module mult_unsigned (
input clk,
input [15:0]A,
input [23:0]B,
output [39:0]RES
);
reg [15:0] rA;
reg [23:0] rB;
reg [39:0] M [3:0];
integer i;
always @(posedge clk)
begin
rA <= A;
rB <= B;
M[0] <= rA * rB;
for (i = 0; i < 3; i = i+1)
M[i+1] <= M[i];
end
assign RES = M[3];
endmodule
除了乘法器,綜合工具還可以通過乘法器、加法器/減法器、寄存器的使用,推斷出乘加結(jié)構(gòu)(Multiply-Add)、乘減結(jié)構(gòu)(Multiply-Sub)、乘加減結(jié)構(gòu)(Multiply-Add/Sub)和乘累加結(jié)構(gòu)(Multiply-Accumulate)。
5.復(fù)數(shù)乘法器
下面給出一個復(fù)數(shù)乘法器的代碼示例,該設(shè)計(jì)會使用三個DSP48單元:
// 復(fù)數(shù)乘法器(pr+i.pi) = (ar+i.ai)*(br+i.bi)
module cmult # (parameter AWIDTH = 16, BWIDTH = 18)
(
input clk,
input signed [AWIDTH-1:0] ar, ai,
input signed [BWIDTH-1:0] br, bi,
output signed [AWIDTH+BWIDTH:0] pr, pi
);
reg signed [AWIDTH-1:0] ai_d, ai_dd, ai_ddd, ai_dddd ;
reg signed [AWIDTH-1:0] ar_d, ar_dd, ar_ddd, ar_dddd ;
reg signed [BWIDTH-1:0] bi_d, bi_dd, bi_ddd, br_d, br_dd, br_ddd ;
reg signed [AWIDTH:0] addcommon ;
reg signed [BWIDTH:0] addr, addi ;
reg signed [AWIDTH+BWIDTH:0] mult0, multr, multi, pr_int, pi_int ;
reg signed [AWIDTH+BWIDTH:0] common, commonr1, commonr2 ;
always @(posedge clk)
begin
ar_d <= ar;
ar_dd <= ar_d;
ai_d <= ai;
ai_dd <= ai_d;
br_d <= br;
br_dd <= br_d;
br_ddd <= br_dd;
bi_d <= bi;
bi_dd <= bi_d;
bi_ddd <= bi_dd;
end
final products
//
always @(posedge clk)
begin
addcommon <= ar_d - ai_d;
mult0 <= addcommon * bi_dd;
common <= mult0;
end
// Real product
//
always @(posedge clk)
begin
ar_ddd <= ar_dd;
ar_dddd <= ar_ddd;
addr <= br_ddd - bi_ddd;
multr <= addr * ar_dddd;
commonr1 <= common;
pr_int <= multr + commonr1;
end
// Imaginary product
//
always @(posedge clk)
begin
ai_ddd <= ai_dd;
ai_dddd <= ai_ddd;
addi <= br_ddd + bi_ddd;
multi <= addi * ai_dddd;
commonr2 <= common;
pi_int <= multi + commonr2;
end
assign pr = pr_int;
assign pi = pi_int;
endmodule
6.DSP塊中的預(yù)加器
當(dāng)使用DSP塊時,設(shè)計(jì)代碼最好使用帶符號數(shù)(signed)運(yùn)算,這樣需要一個額外的bit位寬來存儲預(yù)加器(pre-adder)結(jié)果,這一位也可以封裝在DSP塊中。一個動態(tài)配置預(yù)加器(后面帶一個乘法器和加法器)的Veirlog示例如下:
// 控制信號動態(tài)選擇預(yù)加或預(yù)減
module dynpreaddmultadd # (parameter SIZEIN = 16)
(
input clk, ce, rst, subadd,
input signed [SIZEIN-1:0] a, b, c, d,
output signed [2*SIZEIN:0] dynpreaddmultadd_out
);
reg signed [SIZEIN-1:0] a_reg, b_reg, c_reg;
reg signed [SIZEIN:0] add_reg;
reg signed [2*SIZEIN:0] d_reg, m_reg, p_reg;
always @(posedge clk)
if (rst) begin
a_reg <= 0; b_reg <= 0;
c_reg <= 0; d_reg <= 0;
add_reg <= 0;
m_reg <= 0; p_reg <= 0;
end
else if (ce) begin
a_reg <= a; b_reg <= b;
c_reg <= c; d_reg <= d;
if (subadd)
add_reg <= a - b;
else
add_reg <= a + b;
m_reg <= add_reg * c_reg;
p_reg <= m_reg + d_reg;
end
// 輸出累加結(jié)果
assign dynpreaddmultadd_out = p_reg;
endmodule
7.UltraScale DSP塊中的平方電路
UltraScale DSP塊的原語DSP48E2支持計(jì)算輸入或預(yù)加器輸出的平方。下面的示例代碼計(jì)算了差值的平方根,該設(shè)計(jì)會使用一個DSP塊實(shí)現(xiàn),且有最佳的時序性能:
// DSP48E2支持平方運(yùn)算,預(yù)加器配置為減法器
module squarediffmult # (parameter SIZEIN = 16)
(
input clk, ce, rst,
input signed [SIZEIN-1:0] a, b,
output signed [2*SIZEIN+1:0] square_out
);
reg signed [SIZEIN-1:0] a_reg, b_reg;
reg signed [SIZEIN:0] diff_reg;
reg signed [2*SIZEIN+1:0] m_reg, p_reg;
always @(posedge clk)
if (rst) begin
a_reg <= 0;
b_reg <= 0;
diff_reg <= 0;
m_reg <= 0;
p_reg <= 0;
end
else if (ce) begin
a_reg <= a;
b_reg <= b;
diff_reg <= a_reg - b_reg;
m_reg <= diff_reg * diff_reg;
p_reg <= m_reg;
end
assign square_out = p_reg;
endmodule
8.黑盒子
FPGA設(shè)計(jì)可以包含EDIF網(wǎng)表,這些網(wǎng)表必須通過實(shí)例化與其它設(shè)計(jì)部分連接在一起。在HDL源代碼中使用BLACK_BOX屬性完成實(shí)例化,這部分實(shí)例化還可以使用一些特定的約束。使用BLACK_BOX屬性后,該實(shí)例將被視作黑盒子。下面給出示例代碼:
//模塊定義
(* black_box *) module black_box1
(
input in1, in2,
output dout
);
endmodule
//模塊實(shí)例化
module black_box_1
(
input DI_1, DI_2,
output DOUT
);
black_box1 U1 (
.in1(DI_1),
.in2(DI_2),
.dout(DOUT)
);
endmodule
9.FSM狀態(tài)機(jī)
默認(rèn)情況下,Vivado綜合可以從RTL設(shè)計(jì)中提取出有限狀態(tài)機(jī)(FSM),使用-fsm_extraction off可以關(guān)閉該功能。通常需要設(shè)計(jì)者設(shè)置FSM的編碼方式,便于綜合時根據(jù)設(shè)置調(diào)整優(yōu)化目標(biāo)。
Vivado綜合支持Moore和Mealy型狀態(tài)機(jī)。一個狀態(tài)機(jī)由狀態(tài)寄存器、下一個狀態(tài)功能、輸出功能三部分組成,可用如下框圖表示:
Mealy狀態(tài)機(jī)需要從輸出到輸入的反饋路徑。盡管狀態(tài)寄存器也支持異步復(fù)位,但最好還是使用同步復(fù)位方式。設(shè)置一個復(fù)位或上電狀態(tài),綜合工具即可識別出FSM。默認(rèn)狀態(tài)編碼為auto,綜合時會選擇最佳的編碼方式:
獨(dú)熱碼(One-Hot):最多支持32個狀態(tài),有最快的速度和較低的功耗,每個狀態(tài)由一個獨(dú)立的bit(對應(yīng)一個觸發(fā)器)表示,狀態(tài)間轉(zhuǎn)換時只需要改變兩個bit的狀態(tài)。
格雷碼(Gray):兩個連續(xù)狀態(tài)之間轉(zhuǎn)換時只需要改變一個Bit的狀態(tài),可以最小化冒險和毛刺現(xiàn)象,有最低的功耗表現(xiàn)。
Johnson編碼:適用于包含沒有分支的長路徑的狀態(tài)機(jī);
順序編碼:使用連續(xù)的基數(shù)值表示狀態(tài),跳轉(zhuǎn)到下一個狀態(tài)的狀態(tài)方程最簡單。
下面給出一個FSM的Verilog示例:
// 順序編碼的FSM
module fsm_1(
input clk, reset, flag,
output reg sm_out
);
parameter s1 = 3'b000;
parameter s2 = 3'b001;
parameter s3 = 3'b010;
parameter s4 = 3'b011;
parameter s5 = 3'b111;
reg [2:0] state; //狀態(tài)寄存器
always@(posedge clk)
if(reset) begin
state <= s1;
sm_out <= 1'b1;
end
else begin
case(state)
s1: if(flag) begin
state <= s2;
sm_out <= 1'b1;
end
else begin
state <= s3;
sm_out <= 1'b0;
end
s2: begin state <= s4; sm_out <= 1'b0; end
s3: begin state <= s4; sm_out <= 1'b0; end
s4: begin state <= s5; sm_out <= 1'b1; end
s5: begin state <= s1; sm_out <= 1'b1; end
endcase
end
endmodule
10.ROM設(shè)計(jì)方法
Read-only memory(ROM)使用HDL模型實(shí)現(xiàn)與RAM非常相似。使用ROM_STYLE屬性選擇使用寄存器或塊RAM資源來實(shí)現(xiàn)ROM。使用塊RAM資源實(shí)現(xiàn)ROM的示例代碼如下:
//使用塊RAM資源實(shí)現(xiàn)ROM
module rams_sp_rom_1 (
input clk, en,
input [5:0] addr,
output [19:0] dout
);
(*rom_style = "block" *) reg [19:0] data;
always @(posedge clk)
if (en)
case(addr)
6'b000000: data <= 20'h0200A; 6'b100000: data <= 20'h02222;
6'b000001: data <= 20'h00300; 6'b100001: data <= 20'h04001;
6'b000010: data <= 20'h08101; 6'b100010: data <= 20'h00342;
......
6'b011110: data <= 20'h00301; 6'b111110: data <= 20'h08201;
6'b011111: data <= 20'h00102; 6'b111111: data <= 20'h0400D;
endcase
assign dout = data;
endmodule
至芯科技12年不忘初心、再度起航12月17日北京中心FPGA工程師就業(yè)班開課、線上線下多維教學(xué)、歡迎咨詢!
從EDA技術(shù)演變里看芯片創(chuàng)新之未來
掃碼加微信邀請您加入FPGA學(xué)習(xí)交流群
歡迎加入至芯科技FPGA微信學(xué)習(xí)交流群,這里有一群優(yōu)秀的FPGA工程師、學(xué)生、老師、這里FPGA技術(shù)交流學(xué)習(xí)氛圍濃厚、相互分享、相互幫助、叫上小伙伴一起加入吧!
評論