一般來說,在進行HDL代碼編寫時,不需要直接或間接地進行原語調用,因爲隨着FPGA設計規模越來越龐大,人腦應該集中於抽象層次較高的工作中去,而將這些具體實現細節交給編譯器來完成。不過有些時候,原語或者庫中底層模塊的調用還是十分必要的。例如,在Xilinx的ISE工具中,可以通過edit→Language Templates菜單調出語法模板,在其中的VHDL或Verilog子菜單下,都可以看到一個Device Primitive Instantiation子項,裏面列舉出了所有Xilinx FPGA平臺上開發項目時可能會用到的原語例化方式。舉例簡介如下:
1.時鐘相關原語
如果時鐘信號不是由FPGA芯片的專用時鐘pin(或pad)引入FPGA的,那麼它通常就需要在FPGA內部被顯式地連接到時鐘樹資源上,否則,直接使用這種不經過時鐘樹的時鐘信號,會給FPGA設計的時序帶來非常麻煩的問題,進而導致邏輯行爲失敗。
可是HDL代碼僅僅描述功能,無法向編譯器表達“希望將某一時鐘信號連接到時鐘樹資源”這樣的一層意思,那麼此時,就需要使用類似BUFG這樣的庫裏提供的底層模塊來進行指示。例如:
--VHDL example
SIGNAL innerclk, gclk : STD_LOGIC;
onToGlobalClockTree : BUFG
PORT MAP(
I => innerclk, --in std_logic
O => gclk --out std_logic
);
PROCESS (gclk)
BEGIN
......
END PROCESS;
//Verilog example
wire innerclk,gclk;
BUFG onToGlobalClockTree(
.I (innerclk),
.O (gclk)
);
always@(posedge gclk)
begin
......
end
那麼,像上例這樣,通過顯式調用BUFG這樣一個庫中的底層模塊,就可以告訴編譯器,我們希望將innerclk信號引人全局時鐘樹,而其經過全局時鐘樹後的名字就改爲gclk。後續HDL代碼便可以放心地基於gclk編寫邏輯。需要說明的是,直接從全局時鐘pin(或pad)引入的時鐘信號,對於xilinx的FPGA芯片來說,實際上是通過IBUFG+BUFG這樣的組合直接連接到全局時鐘樹上的,只不過此時不需要顯式例化這兩個原語。
類似的時鐘相關原語還有BUFR、BUFIO等,它們分別對應區域時鐘樹和IO時鐘樹資源,這裏就不再贅述。
2.差分輸入、輸出原語
FPGA的接口具有單端和差分兩種形式,同樣,HDL代碼只能描述功能,無法表達“某兩個pin(或pad)腳互爲一個差分對”這樣的一層意思。那麼此時,就需要使用類似IBUFDS、IBUFGDS、OBUFDS這樣的庫裏提供的底層模塊或原語來進行指示。例如:
--VHDL example
clklvds : IBUFGDS
GENERIC MAP(
DIFF_TERM => TRUE,
IBUF_DELAY_VALUE => "0",
IOSTANDARD => "DEFAULT")
PORT MAP(
I = LVDSCIk_p,
IB => LVDSclk n,
O => sclk);
onToGlobalClockTree : BUFG
PORT MAP(
I => sclk, --in std logic
O => gclk --out std logic
);
//Verilog example
IBUFGDS clklvds(
.I (LVDSClk_p),
.IB (LVDSClk_n),
.O (sclk)
);
BUFG onToGlobalClockTree(
.I (sclk),
.O(gclk)
);
上述代碼通過調用原語IBUFGDS,表達了“LVDSCIk_p、LVDSCIk_n是一對差分時鐘輸入信號且它們是經由FPGA芯片的專用差分時鐘輸人管腳引入FPGA芯片”的意思,這樣編譯器就會採用接口資源中的專用差分時鐘輸入電路來接入這對時鐘輸入信號,並轉換成爲一個單端時鐘sclk,緊接着,在通過一個BUFG原語將其引至全局時鐘網絡,這樣全局時鐘網絡輸出的gclk就可以作爲後續內部邏輯的時鐘源。
3.接口相關原語
當需要使用接口資源中的寄存器來實現高速數據採集時,除了使用寄存器約束外,如果直接調用相關的原語,編譯器便會利用接口資源中的寄存器來做事情。例如,使用ODDR、IDDR、IDDR2CLK、ISERDES、OSERDES等原語後,編譯器便會利用接口資源的寄存器來實現相關接口功能,這樣便能達到比較高的性能。例如:
--VHDL example
isO:ISERDES_NODELAY
generic map (
BITSLIP_ENABLE => TRUE, --TRUE/FALSE to enable bitslip controller
DATA_RATE => "DDR", --Specify data rate of "DDR" or "SDR"
DATA_WIDTH => 6, --Specify data width
--NETWORKING SDR:2,3,4,5,6,7,8:DDR 4,6,8,10
--MEMORY SDR N/A:DDR 4
INTERFACE_TYPE => NETWORKING, --Use model "MEMORY" or "NETWORKING"
NUM_CE => 2, --Define number of clock enables to an integer of 1 or 2
SERDES_MODE => "MASTER" --Set SERDES mode to "MASTER" or "SLAVE"
)
port map (
Q1 => dataOLine(0), --1-bit registered SERDES output
Q2 => dataOLine(1), --1-bit registered SERDES output
Q3 => dataOLine(2), --1-bit registered SERDES output
Q4 => dataOLine(3), --1-bit registered SERDES output
Q5 => dataOLine(4), --1-bit registered SERDES output
Q6 => dataOLine(5), --1-bit registered SERDES output
SHIFTOUT1 => open, --1-bit cascade Master/Slave output
SHIFTOUT2 => open, --1-bit cascade Master/Slave output
BITSLIP => '0', --1-bit Bitslip enable input
CE1 => '1', --1-bit clock enable input
CE2 => '1', --1-bit clock enable input
CLK => clkFast, --1-bit master clock input
CLKB => clkFastInvert, --1-bit secondary clock input for DATA RATE DDR
CLKDIV => clkSlow, --1-bit divided clock input
D => din, --1-bit data input,connects to IODELAY or input buffer
0CLK => '0', --1-bit fast output clock input
RST => rst, --1-bit asynchronous reset input
SHIFTIN1 => '0', --1-bit cascade Master/Slave input
SHIFTIN2 => '0' --1-bit cascade Master/Slave input
)
//Verilog example
ISERDES_NODELAY #(
.BITSLIP_ENABLE("FALSE"), // "TRUE" or "FALSE" to enable bitslip controller
// Must be "FALSE" if INTERFACE_TYPE set to "MEMORY"
.DATA_RATE("DDR"), // Specify data rate of "DDR" or "SDR"
.DATA_WIDTH(4), // Specify data width
// NETWORKING SDR: 2, 3, 4, 5, 6, 7, 8; DDR: 4, 6, 8, 10
// MEMORY SDR: N/A; DDR: 4
.INTERFACE_TYPE("MEMORY"), // Use model - "MEMORY" or "NETWORKING"
.NUM_CE(2), // Number of clock enables used, 1 or 2
.SERDES_MODE(MASTER) // Set SERDES mode to "MASTER" or "SLAVE"
.Q1(dataOLine[0]), // 1-bit registered SERDES output
.Q2(dataOLine[1]), // 1-bit registered SERDES output
.Q3(dataOLine[2]), // 1-bit registered SERDES output
.Q4(dataOLine[3]), // 1-bit registered SERDES output
.Q5(dataOLine[4]), // 1-bit registered SERDES output
.Q6(dataOLine[5]), // 1-bit registered SERDES output
.SHIFTOUT1(open), // 1-bit cascade Master/Slave output
.SHIFTOUT2(open), // 1-bit cascade Master/Slave output
.BITSLIP(1'b0), // 1-bit Bitslip enable input
.CE1(1'b1), // 1-bit clock enable input
.CE2(1'b1), // 1-bit clock enable input
.CLK(clkFast), // 1-bit master clock input
.CLKB(clkFastInvert), // 1-bit secondary clock input for DATA_RATE DDR
.CLKDIV(clkSlow), // 1-bit divided clock input
.D(din), // 1-bit data input, connects to IODELAY or input buffer
.IOCLK(1'b0), // 1-bit fast output clock input
.RST(rst), // 1-bit asynchronous reset input
.SHIFTIN1(1'b0), // 1-bit cascade Master/Slave input
.SHIFTIN2(1'b0) // 1-bit cascade Master/Slave input
);
使用上述原語後,編譯器便會利用接口資源中的寄存器實現一個針對輸入接口的高性能1:6串並轉換器。當然了,爲了保證串並處理的成功,僅僅使用ISERDES原語還遠遠不夠,時鐘信號也必須按照要求來處理,具體的要求可以查閱Xilinx公司相應器件的用戶手冊。
舉個例子,ODDR代表的是雙數據速率輸出寄存器(Output Double Data Rate Register)。這種原語用於在一個時鐘週期內產生兩個數據輸出,通常用於高速數據傳輸和時鐘數據恢復等應用。在以下示例VHDL代碼中,ODDR原語被用來生成一個雙數據速率的輸出信號ad9653_1clk
。
ad1_clk : ODDR
generic map(
DDR_CLK_EDGE => "OPPOSITE_EDGE", -- "OPPOSITE_EDGE" or "SAME_EDGE"
INIT => '0', -- Initial value for Q port ('1' or '0')
SRTYPE => "SYNC") -- Reset Type ("ASYNC" or "SYNC")
port map (
Q => ad9653_1clk, -- 1-bit DDR output
C => clk18, -- 1-bit clock input
CE => '1', -- 1-bit clock enable input
D1 => '1', -- 1-bit data input (positive edge)
D2 => '0', -- 1-bit data input (negative edge)
R => '0', -- 1-bit reset input
S => '0' -- 1-bit set input
);
IDDR的主要功能就是將輸入的雙沿信號轉換爲單沿信號輸出給FPGA內部邏輯進行使用,IDDR位於通1中的ILOGICE部分,在講解IDDR使用前,需要了解ILOGICE的結構及功能。
IDDR的功能就是將雙沿採樣的數據轉換爲單沿數據傳輸給FPGA內部進行使用。FPGA內部的D觸發器一般都是在時鐘上升沿去採集輸出數據,這種方式被稱爲SDR,與SDRAM傳輸數據類似。在SDRAM之後,爲了提高數據傳輸速率,推出了DDR,在時鐘的上升沿和下降沿都能傳輸數據,同樣時鐘頻率下,速率可以提升一倍,這種傳輸數據的方式就是雙沿傳輸,這種方式一般都只存在接口部分,內部電路採用雙沿會比較麻煩,所以會轉換爲單沿進行處理,FPGA調用IDDR原語即可實現。
IDDR原語模板
IDDR #(
.DDR_CLK_EDGE("OPPOSITE_EDGE"), // "OPPOSITE_EDGE", "SAME_EDGE"
// or "SAME_EDGE_PIPELINED"
.INIT_Q1(1'b0), // Initial value of Q1: 1'b0 or 1'b1
.INIT_Q2(1'b0), // Initial value of Q2: 1'b0 or 1'b1
.SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
) IDDR_inst (
.Q1(Q1), // 1-bit output for positive edge of clock
.Q2(Q2), // 1-bit output for negative edge of clock
.C(C), // 1-bit clock input
.CE(CE), // 1-bit clock enable input
.D(D), // 1-bit DDR data input
.R(R), // 1-bit reset
.S(S) // 1-bit set
);
模塊代碼
module iddr_ctrl(
input clk ,//系統時鐘信號;
input rst ,//系統復位信號,高電平有效;
input clk_en ,//時鐘使能信號;
input din ,//輸入數據;
output dout1 ,//輸出數據
output dout2
);
reg clk_en_r ;
//(* IOB = "TRUE" *)reg clk_en_r ;//將clk_en_r放在ILOGICE中;
//將clk_en打拍,用於驗證IOB原語是否有效;
always@(posedge clk)begin
clk_en_r <= clk_en;
end
//例化IDDR原語
IDDR #(
.DDR_CLK_EDGE ("SAME_EDGE_PIPELINED" ),// "OPPOSITE_EDGE", "SAME_EDGE" or "SAME_EDGE_PIPELINED"
.INIT_Q1 (1'b0 ),// Initial value of Q1: 1'b0 or 1'b1
.INIT_Q2 (1'b0 ),// Initial value of Q2: 1'b0 or 1'b1
.SRTYPE ("SYNC" ) // Set/Reset type: "SYNC" or "ASYNC"
)
IDDR_inst (
.Q1 (dout1 ),// 1-bit output for positive edge of clock
.Q2 (dout2 ),// 1-bit output for negative edge of clock
.C (clk ),// 1-bit clock input
.CE (clk_en_r),// 1-bit clock enable input
.D (din ),// 1-bit DDR data input
.R (rst ),// 1-bit reset
.S (1'b0 ) // 1-bit set
);
endmodule
testbench
`timescale 1 ns/1 ns
module test();
parameter CYCLE = 10 ;//系統時鐘週期,單位ns,默認10ns;
parameter RST_TIME = 10 ;//系統復位持續時間,默認10個系統時鐘週期;
reg clk ;//系統時鐘,默認100MHz;
reg rst ;//系統復位,默認高電平有效;
reg clk_en ;
reg din ;
wire dout1 ;
wire dout2 ;
iddr_ctrl u_iddr_ctrl (
.clk ( clk ),
.rst ( rst ),
.clk_en ( clk_en ),
.din ( din ),
.dout1 ( dout1 ),
.dout2 ( dout2 )
);
//生成周期爲CYCLE數值的系統時鐘;
initial begin
clk = 1;
forever #(CYCLE/2) clk = ~clk;
end
//生成復位信號;
initial begin
rst = 0;
#2;
rst = 1;//開始時復位10個時鐘;
#(RST_TIME*CYCLE);
rst = 0;
repeat(120) @(posedge clk);
$stop;//停止仿真;
end
initial begin
#1;
din = 1'b0;clk_en = 1'b0;
#(CYCLE*(10+RST_TIME));
clk_en = 1'b1;
#(CYCLE);
repeat(100)begin//產生100個雙沿時鐘數據。
#(CYCLE/2);
din = ({$random} % 2);
#(CYCLE/2);
din = ({$random} % 2);
end
#(CYCLE);
clk_en = 1'b0;
end
endmodule