Commit | Line | Data |
---|---|---|
86530b38 AT |
1 | // ========== Copyright Header Begin ========================================== |
2 | // | |
3 | // OpenSPARC T2 Processor File: ccu_checker.vr | |
4 | // Copyright (C) 1995-2007 Sun Microsystems, Inc. All Rights Reserved | |
5 | // 4150 Network Circle, Santa Clara, California 95054, U.S.A. | |
6 | // | |
7 | // * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
8 | // | |
9 | // This program is free software; you can redistribute it and/or modify | |
10 | // it under the terms of the GNU General Public License as published by | |
11 | // the Free Software Foundation; version 2 of the License. | |
12 | // | |
13 | // This program is distributed in the hope that it will be useful, | |
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | // GNU General Public License for more details. | |
17 | // | |
18 | // You should have received a copy of the GNU General Public License | |
19 | // along with this program; if not, write to the Free Software | |
20 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
21 | // | |
22 | // For the avoidance of doubt, and except that if any non-GPL license | |
23 | // choice is available it will apply instead, Sun elects to use only | |
24 | // the General Public License version 2 (GPLv2) at this time for any | |
25 | // software where a choice of GPL license versions is made | |
26 | // available with the language indicating that GPLv2 or any later version | |
27 | // may be used, or where a choice of which version of the GPL is applied is | |
28 | // otherwise unspecified. | |
29 | // | |
30 | // Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, | |
31 | // CA 95054 USA or visit www.sun.com if you need additional information or | |
32 | // have any questions. | |
33 | // | |
34 | // ========== Copyright Header End ============================================ | |
35 | #include <vera_defines.vrh> | |
36 | #include "std_display_class.vrh" | |
37 | #include "ucb_top.vri" | |
38 | #include "ccu_top.vri" | |
39 | #include "ccu_clk_packet.vrh" | |
40 | #include "ccu_clks_states.vrh" | |
41 | ||
42 | class CCU_checker { | |
43 | //--public vars to control this checker--- | |
44 | integer is_chk_iosync_loc; // 0: not check; otherwise, check | |
45 | integer is_chk_io2xsync_loc; // 0: not check; otherwise, check | |
46 | integer is_chk_drsync_loc; // 0: not check; otherwise, check | |
47 | integer is_chk_syssync_loc; // 0: not check; otherwise, check | |
48 | ||
49 | //---ports and classes-- | |
50 | local CCU_clk_port clk_port; // each signal is a Vera CLOCK | |
51 | local CCU_mon_port mon_port; // Vera clock is cmp_pll_clk | |
52 | local CCU_clks_internal_port ccu_clks_internal_port; | |
53 | local UCB_port ucb_port; // UCB interface signals (iol2clk) | |
54 | local StandardDisplay dbg; // Standard display for printing | |
55 | local CCU_clks_states ccu_states; // keep track expected values | |
56 | ||
57 | //---vars--- | |
58 | local string name; // name of this object | |
59 | local string dispScope; // for standard display | |
60 | local integer verbose; // 0: disable verbose mode; otherwise, enable | |
61 | local integer error_cnt; // Error count. Init to 0. WARN: become negative if exeed max integer | |
62 | local integer max_error_printed; // not print error msg if (error_cnt > max_error_printed) | |
63 | local integer max_debug_printed; // not print debug msg if (debug_cnt > max_debug_printed) | |
64 | local integer running; // 1: checker is running, 0: not running | |
65 | local CCU_clk_packet clk_pkt; // expected values for clock parameters | |
66 | ||
67 | //---expected locations of IO and IO2X sync pulses--- | |
68 | local integer cmp_io_sync_loc, io_cmp_sync_loc, io2x_sync_loc; // 0: core and IO/IO2X clks are aligned | |
69 | local integer cmp_sys_sync_loc, sys_cmp_sync_loc; // 0: cmp_pll_clk and ccu_rst_sys_clk are aligned | |
70 | local integer dr_sync_loc[4]; // locations of dr sync at CCU boundary | |
71 | local integer total_stage_flops; // total staging flops from CCU output to flop input | |
72 | ||
73 | //---event vars to control checkers--- | |
74 | local event stop_checker_e; // triggered by stop_checker() to terminate start_checker() | |
75 | local event stop_checking_e; // triggered by stop_checking() to terminate start_public() | |
76 | ||
77 | //---public tasks--- | |
78 | task new(string name="CCU_checker", StandardDisplay dbg, CCU_clks_states ccu_states, integer start_it=0); | |
79 | task start_checker(); // start the ccu checker | |
80 | task stop_checker(); // stop the ccu checker | |
81 | task start_clk_stretch_checker(); // start clk stretch checker | |
82 | ||
83 | //---local subroutines--- | |
84 | local task start_checking(); // start checking | |
85 | local task stop_checking(); // stop checking | |
86 | local task check_clk_toggle(string clk_name); | |
87 | local task check_clk_freq_pw(string clk_name); | |
88 | local task check_io_io2x_sync_cnt_pw(string sync_name); | |
89 | local task check_io_io2x_sync_loc(string sync_name); | |
90 | local task check_sys_sync_cnt_pw(string sync_name); | |
91 | local task check_sys_sync_loc(string sync_name); | |
92 | local task check_dr_sync_toggle(); | |
93 | local task check_dr_sync_pw(); | |
94 | local task check_dr_sync_loc(); | |
95 | ||
96 | //---local tasks for clk stretch checker--- | |
97 | local task clk_stretch_checker(); | |
98 | local function integer clk_stretch_chkr_1clk(string clkname, bit st_phase_hi, integer st_delay, integer max_pw_dev, integer max_cycs, integer st_start_cyc); | |
99 | ||
100 | //---supporting subroutines--- | |
101 | local task print_error_msg(integer err_cnt, string error_msg); | |
102 | local task print_debug_msg(integer debug_cnt, string debug_msg); | |
103 | local function integer is_outside_min_max(integer min_val, integer value, integer max_val); | |
104 | local task compute_expected_dr_sync_loc(); | |
105 | local task show_dr_sync_loc(); | |
106 | ||
107 | //---one-line tasks---- | |
108 | function integer abs(integer n) { abs = (n >= 0)? n : n * -1; } // compute absolute value | |
109 | } | |
110 | ||
111 | //################################################################ | |
112 | //######### implementation of subroutines ########### | |
113 | //################################################################ | |
114 | ||
115 | task CCU_checker::new(string name="ccu_checker", StandardDisplay dbg, CCU_clks_states ccu_states, integer start_it=0) { | |
116 | integer i; | |
117 | ||
118 | //---from arg list--- | |
119 | this.name = name; | |
120 | this.dbg = dbg; | |
121 | this.ccu_states = ccu_states; | |
122 | ||
123 | //---control vars--- | |
124 | if (get_plus_arg(CHECK, "ccuChkr_chkIoIo2xDrSyncLoc")) { | |
125 | this.is_chk_iosync_loc = 1; | |
126 | this.is_chk_io2xsync_loc = 1; | |
127 | this.is_chk_drsync_loc = 1; | |
128 | } | |
129 | else { //default is NOT checking sync locations at CCU | |
130 | this.is_chk_iosync_loc = 0; | |
131 | this.is_chk_io2xsync_loc = 0; | |
132 | this.is_chk_drsync_loc = 0; | |
133 | } | |
134 | if (get_plus_arg(CHECK, "ccuChkr_notChkSysSyncLoc")) | |
135 | this.is_chk_syssync_loc = 0; | |
136 | else | |
137 | this.is_chk_syssync_loc = 1; // default is checking locations of sys sync pulses | |
138 | ||
139 | //---others--- | |
140 | this.clk_port = ccu_clk_bind; | |
141 | this.mon_port = ccu_mon_bind; | |
142 | this.ccu_clks_internal_port = ccu_clks_internal_bind; | |
143 | this.ucb_port = ccu_ucb_mon_bind; | |
144 | this.dispScope = this.name; | |
145 | this.verbose = (get_plus_arg(CHECK, "ccuChkr_verbose"))? 1 : 0; | |
146 | this.error_cnt = 0; | |
147 | this.max_error_printed = 20; | |
148 | this.max_debug_printed = 20; | |
149 | this.running = 0; | |
150 | this.clk_pkt = null; // illegal value | |
151 | this.total_stage_flops = 8; // 5 flops in GCT, 2 flops in cluster hdr, 1 after cluster hdr | |
152 | for (i = 0; i < 4; i++) | |
153 | this.dr_sync_loc[i] = -1; // illegal value. | |
154 | ||
155 | //---sync locations---- | |
156 | this.cmp_io_sync_loc = 3; // 3rd posedge of cmp_pll_clk after core and io clks aligned | |
157 | this.io_cmp_sync_loc = 1; // 1st posedge of cmp_pll_clk after core and io clks aligned | |
158 | this.io2x_sync_loc = 1; // 1st posedge of cmp_pll_clk after core and io2x clks aligned | |
159 | this.cmp_sys_sync_loc = 1; // 1st posedge of cmp_pll_clk after cmp_pll_clk and ccu_rst_sys_clk aligned | |
160 | this.sys_cmp_sync_loc = 1; // 1st posedge of cmp_pll_clk after cmp_pll_clk and ccu_rst_sys_clk aligned | |
161 | ||
162 | //----override if runtime options specified--- | |
163 | if (get_plus_arg(CHECK, "ccuChkr_cmpiosync_loc=")) | |
164 | this.cmp_io_sync_loc = get_plus_arg(NUM, "ccuChkr_cmpiosync_loc="); | |
165 | if (get_plus_arg(CHECK, "ccuChkr_iocmpsync_loc=")) | |
166 | this.io_cmp_sync_loc = get_plus_arg(NUM, "ccuChkr_iocmpsync_loc="); | |
167 | if (get_plus_arg(CHECK, "ccuChkr_io2xsync_loc=")) | |
168 | this.io2x_sync_loc = get_plus_arg(NUM, "ccuChkr_io2xsync_loc="); | |
169 | if (get_plus_arg(CHECK, "ccuChkr_maxError=")) | |
170 | this.max_error_printed = get_plus_arg(NUM, "ccuChkr_maxError="); | |
171 | ||
172 | //---start background threads --- | |
173 | if (start_it) | |
174 | start_checker(); | |
175 | } | |
176 | ||
177 | //===================================================================== | |
178 | // WHAT: start CCU checker | |
179 | //===================================================================== | |
180 | task CCU_checker::start_checker() { | |
181 | reg bit_value; | |
182 | ||
183 | if (running) | |
184 | return; // already running | |
185 | dbg.dispmon(this.dispScope, MON_INFO, "starts ..."); | |
186 | this.running = 1; // checker is running | |
187 | fork { | |
188 | fork { | |
189 | while (1) { | |
190 | bit_value = clk_port.$rst_ccu_ async; | |
191 | if (bit_value !== 1'b1) | |
192 | @(posedge clk_port.$rst_ccu_); // PLL is stable, so start checking | |
193 | delay(3); // avoid race condition with ccu_states.get_clk_pkt() | |
194 | this.clk_pkt = ccu_states.get_exp_clk_pkt(); | |
195 | compute_expected_dr_sync_loc(); | |
196 | ccu_states.show_clk_pkt(this.clk_pkt); | |
197 | this.show_dr_sync_loc(); | |
198 | start_checking(); | |
199 | @(negedge clk_port.$rst_ccu_pll_); // Reset PLL, so stop checking | |
200 | stop_checking(); | |
201 | } | |
202 | } join none | |
203 | sync(ALL, this.stop_checker_e); // stop() triggers this event | |
204 | terminate; | |
205 | dbg.dispmon(this.dispScope, MON_INFO, "stopped"); | |
206 | this.running = 0; // checker is not running | |
207 | } join none | |
208 | } | |
209 | ||
210 | //===================================================================== | |
211 | // WHAT: stop CCU checker (ie. terminate start_checker()) | |
212 | //===================================================================== | |
213 | task CCU_checker::stop_checker() { | |
214 | if (this.running) | |
215 | trigger(this.stop_checker_e); // this event terminates start_checker() | |
216 | } | |
217 | ||
218 | //============================================================= | |
219 | // Start to check | |
220 | //============================================================= | |
221 | task CCU_checker::start_checking() { | |
222 | ||
223 | dbg.dispmon(this.dispScope, MON_INFO, "start checking ..."); | |
224 | fork { | |
225 | fork | |
226 | //-----cmp/dr/io/io2x clocks: check toggle, freq, pw--- | |
227 | { | |
228 | check_clk_toggle("cmp_clk"); | |
229 | } | |
230 | { | |
231 | check_clk_freq_pw("cmp_clk"); | |
232 | } | |
233 | { | |
234 | @(posedge clk_port.$ccu_rst_sync_stable); | |
235 | check_clk_toggle("dr_clk"); | |
236 | } | |
237 | { | |
238 | @(posedge clk_port.$ccu_rst_sync_stable); | |
239 | check_clk_freq_pw("dr_clk"); | |
240 | } | |
241 | { | |
242 | @(posedge clk_port.$ccu_rst_sync_stable); | |
243 | check_clk_toggle("io_out"); | |
244 | } | |
245 | { | |
246 | @(posedge clk_port.$ccu_rst_sync_stable); | |
247 | check_clk_freq_pw("io_out"); | |
248 | } | |
249 | { | |
250 | @(posedge clk_port.$ccu_rst_sync_stable); | |
251 | check_clk_toggle("io2x_out"); | |
252 | } | |
253 | { | |
254 | @(posedge clk_port.$ccu_rst_sync_stable); | |
255 | check_clk_freq_pw("io2x_out"); | |
256 | } | |
257 | //------ check IO sync and IO2X sync pulses -------- | |
258 | { | |
259 | @(posedge clk_port.$ccu_rst_sync_stable); | |
260 | check_io_io2x_sync_cnt_pw("cmp_io_sync_en"); | |
261 | } | |
262 | { | |
263 | @(posedge clk_port.$ccu_rst_sync_stable); | |
264 | check_io_io2x_sync_cnt_pw("io_cmp_sync_en"); | |
265 | } | |
266 | { | |
267 | @(posedge clk_port.$ccu_rst_sync_stable); | |
268 | check_io_io2x_sync_cnt_pw("io2x_sync_en"); | |
269 | } | |
270 | { | |
271 | if (this.is_chk_iosync_loc) { | |
272 | @(posedge clk_port.$ccu_rst_sync_stable); | |
273 | check_io_io2x_sync_loc("cmp_io_sync_en"); | |
274 | } | |
275 | else | |
276 | dbg.dispmon(this.dispScope, MON_ALWAYS, "warning: not checking cmp_io_sync_en locations"); | |
277 | } | |
278 | { | |
279 | if (this.is_chk_iosync_loc) { | |
280 | @(posedge clk_port.$ccu_rst_sync_stable); | |
281 | check_io_io2x_sync_loc("io_cmp_sync_en"); | |
282 | } | |
283 | else | |
284 | dbg.dispmon(this.dispScope, MON_ALWAYS, "warning: not checking io_cmp_sync_en locations"); | |
285 | } | |
286 | { | |
287 | if (this.is_chk_io2xsync_loc) { | |
288 | @(posedge clk_port.$ccu_rst_sync_stable); | |
289 | check_io_io2x_sync_loc("io2x_sync_en"); | |
290 | } | |
291 | else | |
292 | dbg.dispmon(this.dispScope, MON_ALWAYS, "warning: not checking io2x_sync_en locations"); | |
293 | } | |
294 | //---- check SYS sync pulses ---- | |
295 | { | |
296 | @(posedge clk_port.$ccu_rst_sync_stable); | |
297 | check_sys_sync_cnt_pw("cmp_sys_sync_en"); | |
298 | } | |
299 | { | |
300 | @(posedge clk_port.$ccu_rst_sync_stable); | |
301 | check_sys_sync_cnt_pw("sys_cmp_sync_en"); | |
302 | } | |
303 | { | |
304 | if (this.is_chk_syssync_loc) { | |
305 | @(posedge clk_port.$ccu_rst_sync_stable); | |
306 | check_sys_sync_loc("cmp_sys_sync_en"); | |
307 | } | |
308 | else | |
309 | dbg.dispmon(this.dispScope, MON_ALWAYS, "warning: not checking cmp_sys_sync_en locations"); | |
310 | } | |
311 | { | |
312 | if (this.is_chk_syssync_loc) { | |
313 | @(posedge clk_port.$ccu_rst_sync_stable); | |
314 | check_sys_sync_loc("sys_cmp_sync_en"); | |
315 | } | |
316 | else | |
317 | dbg.dispmon(this.dispScope, MON_ALWAYS, "warning: not checking sys_cmp_sync_en locations"); | |
318 | } | |
319 | //----- check DR sync pulses ----- | |
320 | { | |
321 | @(posedge clk_port.$ccu_rst_sync_stable); | |
322 | check_dr_sync_toggle(); | |
323 | } | |
324 | { | |
325 | @(posedge clk_port.$ccu_rst_sync_stable); | |
326 | check_dr_sync_pw(); | |
327 | } | |
328 | { | |
329 | if (this.is_chk_drsync_loc) { | |
330 | @(posedge clk_port.$ccu_rst_sync_stable); | |
331 | check_dr_sync_loc(); | |
332 | } | |
333 | else | |
334 | dbg.dispmon(this.dispScope, MON_ALWAYS, "warning: not checking dr_sync_en locations"); | |
335 | } | |
336 | join none | |
337 | sync(ALL, this.stop_checking_e); // stop_checking() triggers this event | |
338 | terminate; | |
339 | dbg.dispmon(this.dispScope, MON_INFO, "checking stopped"); | |
340 | } join none | |
341 | } | |
342 | ||
343 | //============================================================= | |
344 | // | |
345 | //============================================================= | |
346 | task CCU_checker::stop_checking() { | |
347 | trigger(this.stop_checking_e); // this event terminates start_checking() | |
348 | } | |
349 | ||
350 | //============================================================= | |
351 | // WHAT: Check the specified clock for toggling. | |
352 | // ARGs: clk_name: "cmp_clk", "dr_clk", "io_out" or "io2x_out" | |
353 | //============================================================= | |
354 | task CCU_checker::check_clk_toggle(string clk_name) { | |
355 | string golden_clk; // golden clock to use for checking other clock | |
356 | integer num_golden_clks; | |
357 | integer old_cyc, new_cyc, ncycs, exp_min_ncycs, exp_max_ncycs; | |
358 | integer err_cnt = 0, debug_cnt=0; | |
359 | ||
360 | case (clk_name) { | |
361 | "cmp_clk": { | |
362 | golden_clk = "sys_clk"; | |
363 | num_golden_clks = 1; | |
364 | exp_min_ncycs = this.clk_pkt.cmp_mult - 1; // '-1' : account sys and cmp clk edges are PERFECTLY aligned | |
365 | exp_max_ncycs = this.clk_pkt.cmp_mult + 1; // '+1' : account sys and cmp clk edges are PERFECTLY aligned | |
366 | @(posedge clk_port.$sys_clk); | |
367 | old_cyc = get_cycle(clk_port.$cmp_pll_clk); | |
368 | } | |
369 | "dr_clk": { | |
370 | golden_clk = "sys_clk"; | |
371 | num_golden_clks = 2; | |
372 | exp_min_ncycs = (this.clk_pkt.dr_mult * 2) - 1; // '-1' : account sys and dr clk edges are PERFECTLY aligned | |
373 | exp_max_ncycs = (this.clk_pkt.dr_mult * 2) + 1; // '+1' : account sys and dr clk edges are PERFECTLY aligned | |
374 | @(posedge clk_port.$sys_clk); // in DTM mode, dr-to-sys clk ratio is 1:1, so use 2 sys clk cycles | |
375 | old_cyc = get_cycle(clk_port.$dr_pll_clk); | |
376 | } | |
377 | "io_out": { | |
378 | golden_clk = "cmp_pll_clk"; | |
379 | num_golden_clks = this.clk_pkt.cmp2io_ratio; | |
380 | exp_min_ncycs = 1; | |
381 | exp_max_ncycs = 1; | |
382 | @(posedge clk_port.$cmp_pll_clk); | |
383 | old_cyc = get_cycle(clk_port.$ccu_io_out); | |
384 | } | |
385 | "io2x_out": { | |
386 | golden_clk = "cmp_pll_clk"; | |
387 | num_golden_clks = this.clk_pkt.cmp2io2x_ratio; | |
388 | exp_min_ncycs = 1; | |
389 | exp_max_ncycs = 1; | |
390 | @(posedge clk_port.$cmp_pll_clk); | |
391 | old_cyc = get_cycle(clk_port.$ccu_io2x_out); | |
392 | } | |
393 | default: { | |
394 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("CCU_checker::check_clk_toggle(%s) <= bad arg", clk_name)); | |
395 | this.error_cnt++; | |
396 | return; // ignore | |
397 | } | |
398 | } | |
399 | while (1) { | |
400 | case (clk_name) { | |
401 | "cmp_clk": { | |
402 | repeat (num_golden_clks) @(posedge clk_port.$sys_clk); | |
403 | new_cyc = get_cycle(clk_port.$cmp_pll_clk); | |
404 | } | |
405 | "dr_clk": { | |
406 | repeat (num_golden_clks) @(posedge clk_port.$sys_clk); | |
407 | new_cyc = get_cycle(clk_port.$dr_pll_clk); | |
408 | } | |
409 | "io_out": { | |
410 | repeat (num_golden_clks) @(posedge clk_port.$cmp_pll_clk); | |
411 | new_cyc = get_cycle(clk_port.$ccu_io_out); | |
412 | } | |
413 | "io2x_out": { | |
414 | repeat (num_golden_clks) @(posedge clk_port.$cmp_pll_clk); | |
415 | new_cyc = get_cycle(clk_port.$ccu_io2x_out); | |
416 | } | |
417 | } | |
418 | ncycs = new_cyc - old_cyc; | |
419 | if (is_outside_min_max(exp_min_ncycs, ncycs, exp_max_ncycs)) | |
420 | print_error_msg(err_cnt++, psprintf("number %s pulses in %0d %s is %0d. Expect: %0d to %0d", | |
421 | clk_name, num_golden_clks, golden_clk, ncycs, exp_min_ncycs, exp_max_ncycs)); | |
422 | else if (this.verbose) | |
423 | print_debug_msg(debug_cnt++, psprintf("number %s pulses in %0d %s is %0d. Expect: %0d to %0d", | |
424 | clk_name, num_golden_clks, golden_clk, ncycs, exp_min_ncycs, exp_max_ncycs)); | |
425 | old_cyc = new_cyc; | |
426 | } | |
427 | } | |
428 | ||
429 | //============================================================= | |
430 | // WHAT: Check freq and pulse width of the specified clk | |
431 | // ARGs: clk_name: "cmp_clk", "dr_clk", "io_out" or "io2x_out" | |
432 | // ASSUMPTION: checking that the specified clk toggles has been done. | |
433 | //============================================================= | |
434 | task CCU_checker::check_clk_freq_pw(string clk_name) { | |
435 | integer pw_dev; // deviation in clk pw | |
436 | integer per, per_min, per_max; // clk period | |
437 | integer pw_hi, pw_lo, pw_nom, pw_min, pw_max; // pulse width | |
438 | integer clk_posedge, clk_negedge; | |
439 | integer nom_4_min, nom_4_max; // nominal for min and max to minimize Vera rounding effect | |
440 | integer per_error_cnt=0, pw_error_cnt=0; // error in period or pulse width | |
441 | integer debug_cnt = 0; | |
442 | ||
443 | case (clk_name) { | |
444 | "cmp_clk": { | |
445 | per_min = this.clk_pkt.cmp_clk_per_min; | |
446 | per_max = this.clk_pkt.cmp_clk_per_max; | |
447 | pw_dev = this.clk_pkt.cmp_pw_dev; | |
448 | @(posedge clk_port.$cmp_pll_clk); | |
449 | } | |
450 | "dr_clk": { | |
451 | per_min = this.clk_pkt.dr_clk_per_min; | |
452 | per_max = this.clk_pkt.dr_clk_per_max; | |
453 | pw_dev = this.clk_pkt.dr_pw_dev; | |
454 | @(posedge clk_port.$dr_pll_clk); | |
455 | } | |
456 | "io_out": { | |
457 | per_min = this.clk_pkt.io_out_per_min; | |
458 | per_max = this.clk_pkt.io_out_per_max; | |
459 | pw_dev = this.clk_pkt.iox_pw_dev; | |
460 | @(posedge clk_port.$ccu_io_out); | |
461 | } | |
462 | "io2x_out": { | |
463 | per_min = this.clk_pkt.io2x_out_per_min; | |
464 | per_max = this.clk_pkt.io2x_out_per_max; | |
465 | pw_dev = this.clk_pkt.iox_pw_dev; | |
466 | @(posedge clk_port.$ccu_io2x_out); | |
467 | } | |
468 | default: { | |
469 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("CCU_checker::check_clk_freq_pw(%s) <= bad arg", clk_name)); | |
470 | this.error_cnt++; | |
471 | return; // ignore | |
472 | } | |
473 | } | |
474 | clk_posedge = get_time(LO); | |
475 | while (1) { | |
476 | case (clk_name) { | |
477 | "cmp_clk": @(negedge clk_port.$cmp_pll_clk); | |
478 | "dr_clk": @(negedge clk_port.$dr_pll_clk); | |
479 | "io_out": @(negedge clk_port.$ccu_io_out); | |
480 | "io2x_out": @(negedge clk_port.$ccu_io2x_out); | |
481 | } | |
482 | clk_negedge = get_time(LO); | |
483 | pw_hi = clk_negedge - clk_posedge; | |
484 | case (clk_name) { | |
485 | "cmp_clk": @(posedge clk_port.$cmp_pll_clk); | |
486 | "dr_clk": @(posedge clk_port.$dr_pll_clk); | |
487 | "io_out": @(posedge clk_port.$ccu_io_out); | |
488 | "io2x_out": @(posedge clk_port.$ccu_io2x_out); | |
489 | } | |
490 | clk_posedge = get_time(LO); | |
491 | pw_lo = clk_posedge - clk_negedge; | |
492 | per = pw_hi + pw_lo; | |
493 | //---check--- | |
494 | if (is_outside_min_max(per_min, per, per_max)) | |
495 | print_error_msg(per_error_cnt++, psprintf("%s period: %0d, expect: min=%0d, max=%0d", clk_name, per, per_min, per_max)); | |
496 | pw_nom = per / 2; // 50% duty cycle | |
497 | nom_4_min = pw_nom - 1; // rounding down to account vera integer division | |
498 | nom_4_max = pw_nom + 1; // rounding up to account vera integer division | |
499 | pw_min = nom_4_min - (((per / 100) + 1) * pw_dev); // +1: account vera integer division | |
500 | pw_max = nom_4_max + (((per / 100) + 1) * pw_dev); // +1: account vera integer division | |
501 | if (is_outside_min_max(pw_min, pw_hi, pw_max) || is_outside_min_max(pw_min, pw_lo, pw_max)) | |
502 | print_error_msg(pw_error_cnt++, psprintf("%s pulse width: pw_hi=%0d, pw_lo=%0d, expect: min=%0d, max=%0d", | |
503 | clk_name, pw_hi, pw_lo, pw_min, pw_max)); | |
504 | else if (this.verbose) | |
505 | print_debug_msg(debug_cnt++, psprintf("%s per: %0d, expect: min=%0d, max=%0d. pw_hi=%0d, pw_lo=%0d. Expect: min=%0d, max=%0d", | |
506 | clk_name, per, per_min, per_max, pw_hi, pw_lo, pw_min, pw_max)); | |
507 | } | |
508 | } | |
509 | ||
510 | //============================================================= | |
511 | // WHAT: check io_sync_en and io2x_sync_en | |
512 | // -Sync pulse width is one cmp clk. | |
513 | // -One sync pulse for each slow clock | |
514 | // ARGs: sync_name must be "cmp_io_sync_en", "io_cmp_sync_en" or "io2x_sync_en" | |
515 | //============================================================= | |
516 | task CCU_checker::check_io_io2x_sync_cnt_pw(string sync_name) { | |
517 | integer cmp2slow_clk_ratio; // cmp-to-slow clk ratio | |
518 | integer num_cycs_sync_low; // number of cmp clks when sync pulse is low | |
519 | integer first_sync_timeout; // timeout if not seen 1st sync | |
520 | integer got_1st_sync=0; // set to 1 when detect first sync pulse | |
521 | integer err_lo_cnt=0, err_hi_cnt=0, debug_cnt=0; // error/debug counts of sync are low and high | |
522 | bit sync_val; // value of sync_en signal | |
523 | integer i; | |
524 | ||
525 | case (sync_name) { | |
526 | "io2x_sync_en": cmp2slow_clk_ratio = this.clk_pkt.cmp2io2x_ratio; | |
527 | "cmp_io_sync_en", "io_cmp_sync_en": cmp2slow_clk_ratio = this.clk_pkt.cmp2io_ratio; | |
528 | default: { | |
529 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("CCU_checker::check_io_io2x_sync_cnt_pw(%s) <= bad arg", sync_name)); | |
530 | this.error_cnt++; | |
531 | return; // ignore | |
532 | } | |
533 | } | |
534 | num_cycs_sync_low = cmp2slow_clk_ratio - 1; | |
535 | first_sync_timeout = cmp2slow_clk_ratio + 3; // sync pulse should be stable when ccu_rst_sync_stable asserted | |
536 | //---ensure 1st sync pulse occurs within time limit--- | |
537 | fork { | |
538 | repeat (first_sync_timeout) @(posedge clk_port.$cmp_pll_clk); | |
539 | if (got_1st_sync == 0) | |
540 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("not seen first %s in %0d cmp_pll_clk cycles", sync_name, first_sync_timeout)); | |
541 | } join none | |
542 | //---wait for 1st sync pulse--- | |
543 | case (sync_name) { | |
544 | "cmp_io_sync_en": @(posedge mon_port.$ccu_cmp_io_sync_en); // WARN: mon_port, not clk_port. | |
545 | "io_cmp_sync_en": @(posedge mon_port.$ccu_io_cmp_sync_en); // WARN: mon_port, not clk_port. | |
546 | "io2x_sync_en": @(posedge mon_port.$ccu_io2x_sync_en); // WARN: mon_port, not clk_port. | |
547 | } | |
548 | dbg.dispmon(this.dispScope, MON_INFO, psprintf("first %s sync pulse used as ref data point", sync_name)); | |
549 | got_1st_sync = 1; | |
550 | //---check sync pulse --- | |
551 | while (1) { | |
552 | for (i = 0; i < num_cycs_sync_low; i++) { | |
553 | @(posedge mon_port.$cmp_pll_clk); // WARN: mon_port | |
554 | case (sync_name) { | |
555 | "cmp_io_sync_en": sync_val = mon_port.$ccu_cmp_io_sync_en; | |
556 | "io_cmp_sync_en": sync_val = mon_port.$ccu_io_cmp_sync_en; | |
557 | "io2x_sync_en": sync_val = mon_port.$ccu_io2x_sync_en; | |
558 | } | |
559 | if (sync_val !== 1'b0) | |
560 | print_error_msg(err_lo_cnt++, psprintf("%s should be low, but not", sync_name)); | |
561 | } | |
562 | @(posedge mon_port.$cmp_pll_clk); // WARN: mon_port | |
563 | case (sync_name) { | |
564 | "cmp_io_sync_en": sync_val = mon_port.$ccu_cmp_io_sync_en; | |
565 | "io_cmp_sync_en": sync_val = mon_port.$ccu_io_cmp_sync_en; | |
566 | "io2x_sync_en": sync_val = mon_port.$ccu_io2x_sync_en; | |
567 | } | |
568 | if (sync_val !== 1'b1) | |
569 | print_error_msg(err_hi_cnt++, psprintf("%s should be high, but not", sync_name)); | |
570 | else if (this.verbose) | |
571 | print_debug_msg(debug_cnt++, psprintf("%s is %b. Expect: high", sync_name, sync_val)); | |
572 | } | |
573 | } | |
574 | ||
575 | //============================================================= | |
576 | // WHAT: check location of IO or IO2X sync pulse | |
577 | // ARGs: sync_name must be "cmp_io_sync_en", "io_cmp_sync_en" or "io2x_sync_en" | |
578 | // WARNING: since this checker, by default, does NOT check locations of io/io2x/dr | |
579 | // sync pulses, so this task is NOT maintained and may be out of date. | |
580 | //============================================================= | |
581 | task CCU_checker::check_io_io2x_sync_loc(string sync_name) { | |
582 | integer num_core_clks; | |
583 | bit sync_val; | |
584 | ||
585 | //--- sanity check: this task is NOT maintained --- | |
586 | dbg.dispmon(this.dispScope, MON_ERR, "ccu_checker::check_io_io2x_sync_loc() is NOT maintained. Do NOT used it"); | |
587 | ||
588 | //---go to posedge of io_out or io2x_out--- | |
589 | case (sync_name) { | |
590 | "cmp_io_sync_en": { | |
591 | num_core_clks = cmp_io_sync_loc; | |
592 | @(posedge mon_port.$ccu_cmp_io_sync_en); | |
593 | @(posedge clk_port.$ccu_io_out); | |
594 | } | |
595 | "io_cmp_sync_en": { | |
596 | num_core_clks = io_cmp_sync_loc; | |
597 | @(posedge mon_port.$ccu_io_cmp_sync_en); | |
598 | @(posedge clk_port.$ccu_io_out); | |
599 | } | |
600 | "io2x_sync_en": { | |
601 | num_core_clks = io2x_sync_loc; | |
602 | @(posedge mon_port.$ccu_io2x_sync_en); | |
603 | @(posedge clk_port.$ccu_io2x_out); | |
604 | } | |
605 | default: { | |
606 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("CCU_checker::check_io_io2x_sync_loc(sync_name=%s) <= bad arg", sync_name)); | |
607 | return; // ignore | |
608 | } | |
609 | } | |
610 | //---find cmp clk edge where it's aligned with io/io2x_out clk--- | |
611 | @(negedge clk_port.$cmp_pll_clk); | |
612 | repeat (num_core_clks) @(posedge clk_port.$cmp_pll_clk); | |
613 | //---check sync pulse location only once--- | |
614 | case (sync_name) { | |
615 | "cmp_io_sync_en": sync_val = mon_port.$ccu_cmp_io_sync_en; // WARN: mon_port, not clk_port. | |
616 | "io_cmp_sync_en": sync_val = mon_port.$ccu_io_cmp_sync_en; // WARN: mon_port, not clk_port. | |
617 | "io2x_sync_en": sync_val = mon_port.$ccu_io2x_sync_en; // WARN: mon_port, not clk_port. | |
618 | } | |
619 | if (sync_val !== 1'b1) | |
620 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("location of %s : expect high, but it is not", sync_name)); | |
621 | } | |
622 | ||
623 | //============================================================= | |
624 | // WHAT: check cmp_sys_sync_en and sys_cmp_sync_en | |
625 | // -Sync pulse width is one cmp clk. | |
626 | // -One sync pulse for every ref_clk cycle | |
627 | // ARGs: sync_name must be "cmp_sys_sync_en" or "sys_cmp_sync_en" | |
628 | // NOTE: there is one sys sync pulse for every ref clk cycle. The number | |
629 | // of cmp clk cycles in one ref clk cycles is equal to div2_effective. | |
630 | //============================================================= | |
631 | task CCU_checker::check_sys_sync_cnt_pw(string sync_name) { | |
632 | integer div2_eff = this.clk_pkt.pll_ctl[CCU__PLL_CTL__PLL_DIV2__MSB : CCU__PLL_CTL__PLL_DIV2__POS] + 1; | |
633 | integer first_sync_timeout; // timeout (in sys clk cycs) if not seen 1st sync | |
634 | integer got_1st_sync=0; // set to 1 when detect first sync pulse | |
635 | integer num_cycs_sync_low; // number of cmp clks when sync pulse is low | |
636 | integer err_lo_cnt=0, err_hi_cnt=0, debug_cnt=0; // error/debug counts of sync are low and high | |
637 | bit sync_val; // value of sync_en signal | |
638 | integer i; | |
639 | ||
640 | first_sync_timeout = 4 + 1; | |
641 | num_cycs_sync_low = div2_eff - 1; | |
642 | //---ensure 1st sync pulse occurs within time limit--- | |
643 | fork { | |
644 | repeat (first_sync_timeout) @(posedge clk_port.$sys_clk); | |
645 | if (got_1st_sync == 0) | |
646 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("not seen first %s in %0d sys clk cycles", sync_name, first_sync_timeout)); | |
647 | } join none | |
648 | //---wait for 1st sync pulse--- | |
649 | case (sync_name) { | |
650 | "cmp_sys_sync_en": @(posedge mon_port.$ccu_cmp_sys_sync_en); | |
651 | "sys_cmp_sync_en": @(posedge mon_port.$ccu_sys_cmp_sync_en); | |
652 | default: { | |
653 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("CCU_checker::check_sys_sync_cnt_pw(%s) <= bad arg", sync_name)); | |
654 | this.error_cnt++; | |
655 | return; // ignore | |
656 | } | |
657 | } | |
658 | dbg.dispmon(this.dispScope, MON_INFO, psprintf("first %s sync pulse used as ref data point", sync_name)); | |
659 | got_1st_sync = 1; | |
660 | //---check--- | |
661 | while (1) { | |
662 | for (i = 0; i < num_cycs_sync_low; i++) { | |
663 | @(posedge mon_port.$cmp_pll_clk); // WARN: mon_port | |
664 | case (sync_name) { | |
665 | "cmp_sys_sync_en": sync_val = mon_port.$ccu_cmp_sys_sync_en; | |
666 | "sys_cmp_sync_en": sync_val = mon_port.$ccu_sys_cmp_sync_en; | |
667 | } | |
668 | if (sync_val !== 1'b0) | |
669 | print_error_msg(err_lo_cnt++, psprintf("%s should be low, but not", sync_name)); | |
670 | } | |
671 | @(posedge mon_port.$cmp_pll_clk); // WARN: mon_port | |
672 | case (sync_name) { | |
673 | "cmp_sys_sync_en": sync_val = mon_port.$ccu_cmp_sys_sync_en; | |
674 | "sys_cmp_sync_en": sync_val = mon_port.$ccu_sys_cmp_sync_en; | |
675 | } | |
676 | if (sync_val !== 1'b1) | |
677 | print_error_msg(err_hi_cnt++, psprintf("%s should be high, but not", sync_name)); | |
678 | else if (this.verbose) | |
679 | print_debug_msg(debug_cnt++, psprintf("%s is %b. Expect: high", sync_name, sync_val)); | |
680 | } | |
681 | } | |
682 | ||
683 | //============================================================= | |
684 | // WHAT: check location of sys sync sync pulses at ccu boundary and inside RST block. | |
685 | // ARGs: sync_name must be "cmp_sys_sync_en" or "sys_cmp_sync_en" | |
686 | // NOTE: locations of cmp_sys and sys_cmp sync pulses are at phase 0. | |
687 | // RST flops them twice with l2clk, so they should be at phase 2 | |
688 | // inside RST block. | |
689 | //============================================================= | |
690 | task CCU_checker::check_sys_sync_loc(string sync_name) { | |
691 | integer i, debug_cnt=0; | |
692 | integer refclk_posedge, sync_is_high, cmp_clk_per; | |
693 | integer max_clks_skew; // max skew allowed between ccu.ref_clk and ccu.cmp_pll_clk | |
694 | bit rst_sync_val, rst_sync_nchecks; // for checking sys sync inside RST blk | |
695 | ||
696 | @(posedge clk_port.$cmp_pll_clk); | |
697 | cmp_clk_per = get_time(LO); | |
698 | @(posedge clk_port.$cmp_pll_clk); | |
699 | cmp_clk_per = get_time(LO) - cmp_clk_per; | |
700 | max_clks_skew = cmp_clk_per / 8; | |
701 | ||
702 | //--- check sys sync pulses inside the RST block --- | |
703 | rst_sync_nchecks = 0; | |
704 | fork { | |
705 | while (1) { | |
706 | case (sync_name) { | |
707 | "cmp_sys_sync_en": @(posedge mon_port.$ccu_cmp_sys_sync_en); // sys sync is high at CCU boundary | |
708 | "sys_cmp_sync_en": @(posedge mon_port.$ccu_sys_cmp_sync_en); // sys sync is high at CCU boundary | |
709 | } | |
710 | @(negedge clk_port.$cmp_pll_clk); // avoid the skew between ccu.cmp_pll_clk and rst.l2clk | |
711 | repeat (2) @(posedge ccu_clks_internal_port.$l2clk); // RST flops sys sync pulse twice | |
712 | case (sync_name) { | |
713 | "cmp_sys_sync_en": rst_sync_val = ccu_clks_internal_port.$rst_ccu_cmp_sys_sync_en3; | |
714 | "sys_cmp_sync_en": rst_sync_val = ccu_clks_internal_port.$rst_ccu_sys_cmp_sync_en3; | |
715 | } | |
716 | if (rst_sync_val !== 1'b1) | |
717 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("check sys sync location: rst.rst_%s%s is %b, expect: high", sync_name, 3, rst_sync_val)); | |
718 | rst_sync_nchecks++; | |
719 | if (rst_sync_nchecks > 5) break; | |
720 | } | |
721 | } join none | |
722 | //---check sys sync location--- | |
723 | for (i = 0; i < 5; i++) { // check few times is enough since check_sys_sync_cnt_pw() checks sync pulse spacing/count. | |
724 | //---check ccu_cmp_sys_en or ccu_sys_cmp_sync_en--- | |
725 | fork { | |
726 | @(posedge clk_port.$ref_clk); | |
727 | refclk_posedge = get_time(LO); | |
728 | } | |
729 | { | |
730 | case (sync_name) { | |
731 | "cmp_sys_sync_en": @(posedge mon_port.$ccu_cmp_sys_sync_en); | |
732 | "sys_cmp_sync_en": @(posedge mon_port.$ccu_sys_cmp_sync_en); | |
733 | } | |
734 | sync_is_high = get_time(LO); | |
735 | if (this.verbose) | |
736 | print_debug_msg(debug_cnt++, psprintf("check sys sync location: %s is high", sync_name)); | |
737 | } join | |
738 | if (abs(refclk_posedge - sync_is_high) > max_clks_skew) | |
739 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("check sys sync location: ref_clk_posedge=%0d, %s high at %0d <= not correct location", | |
740 | refclk_posedge, sync_name, sync_is_high)); | |
741 | } | |
742 | } | |
743 | ||
744 | //============================================================= | |
745 | // WHAT: check dr_sync_en toggling | |
746 | //============================================================= | |
747 | task CCU_checker::check_dr_sync_toggle() { | |
748 | integer old_cyc, new_cyc, temp_cyc, sync_cnts; | |
749 | integer err_cnt=0; | |
750 | ||
751 | @(posedge clk_port.$dr_pll_clk); | |
752 | old_cyc = get_cycle(clk_port.$ccu_dr_sync_en); | |
753 | while (1) { | |
754 | @(posedge clk_port.$dr_pll_clk); | |
755 | temp_cyc = get_cycle(clk_port.$ccu_dr_sync_en); | |
756 | @(negedge clk_port.$cmp_pll_clk); // account the case of dr_pll_clk and cmp_pll_clk are PERFECTLY aligned | |
757 | new_cyc = get_cycle(clk_port.$ccu_dr_sync_en); | |
758 | sync_cnts = new_cyc - old_cyc; | |
759 | if ((sync_cnts < 1) || (sync_cnts > 2)) | |
760 | print_error_msg(err_cnt++, psprintf("number of dr_sync pulses in 1 DR clk cyc + 0.5 cmp clk cycle: %0d. Expect: 1 or 2", sync_cnts)); | |
761 | old_cyc = temp_cyc; | |
762 | } | |
763 | } | |
764 | ||
765 | //============================================================= | |
766 | // WHAT: check pulse width of dr_sync_en | |
767 | //============================================================= | |
768 | task CCU_checker::check_dr_sync_pw() { | |
769 | bit sync_val; | |
770 | integer err_cnt=0; | |
771 | ||
772 | while (1) { | |
773 | @(posedge mon_port.$ccu_dr_sync_en); | |
774 | @(posedge mon_port.$cmp_pll_clk); | |
775 | sync_val = mon_port.$ccu_dr_sync_en; | |
776 | if (sync_val !== 1'b0) | |
777 | print_error_msg(err_cnt++, psprintf("dr_sync_en is high for more than 1 cmp_clk cycles")); | |
778 | } | |
779 | } | |
780 | ||
781 | //============================================================= | |
782 | // WHAT: check locations of dr_sync_en at CCU boundary. | |
783 | // This indirectly check DR sync pulse is one cmp clk. | |
784 | //============================================================= | |
785 | task CCU_checker::check_dr_sync_loc() { | |
786 | integer div2_eff = this.clk_pkt.pll_ctl[CCU__PLL_CTL__PLL_DIV2__MSB : CCU__PLL_CTL__PLL_DIV2__POS] + 1; | |
787 | integer phase_num; // phase number: 0, 1, .., div2_eff. 0 when clks are aligned | |
788 | bit dr_sync_val, expected_dr_sync_val; | |
789 | integer err_cnt=0, debug_cnt=0; | |
790 | ||
791 | dbg.dispmon(this.dispScope, MON_ERR, "CCU_checker::check_dr_sync_loc() is NOT maintained. Do NOT used it"); | |
792 | ||
793 | //---find first phase--- | |
794 | @(posedge mon_port.$ccu_vco_aligned); // note: vco_aligned is generated 1 clk earlier | |
795 | @(posedge clk_port.$cmp_pll_clk); // this is real VCO aligned | |
796 | phase_num = 0; // first clock phase | |
797 | //--- checking DR sync location--- | |
798 | while (1) { | |
799 | dr_sync_val = mon_port.$ccu_dr_sync_en; | |
800 | if ((phase_num == this.dr_sync_loc[0]) || (phase_num == this.dr_sync_loc[1]) | |
801 | || (phase_num == this.dr_sync_loc[2]) || (phase_num == this.dr_sync_loc[3])) | |
802 | expected_dr_sync_val = 1'b1; | |
803 | else | |
804 | expected_dr_sync_val = 1'b0; | |
805 | if (dr_sync_val != expected_dr_sync_val) | |
806 | print_error_msg(err_cnt++, psprintf("ccu_dr_sync_en=%b, expect: %b", dr_sync_val, expected_dr_sync_val)); | |
807 | if (this.verbose) | |
808 | print_debug_msg(debug_cnt++, psprintf("ccu_dr_sync_en=%b, expect: %b", dr_sync_val, expected_dr_sync_val)); | |
809 | @(posedge clk_port.$cmp_pll_clk); // this is real VCO aligned | |
810 | phase_num++; | |
811 | if (phase_num == div2_eff) | |
812 | phase_num = 0; | |
813 | } | |
814 | } | |
815 | ||
816 | //######################################################################## | |
817 | //######################################################################## | |
818 | //####### supporting subroutines ############# | |
819 | //######################################################################## | |
820 | //######################################################################## | |
821 | ||
822 | //============================================================= | |
823 | // WHAT: print error message | |
824 | // NOTE: | |
825 | // err_cnt is local error count of a particular error type. | |
826 | // this.error_cnt is global error count of this vera class. | |
827 | //============================================================= | |
828 | task CCU_checker::print_error_msg(integer err_cnt, string error_msg) { | |
829 | if (err_cnt <= this.max_error_printed) | |
830 | dbg.dispmon(this.dispScope, MON_ERR, error_msg); | |
831 | this.error_cnt++; // increment global error count | |
832 | } | |
833 | ||
834 | //============================================================= | |
835 | // WHAT: print messages for debug | |
836 | //============================================================= | |
837 | task CCU_checker::print_debug_msg(integer debug_cnt, string debug_msg) { | |
838 | if (debug_cnt <= max_debug_printed) | |
839 | dbg.dispmon(this.dispScope, MON_ALWAYS, debug_msg); | |
840 | } | |
841 | ||
842 | //============================================================= | |
843 | // Return 1 if value is outside min and max; otherwise, return 0. | |
844 | //============================================================= | |
845 | function integer CCU_checker::is_outside_min_max(integer min_val, integer value, integer max_val) { | |
846 | is_outside_min_max = ((value < min_val) || (value > max_val))? 1 : 0; | |
847 | } | |
848 | ||
849 | //============================================================= | |
850 | // WHAT: compute expected locations of DR sync at CCU boundary | |
851 | //============================================================= | |
852 | task CCU_checker::compute_expected_dr_sync_loc() { | |
853 | integer div2_effective = this.clk_pkt.pll_ctl[CCU__PLL_CTL__PLL_DIV2__MSB : CCU__PLL_CTL__PLL_DIV2__POS] + 1; | |
854 | integer i; | |
855 | for (i = 0; i < 4; i++) { | |
856 | this.dr_sync_loc[i] = this.clk_pkt.dr_sync_loc[i] - this.total_stage_flops; // back track from flop header | |
857 | if (this.dr_sync_loc[i] < 0) // cross ref_clk boundary to previous ref_clk cycle | |
858 | this.dr_sync_loc[i] = this.dr_sync_loc[i] + div2_effective; | |
859 | } | |
860 | } | |
861 | ||
862 | //============================================================= | |
863 | // WHAT: show expected DR sync locations at CCU boundary | |
864 | //============================================================= | |
865 | task CCU_checker::show_dr_sync_loc() { | |
866 | dbg.dispmon(this.dispScope, MON_INFO, psprintf("Expected DR sync locations at CCU output: %0d, %0d, %0d and %0d", | |
867 | this.dr_sync_loc[0], this.dr_sync_loc[1], this.dr_sync_loc[2], this.dr_sync_loc[3])); | |
868 | } | |
869 | ||
870 | //######################################################################## | |
871 | //######################################################################## | |
872 | //####### tasks for clk stretch monitor/checker ############# | |
873 | //######################################################################## | |
874 | //######################################################################## | |
875 | ||
876 | //============================================================= | |
877 | // WHAT: clock stretch checker: public/wrapper task | |
878 | // USAGE: diag should call this task before TCU initiates clk stretch (ie. assert tcu_ccu_clk_stretch) | |
879 | //============================================================= | |
880 | task CCU_checker::start_clk_stretch_checker() { | |
881 | bit sig; | |
882 | sig = mon_port.$tcu_ccu_clk_stretch async; | |
883 | if (sig === 1'b1) { | |
884 | dbg.dispmon(this.dispScope, MON_ERR, "tcu_ccu_clk_stretch is 1. Call start_clk_stretch_checker() too late. Exit task"); | |
885 | return; | |
886 | } | |
887 | fork | |
888 | clk_stretch_checker(); | |
889 | join none | |
890 | } | |
891 | ||
892 | //============================================================= | |
893 | // WHAT: clock strech checker: main task | |
894 | //============================================================= | |
895 | task CCU_checker::clk_stretch_checker() { | |
896 | bit [63:0] pll_ctl; // effective value of pll_ctl | |
897 | bit st_phase_hi; | |
898 | integer st_delay_cmp, st_delay_dr; // must be 40, 80, 120 or 160ps | |
899 | integer max_cycs = 12; // number of clk cycs to be checked/monitored | |
900 | integer max_pw_dev = 10; // max deviation in pulse width. Assume clk ctrech diags only use nice pll_div, so no rounding error in clk per. | |
901 | integer st_cmpclk_time, st_ioclk_time, st_io2xclk_time, st_drclk_time; // time when clk is stretched | |
902 | integer cmp2iox_clk_skew_max = 2; // clk skew between cmp and io/io2x clk | |
903 | integer cmp_st_cyc, dr_st_cyc; // cmp/dr cyc when tcu asserts tcu_ccu_clk_stretch | |
904 | ||
905 | dbg.dispmon(this.dispScope, MON_INFO, "starts clk_stretch checker"); | |
906 | //--- wait for TCU initiates the clk stretch --- | |
907 | @(posedge mon_port.$tcu_ccu_clk_stretch); | |
908 | dbg.dispmon(this.dispScope, MON_INFO, "tcu asserts tcu_ccu_clk_stretch to initiate clk stretch"); | |
909 | cmp_st_cyc = get_cycle(this.clk_port.$cmp_pll_clk); // cmp cycle when TCU asserts clk stretch sig | |
910 | dr_st_cyc = get_cycle(this.clk_port.$dr_pll_clk); // dr cycle when TCU asserts clk stretch sig | |
911 | //--- compute expected values --- | |
912 | pll_ctl = ccu_states.get_pll_ctl(); | |
913 | st_phase_hi = pll_ctl[CCU__PLL_CTL__ST_PHASE_HI__POS]; | |
914 | case (pll_ctl[CCU__PLL_CTL__ST_DELAY_CMP__MSB : CCU__PLL_CTL__ST_DELAY_CMP__POS]) { | |
915 | 2'b00: st_delay_cmp = 40; // in ps | |
916 | 2'b01: st_delay_cmp = 80; | |
917 | 2'b10: st_delay_cmp = 120; | |
918 | 2'b11: st_delay_cmp = 160; | |
919 | } | |
920 | case (pll_ctl[CCU__PLL_CTL__ST_DELAY_DR__MSB : CCU__PLL_CTL__ST_DELAY_DR__POS]) { | |
921 | 2'b00: st_delay_dr = 40; // in ps | |
922 | 2'b01: st_delay_dr = 80; | |
923 | 2'b10: st_delay_dr = 120; | |
924 | 2'b11: st_delay_dr = 160; | |
925 | } | |
926 | //--- check clk stretch for cmp/dr/io/io2x clk --- | |
927 | fork | |
928 | st_cmpclk_time = this.clk_stretch_chkr_1clk("cmp_clk", st_phase_hi, st_delay_cmp, max_pw_dev, max_cycs, cmp_st_cyc); | |
929 | st_ioclk_time = this.clk_stretch_chkr_1clk("io_out", st_phase_hi, st_delay_cmp, max_pw_dev, max_cycs, cmp_st_cyc); | |
930 | st_io2xclk_time = this.clk_stretch_chkr_1clk("io2x_out", st_phase_hi, st_delay_cmp, max_pw_dev, max_cycs,cmp_st_cyc); | |
931 | st_drclk_time = this.clk_stretch_chkr_1clk("dr_clk", st_phase_hi, st_delay_dr, max_pw_dev, max_cycs, dr_st_cyc); | |
932 | join | |
933 | //--- check cmp/io/io2x clks stretched in the same cycle --- | |
934 | //if ((abs(st_cmpclk_time - st_ioclk_time) > cmp2iox_clk_skew_max) || (abs(st_cmpclk_time - st_io2xclk_time) > cmp2iox_clk_skew_max)) | |
935 | // dbg.dispmon(this.dispScope, MON_ERR, psprintf("clk stretch at time: cmp_clk=%0d, io_out=%0d, io2x_out=%0d <= not at same cycle", | |
936 | // st_cmpclk_time, st_ioclk_time, st_io2xclk_time)); | |
937 | } | |
938 | ||
939 | //============================================================= | |
940 | // WHAT: clock strech checker: supporting task checking one clock | |
941 | // ARGS: clkname: "cmp_clk", "io_out", "io2x_out" or "dr_clk" | |
942 | //============================================================= | |
943 | function integer CCU_checker::clk_stretch_chkr_1clk(string clkname, bit st_phase_hi, integer st_delay, integer max_pw_dev, integer max_cycs, integer st_start_cyc) { | |
944 | integer i, st_cnt=0, st_time=0; // counting clk stretch, stretch time | |
945 | integer pw_nom, pw_hi, pw_lo, pw_hi_min, pw_hi_max, pw_lo_min, pw_lo_max; // pw: pulse width | |
946 | integer pw_hi_st_min, pw_hi_st_max, pw_lo_st_min, pw_lo_st_max; // pulse width limits for clk stretch | |
947 | integer p_edge, n_edge, prev_p_edge; // clk posedge and negedge | |
948 | CCU_clk_packet temp_clk_pkt; | |
949 | integer st_cyc; // stretch cycle | |
950 | integer st_pipe_del; // stretch pipeline delay | |
951 | ||
952 | //--- monitor clk stretch pipeline latency ---- | |
953 | fork { | |
954 | wait_var(st_cnt); | |
955 | if ((clkname == "cmp_clk") || (clkname == "dr_clk")) { | |
956 | case (clkname) { | |
957 | "cmp_clk": st_cyc = get_cycle(clk_port.$cmp_pll_clk); | |
958 | "dr_clk": st_cyc = get_cycle(clk_port.$dr_pll_clk); | |
959 | } | |
960 | st_pipe_del = st_cyc - st_start_cyc; | |
961 | if ((st_pipe_del < 6) || (st_pipe_del > 8)) | |
962 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: clk stretch pipeline delay %0d (min: 6: max: 8)", clkname, st_pipe_del)); | |
963 | else | |
964 | dbg.dispmon(this.dispScope, MON_INFO, psprintf("%s: clk stretch pipeline delay %0d (min: 6: max: 8)", clkname, st_pipe_del)); | |
965 | } | |
966 | } join none | |
967 | //---compute expected pw ---- | |
968 | temp_clk_pkt = ccu_states.get_exp_clk_pkt(); // warn: need this since diag may not call this.start_checker(), so this.clk_pkt is NULL | |
969 | case (clkname) { | |
970 | "cmp_clk": pw_nom = temp_clk_pkt.cmp_clk_per_nom / 2; | |
971 | "io_out": pw_nom = temp_clk_pkt.io_out_per_nom / 2; | |
972 | "io2x_out": pw_nom = temp_clk_pkt.io2x_out_per_nom / 2; | |
973 | "dr_clk": pw_nom = temp_clk_pkt.dr_clk_per_nom / 2; | |
974 | } | |
975 | pw_hi_min = pw_nom - max_pw_dev; | |
976 | pw_hi_max = pw_nom + max_pw_dev; | |
977 | pw_lo_min = pw_nom - max_pw_dev; | |
978 | pw_lo_max = pw_nom + max_pw_dev; | |
979 | pw_hi_st_min = pw_nom + st_delay - max_pw_dev; | |
980 | pw_hi_st_max = pw_nom + st_delay + max_pw_dev; | |
981 | pw_lo_st_min = pw_nom + st_delay - max_pw_dev; | |
982 | pw_lo_st_max = pw_nom + st_delay + max_pw_dev; | |
983 | //---check --- | |
984 | case (clkname) { | |
985 | "cmp_clk": @(posedge clk_port.$cmp_pll_clk); | |
986 | "io_out": @(posedge clk_port.$ccu_io_out); | |
987 | "io2x_out": @(posedge clk_port.$ccu_io2x_out); | |
988 | "dr_clk": @(posedge clk_port.$dr_pll_clk); | |
989 | } | |
990 | p_edge = get_time(LO); | |
991 | for (i = 0; i < max_cycs; i++) { | |
992 | prev_p_edge = p_edge; // save previous posedge | |
993 | case (clkname) { | |
994 | "cmp_clk": @(negedge clk_port.$cmp_pll_clk); | |
995 | "io_out": @(negedge clk_port.$ccu_io_out); | |
996 | "io2x_out": @(negedge clk_port.$ccu_io2x_out); | |
997 | "dr_clk": @(negedge clk_port.$dr_pll_clk); | |
998 | } | |
999 | n_edge = get_time(LO); | |
1000 | pw_hi = n_edge - p_edge; | |
1001 | case (clkname) { | |
1002 | "cmp_clk": @(posedge clk_port.$cmp_pll_clk); | |
1003 | "io_out": @(posedge clk_port.$ccu_io_out); | |
1004 | "io2x_out": @(posedge clk_port.$ccu_io2x_out); | |
1005 | "dr_clk": @(posedge clk_port.$dr_pll_clk); | |
1006 | } | |
1007 | p_edge = get_time(LO); | |
1008 | pw_lo = p_edge - n_edge; | |
1009 | if ((clkname == "io_out") || (clkname == "io2x_out")) { // can happen either hi or low phase | |
1010 | if (pw_hi > pw_hi_max) { // assume this is clk stretch cycle | |
1011 | st_cnt++; | |
1012 | if (st_cnt == 1) { | |
1013 | st_time = prev_p_edge; | |
1014 | if (is_outside_min_max(pw_hi_st_min, pw_hi, pw_hi_st_max)) | |
1015 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_hi=%0d (stretched), expect: min=%0d, max=%0d", clkname, pw_hi, pw_hi_st_min, pw_hi_st_max)); | |
1016 | else | |
1017 | dbg.dispmon(this.dispScope, MON_INFO, psprintf("%s: pw_hi=%0d (stretched), expect: min=%0d, max=%0d", clkname, pw_hi, pw_hi_st_min, pw_hi_st_max)); | |
1018 | } | |
1019 | else | |
1020 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_hi=%0d exceeds pw_hi_max=%0d, but clk stretch already happened at %0d", clkname, pw_hi, pw_hi_max, st_time)); | |
1021 | } | |
1022 | else { // assume this is not clk stretch cycle | |
1023 | if (is_outside_min_max(pw_hi_min, pw_hi, pw_hi_max)) | |
1024 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_hi=%0d, expect: min=%0d, max=%0d", clkname, pw_hi, pw_hi_min, pw_hi_max)); | |
1025 | } | |
1026 | if (pw_lo > pw_lo_max) { // assume this is clk stretch cycle | |
1027 | st_cnt++; | |
1028 | if (st_cnt == 1) { | |
1029 | st_time = prev_p_edge; | |
1030 | if (is_outside_min_max(pw_lo_st_min, pw_lo, pw_lo_st_max)) | |
1031 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_lo=%0d (stretched), expect: min=%0d, max=%0d", clkname, pw_lo, pw_lo_st_min, pw_lo_st_max)); | |
1032 | else | |
1033 | dbg.dispmon(this.dispScope, MON_INFO, psprintf("%s: pw_lo=%0d (stretched), expect: min=%0d, max=%0d", clkname, pw_lo, pw_lo_st_min, pw_lo_st_max)); | |
1034 | } | |
1035 | else | |
1036 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_lo=%0d exceeds pw_lo_max=%0d, but clk stretch already happened at %0d", clkname, pw_lo, pw_lo_max, st_time)); | |
1037 | } | |
1038 | else { // assume this is not clk stretch cycle | |
1039 | if (is_outside_min_max(pw_lo_min, pw_lo, pw_lo_max)) | |
1040 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_lo=%0d, expect: min=%0d, max=%0d", clkname, pw_lo, pw_lo_min, pw_lo_max)); | |
1041 | } | |
1042 | } | |
1043 | else { // cmp or dr clk | |
1044 | if (st_phase_hi) { | |
1045 | if (is_outside_min_max(pw_lo_min, pw_lo, pw_lo_max)) // stretch high phase, so pw_lo must not change | |
1046 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_lo=%0d, expect: min=%0d, max=%0d", clkname, pw_lo, pw_lo_min, pw_lo_max)); | |
1047 | if (pw_hi > pw_hi_max) { // assume this is clk stretch cycle | |
1048 | st_cnt++; | |
1049 | if (st_cnt == 1) { | |
1050 | st_time = prev_p_edge; | |
1051 | if (is_outside_min_max(pw_hi_st_min, pw_hi, pw_hi_st_max)) | |
1052 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_hi=%0d (stretched), expect: min=%0d, max=%0d", clkname, pw_hi, pw_hi_st_min, pw_hi_st_max)); | |
1053 | else | |
1054 | dbg.dispmon(this.dispScope, MON_INFO, psprintf("%s: pw_hi=%0d (stretched), expect: min=%0d, max=%0d", clkname, pw_hi, pw_hi_st_min, pw_hi_st_max)); | |
1055 | } | |
1056 | else | |
1057 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_hi=%0d exceeds pw_hi_max=%0d, but clk stretch already happened at %0d", clkname, pw_hi, pw_hi_max, st_time)); | |
1058 | } | |
1059 | else // assume this is not clk stretch cycle | |
1060 | if (is_outside_min_max(pw_hi_min, pw_hi, pw_hi_max)) | |
1061 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_hi=%0d, expect: min=%0d, max=%0d", clkname, pw_hi, pw_hi_min, pw_hi_max)); | |
1062 | } | |
1063 | else { | |
1064 | if (is_outside_min_max(pw_hi_min, pw_hi, pw_hi_max)) // stretch low phase, so pw_hi must not change | |
1065 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_hi=%0d, expect: min=%0d, max=%0d", clkname, pw_hi, pw_hi_min, pw_hi_max)); | |
1066 | if (pw_lo > pw_lo_max) { // assume this is clk stretch cycle | |
1067 | st_cnt++; | |
1068 | if (st_cnt == 1) { | |
1069 | st_time = prev_p_edge; | |
1070 | if (is_outside_min_max(pw_lo_st_min, pw_lo, pw_lo_st_max)) | |
1071 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_lo=%0d (stretched), expect: min=%0d, max=%0d", clkname, pw_lo, pw_lo_st_min, pw_lo_st_max)); | |
1072 | else | |
1073 | dbg.dispmon(this.dispScope, MON_INFO, psprintf("%s: pw_lo=%0d (stretched), expect: min=%0d, max=%0d", clkname, pw_lo, pw_lo_st_min, pw_lo_st_max)); | |
1074 | } | |
1075 | else | |
1076 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_lo=%0d exceeds pw_lo_max=%0d, but clk stretch already happened at %0d", clkname, pw_lo, pw_lo_max, st_time)); | |
1077 | } | |
1078 | else // assume this is not clk stretch cycle | |
1079 | if (is_outside_min_max(pw_lo_min, pw_lo, pw_lo_max)) | |
1080 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: pw_lo=%0d, expect: min=%0d, max=%0d", clkname, pw_lo, pw_lo_min, pw_lo_max)); | |
1081 | } // end of else | |
1082 | } // end of else | |
1083 | } // end of for() | |
1084 | if (st_cnt <= 0) | |
1085 | dbg.dispmon(this.dispScope, MON_ERR, psprintf("%s: clk stretch not happens in %0d cycles after tcu asserts clk_stretch", clkname, max_cycs)); | |
1086 | clk_stretch_chkr_1clk = st_time; // return value | |
1087 | } |