在-01-OV7251摄像头与设计规划【Xilinx-LVDS读写功能实现】中将设计分为了几个步骤,下面将介绍OV7251 LVDS信号模拟输出功能的逻辑设计 。
模拟摄像头的输出信号主要是为了方便前期测试和验证,而且本身SerDes的输出信号设计相对输入来说,复杂度要低很多。就按照前一节有提到的OV7251摄像头的时序来模拟出视频信号输出的信号。如下图:
输出模块分为以下两个部分:
- OV7251 LVDS Output Simulation 负责将并行的视频输出信号转化为串行的高速LVDS信号
- Video Test Pattern 负责模拟生成出一个动态的视频数据流
对外的接口:
module video_pattern_gen(
input reset,
input pixel_clk,
input [9:0] pixel_data,
input pattern_gen_enable,
input ext_frame_sync,
output reg [9:0] data_send
);
主要的几个信号,
pixel_clk:像素时钟60MHz
pattern_gen_enable:模拟视频输出使能
ext_frame_sync:帧同步
data_send:像素灰度/同步码数据
因为最后模拟出的视频输出信号是内部包含的高速串行数据,所以Video Test Pattern这个模块不需要生成额外的场、行同步和消隐信号线,所有的SAV,EAV,dummy都包含在了data_send的10位并行数据当中。
在模块中,使用随时间渐变的灰度图块来模拟动态视频。
对外接口:
module LVDS_OUT_DDR(
//system clock and reset input
input sys_clock,
input sys_rst,
//lvds output signals
output clkout_p,
output clkout_n,
output dataout_p,
output dataout_n,
//enable pattern generate
input pattern_gen_enable,
input ext_frame_sync,
output tx_clk, //600MHz/10=60MHz clock frequency
output [9:0] tx_dataout,
input [9:0] tx_datain, //CCIR656 standard input 3FF 000 000 SAV ---- 3FF 000 000 EAV
output sys_clock_200ref
);
首先使用MMCM将输入的100MHz时钟进行倍频和分频处理,分别得到了LVDS串行输出用的的300MHz时钟,每像素数据用到的60MHz时钟,IDELAYCTRL用到的200MHz参考时钟。因为相机的LVDS数据与时钟是DDR传输方式,所以10bit的串行数据,时钟的频率将会是像素时钟频率的10/2=5倍,300MHz = 5 * 60MHz。
针对前一步中生成的data_send 10bit并行数据,使用两个OSERDESE2级联实现10bit数据的串行输出。其中边沿模式配置为DDR模式。OSERDESE2的输出,通过OBUFDS实现单端到差分信号的转换。
代码:
`timescale 1ps / 1ps
module video_pattern_gen(
input reset,
input pixel_clk,
input [9:0] pixel_data,
input pattern_gen_enable,
input ext_frame_sync,
output reg [9:0] data_send
);
reg [9:0] pattern_data;
always@(posedge pixel_clk, posedge reset)
begin
if(reset == 1'b1) begin
data_send <= 10'd0;
end
else begin
data_send <= (pattern_gen_enable == 1'b1)? pattern_data: pixel_data;
end
end
//pattern data generate
//sync the frame data with input signal: ext_frame_sync
parameter PATTERN_H_ACTIVE_SIZE = 10'd640;
parameter PATTERN_H_BLANK_SIZE = 10'd280;
parameter PATTERN_V_ACTIVE_SIZE = 10'd480;
parameter PATTERN_V_BLANK_SIZE = 10'd36;
reg [9:0] pixel_data_add_reg;
reg [1:0] fsync_edge_buf;
always @(posedge pixel_clk, posedge reset)
begin
if(reset == 1'b1) begin
pixel_data_add_reg <= 10'h00;
fsync_edge_buf <= 2'b00;
end
else begin
fsync_edge_buf <= {fsync_edge_buf[0],ext_frame_sync};
if(fsync_edge_buf == 2'b01) begin
pixel_data_add_reg <= pixel_data_add_reg + 10'h8;
end
end
end
localparam [10:0] SYNC_CODE_SAV_ACTIVE = {10'h200},
SYNC_CODE_EAV_ACTIVE = {10'h240},
SYNC_CODE_SAV_BLANK = {10'h2AC},
SYNC_CODE_EAV_BLANK = {10'h2D8},
DUMMY_DATA0 = {10'h200},
DUMMY_DATA1 = {10'h040};
reg [3:0] pattern_state;
reg [9:0] h_count;
reg [9:0] v_count;
always @(posedge pixel_clk, posedge reset)
begin
if(reset == 1'b1) begin
h_count <= 10'd0;
v_count <= 10'd0;
pattern_state <= 4'd0;
pattern_data <= 10'd0;
end
else begin
case(pattern_state)
4'd0: begin
if(pattern_gen_enable == 1'b1) begin
pattern_state <= 4'd1;
h_count <= 10'd0;
v_count <= 10'd0;
pattern_data <= 10'd0;
end
end
4'd1: begin
h_count <= 10'd0;
v_count <= 10'd0;
pattern_data <= 10'd0;
if(ext_frame_sync == 1'b1) begin
pattern_state <= 4'd2;
end
end
4'd2: begin //SAV
h_count <= h_count + 10'd1;
if(h_count >= 10'd927) begin
h_count <= 10'd0;
v_count <= v_count + 10'd1;
if(v_count >= 10'd515) begin
v_count <= 10'd0;
pattern_state <= 4'd1;
end
end
if(v_count < 10'd480) begin
case(h_count)
10'd0, 10'd644: pattern_data <= 10'h3FF;
10'd1, 10'd2, 10'd645, 10'd646: pattern_data <= 10'h000;
10'd3: pattern_data <= SYNC_CODE_SAV_ACTIVE;
10'd647: pattern_data <= SYNC_CODE_EAV_ACTIVE;
default: begin
if(h_count >= 10'd648) begin
pattern_data <= (h_count[0]==1'b0)? DUMMY_DATA0: DUMMY_DATA1;
end
else begin
if((h_count-10'd4) < 10'd128) begin
pattern_data <= 10'h3FF + pixel_data_add_reg;
end
else if((h_count-10'd4) < 10'd256) begin
pattern_data <= 10'h37F + pixel_data_add_reg;
end
else if((h_count-10'd4) < 10'd384) begin
pattern_data <= 10'h2FF + pixel_data_add_reg;
end
else if((h_count-10'd4) < 10'd512) begin
pattern_data <= 10'h27F + pixel_data_add_reg;
end
else begin
pattern_data <= 10'h1FF + pixel_data_add_reg;
end
end
end
endcase
end
else if(v_count == 10'd480) begin
case(h_count)
10'd0: pattern_data <= 10'h3FF;
10'd1, 10'd2: pattern_data <= 10'h000;
10'd3: pattern_data <= SYNC_CODE_SAV_BLANK;
default: begin
pattern_data <= (h_count[0]==1'b0)? DUMMY_DATA0: DUMMY_DATA1;
end
endcase
end
else if(v_count < 10'd515) begin
pattern_data <= (h_count[0]==1'b0)? DUMMY_DATA0: DUMMY_DATA1;
end
else if(v_count == 10'd515) begin
case(h_count)
10'd644: pattern_data <= 10'h3FF;
10'd645, 10'd646: pattern_data <= 10'h000;
10'd647: pattern_data <= SYNC_CODE_EAV_BLANK;
default: begin
pattern_data <= (h_count[0]==1'b0)? DUMMY_DATA0: DUMMY_DATA1;
end
endcase
end
end
endcase
end
end
endmodule
`timescale 1ps / 1ps
module LVDS_OUT_DDR(
//system clock and reset input
input sys_clock,
input sys_rst,
//lvds output signals
output clkout_p,
output clkout_n,
output dataout_p,
output dataout_n,
//enable pattern generate
input pattern_gen_enable,
input ext_frame_sync,
output tx_clk, //600MHz/10=60MHz clock frequency
output [9:0] tx_dataout,
input [9:0] tx_datain, //CCIR656 standard input 3FF 000 000 SAV ---- 3FF 000 000 EAV
output sys_clock_200ref
);
//high speed clock
wire pixel_clk ; // Pixel clock for data output serdes txpllmmcm_x1 100M
//tx clk
BUFG BUFG_tx_clk (
.O(tx_clk), // 1-bit output: Clock output
.I(pixel_clk) // 1-bit input: Clock input
);
//mmcm reset
wire mmcm_lckd;
wire not_tx_mmcm_lckd;
assign not_tx_mmcm_lckd = ~mmcm_lckd;
wire mmcm_reset;
assign mmcm_reset = sys_rst;
//phr reset , serdes reset
wire reset;
assign reset = not_tx_mmcm_lckd;
reg [3:0] count ;
reg reset_intr;
initial reset_intr = 1'b1 ;
always @ (posedge pixel_clk, posedge reset) // local reset
begin
if (reset == 1'b1) begin
reset_intr <= 1'b1 ;
count <= 4'h0 ;
end
else begin
count <= count + 4'h1 ;
if (count == 4'hF) begin
reset_intr <= 1'b0 ;
count <= count;
end
end
end
//pattern data generate
//sync the frame data with input signal: ext_frame_sync
video_pattern_gen video_pattern_gen_inst(
.reset(reset),
.pixel_clk(pixel_clk),
.pixel_data(tx_datain),
.pattern_gen_enable(pattern_gen_enable),
.ext_frame_sync(ext_frame_sync),
.data_send(tx_dataout)
);
//diff data output
//serdes data output
wire tx_data_out;
wire cascade_di;
wire cascade_ti;
wire [9:0] mdataina;
assign mdataina = tx_dataout;
OBUFDS io_data_out (
.O (dataout_p),
.OB (dataout_n),
.I (tx_data_out));
OSERDESE2 #(
.DATA_WIDTH (10), // SERDES word width
.TRISTATE_WIDTH (1),
.DATA_RATE_OQ ("DDR"), // , DDR
.DATA_RATE_TQ ("SDR"), // , DDR
.SERDES_MODE ("MASTER")) // <DEFAULT>, MASTER, SLAVE
oserdes_m (
.OQ (tx_data_out),
.OCE (1'b1),
.CLK (txclk),
.RST (reset_intr),
.CLKDIV (pixel_clk),
.D8 (mdataina[2]),
.D7 (mdataina[3]),
.D6 (mdataina[4]),
.D5 (mdataina[5]),
.D4 (mdataina[6]),
.D3 (mdataina[7]),
.D2 (mdataina[8]),
.D1 (mdataina[9]),
.TQ (),
.T1 (1'b0),
.T2 (1'b0),
.T3 (1'b0),
.T4 (1'b0),
.TCE (1'b1),
.TBYTEIN (1'b0),
.TBYTEOUT (),
.OFB (),
.TFB (),
.SHIFTOUT1 (),
.SHIFTOUT2 (),
.SHIFTIN1 (cascade_di),
.SHIFTIN2 (cascade_ti)) ;
OSERDESE2 #(
.DATA_WIDTH (10), // SERDES word width.
.TRISTATE_WIDTH (1),
.DATA_RATE_OQ ("DDR"), // , DDR
.DATA_RATE_TQ ("SDR"), // , DDR
.SERDES_MODE ("SLAVE")) // <DEFAULT>, MASTER, SLAVE
oserdes_s (
.OQ (),
.OCE (1'b1),
.CLK (txclk),
.RST (reset_intr),
.CLKDIV (pixel_clk),
.D8 (1'b0),
.D7 (1'b0),
.D6 (1'b0),
.D5 (1'b0),
.D4 (mdataina[0]),
.D3 (mdataina[1]),
.D2 (1'b0),
.D1 (1'b0),
.TQ (),
.T1 (1'b0),
.T2 (1'b0),
.T3 (1'b0),
.T4 (1'b0),
.TCE (1'b1),
.TBYTEIN (1'b0),
.TBYTEOUT (),
.OFB (),
.TFB (),
.SHIFTOUT1 (cascade_di),
.SHIFTOUT2 (cascade_ti),
.SHIFTIN1 (1'b0),
.SHIFTIN2 (1'b0)) ;
//diff clock output
parameter [9:0] clk_pattern = 10'b10_1010_1010;
wire tx_clk_out;
wire cascade_cdi;
wire cascade_cti;
OBUFDS io_clk_out (
.O (clkout_p),
.OB (clkout_n),
.I (tx_clk_out));
OSERDESE2 #(
.DATA_WIDTH (10), // SERDES word width
.TRISTATE_WIDTH (1),
.DATA_RATE_OQ ("DDR"), // , DDR
.DATA_RATE_TQ ("SDR"), // , DDR
.SERDES_MODE ("MASTER")) // <DEFAULT>, MASTER, SLAVE
oserdes_cm (
.OQ (tx_clk_out),
.OCE (1'b1),
.CLK (txclk),
.RST (reset_intr),
.CLKDIV (pixel_clk),
.D8 (clk_pattern[2]),
.D7 (clk_pattern[3]),
.D6 (clk_pattern[4]),
.D5 (clk_pattern[5]),
.D4 (clk_pattern[6]),
.D3 (clk_pattern[7]),
.D2 (clk_pattern[8]),
.D1 (clk_pattern[9]),
.TQ (),
.T1 (1'b0),
.T2 (1'b0),
.T3 (1'b0),
.T4 (1'b0),
.TCE (1'b1),
.TBYTEIN (1'b0),
.TBYTEOUT (),
.OFB (),
.TFB (),
.SHIFTOUT1 (),
.SHIFTOUT2 (),
.SHIFTIN1 (cascade_cdi),
.SHIFTIN2 (cascade_cti)) ;
OSERDESE2 #(
.DATA_WIDTH (10), // SERDES word width.
.TRISTATE_WIDTH (1),
.DATA_RATE_OQ ("DDR"), // , DDR
.DATA_RATE_TQ ("SDR"), // , DDR
.SERDES_MODE ("SLAVE")) // <DEFAULT>, MASTER, SLAVE
oserdes_cs (
.OQ (),
.OCE (1'b1),
.CLK (txclk),
.RST (reset_intr),
.CLKDIV (pixel_clk),
.D8 (1'b0),
.D7 (1'b0),
.D6 (1'b0),
.D5 (1'b0),
.D4 (clk_pattern[0]),
.D3 (clk_pattern[1]),
.D2 (1'b0),
.D1 (1'b0),
.TQ (),
.T1 (1'b0),
.T2 (1'b0),
.T3 (1'b0),
.T4 (1'b0),
.TCE (1'b1),
.TBYTEIN (1'b0),
.TBYTEOUT (),
.OFB (),
.TFB (),
.SHIFTOUT1 (cascade_cdi),
.SHIFTOUT2 (cascade_cti),
.SHIFTIN1 (1'b0),
.SHIFTIN2 (1'b0)) ;
//mmcm clock generated
parameter real CLKIN_PERIOD = 10.0; // clock period (ns) of input clock on clkin_p
parameter integer MMCM_MODE = 1 ; // Parameter to set multiplier for MMCM to get VCO in correct operating range. 1 multiplies input clock by 7, 2 multiplies clock by 14, etc
wire clkint;
wire txpllmmcm_x1 ; // pll generated x1 clock
wire txpllmmcm_xn ; // pll generated xn clock
wire txpllmmcm_clkfb;
wire txpllmmcm_200ref;
BUFG BUFG_inst_0 (
.O(clkint), // 1-bit output: Clock output
.I(sys_clock) // 1-bit input: Clock input
);
MMCME2_ADV #(
.BANDWIDTH ("OPTIMIZED"),
.CLKFBOUT_MULT_F (6*MMCM_MODE), //600M
.CLKFBOUT_PHASE (0.0),
.CLKIN1_PERIOD (CLKIN_PERIOD),
.CLKIN2_PERIOD (CLKIN_PERIOD),
.CLKOUT0_DIVIDE_F (2*MMCM_MODE), //300M
.CLKOUT0_DUTY_CYCLE (0.5),
.CLKOUT0_PHASE (0.0),
.CLKOUT1_DIVIDE (10*MMCM_MODE), //60M
.CLKOUT1_DUTY_CYCLE (0.5),
.CLKOUT1_PHASE (0.0),
.CLKOUT2_DIVIDE (3*MMCM_MODE), //200M
.CLKOUT2_DUTY_CYCLE (0.5),
.CLKOUT2_PHASE (0.0),//(90.0),
.CLKOUT3_DIVIDE (10*MMCM_MODE), //60M
.CLKOUT3_DUTY_CYCLE (0.5),
.CLKOUT3_PHASE (0.0),//(22.5),
.CLKOUT4_DIVIDE (10),
.CLKOUT4_DUTY_CYCLE (0.5),
.CLKOUT4_PHASE (0.0),
.CLKOUT5_DIVIDE (10),
.CLKOUT5_DUTY_CYCLE (0.5),
.CLKOUT5_PHASE (0.0),
.COMPENSATION ("ZHOLD"),
.DIVCLK_DIVIDE (1),
.REF_JITTER1 (0.100))
tx_mmcme2_adv_inst (
.CLKFBOUT (txpllmmcm_clkfb),
.CLKFBOUTB (),
.CLKFBSTOPPED (),
.CLKINSTOPPED (),
.CLKOUT0 (txpllmmcm_xn),
.CLKOUT0B (),
.CLKOUT1 (txpllmmcm_x1),
.CLKOUT1B (),
.CLKOUT2 (txpllmmcm_200ref),
.CLKOUT2B (),
.CLKOUT3 (),
.CLKOUT3B (),
.CLKOUT4 (),
.CLKOUT5 (),
.CLKOUT6 (),
.DO (),
.DRDY (),
.PSDONE (),
.PSCLK (1'b0),
.PSEN (1'b0),
.PSINCDEC (1'b0),
.PWRDWN (1'b0),
.LOCKED (mmcm_lckd),
.CLKFBIN (clkfb_clk),
.CLKIN1 (clkint),
.CLKIN2 (1'b0),
.CLKINSEL (1'b1),
.DADDR (7'h00),
.DCLK (1'b0),
.DEN (1'b0),
.DI (16'h0000),
.DWE (1'b0),
.RST (mmcm_reset)) ;
BUFG bufg_mmcm_200ref (
.O(sys_clock_200ref), // 1-bit output: Clock output
.I(txpllmmcm_200ref) // 1-bit input: Clock input
);
BUFR #(
.BUFR_DIVIDE("1"),
.SIM_DEVICE("7SERIES"))
bufr_mmcm_fb (
.I(txpllmmcm_clkfb),
.CE(1'b1),
.O(clkfb_clk),
.CLR(1'b0)) ;
BUFIO bufio_mmcm_xn (
.I(txpllmmcm_xn),
.O(txclk)) ;
BUFR #(
.BUFR_DIVIDE("1"),
.SIM_DEVICE("7SERIES"))
bufr_mmcm_x1 (
.I(txpllmmcm_x1),
.CE(1'b1),
.O(pixel_clk),
.CLR(1'b0)) ;
endmodule