ODDR位于OLOGIC中,可以把单沿传输的数据转换为双沿传输的数据, 在讲解ODDR功能之前,需要先了解OLOGIC的结构及功能。
OLOGIC块位于IOB的内侧,FPGA内部信号想要输出到管脚,都必须经过OLOGIC。OLOGIC资源的类型为OLOGICE2(HP I/O Bank)和OLOGICE3(HR I/O Bank),两者在功能和结构上是相同的,所以本文称为OLOGIC。
图1是OLOGIC的结构框图,分为上下两部分,下半部分用于配置输出数据路径,上半部分用于配置三态控制路径,分别实现对数据、三态信号进行单沿转双沿的功能,两部分具有共同的时钟 (CLK),但是使能信号不同(OCE和TCE)。
如果输出的信号不使用OLOGIC中的ODDR功能,那么此时信号从图1中红线路径进行传输,从组合逻辑电路输出到IOB模块。如果要使用OLOGIC模块中的D触发器功能,那么信号从D1进入OLOGIC模块,沿绿色信号线进行传输。如果要使用OLOGIC的ODDR功能,把单沿传输的信号转换为双沿传输的信号,此时需要两个输入信号D1、D2沿蓝色路径进行传输。
图2是FPGA中OLOGIC实际的框图,因为该电路还可以被配置为OSERDESE2,所以相比图1会多出一些信号端口。
图3是ODDR原语框图,与IDDR一样不支持同时复位和置位。ODDR端口信号如表1所示,表2描述了ODDR原语的可用参数。
端口名 | 含义 |
---|---|
C | 时钟输入信号。 |
CE | 钟使能信号,高电平有效。 |
D1、D2 | ODDR输入信号。 |
S/R | 置位/复位引脚,高电平有效。 |
Q | ODDR输出信号。 |
参数名 | 含义 | 取值 |
---|---|---|
DDR_CLK_EDGE | ODDR工作模式 | OPPOSITE_EDGE (默认), SAME_EDGE |
INIT | 设置Q端口的初始值 | 0(默认),1 |
SRTYPE | 设置复位/置位相对于时钟的类型 | ASYNC, SYNC(默认) |
上述的信号和参数都比较简单,与前面IDDR原语相似,不做过多解释。ODDR只有两种工作模式,相比IDDR会少一种,下文对两种模式进行讲解。
图4是OPPOSITE_EDGE模式的时序图,在时钟CLK上升沿采集D1信号D1A,并在时钟上升沿把D1A输出到OQ。然后在时钟CLK下降沿采集D2信号D2A,并在下降沿将采集到的信号输出。这种模式使用起来会相对麻烦,FPGA内部需要在时钟上升沿给D1赋值,在时钟下降沿给D2赋值,一般不使用。
图5是SAME_EDGE模式的时序图,在时钟CLK上升沿 同时采集D1、D2的数据,OQ再时钟上升沿输出采集的D1数据,再下降沿输出采集的D2数据。这种方式实现比较简单,属于常用模式。
ODDR原语的模板如下所示:
ODDR #(
.DDR_CLK_EDGE("OPPOSITE_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT(1'b0), // Initial value of Q: 1'b0 or 1'b1
.SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_inst (
.Q(Q), // 1-bit DDR output
.C(C), // 1-bit clock input
.CE(CE), // 1-bit clock enable input
.D1(D1), // 1-bit data input (positive edge)
.D2(D2), // 1-bit data input (negative edge)
.R(R), // 1-bit reset
.S(S) // 1-bit set
);
接下来对ODDR的两种工作模式进行仿真,对应的设计文件如下所示,din0在内部D触发器打一拍后得到dout0输出,din0经过OLOGIC内部的D触发器打一拍后得到dout1输出。两个单沿输入的信号din1、din2转化位单沿信号dout2,对应代码如下所示。
module oddr_ctrl(
input clk ,//系统时钟信号;
input rst ,//系统复位信号,高电平有效;
input clk_en ,//时钟使能信号;
input din0 ,//输入数据;
input din1 ,//输入数据;
input din2 ,//输入数据;
output dout0 ,//输出数据
output dout1 ,//输出数据
output dout2
);
reg dout0 ;
(* IOB = "TRUE" *)reg dout1 ;//将dout1放在ILOGICE中;
always@(posedge clk)begin
dout0 <= din0;
dout1 <= din0;
end
//例化ODDR原语
ODDR #(
.DDR_CLK_EDGE ( "OPPOSITE_EDGE" ),// "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT ( 1'b0 ),// Initial value of Q: 1'b0 or 1'b1
.SRTYPE ( "SYNC" ) // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_inst (
.Q ( dout2 ),// 1-bit DDR output
.C ( clk ),// 1-bit clock input
.CE ( clk_en ),// 1-bit clock enable input
.D1 ( din1 ),// 1-bit data input (positive edge)
.D2 ( din2 ),// 1-bit data input (negative edge)
.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;
reg clk ;//系统时钟,默认100MHz;
reg rst ;//系统复位,默认高电平有效;
reg clk_en ;
reg din0 ;
reg din1 ;
reg din2 ;
wire dout0 ;
wire dout1 ;
wire dout2 ;
oddr_ctrl u_oddr_ctrl (
.clk ( clk ),
.rst ( rst ),
.clk_en ( clk_en ),
.din0 ( din0 ),
.din1 ( din1 ),
.din2 ( din2 ),
.dout0 ( dout0 ),
.dout1 ( dout1 ),
.dout2 ( dout2 )
);
//生成周期为CYCLE数值的系统时钟;
initial begin
clk = 1;
forever #(CYCLE/2) clk = ~clk;
end
//生成复位信号;
initial begin
rst = 0;
#2;
rst = 1;//开始时复位10个时钟;
#(10*CYCLE);
rst = 0;
end
initial begin
#1;
clk_en = 1'b0;din2 = 1'b0;
din0 = 1'b0;din1 = 1'b0;
#(CYCLE*20);
clk_en = 1'b1;
#(CYCLE);
repeat(100)begin//产生100个双沿时钟数据。
#(CYCLE/2);
din0 = ({$random} % 2);
din1 = ({$random} % 2);
#(CYCLE/2);
din2 = ({$random} % 2);
end
#(CYCLE);
clk_en = 1'b0;
#(10*CYCLE);
$stop;//停止仿真;
end
endmodule
首先对OPPOSITE_EDGE模式进行仿真,对应的TestBench代码如下所示,仿真结果如图6所示。
图6仿真结果与图4的时序图一致,不做过多解释。
然后对SAME_EDGE模式进行仿真,对应的设计文件:
module oddr_ctrl(
input clk ,//系统时钟信号;
input rst ,//系统复位信号,高电平有效;
input clk_en ,//时钟使能信号;
input din0 ,//输入数据;
input din1 ,//输入数据;
input din2 ,//输入数据;
output dout0 ,//输出数据
output dout1 ,//输出数据
output dout2
);
reg dout0 ;
reg doutr ;
(* IOB = "TRUE" *)reg dout1 ;//将dout1放在ILOGICE中;
always@(posedge clk)begin
doutr <= din0;
dout0 <= doutr;
dout1 <= doutr;
end
//例化ODDR原语
ODDR #(
.DDR_CLK_EDGE ( "SAME_EDGE" ),// "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT ( 1'b0 ),// Initial value of Q: 1'b0 or 1'b1
.SRTYPE ( "SYNC" ) // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_inst (
.Q ( dout2 ),// 1-bit DDR output
.C ( clk ),// 1-bit clock input
.CE ( clk_en ),// 1-bit clock enable input
.D1 ( din1 ),// 1-bit data input (positive edge)
.D2 ( din2 ),// 1-bit data input (negative edge)
.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;
reg clk ;//系统时钟,默认100MHz;
reg rst ;//系统复位,默认高电平有效;
reg clk_en ;
reg din0 ;
reg din1 ;
reg din2 ;
wire dout0 ;
wire dout1 ;
wire dout2 ;
oddr_ctrl u_oddr_ctrl (
.clk ( clk ),
.rst ( rst ),
.clk_en ( clk_en ),
.din0 ( din0 ),
.din1 ( din1 ),
.din2 ( din2 ),
.dout0 ( dout0 ),
.dout1 ( dout1 ),
.dout2 ( dout2 )
);
//生成周期为CYCLE数值的系统时钟;
initial begin
clk = 1;
forever #(CYCLE/2) clk = ~clk;
end
//生成复位信号;
initial begin
rst = 0;
#2;
rst = 1;//开始时复位10个时钟;
#(10*CYCLE);
rst = 0;
end
initial begin
#1;
clk_en = 1'b0;din2 = 1'b0;
din0 = 1'b0;din1 = 1'b0;
#(CYCLE*20);
clk_en = 1'b1;
#(CYCLE);
repeat(100)begin//产生100个双沿时钟数据。
#(CYCLE);
din0 = ({$random} % 2);
din1 = ({$random} % 2);
din2 = ({$random} % 2);
end
#(CYCLE);
clk_en = 1'b0;
#(10*CYCLE);
$stop;//停止仿真;
end
endmodule
仿真结果如图7所示。
图7仿真结果与图5基本一致,不再过多解释。图8是该模式下时钟使能无效时仿真结果,此时输出信号将保持不变。
上述仿真均与前文理论一致,下面将工程信号引脚分配,对工程进行编译,查看走线的图。
在vivado中打开走线的方式在讲解IDDR原语时已经进行了讲解,本文不再赘述。
前文的代码中dout0与dout1的代码都相同,都是使用D触发器对din0打一拍,然后输出,通过查看dout0和dout1的寄存器位置,得到OLOGIC中组合电路和触发器功能的使用方式。图9是din0到dout0信号的走线图,红框处是寄存器所在位置,白线是信号的走线。
dout0信号是没有使用OLOGIC中的触发器和ODDR功能的,图10就是dou0信号经过OLOGIC时的路径,与前文讲解一致,直接经过组合逻辑输出。
如图11所示,是dout1信号在FPGA内部的走向,路径上又两个触发器,其中一个在OLOGIC中。
将OLOGIC放大,如图12所示,可知dout1触发器在OLOGIC中。
最后查看dout2信号的走向,如图13所示,din1和din2输入FPGA后,在OLOGIC进行单沿转双沿信号,然后通过dout2管脚输出。
将对应的OLOGIC放大,可见其实现的是ODDR功能,信号流向与前文讲解一致。
综上,OLOGIC与ILOGIC功能类似,本文主要是讲解ODDR的工作模式,并对工作模式进行仿真,同时将OLOGIC的使用方式进行讲解。掌握ODDR使用方式的同时,也知道如何使用OLOGIC中的触发器(使用IOB=TRUE原语,查看设计文件中dout1信号的定义)功能,以及OLOGIC在FPGA中的位置。
OLOGIC中的触发器相对于FPGA内部触发器更靠近管脚,并且触发器输出与IOB之间的路径是固定的,对于多bit数据输出更有利于对齐。
最后需要此工程文件的用户,在公众号后台回复“ODDR”(不包括引号)即可。