1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
|
//
// Copyright 2011-2014 Ettus Research LLC
// Copyright 2017 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// The two clocks are aligned externally in order to eliminate the need for a FIFO.
// A FIFO cannot be used to transition between clock domains because it can cause
// alignment issues between the output of multiple modules.
module capture_ddrlvds #(
parameter WIDTH = 14, //Width of the SS data bus
parameter PATT_CHECKER = "FALSE", //{TRUE, FALSE}: Is the integrated ramp pattern checker
parameter DATA_IDELAY_MODE = "BYPASSED", //{BYPASSED, FIXED, DYNAMIC}
parameter DATA_IDELAY_VAL = 16, //IDELAY value for FIXED mode. In DYNAMIC mode, this value is used by the timing analyzer
parameter DATA_IDELAY_FREF = 200.0 //Reference clock frequency for the IDELAYCTRL
) (
// ADC IO Pins
input adc_clk_p,
input adc_clk_n,
input [WIDTH-1:0] adc_data_p,
input [WIDTH-1:0] adc_data_n,
//System synchronous clock
input radio_clk,
//IDELAY settings
input data_delay_stb,
input [4:0] data_delay_val,
//Capture clock and output data
output adc_cap_clk,
output [(2*WIDTH)-1:0] data_out,
//Pattern checker options (sync to radio_clk)
input checker_en,
output [3:0] checker_locked,
output [3:0] checker_failed
);
//-------------------------------------------------------------------
// Clock Path
wire adc_buf_clk;
// Route source synchronous clock to differential input clock buffer
// then to a global clock buffer. We route to a global buffer because
// the data bus being capture spans multiple banks.
IBUFGDS ss_clk_ibufgds_i (
.I(adc_clk_p), .IB(adc_clk_n),
.O(adc_buf_clk)
);
BUFG ss_clk_bufg_i (
.I(adc_buf_clk),
.O(adc_cap_clk)
);
//-------------------------------------------------------------------
// Data Path
wire [WIDTH-1:0] adc_data_buf, adc_data_del;
wire [(2*WIDTH)-1:0] adc_data_aclk;
reg [(2*WIDTH)-1:0] adc_data_rclk, adc_data_rclk_sync;
genvar i;
generate for(i = 0; i < WIDTH; i = i + 1) begin : gen_lvds_pins
// Use a differential IO buffer to get the data into the IOB
IBUFDS ibufds_i (
.I(adc_data_p[i]), .IB(adc_data_n[i]),
.O(adc_data_buf[i])
);
// Use an optional IDELAY to tune the capture interface from
// software. This is a clock to data delay calibration so all
// data bits are delayed by the same amount.
if (DATA_IDELAY_MODE != "BYPASSED") begin
// Pipeline IDELAY control signals to ease routing
reg data_delay_stb_reg;
reg [4:0] data_delay_val_reg;
always @(posedge radio_clk)
{data_delay_stb_reg, data_delay_val_reg} <= {data_delay_stb, data_delay_val};
IDELAYE2 #(
.DELAY_SRC("IDATAIN"), // Delay input (IDATAIN, DATAIN)
.IDELAY_TYPE(DATA_IDELAY_MODE=="FIXED"?"FIXED":"VAR_LOAD"), // FIXED, VARIABLE, VAR_LOAD, VAR_LOAD_PIPE
.SIGNAL_PATTERN("DATA"), // DATA, CLOCK input signal
.HIGH_PERFORMANCE_MODE("TRUE"), // Reduced jitter ("TRUE"), Reduced power ("FALSE")
.PIPE_SEL("FALSE"), // Select pipelined mode, FALSE, TRUE
.CINVCTRL_SEL("FALSE"), // Enable dynamic clock inversion (FALSE, TRUE)
.IDELAY_VALUE(DATA_IDELAY_VAL), // Input delay tap setting (0-31)
.REFCLK_FREQUENCY(DATA_IDELAY_FREF) // IDELAYCTRL clock input frequency in MHz (190.0-210.0).
) idelay_i (
.DATAIN(1'b0), // Internal delay data input
.IDATAIN(adc_data_buf[i]), // Data input from the I/O
.DATAOUT(adc_data_del[i]), // Delayed data output
.C(radio_clk), // Clock input
.LD(data_delay_stb_reg), // Load IDELAY_VALUE input
.CE(1'b0), // Active high enable increment/decrement input
.INC(1'b0), // Increment / Decrement tap delay input
.CINVCTRL(1'b0), // Dynamic clock inversion input
.CNTVALUEIN(data_delay_val_reg), // Counter value input
.CNTVALUEOUT(), // Counter value output
.LDPIPEEN(1'b0), // Enable PIPELINE register to load data input
.REGRST(1'b0) // Reset for the pipeline register.Only used in VAR_LOAD_PIPE mode.
);
end else begin
assign adc_data_del[i] = adc_data_buf[i];
end
// Use the global ADC clock to capture delayed data into an IDDR.
// Each IQ sample is transferred in QDR mode i.e. odd and even on
// a rising and falling edge of the clock
IDDR #(
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED")
) iddr_i (
.C(adc_cap_clk), .CE(1'b1),
.D(adc_data_del[i]), .R(1'b0), .S(1'b0),
.Q1(adc_data_aclk[2*i]), .Q2(adc_data_aclk[(2*i)+1])
);
end endgenerate
// Transfer data from the source-synchronous ADC clock domian to the
// system synchronous radio clock domain. We assume that adc_cap_clk
// and radio_clk are generated from the same source and have the same
// frequency however, they have an unknown but constant phase offset.
// In order to cross domains, we use a simple synchronizer to avoid any
// sample-sample delay uncertainty introduced by FIFOs.
// NOTE: The path between adc_data_aclk and adc_data_rclk must be
// constrained to prevent build to build variations. Also, the
// phase of the two clocks must be aligned ensure that the data
// capture is safe
always @(posedge radio_clk)
{adc_data_rclk_sync, adc_data_rclk} <= {adc_data_rclk, adc_data_aclk};
// The synchronized output is the output of this module
assign data_out = adc_data_rclk_sync;
//-------------------------------------------------------------------
// Checkers
generate if (PATT_CHECKER == "TRUE") begin
wire checker_en_aclk;
wire [1:0] checker_locked_aclk, checker_failed_aclk;
synchronizer #(.INITIAL_VAL(1'b0)) checker_en_aclk_sync_i (
.clk(adc_cap_clk), .rst(1'b0), .in(checker_en), .out(checker_en_aclk));
synchronizer #(.INITIAL_VAL(1'b0)) checker_locked_aclk_0_sync_i (
.clk(radio_clk), .rst(1'b0), .in(checker_locked_aclk[0]), .out(checker_locked[0]));
synchronizer #(.INITIAL_VAL(1'b0)) checker_locked_aclk_1_sync_i (
.clk(radio_clk), .rst(1'b0), .in(checker_locked_aclk[1]), .out(checker_locked[1]));
synchronizer #(.INITIAL_VAL(1'b0)) checker_failed_aclk_0_sync_i (
.clk(radio_clk), .rst(1'b0), .in(checker_failed_aclk[0]), .out(checker_failed[0]));
synchronizer #(.INITIAL_VAL(1'b0)) checker_failed_aclk_1_sync_i (
.clk(radio_clk), .rst(1'b0), .in(checker_failed_aclk[1]), .out(checker_failed[1]));
cap_pattern_verifier #( // Q Channel : Synchronous to SSCLK
.WIDTH(WIDTH), .PATTERN("RAMP"), .HOLD_CYCLES(1),
.RAMP_START(0), .RAMP_STOP({WIDTH{1'b1}}), .RAMP_INCR(1)
) aclk_q_checker_i (
.clk(adc_cap_clk), .rst(~checker_en_aclk),
.valid(1'b1), .data(~adc_data_aclk[WIDTH-1:0]),
.count(), .errors(),
.locked(checker_locked_aclk[0]), .failed(checker_failed_aclk[0])
);
cap_pattern_verifier #( // I Channel : Synchronous to SSCLK
.WIDTH(WIDTH), .PATTERN("RAMP"), .HOLD_CYCLES(1),
.RAMP_START(0), .RAMP_STOP({WIDTH{1'b1}}), .RAMP_INCR(1)
) aclk_i_checker_i (
.clk(adc_cap_clk), .rst(~checker_en_aclk),
.valid(1'b1), .data(~adc_data_aclk[(2*WIDTH)-1:WIDTH]),
.count(), .errors(),
.locked(checker_locked_aclk[1]), .failed(checker_failed_aclk[1])
);
cap_pattern_verifier #( // Q Channel : Synchronous to Radio CLK
.WIDTH(WIDTH), .PATTERN("RAMP"), .HOLD_CYCLES(1),
.RAMP_START(0), .RAMP_STOP({WIDTH{1'b1}}), .RAMP_INCR(1)
) rclk_q_checker_i (
.clk(radio_clk), .rst(~checker_en),
.valid(1'b1), .data(~adc_data_rclk_sync[WIDTH-1:0]),
.count(), .errors(),
.locked(checker_locked[2]), .failed(checker_failed[2])
);
cap_pattern_verifier #( // I Channel : Synchronous to Radio CLK
.WIDTH(WIDTH), .PATTERN("RAMP"), .HOLD_CYCLES(1),
.RAMP_START(0), .RAMP_STOP({WIDTH{1'b1}}), .RAMP_INCR(1)
) rclk_i_checker_i (
.clk(radio_clk), .rst(~checker_en),
.valid(1'b1), .data(~adc_data_rclk_sync[(2*WIDTH)-1:WIDTH]),
.count(), .errors(),
.locked(checker_locked[3]), .failed(checker_failed[3])
);
end endgenerate
endmodule // capture_ddrlvds
|