本文转载自:孤独的单刀博客
1、介绍
随着电子行业技术的发展,特别是在传输接口的发展上,IEEE1284被 USB 接口取代,PATA被 SATA 取代,PCI被 PCI-Express 所取代,无一不证明了传统并行接口的速度已经达到一个瓶颈了,取而代之的是速度更快的串行接口,于是原本用于光纤通信的SerDes 技术成为了为高速串行接口的主流。串行接口主要应用了差分信号传输技术,具有功耗低、抗干扰强,速度快的特点,理论上串行接口的最高传输速率可达到10Gbps 以上。
2、理论
Xilinx的原语OSERDESE2是一种专用的并-串转换器,每个OSERDESE2模块都包括一个专用串行化程序用于数据和3状态控制。数据和3状态序列化程序都可以工作在SDR和DDR模式。数据串行化的位宽可以达到8:1(如果使用原语模块级联,则可以到10:1和14:1)。3状态序列化最高可达14:1,有一个专用的DDR3模式可用于支持高速内存应用程序。
OSERDESE2的框图如下:
OSERDESE2的端口说明如下:
需要例化的一些可用属性如下:
TRISTATE_WIDTH的选取见下表:
SDR、DDR输出模式下位宽的选取有如下限制:
输出数据会有一些时钟周期的延迟,具体见下表:
3、8位数据的并串转换
接下里例化一个原语来熟悉下用法,8位数据的并串转换,采用DDR输出。
官方手册的8位DDR转化时序图如下:
例化原语OSERDESE2(打开VIvado--Tools--Language Templates,搜索“OSERDESE2”,可以找到Xilinx提供的模板),与DDR、3态控制相关的端口,输入全设置为0,输出不关心,编写Verilog如下:
//------------------------------------------------------------------------
//--OSERDESE2测试模块
//------------------------------------------------------------------------
//------------<模块及端口声明>--------------------------------------------
module serializer(
input clk_ser , //串行输出时钟,50M*4=200M
input clk_per , //并行输入时钟,50M
input rst_n , //复位信号,低电平有效
input [7:0] par_data , //并行输入数据
output ser_data //串行输出数据
);
//------------<例化原语>---------------------------------------------------
OSERDESE2 #(
.DATA_RATE_OQ ("DDR") , // DDR, SDR
.DATA_RATE_TQ ("SDR") , // DDR, BUF, SDR
.DATA_WIDTH (8) , // Parallel data width (2-8,10,14)
.INIT_OQ (1'b0) , // Initial value of OQ output (1'b0,1'b1)
.INIT_TQ (1'b0) , // Initial value of TQ output (1'b0,1'b1)
.SERDES_MODE ("MASTER") , // MASTER, SLAVE
.SRVAL_OQ (1'b0) , // OQ output value when SR is used (1'b0,1'b1)
.SRVAL_TQ (1'b0) , // TQ output value when SR is used (1'b0,1'b1)
.TBYTE_CTL ("FALSE") , // Enable tristate byte operation (FALSE, TRUE)
.TBYTE_SRC ("FALSE") , // Tristate byte source (FALSE, TRUE)
.TRISTATE_WIDTH (1) // 3-state converter width (1,4)
)
OSERDESE2_inst (
.OFB () , // 1-bit output: Feedback path for data
.OQ (ser_data) , // 1-bit output: Data path output
.SHIFTOUT1 () ,
.SHIFTOUT2 () ,
.TBYTEOUT () , // 1-bit output: Byte group tristate
.TFB () , // 1-bit output: 3-state control
.TQ () , // 1-bit output: 3-state control
.CLK (clk_ser) , // 1-bit input: High speed clock
.CLKDIV (clk_per) , // 1-bit input: Divided clock
.D1 (par_data[0]) ,
.D2 (par_data[1]) ,
.D3 (par_data[2]) ,
.D4 (par_data[3]) ,
.D5 (par_data[4]) ,
.D6 (par_data[5]) ,
.D7 (par_data[6]) ,
.D8 (par_data[7]) ,
.OCE (1'b1) , // 1-bit input: Output data clock enable
.RST (~rst_n) , // 1-bit input: Reset
.SHIFTIN1 () ,
.SHIFTIN2 () ,
.T1 (1'b0) ,
.T2 (1'b0) ,
.T3 (1'b0) ,
.T4 (1'b0) ,
.TBYTEIN (1'b0) , // 1-bit input: Byte group tristate
.TCE (1'b0) // 1-bit input: 3-state clock enable
);
endmodule
每隔20ns随机生成1个8位2进制数据作为并行输入,观察串行输出,Testbench如下:
//------------------------------------------------
//--OSERDESE2原语仿真
//------------------------------------------------
`timescale 1ns / 1ps //时间单位/精度
//------------<模块及端口声明>----------------------------------------
module tb_serializer();
reg clk_per ;
reg clk_ser ;
reg rst_n ;
reg [7:0] par_data ;
wire ser_data ;
//------------<设置初始测试条件>----------------------------------------
initial begin
clk_per <= 1'b0;
clk_ser <= 1'b1;
rst_n <= 1'b0;
par_data <= 8'd0;
#180
rst_n <= 1'b1;
end
//------------<设置时钟>----------------------------------------------
always #10 clk_per = ~clk_per;
always #2.5 clk_ser = ~clk_ser;
always #20 par_data <= $random % 256; //每20ns随机生成一个8位数据用于并行输入
//------------<例化被测试模块>----------------------------------------
serializer serializer_inst(
.clk_per (clk_per) ,
.clk_ser (clk_ser) ,
.rst_n (rst_n) ,
.par_data (par_data) ,
.ser_data (ser_data)
);
endmodule
仿真结果如下:
可以看出:
在第1条蓝线处,是复位后并行时钟的第1个上升沿,此时采集到的数据为0000_0001
在第2条蓝线处,也就是4个串行时钟的延迟后,串行输出开始有数据,分别为1-0-0-0-0-0-0-0,可以看出是第1个上升沿采集到的数据输出(0000_0001从低位往高位输出)
在第2条蓝线处,是复位后并行时钟的第2个上升沿,此时采集到的数据为0000_1101
在第3条蓝线处,也就是4个串行时钟的延迟后,串行输出开始有数据,分别为1-0-1-1-0-0-0-0,可以看出是第2个上升沿采集到的数据输出(0000_1101从低位往高位输出)
其他与上述相同,符合官方给出的时序图
4、10位数据的并串转换(级联)
OSERDESE2原语还支持例化两次原语级联,以便实现10位、14位位宽的串行化转换。下图是10位位宽的级联框图,其中一个设置位MASTER,另一个设置为SLAVE,通过SHIFTIN与SHIFTOUT连接。
需要注意:数据的输出从MASTER输出,数据的高位输入到SLAVE模块时,需要从D3开始(只能使用D3~D8)
接下里例化一个原语来实现10位数据的并串转换,采用DDR输出。
例化2个原语OSERDESE2级联(打开VIvado--Tools--Language Templates,搜索“OSERDESE2”,可以找到Xilinx提供的模板),与DDR、3态控制相关的端口,输入全设置为0,输出不关心,编写Verilog如下:
//------------------------------------------------------------------------
//--OSERDESE2测试模块
//------------------------------------------------------------------------
//------------<模块及端口声明>--------------------------------------------
module serializer(
input clk_ser , //串行输出时钟,50M*5=250M
input clk_per , //并行输入时钟,50M
input rst_n , //复位信号,低电平有效
input [9:0] par_data , //并行输入数据,位宽10
output ser_data //串行输出数据
);
//------------
wire shift1; //级联线1
wire shift2; //级联线2
//------------<例化原语>------------------------------------------------------
//例化主模块MASTER
OSERDESE2 #(
.DATA_RATE_OQ ("DDR") , // DDR, SDR
.DATA_RATE_TQ ("SDR") , // DDR, BUF, SDR
.DATA_WIDTH (10) , // Parallel data width (2-8,10,14)
.INIT_OQ (1'b0) , // Initial value of OQ output (1'b0,1'b1)
.INIT_TQ (1'b0) , // Initial value of TQ output (1'b0,1'b1)
.SERDES_MODE ("MASTER") , // MASTER, SLAVE
.SRVAL_OQ (1'b0) , // OQ output value when SR is used (1'b0,1'b1)
.SRVAL_TQ (1'b0) , // TQ output value when SR is used (1'b0,1'b1)
.TBYTE_CTL ("FALSE") , // Enable tristate byte operation (FALSE, TRUE)
.TBYTE_SRC ("FALSE") , // Tristate byte source (FALSE, TRUE)
.TRISTATE_WIDTH (1) // 3-state converter width (1,4)
)
OSERDESE2_inst1 (
.OFB () , // 1-bit output: Feedback path for data
.OQ (ser_data) , // 1-bit output: Data path output
.SHIFTOUT1 () ,
.SHIFTOUT2 () ,
.TBYTEOUT () , // 1-bit output: Byte group tristate
.TFB () , // 1-bit output: 3-state control
.TQ () , // 1-bit output: 3-state control
.CLK (clk_ser) , // 1-bit input: High speed clock
.CLKDIV (clk_per) , // 1-bit input: Divided clock
.D1 (par_data[0]) ,
.D2 (par_data[1]) ,
.D3 (par_data[2]) ,
.D4 (par_data[3]) ,
.D5 (par_data[4]) ,
.D6 (par_data[5]) ,
.D7 (par_data[6]) ,
.D8 (par_data[7]) ,
.OCE (1'b1) , // 1-bit input: Output data clock enable
.RST (~rst_n) , // 1-bit input: Reset
.SHIFTIN1 (shift1) ,
.SHIFTIN2 (shift2) ,
.T1 (1'b0) ,
.T2 (1'b0) ,
.T3 (1'b0) ,
.T4 (1'b0) ,
.TBYTEIN (1'b0) , // 1-bit input: Byte group tristate
.TCE (1'b0) // 1-bit input: 3-state clock enable
);
//例化从模块SLAVE
OSERDESE2 #(
.DATA_RATE_OQ ("DDR") , // DDR, SDR
.DATA_RATE_TQ ("SDR") , // DDR, BUF, SDR
.DATA_WIDTH (10) , // Parallel data width (2-8,10,14)
.INIT_OQ (1'b0) , // Initial value of OQ output (1'b0,1'b1)
.INIT_TQ (1'b0) , // Initial value of TQ output (1'b0,1'b1)
.SERDES_MODE ("SLAVE") , // MASTER, SLAVE
.SRVAL_OQ (1'b0) , // OQ output value when SR is used (1'b0,1'b1)
.SRVAL_TQ (1'b0) , // TQ output value when SR is used (1'b0,1'b1)
.TBYTE_CTL ("FALSE") , // Enable tristate byte operation (FALSE, TRUE)
.TBYTE_SRC ("FALSE") , // Tristate byte source (FALSE, TRUE)
.TRISTATE_WIDTH (1) // 3-state converter width (1,4)
)
OSERDESE2_inst2 (
.OFB () , // 1-bit output: Feedback path for data
.OQ () , // 1-bit output: Data path output
.SHIFTOUT1 (shift1) ,
.SHIFTOUT2 (shift2) ,
.TBYTEOUT () , // 1-bit output: Byte group tristate
.TFB () , // 1-bit output: 3-state control
.TQ () , // 1-bit output: 3-state control
.CLK (clk_ser) , // 1-bit input: High speed clock
.CLKDIV (clk_per) , // 1-bit input: Divided clock
.D1 () ,
.D2 () ,
.D3 (par_data[8]) ,
.D4 (par_data[9]) ,
.D5 () ,
.D6 () ,
.D7 () ,
.D8 () ,
.OCE (1'b1) , // 1-bit input: Output data clock enable
.RST (~rst_n) , // 1-bit input: Reset
.SHIFTIN1 () ,
.SHIFTIN2 () ,
.T1 (1'b0) ,
.T2 (1'b0) ,
.T3 (1'b0) ,
.T4 (1'b0) ,
.TBYTEIN (1'b0) , // 1-bit input: Byte group tristate
.TCE (1'b0) // 1-bit input: 3-state clock enable
);
endmodule
每隔20ns随机生成1个10位2进制数据作为并行输入,观察串行输出,Testbench如下:
//------------------------------------------------
//--OSERDESE2原语仿真
//------------------------------------------------
`timescale 1ns / 1ps //时间单位/精度
//------------<模块及端口声明>----------------------------------------
module tb_serializer();
reg clk_per ;
reg clk_ser ;
reg rst_n ;
reg [9:0] par_data ;
wire ser_data ;
//------------<设置初始测试条件>----------------------------------------
initial begin
clk_per <= 1'b0;
clk_ser <= 1'b0;
rst_n <= 1'b0;
par_data <= 10'd0;
#180
rst_n <= 1'b1;
end
//------------<设置时钟>----------------------------------------------
always #10 clk_per = ~clk_per;
always #2 clk_ser = ~clk_ser;
always #20 par_data <= $random % 1024; //每20ns随机生成一个10位数据用于并行输入
//------------<例化被测试模块>----------------------------------------
serializer serializer_inst(
.clk_per (clk_per) ,
.clk_ser (clk_ser) ,
.rst_n (rst_n) ,
.par_data (par_data) ,
.ser_data (ser_data)
);
endmodule
仿真结果如下:
可以看出:
在第1条蓝线处,是复位后并行时钟的第1个上升沿,此时采集到的数据为11000_00001
在第2条蓝线处,也就是4个串行时钟的延迟后,串行输出开始有数据,分别为1-0-0-0-0-0-0-0-1-1,可以看出是第1个上升沿采集到的数据输出(11000_00001从低位往高位输出)
在第2条蓝线处,是复位后并行时钟的第2个上升沿,此时采集到的数据为01000_01101
在第3条蓝线处,也就是4个串行时钟的延迟后,串行输出开始有数据,分别为1-0-1-1-0-0-0-0-1-0,可以看出是第2个上升沿采集到的数据输出(01000_01101从低位往高位输出)
其他与上述相同。
这里官方手册没有给出10bitDDR输出的时序图,输出延迟给出的表如下图:
可以看到这里10:1的输出延迟应该是5个时钟周期,但是我上面仿真的却是4个时钟周期,一开始我还以为是哪里错了,搞得我重复仿真了好几遍,后面又看了一下手册,终于在延迟表的下面发现了这句话(上图标红):CLK、CLKDIV的时钟沿通常不是相位一致的。当这两个时钟的时钟沿相位一致时,延迟会存在一个周期的差异。
这样的话,仿真结果应该是没有问题。
5、参考
7 Series FPGAs SelectIO Resources User Guide(UG471)--Xilinx