需求
最近在调索尼的IMX183传感器,使用的是LVDS格式传输,一共6个LVDS通道,每个通道串行输出12比特的像素信息。
我一看,这不直接级联两个6:1的ISERDES2就行了吗?然而在查看官方文档以后,却发现ISERDES2级联模式下,可以支持10、14比特模式,但却不支持12比特模式。
其实本来不支持也没啥大问题,我可以用4:1或者6:1的模式,配合移位寄存器,同样能够完成传感器数据接收。具体可见文章:在Lattice上实现IMX178传感器12比特接收
用4:1解串比+移位寄存器滑窗完成12比特传感器接收
可能有人会问为啥这么执着要ISERDES2工作在12:1模式下,你不是都说用4:1或者6:1也能完成任务吗?
主要还是因为12:1解串后,时钟速度大幅降低为源时钟的1/6,对时序有好处。另外后端接收、解码逻辑相比于4:1和6:1都能简化(省去了滑窗寄存器和计数器)
当然还有一点:FPGA在我手上,怎么用这玩意就由不得厂商了,走歪门邪道也得强行支持12:1😡。
Google一下相关问题,发现早在10年前就有人提出,并且还真给这个限制绕过去了。所以本文不是原创方法,只是将前人的方法重新讲述一遍。
原文章:12bit SERDES with ISERDESE2 (7series) possible?
思路
整体思路其实很简单,如下图所示:
12:1的DDR模式 = 两个6:1的SDR模式,一个在上升沿时采样,另一个在下降沿时采样。
不过在实现中,还是有一些地方需要注意,从图里就能看出来:
-
LVDS的差分信号在FPGA内部并没有转化成单端信号后同时分配给两个ISERDES,而是经过差分缓冲器,正/负信号各自进入一个ISERDES,其中接入了负信号的ISERDES在输出端需要加反相器才能得到正确结果。
-
驱动ISERDES的时钟由MMCM或者PLL生成。
这一通操作下来看的确实让人迷糊,为啥要整这么麻烦呢?其实这一切都是为了绕过vivado限制,原帖中也进行了相应的解释。
第一点:如果使用IBUFDS将差分信号转成单端,那么这个信号只能由一个ISERDES独享,不能接入另一个ISERDES,而使用上面的方法就没这个限制。
另外,反相器不能在ISERDES输入端插入(解串前),因为此时数据速率很高,插入反相器会引入skew(歪斜),采样时机就会出问题。解串后插入反相器也会造成歪斜,但此时时钟速度已经降低,造成的影响不大。
第二点:可能有些人会用DCK转单端后直接驱动ISERDES,同时用BUFR从DCK分频CLKDIV出来,但BUFR的问题在于其相位不可控,如果系统发生干扰或者复位,BUFR分频出来的时钟相位可能发生错位,造成不良影响。
使用MMCM/PLL生成ISERDES采样时钟是官方推荐方法,这样生成出来的时钟更加稳定可控。将DCK作为参考时钟,生成一个与DCK同频的采样时钟CLK,低频数据时钟CLKDIV(频率:DCK = CLK = 6 * CLKDIV,相位:三者同相,不然PLL就不叫PLL了)
理论存在,实践开始
实践
根据上面的框图,可以写出如下代码:
`timescale 1ns / 1ps
module iserdes_12_to_1(
input i_clk_highspeed,
input i_clk_div,
input i_rst,
input i_din_p,
input i_din_n,
input i_bitslip,
output [11:0] o_dout
);
//....完整源码在文末直接复制即可,放这里太影响观感
这个就是单个通道的12:1 LVDS解串代码,7系列FPGA可以直接拿去用。多个通道直接例化多个即可。
时钟:在外面用MMCM/PLL的IP生成,其中i_clk_highspeed是LVDS采样信号,i_clk_div是解串后的数据时钟信号。参考时钟是传感器发来的DCK
剩下的信号应该不用过多介绍了,bitslip功能也是能够正常工作的。
需要注意的一点,在原帖中也提到了:在上板使用时,DYNCLKDIVSEL和DYNCLKSEL这两个选项需要打开,并做对应调整。而仿真时需要关掉才能得到正确结果。
DYNCLKDIVSEL和DYNCLKSEL是控制时钟极性的,可能有眼尖的观众已经发现了,为什么在ISERDESE2_even中,我已经给输入时钟用“~”取反了,但又打开时钟极性翻转,这样一来不就等于没有翻转吗?
欸嘿,其实我也不懂我为啥要这样干,但只有这样子调才能得到正确的数据。只能说,这种Hack有时候确实就是比较玄学,It just works。
总之就是如果你得到的结果不正确,记得多调调这些选项。
测试
除了这个12:1 ISERDES模块,我还编写了一个上电自动校准字对其的模块,将这两个模块搭配使用,就可以保证与传感器的通信在上电时自动校准,并且运行过程中不会失去锁定。
下面是测试结果:
中间三个数据包是传感器6条LVDS通道发来的行结束码,并且在任何时候采集数据包的字节都是正确对齐的。长时间运行(指24小时)也没有出现问题。
并且时序完全符合要求(已进行时序约束)
可选改进
本文相对于原教程其实省略了IDELAY这个组件,因为传感器速率不高,并且PCB设计时严格进行了等长,从物理上保证所有通道相对于时钟的偏斜都在要求范围内。
如果有需要,可以在IBUFDS_DIFF_OUT缓冲与ISERDES模块间插入IDELAY原语即可。
源码
`timescale 1ns / 1ps
module iserdes_12_to_1(
input i_clk_highspeed,
input i_clk_div,
input i_rst,
input i_din_p,
input i_din_n,
input i_bitslip,
output [11:0] o_dout
);
wire data_in_buffered_p;
wire data_in_buffered_n;
IBUFDS_DIFF_OUT #(
.DIFF_TERM("FALSE"), // Differential Termination, "TRUE"/"FALSE"
.IBUF_LOW_PWR("FALSE"), // Low power="TRUE", Highest performance="FALSE"
.IOSTANDARD("LVDS_25") // Specify the input I/O standard
) IBUFDS_DIFF_OUT_inst (
.O(data_in_buffered_p), // Buffer diff_p output
.OB(data_in_buffered_n), // Buffer diff_n output
.I(i_din_p), // Diff_p buffer input (connect directly to top-level port)
.IB(i_din_n) // Diff_n buffer input (connect directly to top-level port)
);
wire [5:0] data_output_odd;
wire [5:0] data_output_even;
assign o_dout = {data_output_odd[5], ~data_output_even[5], data_output_odd[4], ~data_output_even[4], data_output_odd[3], ~data_output_even[3],
data_output_odd[2], ~data_output_even[2], data_output_odd[1], ~data_output_even[1], data_output_odd[0], ~data_output_even[0]};
ISERDESE2 #(
.DATA_RATE("SDR"), // DDR, SDR
.DATA_WIDTH(6), // Parallel data width (2-8,10,14)
.DYN_CLKDIV_INV_EN("TRUE"), // Enable DYNCLKDIVINVSEL inversion (FALSE, TRUE)
.DYN_CLK_INV_EN("TRUE"), // Enable DYNCLKINVSEL inversion (FALSE, TRUE)
// INIT_Q1 - INIT_Q4: Initial value on the Q outputs (0/1)
.INIT_Q1(1'b0),
.INIT_Q2(1'b0),
.INIT_Q3(1'b0),
.INIT_Q4(1'b0),
.INTERFACE_TYPE("NETWORKING"), // MEMORY, MEMORY_DDR3, MEMORY_QDR, NETWORKING, OVERSAMPLE
.IOBDELAY("NONE"), // NONE, BOTH, IBUF, IFD
.NUM_CE(1), // Number of clock enables (1,2)
.OFB_USED("FALSE"), // Select OFB path (FALSE, TRUE)
.SERDES_MODE("MASTER"), // MASTER, SLAVE
// SRVAL_Q1 - SRVAL_Q4: Q output values when SR is used (0/1)
.SRVAL_Q1(1'b0),
.SRVAL_Q2(1'b0),
.SRVAL_Q3(1'b0),
.SRVAL_Q4(1'b0)
)
ISERDESE2_odd (
.O(), // 1-bit output: Combinatorial output
// Q1 - Q8: 1-bit (each) output: Registered data outputs
.Q1(data_output_odd[0]),
.Q2(data_output_odd[1]),
.Q3(data_output_odd[2]),
.Q4(data_output_odd[3]),
.Q5(data_output_odd[4]),
.Q6(data_output_odd[5]),
.Q7(),
.Q8(),
// SHIFTOUT1, SHIFTOUT2: 1-bit (each) output: Data width expansion output ports
.SHIFTOUT1(),
.SHIFTOUT2(),
.BITSLIP(i_bitslip), // 1-bit input: The BITSLIP pin performs a Bitslip operation synchronous to
// CLKDIV when asserted (active High). Subsequently, the data seen on the Q1
// to Q8 output ports will shift, as in a barrel-shifter operation, one
// position every time Bitslip is invoked (DDR operation is different from
// SDR).
// CE1, CE2: 1-bit (each) input: Data register clock enable inputs
.CE1(1'b1),
.CE2(1'b1),
.CLKDIVP(1'b0), // 1-bit input: TBD
// Clocks: 1-bit (each) input: ISERDESE2 clock input ports
.CLK(i_clk_highspeed), // 1-bit input: High-speed clock
.CLKB(~i_clk_highspeed), // 1-bit input: High-speed secondary clock
.CLKDIV(i_clk_div), // 1-bit input: Divided clock
.OCLK(1'b0), // 1-bit input: High speed output clock used when INTERFACE_TYPE="MEMORY"
// Dynamic Clock Inversions: 1-bit (each) input: Dynamic clock inversion pins to switch clock polarity
.DYNCLKDIVSEL(1'b0), // 1-bit input: Dynamic CLKDIV inversion
.DYNCLKSEL(1'b0), // 1-bit input: Dynamic CLK/CLKB inversion
// Input Data: 1-bit (each) input: ISERDESE2 data input ports
.D(data_in_buffered_p), // 1-bit input: Data input
.DDLY(1'b0), // 1-bit input: Serial data from IDELAYE2
.OFB(1'b0), // 1-bit input: Data feedback from OSERDESE2
.OCLKB(1'b0), // 1-bit input: High speed negative edge output clock
.RST(i_rst), // 1-bit input: Active high asynchronous reset
// SHIFTIN1, SHIFTIN2: 1-bit (each) input: Data width expansion input ports
.SHIFTIN1(1'b0),
.SHIFTIN2(1'b0)
);
ISERDESE2 #(
.DATA_RATE("SDR"), // DDR, SDR
.DATA_WIDTH(6), // Parallel data width (2-8,10,14)
.DYN_CLKDIV_INV_EN("TRUE"), // Enable DYNCLKDIVINVSEL inversion (FALSE, TRUE)
.DYN_CLK_INV_EN("TRUE"), // Enable DYNCLKINVSEL inversion (FALSE, TRUE)
// INIT_Q1 - INIT_Q4: Initial value on the Q outputs (0/1)
.INIT_Q1(1'b0),
.INIT_Q2(1'b0),
.INIT_Q3(1'b0),
.INIT_Q4(1'b0),
.INTERFACE_TYPE("NETWORKING"), // MEMORY, MEMORY_DDR3, MEMORY_QDR, NETWORKING, OVERSAMPLE
.IOBDELAY("NONE"), // NONE, BOTH, IBUF, IFD
.NUM_CE(1), // Number of clock enables (1,2)
.OFB_USED("FALSE"), // Select OFB path (FALSE, TRUE)
.SERDES_MODE("MASTER"), // MASTER, SLAVE
// SRVAL_Q1 - SRVAL_Q4: Q output values when SR is used (0/1)
.SRVAL_Q1(1'b0),
.SRVAL_Q2(1'b0),
.SRVAL_Q3(1'b0),
.SRVAL_Q4(1'b0)
)
ISERDESE2_even (
.O(), // 1-bit output: Combinatorial output
// Q1 - Q8: 1-bit (each) output: Registered data outputs
.Q1(data_output_even[0]),
.Q2(data_output_even[1]),
.Q3(data_output_even[2]),
.Q4(data_output_even[3]),
.Q5(data_output_even[4]),
.Q6(data_output_even[5]),
.Q7(),
.Q8(),
// SHIFTOUT1, SHIFTOUT2: 1-bit (each) output: Data width expansion output ports
.SHIFTOUT1(),
.SHIFTOUT2(),
.BITSLIP(i_bitslip), // 1-bit input: The BITSLIP pin performs a Bitslip operation synchronous to
// CLKDIV when asserted (active High). Subsequently, the data seen on the Q1
// to Q8 output ports will shift, as in a barrel-shifter operation, one
// position every time Bitslip is invoked (DDR operation is different from
// SDR).
// CE1, CE2: 1-bit (each) input: Data register clock enable inputs
.CE1(1'b1),
.CE2(1'b1),
.CLKDIVP(1'b0), // 1-bit input: TBD
// Clocks: 1-bit (each) input: ISERDESE2 clock input ports
.CLK(~i_clk_highspeed), // 1-bit input: High-speed clock
.CLKB(i_clk_highspeed), // 1-bit input: High-speed secondary clock
.CLKDIV(i_clk_div), // 1-bit input: Divided clock
.OCLK(1'b0), // 1-bit input: High speed output clock used when INTERFACE_TYPE="MEMORY"
// Dynamic Clock Inversions: 1-bit (each) input: Dynamic clock inversion pins to switch clock polarity
.DYNCLKDIVSEL(1'b0), // 1-bit input: Dynamic CLKDIV inversion
.DYNCLKSEL(1'b1), // 1-bit input: Dynamic CLK/CLKB inversion
// Input Data: 1-bit (each) input: ISERDESE2 data input ports
.D(data_in_buffered_n), // 1-bit input: Data input
.DDLY(1'b0), // 1-bit input: Serial data from IDELAYE2
.OFB(1'b0), // 1-bit input: Data feedback from OSERDESE2
.OCLKB(1'b0), // 1-bit input: High speed negative edge output clock
.RST(i_rst), // 1-bit input: Active high asynchronous reset
// SHIFTIN1, SHIFTIN2: 1-bit (each) input: Data width expansion input ports
.SHIFTIN1(1'b0),
.SHIFTIN2(1'b0)
);
endmodule