Skip to content

Commit

Permalink
[dv/clkmgr] Add test for extclk feature
Browse files Browse the repository at this point in the history
Add randomized test for the external clock clkmgr feature.
Check the ast request/response handshake.
Check that the clock divisors get ramped up unless in scan mode.

Signed-off-by: Guillermo Maturana <[email protected]>
  • Loading branch information
matutem authored and udinator committed Jun 17, 2021
1 parent b81b719 commit 137bebc
Show file tree
Hide file tree
Showing 10 changed files with 385 additions and 21 deletions.
22 changes: 11 additions & 11 deletions hw/ip/clkmgr/data/clkmgr_testplan.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,22 @@
desc: '''
Tests the functionality of enabling external clocks.

- External clocks can be enabled writing `lc_ctrl_pkg::On` to CSR
`extclk_sel`.
- Writes to CSR `extclk_sel` are ignored unless the value of CSR
`extclk_sel_regwen` is 1.
- Regardless of the `extclk_sel` contents, external clocks won't
be enabled unless `lc_dtl_en_i == lc_ctrl_pkg::On`.
- External clocks can also be enabled if the input
`lc_clk_byp_req_i == lc_ctrl_pkg::On`.
- External clock is enabled if the `lc_clk_byp_req_i` input from
`lc_ctrl` is `lc_ctrl_pkg::On`.
- External clock is also be enabled when CSR `extclk_sel` is set to
`lc_ctrl_pkg::On` and the `lc_dtl_en_i` input from `lc_ctrl` is
`lc_ctrl_pkg::On`.
- Notice writes to the `extclk_sel` register are ignored unless the
CSR `extclk_sel_regwen` is 1.
- A successful switch to external clocks will cause the clkmgr
to undo a divide by 2 for io_div4 and io_div2 clocks unless in
scan mode `(scanmode_i == lc_ctrl_pkg::On)`.
to undo a divide by 2 for io_div4 and io_div2 clocks except when
`(scanmode_i == lc_ctrl_pkg::On)`.

**Stimulus**:
- CSR writes to `extclk_sel` and `extclk_sel_regwen`.
- Setting `lc_dft_en_i`, `lc_clk_byp_req_i`, and the handshake to
ast via `ast_clk_byp_req_o` and `ast_clk_byp_ack_i`.
- Setting `scanmode_i`.

**Checks**:
- SVA assertions:
Expand All @@ -108,7 +108,7 @@
clocks to ramp up unless `scanmode_i == lc_ctrl_pkg::On`.
'''
milestone: V2
tests: []
tests: ["clkmgr_extclk"]
}
]
covergroups: [
Expand Down
4 changes: 4 additions & 0 deletions hw/ip/clkmgr/dv/clkmgr_sim_cfg.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
name: clkmgr_smoke
uvm_test_seq: clkmgr_smoke_vseq
}
{
name: clkmgr_extclk
uvm_test_seq: clkmgr_extclk_vseq
}
{
name: clkmgr_peri
uvm_test_seq: clkmgr_peri_vseq
Expand Down
1 change: 1 addition & 0 deletions hw/ip/clkmgr/dv/env/clkmgr_env.core
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ filesets:
- seq_lib/clkmgr_vseq_list.sv: {is_include_file: true}
- seq_lib/clkmgr_base_vseq.sv: {is_include_file: true}
- seq_lib/clkmgr_common_vseq.sv: {is_include_file: true}
- seq_lib/clkmgr_extclk_vseq.sv: {is_include_file: true}
- seq_lib/clkmgr_peri_vseq.sv: {is_include_file: true}
- seq_lib/clkmgr_smoke_vseq.sv: {is_include_file: true}
- seq_lib/clkmgr_trans_vseq.sv: {is_include_file: true}
Expand Down
2 changes: 1 addition & 1 deletion hw/ip/clkmgr/dv/env/clkmgr_env_cov.sv
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class clkmgr_trans_cg_wrap;
trans_cg = new(name);
endfunction

function sample(bit hint, bit ip_clk_en, bit scanmode, bit idle);
function void sample(bit hint, bit ip_clk_en, bit scanmode, bit idle);
trans_cg.sample(hint, ip_clk_en, scanmode, idle);
endfunction
endclass
Expand Down
65 changes: 60 additions & 5 deletions hw/ip/clkmgr/dv/env/clkmgr_if.sv
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ interface clkmgr_if(input logic clk, input logic rst_n, input logic rst_main_n);
lc_ctrl_pkg::lc_tx_t extclk_sel;
logic jitter_enable;

// The expected and actual divided io clocks.
logic exp_clk_io_div2;
logic actual_clk_io_div2;
logic exp_clk_io_div4;
logic actual_clk_io_div4;

function automatic void update_extclk_sel(lc_ctrl_pkg::lc_tx_t value);
extclk_sel = value;
endfunction
Expand All @@ -82,14 +88,39 @@ interface clkmgr_if(input logic clk, input logic rst_n, input logic rst_main_n);
scanmode_i = value;
endfunction

function automatic void update_lc_dft_en(lc_ctrl_pkg::lc_tx_t value);
lc_dft_en_i = value;
endfunction

function automatic void update_lc_clk_byp_req(lc_ctrl_pkg::lc_tx_t value);
lc_clk_byp_req = value;
endfunction

function automatic void update_ast_clk_byp_ack(lc_ctrl_pkg::lc_tx_t value);
ast_clk_byp_ack = value;
endfunction

function automatic logic get_clk_status();
return pwr_o.clk_status;
endfunction

task automatic init(logic [NUM_TRANS-1:0] idle, logic ip_clk_en, lc_ctrl_pkg::lc_tx_t scanmode);
lc_clk_byp_req = lc_ctrl_pkg::Off;
ast_clk_byp_ack = lc_ctrl_pkg::Off;
lc_dft_en_i = lc_ctrl_pkg::Off;
function automatic void update_exp_clk_io_divs(logic exp_div2_value,
logic actual_div2_value,
logic exp_div4_value,
logic actual_div4_value);
exp_clk_io_div2 = exp_div2_value;
actual_clk_io_div2 = actual_div2_value;
exp_clk_io_div4 = exp_div4_value;
actual_clk_io_div4 = actual_div4_value;
endfunction

task automatic init(logic [NUM_TRANS-1:0] idle, logic ip_clk_en, lc_ctrl_pkg::lc_tx_t scanmode,
lc_ctrl_pkg::lc_tx_t lc_dft_en = lc_ctrl_pkg::Off,
lc_ctrl_pkg::lc_tx_t lc_clk_byp_req = lc_ctrl_pkg::Off,
lc_ctrl_pkg::lc_tx_t ast_clk_byp_ack = lc_ctrl_pkg::Off);
update_ast_clk_byp_ack(ast_clk_byp_ack);
update_lc_clk_byp_req(lc_clk_byp_req);
update_lc_dft_en(lc_dft_en);
update_idle(idle);
update_ip_clk_en(ip_clk_en);
update_scanmode(scanmode);
Expand Down Expand Up @@ -157,7 +188,6 @@ interface clkmgr_if(input logic clk, input logic rst_n, input logic rst_main_n);
endclocking

// Pipelining and clocking block for transactional unit clocks.

logic [PIPELINE_DEPTH-1:0][NUM_TRANS-1:0] clk_hints_ffs;
logic [PIPELINE_DEPTH-1:0] trans_clk_en_ffs;
always @(posedge clocks_o.clk_main_powerup) begin
Expand All @@ -172,4 +202,29 @@ interface clkmgr_if(input logic clk, input logic rst_n, input logic rst_main_n);
input idle_i;
endclocking

clocking extclk_cb @(posedge clk);
input extclk_sel;
input lc_dft_en_i;
input ast_clk_byp_req;
input lc_clk_byp_req;
endclocking

// Pipelining and clocking block for external clock bypass. The divisor control is
// triggered by an ast ack, which goes through synchronizers.
logic step_down_ff;
always @(posedge clk) begin
if (rst_n) begin
step_down_ff <= ast_clk_byp_ack == lc_ctrl_pkg::On;
end
end
clocking step_down_cb @(posedge clk);
input step_down = step_down_ff;
endclocking

clocking div_clks_cb @(posedge clocks_o.clk_io_powerup);
input exp_clk_io_div2;
input actual_clk_io_div2;
input exp_clk_io_div4;
input actual_clk_io_div4;
endclocking
endinterface
169 changes: 169 additions & 0 deletions hw/ip/clkmgr/dv/env/clkmgr_scoreboard.sv
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,82 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

// Auxiliary class to deal with divided clocks. It predicts the divided clocks depending
// on whether the clock is divided as usual (step up), or if one division is undone (step
// down).
//
// The internal div2 clock is always running, and step down means we select the base
// clk_io instead of the internal clock.
//
// The div4 clock is handled differently: we internally maintain both a max and cnt. In
// normal operation the max is 1, so the div4 clock flips every two cycles; when stepped down
// the max is set to 0, so the clock flips every clk_io cycle.
class clock_dividers;

// Step down means undo one clock divide: runs faster, as in "step down on the gas".
typedef enum {DivStepDown, DivStepUp} div_step_e;
typedef enum {Div2SelDiv2, Div2SelIo} div2_sel_e;

// The internal div2 clock, always running.
local bit clk_io_div2 = 1'b0;
// This selects the io clock when stepped down.
local div2_sel_e div2_sel = Div2SelDiv2;
// The predicted div4 clock.
local bit clk_io_div4 = 1'b0;
// The maximum value of cnt: becomes 0 when stepped down.
local bit clk_io_div4_max = 1;
local bit cnt = 0;

function new();
reset();
endfunction

function void reset();
clk_io_div2 = 1'b0;
div2_sel = Div2SelDiv2;
clk_io_div4 = 1'b0;
clk_io_div4_max = 1;
cnt = 0;
endfunction

function void increment_div2();
clk_io_div2 = ~clk_io_div2;
endfunction

function void increment_div4(bit in_scan_mode);
bit real_limit = in_scan_mode ? 1 : clk_io_div4_max;
if (cnt < real_limit) begin
cnt++;
end else begin
clk_io_div4 = ~clk_io_div4;
cnt = 0;
end
endfunction

function void step_div4(div_step_e step);
if (step == DivStepUp) clk_io_div4_max = 1;
else clk_io_div4_max = 0;
endfunction

function void step_div2(div_step_e step);
if (step == DivStepUp) div2_sel = Div2SelDiv2;
else div2_sel = Div2SelIo;
endfunction

function string show();
return $sformatf("clk_div2=%b div2_sel=%0s clk_div4=%b cnt=%b max=%b",
clk_io_div2, div2_sel.name, clk_io_div4, cnt, clk_io_div4_max);
endfunction

function bit get_div2_clk(bit actual_clk_io);
return div2_sel == Div2SelIo ? actual_clk_io : clk_io_div2;
endfunction

function bit get_div4_clk();
return clk_io_div4;
endfunction
endclass : clock_dividers

class clkmgr_scoreboard extends cip_base_scoreboard #(
.CFG_T(clkmgr_env_cfg),
.RAL_T(clkmgr_reg_block),
Expand All @@ -28,9 +104,11 @@ class clkmgr_scoreboard extends cip_base_scoreboard #(
task run_phase(uvm_phase phase);
super.run_phase(phase);
fork
monitor_ip_clk_en();
monitor_idle();
monitor_ip_clk_en();
monitor_scanmode();
monitor_ast_clk_byp();
begin : post_reset
fork
monitor_div4_peri_clock();
Expand All @@ -44,6 +122,7 @@ class clkmgr_scoreboard extends cip_base_scoreboard #(
monitor_trans_clock(trans_index);
join_none
end
monitor_clk_dividers();
join_none
end
join_none
Expand Down Expand Up @@ -166,6 +245,96 @@ class clkmgr_scoreboard extends cip_base_scoreboard #(
end
endtask

task monitor_ast_clk_byp();
lc_tx_t prev_lc_clk_byp_req = Off;
forever @cfg.clkmgr_vif.extclk_cb begin
if (cfg.clkmgr_vif.lc_clk_byp_req != prev_lc_clk_byp_req) begin
`uvm_info(`gfn, $sformatf("Got lc_clk_byp_req %s",
cfg.clkmgr_vif.lc_clk_byp_req == On ? "On" : "Off"),
UVM_MEDIUM)
prev_lc_clk_byp_req = cfg.clkmgr_vif.lc_clk_byp_req;
end
if (((cfg.clkmgr_vif.extclk_cb.extclk_sel == On) &&
(cfg.clkmgr_vif.extclk_cb.lc_dft_en_i == On)) ||
(cfg.clkmgr_vif.extclk_cb.lc_clk_byp_req == On)) begin
`DV_CHECK_EQ(cfg.clkmgr_vif.ast_clk_byp_req, On,
"Expected ast_clk_byp_req to be On")
end
end
endtask

task monitor_clk_dividers();
clock_dividers dividers = new();
clock_dividers::div_step_e prev_div_step = clock_dividers::DivStepUp;

#1;
cfg.io_clk_rst_vif.wait_for_reset();
fork
forever @(posedge cfg.io_clk_rst_vif.rst_n) begin : handle_dividers_reset
dividers.reset();
`uvm_info(`gfn, $sformatf("Reset divided clocks: %0s", dividers.show()), UVM_MEDIUM)
end
forever @cfg.clkmgr_vif.step_down_cb begin : handle_divider_step_change
clock_dividers::div_step_e div_step;
bit step_down = cfg.clkmgr_vif.step_down_cb.step_down;
#0;

step_down &= (cfg.clkmgr_vif.scanmode_i != On);
div_step = step_down ? clock_dividers::DivStepDown : clock_dividers::DivStepUp;
if (div_step != prev_div_step) begin
`uvm_info(`gfn, $sformatf("Got a %0s request", div_step.name), UVM_LOW)
dividers.step_div4(div_step);
prev_div_step = div_step;
@(negedge cfg.clkmgr_vif.clocks_o.clk_io_powerup) begin
// Reconsider scanmode_i since it is asynchronous.
dividers.step_div2(step_down && (cfg.clkmgr_vif.scanmode_i != On) ?
clock_dividers::DivStepDown : clock_dividers::DivStepUp);
end
`uvm_info(`gfn, $sformatf("Stepped divided clocks: %0s", dividers.show()), UVM_MEDIUM)
end
end
// Compare divided clocks, always based on values from clocking block (thus preponed).
forever @cfg.clkmgr_vif.div_clks_cb begin : check_clocks
`DV_CHECK_EQ(cfg.clkmgr_vif.div_clks_cb.actual_clk_io_div4,
cfg.clkmgr_vif.div_clks_cb.exp_clk_io_div4,
$sformatf("Mismatch for clk_io_div4_powerup, expected %b, got %b",
cfg.clkmgr_vif.div_clks_cb.exp_clk_io_div4,
cfg.clkmgr_vif.div_clks_cb.actual_clk_io_div4))
`DV_CHECK_EQ(cfg.clkmgr_vif.div_clks_cb.actual_clk_io_div2,
cfg.clkmgr_vif.div_clks_cb.exp_clk_io_div2,
$sformatf("Mismatch for clk_io_div2_powerup, expected %b, got %b",
cfg.clkmgr_vif.div_clks_cb.exp_clk_io_div2,
cfg.clkmgr_vif.div_clks_cb.actual_clk_io_div2))
end
forever @(posedge cfg.clkmgr_vif.clocks_o.clk_io_powerup) begin : increment_clocks
if (cfg.io_clk_rst_vif.rst_n) begin
bit in_scan_mode = cfg.clkmgr_vif.scanmode_i == On;
#0;
// Deal with div4 update, accounting for scanmode's asynchronicity.
// The incremented clock will be useful for the next comparison cycle.
dividers.increment_div4(in_scan_mode);
dividers.increment_div2();
`uvm_info(`gfn, $sformatf("Incremented divided clocks: %0s", dividers.show()), UVM_MEDIUM)
`uvm_info(`gfn,
$sformatf(
"Update for div clk cb: div2 exp=%b, actual=%b, div4 exp=%b, actual=%b",
dividers.get_div2_clk(cfg.clkmgr_vif.clocks_o.clk_io_powerup),
cfg.clkmgr_vif.clocks_o.clk_io_div2_powerup,
dividers.get_div4_clk(),
cfg.clkmgr_vif.clocks_o.clk_io_div4_powerup),
UVM_LOW)
// This delay seems to help with xcelium: without it the actual divided clocks are stale.
#1;
cfg.clkmgr_vif.update_exp_clk_io_divs(
.exp_div2_value(dividers.get_div2_clk(cfg.clkmgr_vif.clocks_o.clk_io_powerup)),
.actual_div2_value(cfg.clkmgr_vif.clocks_o.clk_io_div2_powerup),
.exp_div4_value(dividers.get_div4_clk()),
.actual_div4_value(cfg.clkmgr_vif.clocks_o.clk_io_div4_powerup));
end
end
join
endtask

virtual task process_tl_access(tl_seq_item item, tl_channels_e channel, string ral_name);
uvm_reg csr;
bit do_read_check = 1'b1;
Expand Down
Loading

0 comments on commit 137bebc

Please sign in to comment.