系列文章目录
LVDS学习笔记之 IDELAYE2应用及仿真
LVDS学习笔记之 ISERDESE2应用及仿真
文章目录
前言
这期的文章内容是手把手使用Xlinx SelectIO IP核创建LVDS接收模块,如果对IDELAYE2和ISERDESE2不懂的同学建议先看笔者的LVDS系列文章。
一、使用SelectIO IP核创建LVDS接收模块
使用软件版本Vivado 18.3
-
打开Vivado并创建名为LVDS_RX的工程,芯片型号可以选择你使用的芯片,笔者使用的是正点原子领航者开发板,这里选择xc7z020clg400-2芯片。
-
在IP Catalog中搜索selectIO,双击并打开配置页面。
-
本次仿真只对一对LVDS信号以SDR进行仿真,在Data Bus Setup设置如下。
本次是LVDS的读操作,因此Data Bus Direction要选择Input类型;
仿真的是单倍速率LVDS读信号,因此Data Rate选择SDR;
串行数据选择8;
I/O信号类型选择差分,标准选择LVDS25; -
在Clock Setup设置如下。
-
在Data And Clock Delay页面作如下设置。
该页面主要是设置数据延时类型,本次选择的是可加载可变延迟模式,这种方式使用最为灵活。
该页面设置会使IP核内部产生一个IDELAYE2原语,后面会介绍到,因此include DELAYCTRL复选框需要打勾
选择使能DELAY的高性能模式,减少输出抖动。 -
最后点击OK生成此IP。
二、生成的LVDS_RX模块代码详解
1.IP代码
在生成的IP中点击下三角,并打开selectio_lvds_rx_selectio_wiz代码。
代码如下:
// file: selectio_lvds_rx_selectio_wiz.v
// (c) Copyright 2009 - 2013 Xilinx, Inc. All rights reserved.
//
// This file contains confidential and proprietary information
// of Xilinx, Inc. and is protected under U.S. and
// international copyright and other intellectual property
// laws.
//
// DISCLAIMER
// This disclaimer is not a license and does not grant any
// rights to the materials distributed herewith. Except as
// otherwise provided in a valid license issued to you by
// Xilinx, and to the maximum extent permitted by applicable
// law: (1) THESE MATERIALS ARE MADE AVAILABLE "AS IS" AND
// WITH ALL FAULTS, AND XILINX HEREBY DISCLAIMS ALL WARRANTIES
// AND CONDITIONS, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING
// BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON-
// INFRINGEMENT, OR FITNESS FOR ANY PARTICULAR PURPOSE; and
// (2) Xilinx shall not be liable (whether in contract or tort,
// including negligence, or under any other theory of
// liability) for any loss or damage of any kind or nature
// related to, arising under or in connection with these
// materials, including for any direct, or any indirect,
// special, incidental, or consequential loss or damage
// (including loss of data, profits, goodwill, or any type of
// loss or damage suffered as a result of any action brought
// by a third party) even if such damage or loss was
// reasonably foreseeable or Xilinx had been advised of the
// possibility of the same.
//
// CRITICAL APPLICATIONS
// Xilinx products are not designed or intended to be fail-
// safe, or for use in any application requiring fail-safe
// performance, such as life-support or safety devices or
// systems, Class III medical devices, nuclear facilities,
// applications related to the deployment of airbags, or any
// other applications that could lead to death, personal
// injury, or severe property or environmental damage
// (individually and collectively, "Critical
// Applications"). Customer assumes the sole risk and
// liability of any use of Xilinx products in Critical
// Applications, subject only to applicable laws and
// regulations governing limitations on product liability.
//
// THIS COPYRIGHT NOTICE AND DISCLAIMER MUST BE RETAINED AS
// PART OF THIS FILE AT ALL TIMES.
//----------------------------------------------------------------------------
// User entered comments
//----------------------------------------------------------------------------
// None
//----------------------------------------------------------------------------
`timescale 1ps/1ps
module selectio_lvds_rx_selectio_wiz
// width of the data for the system
#(parameter SYS_W = 1,
// width of the data for the device
parameter DEV_W = 8)
(
// From the system into the device
input [SYS_W-1:0] data_in_from_pins_p,
input [SYS_W-1:0] data_in_from_pins_n,
output [DEV_W-1:0] data_in_to_device,
input in_delay_reset, // Active high synchronous reset for input delay
input [SYS_W -1 :0] in_delay_data_ce, // Enable signal for delay
input [SYS_W -1 :0] in_delay_data_inc, // Delay increment (high), decrement (low) signal
input [5*SYS_W -1:0] in_delay_tap_in, // Dynamically loadable delay tap value for input delay
output [5*SYS_W -1:0] in_delay_tap_out, // Delay tap value for monitoring input delay
output delay_locked, // Locked signal from IDELAYCTRL
input ref_clock, // Reference clock for IDELAYCTRL. Has to come from BUFG.
input [SYS_W-1:0] bitslip, // Bitslip module is enabled in NETWORKING mode
// User should tie it to '0' if not needed
input clk_in, // Fast clock input from PLL/MMCM
input clk_div_in, // Slow clock input from PLL/MMCM
input io_reset);
localparam num_serial_bits = DEV_W/SYS_W;
wire clock_enable = 1'b1;
// Signal declarations
------------------------------
// After the buffer
wire [SYS_W-1:0] data_in_from_pins_int;
// Between the delay and serdes
wire [SYS_W-1:0] data_in_from_pins_delay;
wire [SYS_W-1:0] in_delay_ce;
wire [SYS_W-1:0] in_delay_inc_dec;
wire [4:0] in_delay_tap_in_int[0:SYS_W - 1]; // fills in starting with 0
wire [4:0] in_delay_tap_out_int[0:SYS_W - 1]; // fills in starting with 0
wire ref_clock_bufg;
// Array to use intermediately from the serdes to the internal
// devices. bus "0" is the leftmost bus
wire [SYS_W-1:0] iserdes_q[0:13]; // fills in starting with 0
assign in_delay_ce = { in_delay_data_ce[0]};
assign in_delay_inc_dec = { in_delay_data_inc[0]};
assign in_delay_tap_in_int[0] = in_delay_tap_in[5*(0 + 1) -1:5*(0)] ;
assign in_delay_tap_out[5*(0 + 1) -1:5*(0)] = in_delay_tap_out_int[0];
// Create the clock logic
// We have multiple bits- step over every bit, instantiating the required elements
genvar pin_count;
genvar slice_count;
generate for (pin_count = 0; pin_count < SYS_W; pin_count = pin_count + 1) begin: pins
// Instantiate the buffers
------------------------------
// Instantiate a buffer for every bit of the data bus
IBUFDS
#(.DIFF_TERM ("FALSE"), // Differential termination
.IOSTANDARD ("LVDS_25"))
ibufds_inst
(.I (data_in_from_pins_p [pin_count]),
.IB (data_in_from_pins_n [pin_count]),
.O (data_in_from_pins_int[pin_count]));
// Instantiate the delay primitive
-------------------------------
(* IODELAY_GROUP = "selectio_lvds_rx_group" *)
IDELAYE2
# (
.CINVCTRL_SEL ("FALSE"), // TRUE, FALSE
.DELAY_SRC ("IDATAIN"), // IDATAIN, DATAIN
.HIGH_PERFORMANCE_MODE ("TRUE"), // TRUE, FALSE
.IDELAY_TYPE ("VAR_LOAD"), // FIXED, VARIABLE, or VAR_LOADABLE
.IDELAY_VALUE (0), // 0 to 31
.REFCLK_FREQUENCY (200.0),
.PIPE_SEL ("FALSE"),
.SIGNAL_PATTERN ("DATA")) // CLOCK, DATA
idelaye2_bus
(
.DATAOUT (data_in_from_pins_delay[pin_count]),
.DATAIN (1'b0), // Data from FPGA logic
.C (clk_div_in),
.CE (in_delay_ce[pin_count]), //(in_delay_data_ce),
.INC (in_delay_inc_dec[pin_count]), //in_delay_data_inc),
.IDATAIN (data_in_from_pins_int [pin_count]), // Driven by IOB
.LD (in_delay_reset),
.REGRST (io_reset),
.LDPIPEEN (1'b0),
.CNTVALUEIN (in_delay_tap_in_int[pin_count]), //in_delay_tap_in),
.CNTVALUEOUT (in_delay_tap_out_int[pin_count]), //in_delay_tap_out),
.CINVCTRL (1'b0)
);
// Instantiate the serdes primitive
------------------------------
// local wire only for use in this generate loop
wire cascade_shift;
wire [SYS_W-1:0] icascade1;
wire [SYS_W-1:0] icascade2;
wire clk_in_int_inv;
assign clk_in_int_inv = ~ clk_in;
// declare the iserdes
ISERDESE2
# (
.DATA_RATE ("SDR"),
.DATA_WIDTH (8),
.INTERFACE_TYPE ("NETWORKING"),
.DYN_CLKDIV_INV_EN ("FALSE"),
.DYN_CLK_INV_EN ("FALSE"),
.NUM_CE (2),
.OFB_USED ("FALSE"),
.IOBDELAY ("IFD"), // Use input at DDLY to output the data on Q
.SERDES_MODE ("MASTER"))
iserdese2_master (
.Q1 (iserdes_q[0][pin_count]),
.Q2 (iserdes_q[1][pin_count]),
.Q3 (iserdes_q[2][pin_count]),
.Q4 (iserdes_q[3][pin_count]),
.Q5 (iserdes_q[4][pin_count]),
.Q6 (iserdes_q[5][pin_count]),
.Q7 (iserdes_q[6][pin_count]),
.Q8 (iserdes_q[7][pin_count]),
.SHIFTOUT1 (),
.SHIFTOUT2 (),
.BITSLIP (bitslip[pin_count]), // 1-bit Invoke Bitslip. This can be used with any DATA_WIDTH, cascaded or not.
// The amount of BITSLIP is fixed by the DATA_WIDTH selection.
.CE1 (clock_enable), // 1-bit Clock enable input
.CE2 (clock_enable), // 1-bit Clock enable input
.CLK (clk_in), // Fast clock driven by MMCM
.CLKB (clk_in_int_inv), // Locally inverted fast
.CLKDIV (clk_div_in), // Slow clock from MMCM
.CLKDIVP (1'b0),
.D (1'b0), // 1-bit Input signal from IOB
.DDLY (data_in_from_pins_delay[pin_count]), // 1-bit Input from Input Delay component
.RST (io_reset), // 1-bit Asynchronous reset only.
.SHIFTIN1 (1'b0),
.SHIFTIN2 (1'b0),
// unused connections
.DYNCLKDIVSEL (1'b0),
.DYNCLKSEL (1'b0),
.OFB (1'b0),
.OCLK (1'b0),
.OCLKB (1'b0),
.O ()); // unregistered output of ISERDESE1
// Concatenate the serdes outputs together. Keep the timesliced
// bits together, and placing the earliest bits on the right
// ie, if data comes in 0, 1, 2, 3, 4, 5, 6, 7, ...
// the output will be 3210, 7654, ...
---------------------------------------------------------
for (slice_count = 0; slice_count < num_serial_bits; slice_count = slice_count + 1) begin: in_slices
// This places the first data in time on the right
assign data_in_to_device[slice_count] =
iserdes_q[num_serial_bits-slice_count-1];
// To place the first data in time on the left, use the
// following code, instead
// assign data_in_to_device[slice_count] =
// iserdes_q[slice_count];
end
end
endgenerate
// IDELAYCTRL is needed for calibration
(* IODELAY_GROUP = "selectio_lvds_rx_group" *)
IDELAYCTRL
delayctrl (
.RDY (delay_locked),
.REFCLK (ref_clock_bufg),
.RST (io_reset));
BUFG
ref_clk_bufg (
.I (ref_clock),
.O (ref_clock_bufg));
endmodule
2.代码详解
可以看到整个IP中使用了IBUFDS、IDELAYE2、ISERDESE2、IDELAYCTRL及BUFG原语。这时大家应该明白为什么笔者前期要作IDELAYE2和ISERDESE2两个原语的学习了吧。
下们讲解以下各个原语的功能:
IBUFDS:这个原语主要是将差分信号转成单端信号,为了降低外界的干扰,lvds采用差分传输,转换后的信号单端信号较为稳定,传给IDELAYE2进行数据延时操作。
IDELAYE2:主要是将传过来的lvds信号进行延时,能够防止在数据不稳定时采集数据,如果要详细了解其中各个端口及属性的意义,请看博主的LVDS学习笔记之 IDELAYE2应用及仿真文章。
ISERDESE2:实现串并转换,想更深入了解原语的端口及属性的意义,请看博主的LVDS学习笔记之 ISERDESE2应用及仿真文章。
IDELAYCTRL:减少过程、电压和温度变化对参考时钟的影响。
BUFG:全局始终缓存。
总的来说,该IP的运行过程为:首先通过IBUFDS将LVDS转换成单端信号,并通过IDELAYE2进行数据延时,整个延时过程由IDELAYCTRL提供参考时钟进行协助;然后将延时后稳定的数据传输给ISERDESE2进行串并转换,实现LVDS信号的输出。
三、LVDS训练的原因及自动训练思想
1.LVDS训练原因
这个是整个LVDS乃至SelectIO的重点。数据高速传输过程中,由于线的延时可能会导致时钟信号和数据信号未能同时到达,中间多多少少存在一定的时间差,在高速传输模式下,时钟周期为ns甚至ps为单位,布线的线网的延时多多少少会存在一定的差异。
大家可能会觉得,整个传输过程中LVDS信号明明就只有一对差分信号,哪里来的时钟信号。你有这个思想说明你看进去了,还记得ISERDESE2中的CLK和CLK_DIV吧,CLK_DIV就是采样的起始,CLK是采样信号。
2.LVDS训练思想
下面我将以图像的形式IDELAYE2及ISERDESE2的作用:
上图为一帧传输的过程,CLK_DIV的上升沿标志着一个采样的开始(其实是CLK_DIV上升沿后CLK的第三个上升沿才是第一个采样的开始,为了说明这里先这样讲)。然后来一个CLK,对应采集一次data的信号,这样就完成LVDS信号的采集。
但上图CLK信号与data变化过程中对其,此时采集的信号并不稳定,由于CLK信号无法移动,因此通过IDELAYE2将data信号进行延时操作,使CLK采集到数据稳定的位置。
3.LVDS自动训练思想
在训练初期,我们需要设置一个同步字,本文设置的同步字是8‘h93(8’b1001_0011)。用training_finish信号表示训练完成与否,0表示未完成训练,1表示以完成训练。训练初期,使用LVDS_TX模块往LVDS_RX模块一直发送同步字(发送8’b1001_0011),由于LVDS_TX模块尚未编写,在测试文件模拟LVDS_TX模块发送8’b1001_0011。
因此,lvds_rx_p端信号会循环1001_0011_1001_0011_1001_0011_1001_0011_1001_0011…。由于线网延时不定,以及ISERDESE2采样位置跟信号起始位置不固定,因此ISERDESE2接收的数据只有8种可能:8’b1001_0011、8’b0010_0111、8’b0100_1110、8’b1001_1100、8’b0011_1001、8’b0111_0010、8’b1110_0100、8’b1100_1001。
我们训练的中心目的就两个,一个是保证数据输出稳定,另一个是保证数据位对齐。
首先,我们得到的数据要为以上的任意一个,我们的训练才能开始。
然后,控制tap的值,寻找一个输出稳定的tap值。这个是首要考虑的问题,这里也是最重要的部分,这里弄懂了,自动训练的第一个思想就懂了,下面我用图的方式解释怎么作,为什么这样做:
分别延时tap1和tap2,有一种可能跟上图一样,延时tap1时ISERDESE2输出值为一个,延时tap2时ISERDESE2的输出值为另一个,两者不相等。可见没有我们想要的tap值,因此需要继续增加tap的值。
还有一种可能是上图这样,延时tap1和延时tap2时ISERDESE2输出值一致,为了防止tap1或者tap2在数据变化的边界,因此我们取tap的中间值作为最终的tap值。
在第一种情况之下,随着继续增大tap的值,最终会到达第二种情况。
当然,tap的值不是可以随意增大的,它的最大值为31,若tap值增大到31时,两者数据还未稳定,这就有可能是干扰太大,或者其他原因,这样的话训练会失败,此时将training_fail信号置高,通知其他模块目前lvds训练失败,好采取重新训练或者检查原因。
其次,找到合适的tap值之后,需要做的就是将位对齐,上面我也讲了,ISERDESE2输出的数据有以上8种,但只有一种8’b1001_0011是我们LVDS_TX模块发送的,因此,通过置高一个时钟的bitslip信号,使得ISERDESE2移为输出,直至输出数据与同步码相等,此时我们的训练才最终结束。
值得一提的是,并不是两次tap值取得的输出数据相等时,位对齐一定相等。为了防止干扰导致误判,此时还需要对bitslip信号计数。这里的数据是8位,也就是8次bitslip脉冲数据输出一个循环。当bitslip计数值达到16时,输出数据还未与同步码相等,这里就有可能是因为误判了,此时训练失败。
目前我是用这种方式来进行LVDS自动训练的,其思想大同小异,明白以上的过程,将对LVDS的自动训练有更深的认识。
如果你看了这段还没明白什么意思,可以私信或者留言,共同进步。
如果你看了这段对你了解LVDS自动训练思想有所启发,不妨点赞、关注、收藏一波,对博主支持下。
创作不易,谢谢大家!
四、LVDS_RX工程代码及仿真代码
1.LVDS_RX工程代码
代码框架如下图所示
clk_manager模块代码
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2021/12/30 22:31:46
// Design Name:
// Module Name: clk_manager
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module clk_manager(
input clk_in, //50M,
output clk_100m,
output clk_200m,
output clk_25m,
output reg sys_reset
);
wire pll_locked;
reg [7:0] locked_r = 8'b0;
clk_wiz_0
clk_wiz_0_inst
(
.clk_100m(clk_100m), // output clk_100m
.clk_200m(clk_200m), // output clk_200m
.clk_25m(clk_25m), // output clk_25m
.locked(pll_locked), // output locked
.clk_in(clk_in)); // input clk_in
always @ (posedge clk_100m)
begin
locked_r <= {locked_r[6:0], pll_locked};
if (&locked_r)
sys_reset <= 1'b0;
else
sys_reset <= 1'b1;
end
endmodule
lvds_rx_recv模块代码
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2021/12/30 22:32:54
// Design Name:
// Module Name: lvds_rx_recv
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module lvds_rx_recv(
input clk_100m,
input clk_200m,
input clk_25m,
input reset,
// control interface
input fifo_rden,
output [7:0] fifo_dout,
output fifo_empty,
output fifo_rd_rst_busy,
// training inteface
output delay_locked,
output [7:0] rx_dout,
input delay_ld,
input [4:0] delay_tap,
input bitslip,
input training_finish,
// LVDS interface
input lvds_rx_p,
input lvds_rx_n
);
reg fifo_wren = 1'b0;
reg io_reset = 1'b1;
wire [4:0] in_delay_tap_out;
wire [7:0] fifo_din;
wire fifo_wr_rst_busy, fifo_full;
assign rx_dout = fifo_din;
always @ (posedge clk_25m)
if ((~reset) & (~fifo_wr_rst_busy) & (~fifo_full) & training_finish)
fifo_wren <= 1'b1;
else
fifo_wren <= 1'b0;
lvds_rx_fifo
lvds_rx_fifo_inst (
.rst(reset),
.wr_clk(clk_25m),
.rd_clk(clk_100m),
.din(fifo_din),
.wr_en(fifo_wren),
.rd_en(fifo_rden),
.dout(fifo_dout),
.full(fifo_full),
.empty(fifo_empty),
.wr_rst_busy(fifo_wr_rst_busy),
.rd_rst_busy(fifo_rd_rst_busy)
);
always @ (posedge clk_25m)
if (reset)
io_reset <= 1'b1;
else
io_reset <= 1'b0;
selectio_lvds_rx
LVDS_RX_inst(
.data_in_from_pins_p(lvds_rx_p),
.data_in_from_pins_n(lvds_rx_n),
.data_in_to_device(fifo_din),
.in_delay_reset(delay_ld),
.in_delay_data_ce(1'b0),
.in_delay_data_inc(1'b0),
.in_delay_tap_in(delay_tap),
.in_delay_tap_out(in_delay_tap_out),
.delay_locked(delay_locked),
.ref_clock(clk_200m),
.bitslip(bitslip),
.clk_in(clk_200m),
.clk_div_in(clk_25m),
.io_reset(io_reset)
);
endmodule
lvds_rx_training模块代码
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2021/12/30 22:30:49
// Design Name:
// Module Name: lvds_rx_training
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module lvds_rx_training(
input clk_25m,
input reset,
input delay_locked,
input [7:0] rx_din,
output reg delay_ld,
output reg [5:0] delay_tap,
output reg bitslip,
output reg training_finish
);
reg training_start = 1'b0;
reg [7:0] rx_count = 8'b0;
reg [7:0] rx_din_last = 8'b0;
always @ (posedge clk_25m)
begin
rx_din_last <= rx_din;
if ((~training_start) && (rx_count >= 8'd10))
training_start <= 1'b1;
end
always @ (posedge clk_25m)
case (rx_din)
8'b1001_0011:
if (rx_din_last == rx_din)
rx_count <= rx_count + 1'b1;
else
rx_count <= 8'b0;
8'b0010_0111:
if (rx_din_last == rx_din)
rx_count <= rx_count + 1'b1;
else
rx_count <= 8'b0;
8'b0100_1110:
if (rx_din_last == rx_din)
rx_count <= rx_count + 1'b1;
else
rx_count <= 8'b0;
8'b1001_1100:
if (rx_din_last == rx_din)
rx_count <= rx_count + 1'b1;
else
rx_count <= 8'b0;
8'b0011_1001:
if (rx_din_last == rx_din)
rx_count <= rx_count + 1'b1;
else
rx_count <= 8'b0;
8'b0111_0010:
if (rx_din_last == rx_din)
rx_count <= rx_count + 1'b1;
else
rx_count <= 8'b0;
8'b1110_0100:
if (rx_din_last == rx_din)
rx_count <= rx_count + 1'b1;
else
rx_count <= 8'b0;
8'b1100_1001:
if (rx_din_last == rx_din)
rx_count <= rx_count + 1'b1;
else
rx_count <= 8'b0;
default:
rx_count <= 8'b0;
endcase
localparam SYNC_CODE = 8'h93;
localparam STATE_IDLE = 16'b0000_0000_0000_0001,
STATE_DELAY_SET1 = 16'b0000_0000_0000_0010,
STATE_WAIT1 = 16'b0000_0000_0000_0100,
STATE_READ1 = 16'b0000_0000_0000_1000,
STATE_DELAY_SET2 = 16'b0000_0000_0001_0000,
STATE_WAIT2 = 16'b0000_0000_0010_0000,
STATE_READ_CHK = 16'b0000_0000_0100_0000,
STATE_DELAY_SET3 = 16'b0000_0000_1000_0000,
STATE_WAIT3 = 16'b0000_0001_0000_0000,
STATE_READ2 = 16'b0000_0010_0000_0000,
STATE_BITSLIP_EN = 16'b0000_0100_0000_0000,
STATE_FINISH = 16'b0000_1000_0000_0000,
STATE_FAIL = 16'b0001_0000_0000_0000;
(*mark_debug = "true"*)reg [15:0] cstate;
reg [15:0] nstate;
reg [3:0] wait_count;
reg [7:0] bitslip_count;
reg [4:0] delay_tap_temp;
reg [7:0] rx_din_temp;
(*mark_debug = "true"*)reg training_fail;
always @ (posedge clk_25m)
if (reset)
cstate <= STATE_IDLE;
else
cstate <= nstate;
always @ (*)
case (cstate)
STATE_IDLE:
if (delay_locked & training_start)
nstate = STATE_DELAY_SET1;
else
nstate = STATE_IDLE;
STATE_DELAY_SET1:
nstate = STATE_WAIT1;
STATE_WAIT1:
if (wait_count >= 4'd10)
nstate = STATE_READ1;
else
nstate = STATE_WAIT1;
STATE_READ1:
nstate = STATE_DELAY_SET2;
STATE_DELAY_SET2:
nstate = STATE_WAIT2;
STATE_WAIT2:
if (wait_count >= 4'd10)
nstate = STATE_READ_CHK;
else
nstate = STATE_WAIT2;
STATE_READ_CHK:
if (rx_din_temp == rx_din)
nstate = STATE_DELAY_SET3;
else if(delay_tap_temp > 5'd20)
nstate = STATE_FAIL;
else
nstate = STATE_DELAY_SET1;
STATE_DELAY_SET3:
nstate = STATE_WAIT3;
STATE_WAIT3:
if (wait_count >= 4'd10)
nstate = STATE_READ2;
else
nstate = STATE_WAIT3;
STATE_READ2:
if (rx_din == SYNC_CODE)
nstate = STATE_FINISH;
else if(bitslip_count > 8'd16)
nstate = STATE_FAIL;
else
nstate = STATE_BITSLIP_EN;
STATE_BITSLIP_EN:
nstate = STATE_WAIT3;
STATE_FINISH:
nstate = STATE_FINISH;
STATE_FAIL:
nstate = STATE_IDLE;
default:
nstate = STATE_IDLE;
endcase
always @ (posedge clk_25m)
if (reset)
begin
training_finish <= 1'b0;
bitslip <= 1'b0;
delay_ld <= 1'b0;
delay_tap <= 5'b0;
wait_count <= 4'b0;
bitslip_count <= 8'b0;
delay_tap_temp <= 5'b0;
rx_din_temp <= 8'b0;
training_fail <= 1'b0;
end
else case (cstate)
STATE_IDLE:
begin
training_finish <= 1'b0;
bitslip <= 1'b0;
delay_ld <= 1'b0;
delay_tap <= 5'b0;
wait_count <= 4'b0;
bitslip_count <= 8'b0;
delay_tap_temp <= 5'b0;
rx_din_temp <= 8'b0;
training_fail <= 1'b0;
end
STATE_DELAY_SET1:
begin
training_finish <= 1'b0;
bitslip <= 1'b0;
delay_ld <= 1'b1;
delay_tap <= delay_tap_temp;
wait_count <= 4'b0;
bitslip_count <= 8'b0;
end
STATE_WAIT1:
begin
training_finish <= 1'b0;
bitslip <= 1'b0;
delay_ld <= 1'b0;
wait_count <= wait_count + 1'b1;
bitslip_count <= 8'b0;
end
STATE_READ1:
begin
training_finish <= 1'b0;
bitslip <= 1'b0;
delay_ld <= 1'b0;
wait_count <= 4'b0;
bitslip_count <= 8'b0;
rx_din_temp <= rx_din;
end
STATE_DELAY_SET2:
begin
training_finish <= 1'b0;
bitslip <= 1'b0;
delay_ld <= 1'b1;
delay_tap <= delay_tap_temp + 5'd10;
wait_count <= 4'b0;
bitslip_count <= 8'b0;
end
STATE_WAIT2:
begin
training_finish <= 1'b0;
bitslip <= 1'b0;
delay_ld <= 1'b0;
wait_count <= wait_count + 1'b1;
bitslip_count <= 8'b0;
end
STATE_READ_CHK:
begin
training_finish <= 1'b0;
bitslip <= 1'b0;
delay_ld <= 1'b0;
wait_count <= 4'b0;
bitslip_count <= 8'b0;
delay_tap_temp <= delay_tap_temp + 1'b1;
end
STATE_DELAY_SET3:
begin
training_finish <= 1'b0;
bitslip <= 1'b0;
delay_ld <= 1'b1;
wait_count <= 4'b0;
bitslip_count <= 8'b0;
delay_tap <= delay_tap_temp + 5'd4;
end
STATE_WAIT3:
begin
training_finish <= 1'b0;
bitslip <= 1'b0;
delay_ld <= 1'b0;
wait_count <= wait_count + 1'b1;
end
STATE_READ2:
begin
training_finish <= 1'b0;
bitslip <= 1'b0;
delay_ld <= 1'b0;
wait_count <= 4'b0;
end
STATE_BITSLIP_EN:
begin
training_finish <= 1'b0;
bitslip <= 1'b1;
delay_ld <= 1'b0;
wait_count <= 4'b0;
bitslip_count <= bitslip_count + 1'b1;
end
STATE_FINISH:
begin
training_finish <= 1'b1;
bitslip <= 1'b0;
delay_ld <= 1'b0;
wait_count <= 4'b0;
end
STATE_FAIL:
begin
training_finish <= 1'b0;
bitslip <= 1'b0;
delay_ld <= 1'b0;
wait_count <= 4'b0;
training_fail <= 1'b1;
end
endcase
endmodule
2.LVDS_RX仿真代码
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2021/12/30 22:54:53
// Design Name:
// Module Name: lvds_rx_top_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
`define clk_period 20
module lvds_rx_top_tb();
reg sys_clk_in ;
reg lvds_rx_p ;
reg lvds_rx_n ;
wire[7:0] recv_fifo_dout ;
lvds_rx_top
lvds_rx_top_inst(
.sys_clk_in (sys_clk_in),
.lvds_rx_p (lvds_rx_p),
.lvds_rx_n (lvds_rx_n),
.recv_fifo_dout (recv_fifo_dout)
);
initial sys_clk_in = 0;
always #(`clk_period/2) sys_clk_in = ~sys_clk_in;
initial begin
lvds_rx_p = 1'b0;
lvds_rx_n = ~lvds_rx_p;
@(posedge lvds_rx_top_inst.clk_manager_inst.pll_locked);
#(`clk_period*10);
while(lvds_rx_top_inst.training_finish == 1'b0)begin
lvds_data_gen;
end
#(`clk_period*10);
$stop;
end
task lvds_data_gen;
reg [7:0] data_send;
begin
data_send = 8'h93;
repeat(8)begin
lvds_rx_p = data_send[0];
lvds_rx_n = ~lvds_rx_p;
data_send = {1'b0,data_send[7:1]};
#(5);
end
end
endtask
endmodule
五、LVDS_RX仿真截图
1.lvds_rx_top模块仿真截图
2.lvds_rx_training模块仿真截图
六、工程文件
本工程打包后的工程文件连接:https://download.csdn.net/download/weixin_45372778/72415258
如果你觉得这篇文章对你有所帮助,给个三连支持下,创作不易。
有不懂的欢迎私信,留言。
下期开始LVDS_TX模块的学习。