FPGA原语及IDDR仿真
一般来说,在进行HDL代码编写时,不需要直接或间接地进行原语调用,因为随着FPGA设计规模越来越庞大,人脑应该集中于抽象层次较高的工作中去,而将这些具体实现细节交给编译器来完成。不过有些时候,原语或者库中底层模块的调用还是十分必要的。
相关文档如下:
ug471_7Series_SelectIO.pdf at master · Radiumlrb/cnblogs_documents (github.com)
ug768_7series_hdl.pdf at master · Radiumlrb/cnblogs_documents (github.com)
例如,在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双边沿转单边沿
某些AD芯片通信数据接口采用了DDR双沿采样模式,如AD9361/AD9653与Xilinx FPGA通信需要用到其IDDR和ODDR原语。
ODDR是将2bit单沿采样输入转换为1bit的双沿采样输出,ODDR框图如下图所示,其中D1和D1为单沿采样输入端,C为时钟输入,CE为时钟使能,S为置位信号,R为复位信号,Q为双沿采样输出端。
两种工作模式如下
实例代码如下
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单边沿转双边沿
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
);
从上面例化的模块说明IDDR的参数,其中INIT_Q1和INIT_Q1为输出初始值,SRTYPE表示同步或异步复位,而DDR_CLK_EDGE有以下三种模式:
IDDR 有三种工作模式,模式配置如下图所示,分别为:OPPOSITE_EDGE、SAME_EDGE 、SAME_EDGE_PIPELINED。
代码与仿真
这里对IDDR进行仿真,模块代码如下
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
仿真结果如图,可以看到时钟的上边沿采样为dout1,下边沿采样为dout2