Initial commit of OpenSPARC T2 design and verification files.
[OpenSPARC-T2-DV] / verif / env / tcu / vera / classes / cluster_hdr_chkr.vr
// ========== Copyright Header Begin ==========================================
//
// OpenSPARC T2 Processor File: cluster_hdr_chkr.vr
// Copyright (C) 1995-2007 Sun Microsystems, Inc. All Rights Reserved
// 4150 Network Circle, Santa Clara, California 95054, U.S.A.
//
// * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// For the avoidance of doubt, and except that if any non-GPL license
// choice is available it will apply instead, Sun elects to use only
// the General Public License version 2 (GPLv2) at this time for any
// software where a choice of GPL license versions is made
// available with the language indicating that GPLv2 or any later version
// may be used, or where a choice of which version of the GPL is applied is
// otherwise unspecified.
//
// Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
// CA 95054 USA or visit www.sun.com if you need additional information or
// have any questions.
//
// ========== Copyright Header End ============================================
#include <vera_defines.vrh>
#include "std_display_class.vrh"
#include "ccu_top.vri"
#include "cluster_hdr_top.vri"
#include "ccu_clk_packet.vrh"
#include "ccu_clks_states.vrh"
class CLUSTER_hdr_chkr {
local string name; // name of the cluster header
local string dispScope; // standard display: display scope
local integer verbose; // 0: disable verbose mode; otherwise, enable
//---ports and classes---
local CLKGEN_port clkgen_port; // port of this cluster header
local CLKGEN_port clkgen_dr_port; // DR cluster hdr for checking DR sync locations
local CLKGEN_port clkgen_io_port; // IO cluster hdr for checking IO sync locations
local CLKGEN_port clkgen_io2x_port; // IO2X cluster hdr for checking IO2X sync locations
local CCU_clk_port ccu_clk_port;
local StandardDisplay dbg; // Standard display for printing
local CCU_clks_states ccu_states; // keep track CCU states
//--- vars for start/stop checker/checking----
local integer running; // 0: not running; otherwise, running
local event stop_checker_e; // triggered by stop_checker() to terminate start_checker()
local event stop_checking_e; // triggered by stop_checking() to terminate start_checking()
local event clkstop_e; // triggered when tcu_clk_stop detected
//---vars for error reporting---
local integer error_cnt; // Global error count. Init to 0. WARN: negative if exeed max integer
local integer max_error_printed; // stop print out error if num errors exceed this value
local integer max_debug_printed; // stop print out debug msg if debug cnt exceeds this value
local integer dr_sync_loc_err_cnt, dr_sync_loc_debug_cnt;
//---vars for cluster parameters---
local cluster_hdr_type_e hdr_type; // cluster type
local CCU_clk_packet clk_pkt; // clock-related paramters
local integer l2clk_per_nom, l2clk_per_min, l2clk_per_max; // l2clk period: nominal, min, max
local integer gclk_per_nom, gclk_per_min, gclk_per_max; // gclk period: nominal, min, max
local integer pw_dev; // deviation of l2clk pulse width (in %)
local integer cmpslow_sync_is_io2x; // cmp_slow_sync_en is actually io2x_sync_en (MIO block does this)
//---vars for enable/disable checking sync pulses----
local integer chk_cmpslow_sync, chk_slowcmp_sync; // 0: not check; otherwise, check
local integer chk_dr_sync, chk_io2x_sync; // 0: not check; otherwise, check
local integer chk_iosync_loc, chk_io2xsync_loc, chk_drsync_loc; // 0: not check; otherwise, check
//--public tasks---
task new(string name, StandardDisplay dbg,
CCU_clks_states ccu_states, cluster_hdr_type_e hdr_type,
CLKGEN_port clkgen_port, CLKGEN_port clkgen_dr_port,
CLKGEN_port clkgen_io_port, CLKGEN_port clkgen_io2x_port,
integer chk_cmpslow_sync,
integer chk_slowcmp_sync, integer chk_dr_sync,
integer chk_io2x_sync,
integer cmpslow_sync_is_io2x=0, integer start_it=1);
task start_checker();
task stop_checker();
//--control tasks---
local task start_checking();
local task stop_checking();
local task check_l2clk_and_sync_pulses();
//---tasks do the checks---
local task check_gclk_toggle();
local task check_gclk_freq_dutycycle();
local task check_l2clk_toggle();
local task check_l2clk_freq_dutycyc();
local task check_iox_sync(string sync_name);
local task check_dr_sync();
local task check_cmp_io_sync_loc(integer nchecks=5);
local task check_io_cmp_sync_loc(integer nchecks=5);
local task check_io2x_sync_loc(integer nchecks=5);
local task check_dr_sync_loc(integer nchecks=20);
local task check_next_dr_sync_loc();
//---- supporting subroutines ----
local task compute_expected_params();
local task print_error_msg(integer err_cnt, string error_msg);
local task print_debug_msg(integer debug_cnt, string debug_msg);
local function integer is_outside_min_max(integer min_val, integer value, integer max_val);
local function integer compute_abs(integer n);
local task wait_1st_posedge(string sig_name, integer timeout_val);
}
//################################################################
//######### implementation of subroutines ###########
//################################################################
task CLUSTER_hdr_chkr::new(string name, StandardDisplay dbg,
CCU_clks_states ccu_states, cluster_hdr_type_e hdr_type,
CLKGEN_port clkgen_port, CLKGEN_port clkgen_dr_port,
CLKGEN_port clkgen_io_port, CLKGEN_port clkgen_io2x_port,
integer chk_cmpslow_sync,
integer chk_slowcmp_sync, integer chk_dr_sync,
integer chk_io2x_sync,
integer cmpslow_sync_is_io2x=0, integer start_it=1) {
//---from arg list---
this.name = name;
this.dbg = dbg;
this.ccu_states = ccu_states;
this.hdr_type = hdr_type;
this.clkgen_port = clkgen_port;
this.clkgen_dr_port = clkgen_dr_port;
this.clkgen_io_port = clkgen_io_port;
this.clkgen_io2x_port = clkgen_io2x_port;
this.chk_cmpslow_sync = chk_cmpslow_sync;
this.chk_slowcmp_sync = chk_slowcmp_sync;
this.chk_dr_sync = chk_dr_sync;
this.chk_io2x_sync = chk_io2x_sync;
this.cmpslow_sync_is_io2x = cmpslow_sync_is_io2x;
//---the rest---
this.dispScope = this.name;
this.verbose = 0; //default is disabling verbose mode
this.ccu_clk_port = ccu_clk_bind;
this.running = 0;
this.error_cnt = 0;
this.max_error_printed = 20;
this.max_debug_printed = 20;
this.dr_sync_loc_err_cnt = 0; // reset error counter
this.dr_sync_loc_debug_cnt = 0; // reset debug counter
this.clk_pkt = null; // init to illegal value
this.l2clk_per_nom = -1; // init to illegal value
this.l2clk_per_min = -1; // init to illegal value
this.l2clk_per_max = -1; // init to illegal value
this.gclk_per_nom = -1; // init to illegal value
this.gclk_per_min = -1; // init to illegal value
this.gclk_per_max = -1; // init to illegal value
this.pw_dev = -1; // init to illegal value
//--checking sync pulse locations---
this.chk_iosync_loc = 1; // by default: checkin sync locations
this.chk_io2xsync_loc = 1;
this.chk_drsync_loc = 1;
//---override if runtime options specified---
if (get_plus_arg(CHECK, "clstrHdrChkr_maxError=n"))
this.max_error_printed = get_plus_arg(NUM, "clstrHdrChkr_maxError=n");
if (get_plus_arg(CHECK, "clstrHdrChkr_noSyncLocChk")) {
this.chk_iosync_loc = 0;
this.chk_io2xsync_loc = 0;
this.chk_drsync_loc = 0;
}
if (get_plus_arg(CHECK, "clstrHdrChkr_verbose"))
this.verbose = 1;
//---start background threads---
if (start_it)
this.start_checker();
}
//=============================================================
// WHAT: start the checker
//=============================================================
task CLUSTER_hdr_chkr::start_checker() {
reg bit_value;
if (this.running)
return; // it's already running
dbg.dispmon(this.dispScope, MON_INFO, "starts ...");
this.running = 1;
fork {
fork {
while (1) {
bit_value = ccu_clk_port.$ccu_rst_sync_stable;
if (bit_value !== 1'b1)
@(posedge ccu_clk_port.$ccu_rst_sync_stable); // Sync is stable, so start checking
delay(3); // avoid race condition with ccu_states.get_clk_pkt()
this.clk_pkt = ccu_states.get_exp_clk_pkt();
compute_expected_params();
start_checking();
@(negedge ccu_clk_port.$rst_ccu_pll_); // reset PLL
stop_checking();
}
}
join none
sync(ALL, this.stop_checker_e); // stop_checker() triggers this event
terminate;
dbg.dispmon(this.dispScope, MON_INFO, "stopped");
this.running = 0; // checker is not running
} join none
}
//=============================================================
// WHAT: stop the checker
//=============================================================
task CLUSTER_hdr_chkr::stop_checker() {
if (this.running)
trigger(this.stop_checker_e); // this event terminates start_checker()
}
//=============================================================
// Start to check
//=============================================================
task CLUSTER_hdr_chkr::start_checking() {
dbg.dispmon(this.dispScope, MON_INFO, "start checking ...");
fork {
fork
{
check_gclk_toggle();
}
{
check_gclk_freq_dutycycle();
}
{
check_l2clk_and_sync_pulses();
}
join none
sync(ALL, this.stop_checking_e); // stop_checking() triggers this event
terminate;
dbg.dispmon(this.dispScope, MON_INFO, "checking stopped");
} join none
}
//=============================================================
// WHAT: stop checking by terminating start_checking()
//=============================================================
task CLUSTER_hdr_chkr::stop_checking() {
trigger(this.stop_checking_e); // this event terminates start_checking()
}
//=============================================================
// WHAT: control task for start/stop checking l2clk and sync pulses
//=============================================================
task CLUSTER_hdr_chkr::check_l2clk_and_sync_pulses() {
event stop_check_l2clk_sync;
while (1) {
//----not checking during clock stop or cluster_arst_l----
while (1) {
if ((clkgen_port.$tcu_clk_stop__gclk == 1'b0)
&& (clkgen_port.$cluster_arst_l__gclk == 1'b1))
break;
@(posedge clkgen_port.$gclk);
}
//----check l2clk----
fork { check_l2clk_toggle(); } join none
fork { check_l2clk_freq_dutycyc(); } join none
//--- check sync pulse ---
if (this.chk_cmpslow_sync) {
fork { check_iox_sync("cmp_slow_sync_en"); } join none
if (this.chk_iosync_loc) {
if (this.cmpslow_sync_is_io2x)
fork { check_io2x_sync_loc(); } join none
else
fork { check_cmp_io_sync_loc(); } join none
}
}
if (this.chk_slowcmp_sync) {
fork { check_iox_sync("slow_cmp_sync_en"); } join none
if (this.chk_iosync_loc)
fork { check_io_cmp_sync_loc(); } join none
}
if (this.chk_io2x_sync) {
fork { check_iox_sync("io2x_sync_en"); } join none
if (this.chk_io2xsync_loc)
fork { check_io2x_sync_loc(); } join none
}
if (this.chk_dr_sync) {
fork { check_dr_sync(); } join none
if (this.chk_drsync_loc)
fork { check_dr_sync_loc(); } join none
}
//--- stop checking when tcu_clk_stop or scan_en asserted---
fork {
@(posedge clkgen_port.$tcu_clk_stop__gclk or negedge clkgen_port.$cluster_arst_l__gclk);
trigger(stop_check_l2clk_sync);
} join none
sync(ALL, stop_check_l2clk_sync);
terminate;
}
}
//=============================================================
// Compute expected values for this checker
//=============================================================
task CLUSTER_hdr_chkr::compute_expected_params() {
//---compute l2clk period---
case (this.hdr_type) {
CLUSTER_HDR_CMP, CLUSTER_HDR_CMP1 : {
this.l2clk_per_nom = clk_pkt.cmp_clk_per_nom;
this.l2clk_per_min = clk_pkt.cmp_clk_per_min;
this.l2clk_per_max = clk_pkt.cmp_clk_per_max;
}
CLUSTER_HDR_DR : {
this.l2clk_per_nom = clk_pkt.dr_clk_per_nom;
this.l2clk_per_min = clk_pkt.dr_clk_per_min;
this.l2clk_per_max = clk_pkt.dr_clk_per_max;
}
CLUSTER_HDR_IO : {
this.l2clk_per_nom = clk_pkt.io_out_per_nom;
this.l2clk_per_min = clk_pkt.io_out_per_min;
this.l2clk_per_max = clk_pkt.io_out_per_max;
}
CLUSTER_HDR_IO2X : {
this.l2clk_per_nom = clk_pkt.io2x_out_per_nom;
this.l2clk_per_min = clk_pkt.io2x_out_per_min;
this.l2clk_per_max = clk_pkt.io2x_out_per_max;
}
default:
dbg.dispmon(this.dispScope, MON_ERR, psprintf("%0d is illegal value for hdr type", hdr_type));
}
//---compute gclk period and pw_dev---
if (hdr_type == CLUSTER_HDR_DR) {
this.gclk_per_nom = clk_pkt.dr_clk_per_nom;
this.gclk_per_min = clk_pkt.dr_clk_per_min;
this.gclk_per_max = clk_pkt.dr_clk_per_max;
this.pw_dev = clk_pkt.dr_pw_dev;
}
else { // cmp/io/io2x header
this.gclk_per_nom = clk_pkt.cmp_clk_per_nom;
this.gclk_per_min = clk_pkt.cmp_clk_per_min;
this.gclk_per_max = clk_pkt.cmp_clk_per_max;
this.pw_dev = clk_pkt.cmp_pw_dev;
}
}
//=============================================================
// WHAT: check gclk is toggles in every 1.5 ccu.dr_pll_clk (for DR
// cluster hdr) or ccu.cmp_pll_clk (for other cluster hdrs)
// ASSUMPTION: ccu.cmp_pll_clk and ccu.dr_pll_clk clocks toggle.
// NOTE: ccu generates sync_stable from gclk, so gclk must toggle
//=============================================================
task CLUSTER_hdr_chkr::check_gclk_toggle() {
integer old_cyc, new_cyc, ncycs; // gclk cycle counts
string ref_clk_name;
integer err_cnt=0, debug_cnt=0; // error count
dbg.dispmon(this.dispScope, MON_INFO, "monitor: gclk toggles");
case (this.hdr_type) {
CLUSTER_HDR_DR: {
ref_clk_name = "ccu.dr_pll_clk";
@(posedge ccu_clk_port.$dr_pll_clk);
}
default: {
ref_clk_name = "ccu.cmp_pll_clk";
@(posedge ccu_clk_port.$cmp_pll_clk);
}
}
old_cyc = get_cycle(clkgen_port.$gclk);
while (1) {
case (this.hdr_type) {
CLUSTER_HDR_DR: repeat (3) @(ccu_clk_port.$dr_pll_clk); // 1.5 clk cycles
default: repeat (3) @(ccu_clk_port.$cmp_pll_clk); // 1.5 clk cycles
}
new_cyc = get_cycle(clkgen_port.$gclk);
ncycs = new_cyc - old_cyc;
if (is_outside_min_max(1, ncycs, 2))
print_error_msg(err_cnt++, psprintf("check_gclk_toggle: num gclk clks in 1.5 %s clk cycles: %0d. Expect: 1 or 2", ref_clk_name, ncycs));
old_cyc = new_cyc;
}
}
//=============================================================
// WHAT: check gclk period and duty cycle.
// NOTE: gclk should be stable when ccu_rst_sync_stable asserted.
//=============================================================
task CLUSTER_hdr_chkr::check_gclk_freq_dutycycle() {
integer per; // gclk period
integer pw_hi, pw_lo, pw_min, pw_nom, pw_max; // gclk pulse width.
integer pos_edge, neg_edge; // clk posedge and negedge
integer per_err_cnt = 0, pw_err_cnt=0; // error counts
integer debug_cnt=0; // debug msg counts
dbg.dispmon(this.dispScope, MON_INFO, psprintf("monitor: gclk period (min=%0d, max=%0d) and duty cycle", this.gclk_per_min, this.gclk_per_max));
@(posedge clkgen_port.$gclk);
pos_edge = get_time(LO);
while (1) {
@(negedge clkgen_port.$gclk);
neg_edge = get_time(LO);
pw_hi = neg_edge - pos_edge;
@(posedge clkgen_port.$gclk);
pos_edge = get_time(LO);
pw_lo = pos_edge - neg_edge;
per = pw_hi + pw_lo;
pw_nom = per / 2; // nominal is 50% duty cycle
pw_min = (pw_nom - 1) - (((this.gclk_per_nom / 100) + 1) * this.pw_dev); // rounding down
pw_max = (pw_nom + 1) + (((this.gclk_per_nom / 100) + 1) * this.pw_dev); // rounding up
if (is_outside_min_max(this.gclk_per_min, per, this.gclk_per_max))
print_error_msg(per_err_cnt++, psprintf("check_gclk_freq_pw: gclk: period=%0d, expect: min=%0d, max=%0d", per, this.gclk_per_min, this.gclk_per_max));
if (is_outside_min_max(pw_min, pw_lo, pw_max) || is_outside_min_max(pw_min, pw_hi, pw_max))
print_error_msg(pw_err_cnt++, psprintf("check_gclk_freq_pw: gclk: pw_hi=%0d, pw_lo=%0d. expect: pw_min=%0d, pw_max=%0d",
pw_hi, pw_lo, pw_min, pw_max));
if (this.verbose)
print_debug_msg(debug_cnt++, psprintf("check_gclk_freq_pw: gclk: per=%0d, pw_hi=%0d, pw_lo=%0d. expect: per_min=%0d, per_max=%0d, pw_min=%0d, pw_max=%0d",
per, pw_hi, pw_lo, this.gclk_per_min, this.gclk_per_max, pw_min, pw_max));
}
}
//=============================================================
// WHAT: check l2clk toggles in each gclk (for cmp and dr headers),
// every clk_pkt.cmp2io_ratio gclk (for IO header) or every
// clk_pkt.cmp2io2x_ratio gclk (for IO2X header)
//=============================================================
task CLUSTER_hdr_chkr::check_l2clk_toggle() {
integer gclk_ncycs; // number of gclk cycles that l2clk toggles once
integer old_cyc, new_cyc, ncycs; // cycle count of l2clk
integer err_cnt=0, debug_cnt=0;
case (this.hdr_type) { // note: +1 is needed in case clk edges are perfectly aligned.
CLUSTER_HDR_IO: gclk_ncycs = clk_pkt.cmp2io_ratio + 1; // gclk is cmp clk and l2clk is IO clk
CLUSTER_HDR_IO2X: gclk_ncycs = clk_pkt.cmp2io2x_ratio + 1; // gclk is cmp clk and l2clk is IO2X clk
default: gclk_ncycs = 1 + 1; // gclk is cmp clk or dr clk
}
dbg.dispmon(this.dispScope, MON_INFO, psprintf("monitor: l2clk toggles at least once for every %0d gclk cycles", gclk_ncycs));
//---wait for first l2clk---
wait_1st_posedge("l2clk", 10 * gclk_ncycs); // timeout in gclk cycs
//--- checking ---
@(posedge clkgen_port.$gclk);
old_cyc = get_cycle(clkgen_port.$l2clk);
while (1) {
repeat (gclk_ncycs) @(posedge clkgen_port.$gclk);
new_cyc = get_cycle(clkgen_port.$l2clk);
ncycs = new_cyc - old_cyc;
if (is_outside_min_max(1, ncycs, 2))
print_error_msg(err_cnt++, psprintf("check_l2clk_toggle: num l2clk clk in %0d gclk: %0d. Expect: 1 or 2", gclk_ncycs, ncycs));
else if (this.verbose)
print_debug_msg(debug_cnt++, psprintf("check_l2clk_toggle: num l2clk clk in %0d gclk: %0d. Expect: 1 or 2", gclk_ncycs, ncycs));
old_cyc = new_cyc;
}
}
//=============================================================
// WHAT: check freq and duty cycle of l2clk.
//=============================================================
task CLUSTER_hdr_chkr::check_l2clk_freq_dutycyc() {
integer clk_posedge, clk_negedge, period; // l2clk
integer pw_hi, pw_lo, pw_nom, pw_min, pw_max; // pw: pulse width
integer per_err_cnt=0, pw_err_cnt=0; // error counts in period or pulse width
integer debug_cnt=0;
dbg.dispmon(this.dispScope, MON_INFO, psprintf("monitor: l2clk period (min=%0d, max=%0d) and duty cycle", this.l2clk_per_min, this.l2clk_per_max));
@(posedge clkgen_port.$l2clk); // wait for first l2clk
clk_posedge = get_time(LO);
while (1) {
@(negedge clkgen_port.$l2clk);
clk_negedge = get_time(LO);
pw_hi = clk_negedge - clk_posedge;
@(posedge clkgen_port.$l2clk);
clk_posedge = get_time(LO);
pw_lo = clk_posedge - clk_negedge;
period = pw_hi + pw_lo;
pw_nom = period / 2; // nominal is 50% duty cycle
pw_min = (pw_nom - 1) - (((this.l2clk_per_nom / 100) + 1) * this.pw_dev); // rounding down
pw_max = (pw_nom + 1) + (((this.l2clk_per_nom / 100) + 1) * this.pw_dev); // rounding down
if (is_outside_min_max(this.l2clk_per_min, period, this.l2clk_per_max))
print_error_msg(per_err_cnt++, psprintf("check_l2clk_freq_pw: l2clk: period=%0d, expected: min=%0d, max=%0d", period, this.l2clk_per_min, this.l2clk_per_max));
if (is_outside_min_max(pw_min, pw_hi, pw_max) || is_outside_min_max(pw_min, pw_lo, pw_max))
print_error_msg(pw_err_cnt++, psprintf("check_l2clk_freq_pw: l2clk duty cycle: pw_hi=%0d, pw_lo=%0d, expected: min=%0d, max=%0d (per=%0d)",
pw_hi, pw_lo, pw_min, pw_max, period));
if (this.verbose)
print_debug_msg(debug_cnt++, psprintf("check_l2clk_freq_pw: l2clk: per: %0d. Expect: min=%0d, max=%0d. pw_hi=%0d, pw_lo=%0d. Expect: min=%0d, max=%0d",
period, this.l2clk_per_min, this.l2clk_per_max, pw_hi, pw_lo, pw_min, pw_max));
}
}
//=============================================================
// WHAT: check IO and IO2x sync pulse
// -there is one sync pulse in each IO/IO2X clk cycle
// -sync pulse is one cmp clk
// -next sync pulse is separated from prev sync pulse by one IO/IO2X clk cycle.
// ARGs: sync_name must be "cmp_slow_sync_en", "slow_cmp_sync_en" or "io2x_sync_en"
// WARNING: this task does NOT check sync pulse locations
//=============================================================
task CLUSTER_hdr_chkr::check_iox_sync(string sync_name) {
integer cmp2slowclk_ratio; // cmp-to-io or cmp-to-io2x clk ratio
integer ncycs_no_sync; // number of l2clk cycs that sync is low
integer err_cnt = 0, debug_cnt = 0, i;
bit sync_val;
string special_note = ((sync_name == "cmp_slow_sync_en") && (this.cmpslow_sync_is_io2x))? "(actually is IO2X sync)" : "";
case (sync_name) {
"cmp_slow_sync_en": {
if (this.cmpslow_sync_is_io2x) cmp2slowclk_ratio = clk_pkt.cmp2io2x_ratio; // cmp_slow_sync_en is io2x sync
else cmp2slowclk_ratio = clk_pkt.cmp2io_ratio;
}
"slow_cmp_sync_en": cmp2slowclk_ratio = clk_pkt.cmp2io_ratio;
"io2x_sync_en": cmp2slowclk_ratio = clk_pkt.cmp2io2x_ratio;
default: {
dbg.dispmon(this.dispScope, MON_ERR, psprintf("CLUSTER_hdr_chkr::check_iox_sync(sync_name=%s) <= bad arg", sync_name));
this.error_cnt++;
return; // ignore
}
}
ncycs_no_sync = cmp2slowclk_ratio - 1;
dbg.dispmon(this.dispScope, MON_INFO, psprintf("monitor: %s %s: 1 sync pulse in %0d l2clk cycs", sync_name, special_note, cmp2slowclk_ratio));
//--wait for first sync pulse---
wait_1st_posedge(sync_name, 10 * cmp2slowclk_ratio ); // timeout in gclks
dbg.dispmon(this.dispScope, MON_INFO, psprintf("check_iox_sync: first %s <= WARNING: not checking", sync_name));
wait_1st_posedge(sync_name, 2 * cmp2slowclk_ratio ); // timeout in gclks
dbg.dispmon(this.dispScope, MON_INFO, psprintf("check_iox_sync: this %s pulse is used as reference data point", sync_name));
//--check sync pulse----
while (1) {
for (i = 0; i < ncycs_no_sync; i++) {
@(posedge clkgen_port.$l2clk);
case (sync_name) {
"cmp_slow_sync_en": sync_val = clkgen_port.$cmp_slow_sync_en__l2clk;
"slow_cmp_sync_en": sync_val = clkgen_port.$slow_cmp_sync_en__l2clk;
"io2x_sync_en": sync_val = clkgen_port.$io2x_sync_en__l2clk;
}
if (sync_val !== 1'b0)
print_error_msg(err_cnt++, psprintf("check_iox_sync: %s %s is %b. Expect: low", sync_name, special_note, sync_val));
}
@(posedge clkgen_port.$l2clk);
case (sync_name) {
"cmp_slow_sync_en": sync_val = clkgen_port.$cmp_slow_sync_en__l2clk;
"slow_cmp_sync_en": sync_val = clkgen_port.$slow_cmp_sync_en__l2clk;
"io2x_sync_en": sync_val = clkgen_port.$io2x_sync_en__l2clk;
}
if (sync_val !== 1'b1)
print_error_msg(err_cnt++, psprintf("check_iox_sync: %s %s is %b. Expect: high", sync_name, special_note, sync_val));
else if (this.verbose)
print_debug_msg(debug_cnt++, psprintf("check_iox_sync: %s %s is %b. Expect: high", sync_name, special_note, sync_val));
}
}
//=============================================================
// WHAT: check dr_sync_en
// -DR sync pulse width is one cmp clk cycle
// -Number of DR sync pulses in 1.5 DR clk cycles is 1 or 2
// WARN: this task does NOT check DR sync locations.
//=============================================================
task CLUSTER_hdr_chkr::check_dr_sync() {
integer err_cnt=0, debug_cnt=0;
integer old_sync_cnt, new_sync_cnt, temp_sync_cnt, num_sync; // counts of number of sync pulses
integer sync_cnt;
wait_1st_posedge("dr_sync_en", 30); // timeout after 30 gclks
@(posedge clkgen_port.$dr_sync_en__l2clk); // skip the 1st sync since sync_en can be during clk stop (ie. WMR)
dbg.dispmon(this.dispScope, MON_INFO, "check_dr_sync: first dr_sync_en used as reference data point");
//--- check dr_sync_en width is one cmp clk ---
fork {
while (1) {
@(posedge clkgen_port.$l2clk);
if (clkgen_port.$dr_sync_en__l2clk != 1'b0)
print_error_msg(err_cnt++, "check_dr_sync: dr_sync_en is high for more than one cmp cycle");
@(posedge clkgen_port.$dr_sync_en__l2clk); // cmp posedge at which dr_sync is high
}
} join none
//--- increment counter for every dr_sync pulse---
sync_cnt = 1; // first sync pulse counted
fork {
while (1) {
@(posedge clkgen_port.$dr_sync_en__l2clk);
sync_cnt++;
}
} join none
//--- check number of dr_sync pulses in 1 dr clk + 0.5 cmp clk---
@(posedge clkgen_dr_port.$l2clk);
old_sync_cnt = sync_cnt;
while (1) {
@(posedge clkgen_dr_port.$l2clk);
temp_sync_cnt = sync_cnt;
repeat (2) @(clkgen_port.$l2clk); // repeat (1): not work for div2_eff is 9
new_sync_cnt = sync_cnt;
num_sync = new_sync_cnt - old_sync_cnt;
if (is_outside_min_max(1, num_sync, 2))
print_error_msg(err_cnt++, psprintf("check_dr_sync: number of dr_sync_en pulses in 1 DR clk + 1 cmp clk: %0d. Expect: 1 or 2", num_sync));
else if (this.verbose)
print_debug_msg(debug_cnt++, psprintf("check_dr_sync: number of dr_sync_en pulses in 1 DR clk + 1 cmp clk: %0d. Expect: 1 or 2", num_sync));
old_sync_cnt = temp_sync_cnt;
}
}
//############################################################################
//############################################################################
//#### tasks to check sync pulse locations ####################
//############################################################################
//############################################################################
//=============================================================
// WHAT: check locations of cmp_io_sync_en
// WARNING: do NOT use this task for MIO cmp hdrs since cmp_slow_sync_en is IO2X sync.
// Use check_io2x_sync_loc() instead.
//=============================================================
task CLUSTER_hdr_chkr::check_cmp_io_sync_loc(integer nchecks=10) {
integer err_cnt=0, debug_cnt=0;
reg sync_value;
integer i;
integer sync_time=0, clk_edge, old_clk_edge=0, clk_mid;
integer sync_loc_dev; // deviation in sync pulse location
if (this.cmpslow_sync_is_io2x) // call wrong task
check_io2x_sync_loc(nchecks); // do a favor here
//---wait for first IO sync, cmp clk and IO clk---
fork {
@(clkgen_port.$cmp_slow_sync_en__l2clk); // wait for first IO sync pulse
}
{
@(posedge clkgen_port.$l2clk); // wait for first cmp clk
}
{
@(posedge clkgen_io_port.$l2clk); // wait for first IO l2clk
} join
dbg.dispmon(this.dispScope, MON_INFO, psprintf("checking sync pulse locations of cmp_slow_sync_en ..."));
//----check sync location for DTM mode -----
if (this.clk_pkt.mode == CCU_DTM_MODE) {
sync_loc_dev = this.clk_pkt.cmp_clk_per_nom;
fork {
while (1) {
@(posedge clkgen_port.$cmp_slow_sync_en__l2clk);
@(posedge clkgen_port.$l2clk);
sync_time = get_time(LO); // at flop header
}
} join none
@(posedge clkgen_io_port.$l2clk);
old_clk_edge = get_time(LO);
for (i = 0; i < nchecks; i++) {
@(posedge clkgen_io_port.$l2clk);
clk_edge = get_time(LO);
clk_mid = (clk_edge + old_clk_edge) / 2;
if (compute_abs(sync_time - clk_mid) > sync_loc_dev)
print_error_msg(err_cnt++, psprintf("sync location check: middle of IO l2clk: %0d, cmp_slow sync pulse at flop input: %0d <= more than %0d", clk_mid, sync_time, sync_loc_dev));
else if (this.verbose)
print_debug_msg(debug_cnt++, psprintf("sync location check: middle of IO l2clk: %0d, cmp_slow sync pulse at flop input: %0d", clk_mid, sync_time));
old_clk_edge = clk_edge;
}
}
else {
//---check io sync locations---
for (i = 0; i < nchecks; i++) {
@(negedge clkgen_io_port.$l2clk); // falling edge of iol2clk
repeat (2) @(negedge clkgen_port.$l2clk); // skip 2 cmp clk falling edges
@(posedge clkgen_port.$l2clk); // cmp clk rising edge
sync_value = clkgen_port.$cmp_slow_sync_en__l2clk;
if (sync_value !== 1'b1)
print_error_msg(err_cnt++, psprintf("sync location check: cmp_slow_sync_en is %b. Expect: high", sync_value));
else if (this.verbose)
print_debug_msg(debug_cnt++, psprintf("sync location check: cmp_slow_sync_en is %b. Expect: high", sync_value));
}
}
}
//=============================================================
// WHAT: check locations of io_cmp_sync_en
//=============================================================
task CLUSTER_hdr_chkr::check_io_cmp_sync_loc(integer nchecks=10) {
integer err_cnt=0, debug_cnt=0;
reg sync_value;
integer i;
integer sync_time=0, clk_edge, old_clk_edge=0, clk_mid;
integer sync_loc_dev; // deviation in sync pulse location
integer sync_at_phase; // phase at which sync pulse is high
//---wait for first IO sync, cmp clk and IO clk---
fork {
@(clkgen_port.$slow_cmp_sync_en__l2clk); // wait for first IO sync pulse
}
{
@(posedge clkgen_port.$l2clk); // wait for first cmp clk
}
{
@(posedge clkgen_io_port.$l2clk); // wait for first IO l2clk
} join
dbg.dispmon(this.dispScope, MON_INFO, psprintf("checking sync pulse locations of slow_cmp_sync_en ..."));
//----check sync location for DTM mode -----
//if (this.clk_pkt.mode == CCU_DTM_MODE) { //!!!! WARNING: this is OLD when cmp_slow/slow_cmp sync are at same locations
// sync_loc_dev = this.clk_pkt.cmp_clk_per_nom;
// fork {
// while (1) {
// @(posedge clkgen_port.$slow_cmp_sync_en__l2clk);
// @(posedge clkgen_port.$l2clk);
// sync_time = get_time(LO); // at flop header
// }
// } join none
// @(posedge clkgen_io_port.$l2clk);
// old_clk_edge = get_time(LO);
// for (i = 0; i < nchecks; i++) {
// @(posedge clkgen_io_port.$l2clk);
// clk_edge = get_time(LO);
// clk_mid = (clk_edge + old_clk_edge) / 2;
// if (compute_abs(sync_time - clk_mid) > sync_loc_dev) {
// print_error_msg(err_cnt++, psprintf("sync location check: middle of IO l2clk: %0d, slow_cmp sync pulse at flop input: %0d <= more than %0d",
// clk_mid, sync_time, sync_loc_dev));
// }
// old_clk_edge = clk_edge;
// }
//}
if (this.clk_pkt.mode == CCU_DTM_MODE) {
sync_at_phase = this.clk_pkt.cmp2io_ratio - 2; // at flop input: sync is high 1 clk before next IO clk posedge
for (i = 0; i < nchecks; i++) { // so, atclkgen output, sync is high 2 clks before next IO clk posedge
@(posedge clkgen_io_port.$l2clk);
@(negedge clkgen_port.$l2clk);
repeat (sync_at_phase) @(posedge clkgen_port.$l2clk);
sync_value = clkgen_port.$slow_cmp_sync_en__l2clk;
if (sync_value !== 1'b1)
print_error_msg(err_cnt++, psprintf("sync location check: slow_cmp sync pulse is %b, expect: high", sync_value));
else if (this.verbose)
print_debug_msg(debug_cnt++, psprintf("sync location check: slow_cmp sync pulse is %b, expect: high", sync_value));
}
}
else {
//---check io sync locations----
for (i = 0; i < nchecks; i++) {
@(posedge clkgen_io_port.$l2clk);
repeat (2) @(negedge clkgen_port.$l2clk); // skip 2 cmp clk falling edges
@(posedge clkgen_port.$l2clk); // cmp clk rising edge
sync_value = clkgen_port.$slow_cmp_sync_en__l2clk;
if (sync_value !== 1'b1)
print_error_msg(err_cnt++, psprintf("sync location check: slow_cmp_sync_en is %b. Expect: high", sync_value));
else if (this.verbose)
print_debug_msg(debug_cnt++, psprintf("sync location check: slow_cmp_sync_en is %b. Expect: high", sync_value));
}
}
}
//=============================================================
// WHAT: check locations of IO2X sync pulse
// WARNING: cmp_slow_sync_en of MIO cmp clusters is IO2X sync en
//=============================================================
task CLUSTER_hdr_chkr::check_io2x_sync_loc(integer nchecks=10) {
integer err_cnt=0, debug_cnt=0;
reg sync_value;
integer i;
string special_note = (this.cmpslow_sync_is_io2x)? "(actually is IO2X sync)" : "";
string sync_name = (this.cmpslow_sync_is_io2x)? "cmp_slow_sync_en" : "io2x_sync_en";
integer sync_time=0, clk_edge, old_clk_edge=0, clk_mid;
integer sync_loc_dev; // deviation in sync pulse location
//---wait for first IO2X sync, cmp clk and IO2X clk---
fork {
if (this.cmpslow_sync_is_io2x)
@(posedge clkgen_port.$cmp_slow_sync_en__l2clk);
else
@(posedge clkgen_port.$io2x_sync_en__l2clk); // wait for first IO2X sync pulse
}
{
@(posedge clkgen_port.$l2clk); // wait for first cmp clk
}
{
@(posedge clkgen_io2x_port.$l2clk); // wait for first IO2X l2clk
} join
dbg.dispmon(this.dispScope, MON_INFO, psprintf("checking sync pulse locations of %s %s...", sync_name, special_note));
//----check sync location for DTM mode -----
if (this.clk_pkt.mode == CCU_DTM_MODE) {
sync_loc_dev = this.clk_pkt.cmp_clk_per_nom;
fork {
while (1) {
if (this.cmpslow_sync_is_io2x)
@(posedge clkgen_port.$cmp_slow_sync_en__l2clk);
else
@(posedge clkgen_port.$io2x_sync_en__l2clk);
@(posedge clkgen_port.$l2clk);
sync_time = get_time(LO); // at flop header
}
} join none
@(posedge clkgen_io2x_port.$l2clk);
old_clk_edge = get_time(LO);
for (i = 0; i < nchecks; i++) {
@(posedge clkgen_io2x_port.$l2clk);
clk_edge = get_time(LO);
clk_mid = (clk_edge + old_clk_edge) / 2;
if (compute_abs(sync_time - clk_mid) > sync_loc_dev)
print_error_msg(err_cnt++, psprintf("sync location check: middle of IO2X l2clk: %0d, io2x sync pulse at flop input: %0d <= more than %0d", clk_mid, sync_time, sync_loc_dev));
else if (this.verbose)
print_debug_msg(debug_cnt++, psprintf("sync location check: middle of IO2X l2clk: %0d, io2x sync pulse at flop input: %0d", clk_mid, sync_time));
old_clk_edge = clk_edge;
}
}
else {
//--- check io2x_sync_en location---
for (i = 0; i < nchecks; i++) {
@(negedge clkgen_io2x_port.$l2clk); // falling edge of io2x l2clk
@(negedge clkgen_port.$l2clk); // falling edge of cmp clk
@(posedge clkgen_port.$l2clk); // rising edge of cmp clk. Sync pulse should be high
if (this.cmpslow_sync_is_io2x)
sync_value = clkgen_port.$cmp_slow_sync_en__l2clk;
else
sync_value = clkgen_port.$io2x_sync_en__l2clk;
if (sync_value !== 1'b1)
print_error_msg(err_cnt++, psprintf("sync location check: %s %s is %b. Expect: high", sync_name, special_note, sync_value));
else if (this.verbose)
print_debug_msg(debug_cnt++, psprintf("sync location check: %s %s is %b. Expect: high", sync_name, special_note, sync_value));
}
}
}
//=============================================================
// WHAT: check expected DR sync location at the flop input. DR sync
// is flopped once after clkgen output.
// HOW: the expected location of dr sync pulse is the cmp clk posedge
// that is closet to the mid point of dr clk cycle (ie. delta). Since
// the dr and cmp clk edges can shift a little, so testbench needs to
// check the actual delta vs. (delta_min plus 1/3 of cmp clk cycle).
//=============================================================
task CLUSTER_hdr_chkr::check_dr_sync_loc(integer nchecks=20) {
integer i, err_cnt=0, debug_cnt=0;
integer sync_time=0, clk_edge, old_clk_edge=0, clk_mid;
integer sync_loc_dev; // deviation in sync pulse location
this.dr_sync_loc_err_cnt = 0; // reset error counter
this.dr_sync_loc_debug_cnt = 0; // reset debug counter
//---wait for first DR sync, cmp clk and DR clk---
fork {
@(posedge clkgen_port.$dr_sync_en__l2clk); // wait for first dr sync pulse
dbg.dispmon(this.dispScope, MON_INFO, "first dr sync pulse");
}
{
@(posedge clkgen_port.$l2clk); // wait for first cmp clk
}
{
@(posedge clkgen_dr_port.$l2clk); // wait for first dr clk
} join
dbg.dispmon(this.dispScope, MON_INFO, "monitor: checking dr sync locations");
//----check sync location for DTM mode -----
if (this.clk_pkt.mode == CCU_DTM_MODE) {
sync_loc_dev = this.clk_pkt.cmp_clk_per_nom;
fork {
while (1) {
@(posedge clkgen_port.$dr_sync_en__l2clk);
@(posedge clkgen_port.$l2clk);
sync_time = get_time(LO); // at flop header
}
} join none
@(posedge clkgen_dr_port.$l2clk);
old_clk_edge = get_time(LO);
for (i = 0; i < nchecks; i++) {
@(posedge clkgen_dr_port.$l2clk);
clk_edge = get_time(LO);
clk_mid = (clk_edge + old_clk_edge) / 2;
if (compute_abs(sync_time - clk_mid) > sync_loc_dev) {
print_error_msg(err_cnt++, psprintf("sync location check: middle of DR l2clk: %0d, dr sync pulse at flop input: %0d <= more than %0d", clk_mid, sync_time, sync_loc_dev));
}
else if (this.verbose)
print_debug_msg(debug_cnt++, psprintf("sync location check: middle of DR l2clk: %0d, dr sync pulse at flop input: %0d", clk_mid, sync_time));
old_clk_edge = clk_edge;
}
}
else {
//---check DR sync locations---
for (i = 0; i < nchecks; i++) {
@(posedge clkgen_port.$dr_sync_en__l2clk); // dr sync is high at clkgen output
fork { check_next_dr_sync_loc(); } join none
}
}
}
//=============================================================
// WHAT: check expected DR sync location at the flop input. DR sync
// is flopped once after clkgen output.
// HOW: the expected location of dr sync pulse is the cmp clk posedge
// that is closet to the mid point of dr clk cycle (ie. delta). Since
// the dr and cmp clk edges can shift a little, so testbench needs to
// check the actual delta vs. (delta_min plus 1/3 of cmp clk cycle).
//=============================================================
task CLUSTER_hdr_chkr::check_next_dr_sync_loc() {
integer prev_dr_posedge, next_dr_posedge, at_end_dr_cyc=0, dr_clk_midpoint;
integer sync_at_clkgen, sync_at_flop; // sync pulse
integer cmp_edges[40], n = 0, i; // high ratio is in DTM mode 15:1 ratio
integer delta, min_delta, actual_delta; // time difference between cmp edge and dr clk midpoint
integer clks_shift_limit;
// when this task is called, DR sync is high at clkgen output
at_end_dr_cyc = 0;
n = 0;
fork
{ // find next sync pulse (at flop input)
@(posedge clkgen_port.$dr_sync_en__l2clk); // sync is high at clkgen output
sync_at_clkgen = get_time(LO);
@(posedge clkgen_port.$l2clk); // sync is high at flop input
sync_at_flop = get_time(LO);
}
{ // find next DR clk rising edges
@(negedge clkgen_port.$l2clk); // when dr_sync is at phase0 (ie. DR clk edge), avoid this dr clk edge
@(posedge clkgen_dr_port.$l2clk);
prev_dr_posedge = get_time(LO);
@(posedge clkgen_dr_port.$l2clk);
next_dr_posedge = get_time(LO);
at_end_dr_cyc = 1;
}
{ // record timestamps of all cmp edges
while (1) {
@(posedge clkgen_port.$l2clk);
if (n >= 40) {
dbg.dispmon(this.dispScope, MON_ERR, psprintf("CLUSTER_hdrchkr::check_next_dr_sync_loc(n=%0d) <= programming error", n));
break;
}
cmp_edges[n] = get_time(LO);
n++;
if (at_end_dr_cyc) break; // reach the end of DR clk cycle
}
} join
//---find cmp posedge that is nearest to the DR clk mid point
dr_clk_midpoint = (prev_dr_posedge + next_dr_posedge) / 2; // rounding error is 1 tick
min_delta = compute_abs(cmp_edges[0] - dr_clk_midpoint);
for (i = 1; i < n ; i++) {
delta = compute_abs(cmp_edges[i] - dr_clk_midpoint);
if (delta < min_delta) // closer to the mid point
min_delta = delta;
}
//---check location of DR sync pulse---
actual_delta = compute_abs(sync_at_flop - dr_clk_midpoint);
clks_shift_limit = this.clk_pkt.cmp_clk_per_nom / 3; // 1/3 of nominal value of cmp clk cycle
if (actual_delta > (min_delta + clks_shift_limit)) {
this.dr_sync_loc_err_cnt++;
if (this.dr_sync_loc_err_cnt <= this.max_error_printed)
dbg.dispmon(this.dispScope, MON_ERR, psprintf(
"sync location check: dr sync @flop=%0d. dr clk: edges=%0d and %0d, midpoint=%0d. delta2midpoint=%0d <= exceed min_delta=%0d + %0d",
sync_at_flop, prev_dr_posedge, next_dr_posedge, dr_clk_midpoint, actual_delta, min_delta, clks_shift_limit));
}
else if (this.dr_sync_loc_debug_cnt <= this.max_error_printed) {
dbg.dispmon(this.dispScope, MON_INFO, psprintf(
"sync location check: dr sync @flop=%0d. dr clk: edges=%0d and %0d, midpoint=%0d, delta2midpoint=%0d, min_delta=%0d, tolerance=%0d",
sync_at_flop, prev_dr_posedge, next_dr_posedge, dr_clk_midpoint, actual_delta, min_delta, clks_shift_limit));
this.dr_sync_loc_debug_cnt++;
}
}
//############################################################################
//############################################################################
//######### supporting subroutines #########################################
//############################################################################
//############################################################################
//=============================================================
// WHAT: print error message
// NOTE:
// err_cnt is local error count of a particular error type.
// this.error_cnt is global error count of this vera class.
//=============================================================
task CLUSTER_hdr_chkr::print_error_msg(integer err_cnt, string error_msg) {
if (err_cnt <= max_error_printed)
dbg.dispmon(this.dispScope, MON_ERR, error_msg);
this.error_cnt++; // increment global error count
}
//=============================================================
// WHAT: print messages for debug
//=============================================================
task CLUSTER_hdr_chkr::print_debug_msg(integer debug_cnt, string debug_msg) {
if (debug_cnt <= max_debug_printed)
dbg.dispmon(this.dispScope, MON_ALWAYS, debug_msg);
}
//=============================================================
// Return 1 if value is outside min and max; otherwise, return 0.
//=============================================================
function integer CLUSTER_hdr_chkr::is_outside_min_max(integer min_val, integer value, integer max_val) {
is_outside_min_max = ((value < min_val) || (value > max_val))? 1 : 0;
}
//=============================================================
// WHAT: compute absolute value
//=============================================================
function integer CLUSTER_hdr_chkr::compute_abs(integer n) {
compute_abs = (n >= 0)? n : n * -1;
}
//=============================================================
// WHAT: timeout if not seen posedge of 'sig_name' in 'timeout_val' gclk cycles.
// ARGs: sig_name must be "l2clk", "cmp_slow_sync_en", "slow_cmp_sync_en",
// "io2x_sync_en" or "dr_sync_en"
//=============================================================
task CLUSTER_hdr_chkr::wait_1st_posedge(string sig_name, integer timeout_val) {
integer got_1st_posedge=0;
if (timeout_val <= 0)
return;
fork {
repeat (timeout_val) @(posedge clkgen_port.$gclk);
if (got_1st_posedge == 0)
dbg.dispmon(this.dispScope, MON_ERR, psprintf("not seen %s posedge in %0d gclks", sig_name, timeout_val));
} join none
case (sig_name) {
"l2clk": @(posedge clkgen_port.$l2clk);
"cmp_slow_sync_en": @(posedge clkgen_port.$cmp_slow_sync_en__l2clk);
"slow_cmp_sync_en": @(posedge clkgen_port.$slow_cmp_sync_en__l2clk);
"io2x_sync_en": @(posedge clkgen_port.$io2x_sync_en__l2clk);
"dr_sync_en": @(posedge clkgen_port.$dr_sync_en__l2clk);
default: {
dbg.dispmon(this.dispScope, MON_ERR, psprintf("wait_1st_posedge(sig_name=%s) <= bad arg", sig_name));
this.error_cnt++;
}
}
got_1st_posedge = 1;
terminate;
}