| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| // |
| // Interface: clk_rst_if |
| // Generic clock and reset interface for clock events in various utilities |
| // It also generates o_clk and o_rst_n signals for driving clk and rst_n in the tb. The advantage is |
| // clk and rst_n can be completely controlled in course of the simulation. |
| // This interface provides methods to set freq/period, wait for clk/rst_n, apply rst_n among other |
| // things. See individual method descriptions below. |
| // inout clk |
| // inout rst_n |
| |
| interface clk_rst_if #( |
| parameter string IfName = "main" |
| ) ( |
| inout clk, |
| inout rst_n |
| ); |
| |
| `ifndef VERILATOR |
| // include macros and import pkgs |
| `include "dv_macros.svh" |
| `include "uvm_macros.svh" |
| import uvm_pkg::*; |
| import common_ifs_pkg::*; |
| `endif |
| |
| // Enables clock to be generated and driven by this interface. |
| bit drive_clk; |
| |
| // The internal output clock value. |
| logic o_clk; |
| |
| // Enables the rst_n to be generated and driven by this interface. |
| bit drive_rst_n; |
| |
| // The internal output reset value. |
| logic o_rst_n; |
| |
| // Applies clock gating. |
| bit clk_gate = 1'b0; |
| |
| // The nominal (chosen) frequency (as period in ps) of the driven clock (50MHz by default). |
| int clk_period_ps = 20_000; |
| |
| // The variation of clock period (mimics uncalibrated clocks), to scale the nominal frequency |
| // down. If set to non-zero value, the clock frequency is scaled randomly on every edge. |
| int clk_freq_scaling_pc = 0; |
| |
| // The percentage chance of freq scaled down randomly on each edge. |
| int clk_freq_scaling_chance_pc = 50; |
| |
| // Clock frequency is scaled down. This enables the frequency to be randomly scaled up as well. |
| // |
| // Note: If set, the randomness of the clock frequency being scaled up or down may result in a |
| // bigger frequency distribution than the intended clk_freq_scaling_pc setting. For example, 50MHz |
| // with 10% scaling may result in pulses that are < 45MHz and > 55MHz wide as well. |
| bit clk_freq_scale_up = 1'b0; |
| |
| // The computed clock frequency in MHz. |
| real clk_freq_mhz = 50; |
| |
| // The duty cycle of the clock period as percentage. If jitter and scaling is applied, then the |
| // duty cycle will not be maintained. |
| int duty_cycle = 50; |
| |
| // Maximum jitter applied to each period of the clock - this is expected to be about 20% or less |
| // than the clock period. |
| // The jitter is divided to two values - plus-jitter and minus-jitter. |
| // Plus jitter is the possible time can be added to the clock period, while the minus jittter is |
| // the possible time can be subtracted from the clock period. |
| // _________ |
| // _____:_| : : |_:_______ |
| // |
| // The actual jitter value is picked randomly within the window |
| // {[-max_minus_jitter_ps:max_plus_jitter_ps]} |
| // and is added to the time to next edge. |
| int max_plus_jitter_ps = 1000; |
| int max_minus_jitter_ps = 1000; |
| |
| // The percentage chance of jitter occurring on each edge. If 0 (default value), then jitter is |
| // disabled altogether. If 100, jitter is computed and applied at every edge. |
| int jitter_chance_pc = 0; |
| |
| // Internal signal indicating the clock half periods need to be recomputed. |
| bit recompute = 1'b1; |
| |
| // Internal signal indicating the amount of time for which the clock stays high / lo in the next |
| // cycle. |
| int clk_hi_ps; |
| int clk_lo_ps; |
| real clk_hi_modified_ps; |
| real clk_lo_modified_ps; |
| |
| // If true, this is the only clock in the system; there is no need to add initial jitter. |
| bit sole_clock = 1'b0; |
| |
| // use IfName as a part of msgs to indicate which clk_rst_vif instance |
| string msg_id = $sformatf("[%m(clk_rst_if):%s]", IfName); |
| |
| clocking cb @(posedge clk); |
| endclocking |
| |
| clocking cbn @(negedge clk); |
| endclocking |
| |
| // Wait for 'n' clocks based of postive clock edge |
| task automatic wait_clks(int num_clks); |
| repeat (num_clks) @cb; |
| endtask |
| |
| // Wait for 'n' clocks based of negative clock edge |
| task automatic wait_n_clks(int num_clks); |
| repeat (num_clks) @cbn; |
| endtask |
| |
| // Wait for 'num_clks' clocks based on the positive clock edge or reset, whichever comes first. |
| task automatic wait_clks_or_rst(int num_clks); |
| fork |
| wait_clks(num_clks); |
| wait_for_reset(.wait_negedge(1'b1), .wait_posedge(1'b0)); |
| join_any |
| endtask |
| |
| // wait for rst_n to assert and then deassert |
| task automatic wait_for_reset(bit wait_negedge = 1'b1, bit wait_posedge = 1'b1); |
| if (wait_negedge && ($isunknown(rst_n) || rst_n === 1'b1)) @(negedge rst_n); |
| if (wait_posedge && (rst_n === 1'b0)) @(posedge rst_n); |
| endtask |
| |
| // set the clk frequency in khz |
| function automatic void set_freq_khz(int freq_khz); |
| clk_freq_mhz = $itor(freq_khz) / 1000; |
| clk_period_ps = 1000_000 / clk_freq_mhz; |
| recompute = 1'b1; |
| endfunction |
| |
| // set the clk frequency in mhz |
| function automatic void set_freq_mhz(int freq_mhz); |
| set_freq_khz(freq_mhz * 1000); |
| endfunction |
| |
| // Set the clk frequency scaling, chance in percentage and scaling up. |
| // |
| // freq_scaling_pc is a positive integer that determines by what amount (as percentage of the |
| // nominal frequency) is the frequency scaled (jittered) down. |
| // freq_scaling_chance_pc is a percentage number between 0 and 100 that determines how often is |
| // the scaling randomly recomputed and applied. |
| // freq_scale_up is a bit that enables the random scaling up of the frequency as well. |
| function automatic void set_freq_scaling(int freq_scaling_pc, int freq_scaling_chance_pc = 50, |
| bit freq_scale_up = 1'b0); |
| `DV_CHECK_FATAL(freq_scaling_pc >= 0, , msg_id) |
| `DV_CHECK_FATAL(freq_scaling_chance_pc inside {[0:100]}, , msg_id) |
| clk_freq_scaling_pc = freq_scaling_pc; |
| clk_freq_scaling_chance_pc = freq_scaling_chance_pc; |
| clk_freq_scale_up = freq_scale_up; |
| endfunction |
| |
| // Enables the clock and reset to be driven. |
| function automatic void set_active(bit drive_clk_val = 1'b1, bit drive_rst_n_val = 1'b1); |
| drive_clk = drive_clk_val; |
| drive_rst_n = drive_rst_n_val; |
| endfunction |
| |
| // set the clk period in ps |
| function automatic void set_period_ps(int period_ps); |
| clk_period_ps = period_ps; |
| clk_freq_mhz = 1000_000 / clk_period_ps; |
| recompute = 1'b1; |
| endfunction |
| |
| // set the duty cycle (1-99) |
| function automatic void set_duty_cycle(int duty); |
| `DV_CHECK_FATAL(duty inside {[1:99]}, , msg_id) |
| duty_cycle = duty; |
| recompute = 1'b1; |
| endfunction |
| |
| // set maximum jitter in ps, separating the plus jitter and the minus jitter. |
| // In default the plus and minus jitters are the same. |
| function automatic void set_max_jitter_ps(int plus_jitter_ps, |
| int minus_jitter_ps = plus_jitter_ps); |
| max_plus_jitter_ps = plus_jitter_ps; |
| max_minus_jitter_ps = minus_jitter_ps; |
| endfunction |
| |
| // set jitter chance in percentage (0 - 100) |
| // 0 - dont add any jitter; 100 - add jitter on every clock edge |
| function automatic void set_jitter_chance_pc(int jitter_chance); |
| `DV_CHECK_FATAL(jitter_chance inside {[0:100]}, , msg_id) |
| jitter_chance_pc = jitter_chance; |
| endfunction |
| |
| // Set whether this is the only clock in the system. If true, various bits of timing randomisation |
| // are disabled. If there's no other clock to (de)synchronise with, this should not weaken the |
| // test at all. |
| function automatic void set_sole_clock(bit is_sole = 1'b1); |
| sole_clock = is_sole; |
| endfunction |
| |
| // start / ungate the clk |
| task automatic start_clk(bit wait_for_posedge = 1'b0); |
| clk_gate = 1'b0; |
| if (wait_for_posedge) wait_clks(1); |
| endtask |
| |
| // stop / gate the clk |
| function automatic void stop_clk(); |
| clk_gate = 1'b1; |
| endfunction |
| |
| // Scales the clock frequency up and down on every edge. |
| function automatic void apply_freq_scaling(); |
| real scaling; |
| real mult = $urandom_range(0, int'(clk_freq_scale_up)) ? 1.0 : -1.0; |
| |
| if ($urandom_range(1, 100) <= clk_freq_scaling_chance_pc) begin |
| scaling = 1.0 + mult * real'($urandom_range(0, clk_freq_scaling_pc)) / 100; |
| clk_hi_modified_ps = clk_hi_ps * scaling; |
| scaling = 1.0 + mult * real'($urandom_range(0, clk_freq_scaling_pc)) / 100; |
| clk_lo_modified_ps = clk_lo_ps * scaling; |
| end |
| endfunction |
| |
| // Applies jitter to clk_hi and clk_lo half periods based on jitter_chance_pc. |
| function automatic void apply_jitter(); |
| int jitter; |
| int plus_jitter; |
| int minus_jitter; |
| |
| if ($urandom_range(1, 100) <= jitter_chance_pc) begin |
| plus_jitter = $urandom_range(0, max_plus_jitter_ps/2); |
| minus_jitter = $urandom_range(0, max_minus_jitter_ps/2); |
| jitter = ($urandom_range(0, 1) ? plus_jitter : (-1 * minus_jitter)); |
| clk_hi_modified_ps = clk_hi_ps + jitter; |
| end |
| if ($urandom_range(1, 100) <= jitter_chance_pc) begin |
| plus_jitter = $urandom_range(0, max_plus_jitter_ps/2); |
| minus_jitter = $urandom_range(0, max_minus_jitter_ps/2); |
| jitter = ($urandom_range(0, 1) ? plus_jitter : (-1 * minus_jitter)); |
| clk_lo_modified_ps = clk_lo_ps + jitter; |
| end |
| endfunction |
| |
| // can be used to override clk/rst pins, e.g. at the beginning of the simulation |
| task automatic drive_rst_pin(logic val = 1'b0); |
| o_rst_n = val; |
| endtask |
| |
| // apply reset with specified scheme |
| // Note: for power on reset, please ensure pre_reset_dly_clks is set to 0 |
| task automatic apply_reset(int pre_reset_dly_clks = 0, |
| int reset_width_clks = $urandom_range(50, 100), |
| int post_reset_dly_clks = 0, |
| rst_scheme_e rst_n_scheme = RstAssertAsyncDeassertSync); |
| int dly_ps; |
| dly_ps = $urandom_range(0, clk_period_ps); |
| wait_clks(pre_reset_dly_clks); |
| case (rst_n_scheme) |
| RstAssertSyncDeassertSync: begin |
| o_rst_n <= 1'b0; |
| wait_clks(reset_width_clks); |
| o_rst_n <= 1'b1; |
| end |
| RstAssertAsyncDeassertSync: begin |
| #(dly_ps * 1ps); |
| o_rst_n <= 1'b0; |
| wait_clks(reset_width_clks); |
| o_rst_n <= 1'b1; |
| end |
| RstAssertAsyncDeassertASync: begin |
| #(dly_ps * 1ps); |
| o_rst_n <= 1'b0; |
| wait_clks(reset_width_clks); |
| dly_ps = $urandom_range(0, clk_period_ps); |
| #(dly_ps * 1ps); |
| o_rst_n <= 1'b1; |
| end |
| default: begin |
| `dv_fatal($sformatf("rst_n_scheme %0d not supported", rst_n_scheme), msg_id) |
| end |
| endcase |
| wait_clks(post_reset_dly_clks); |
| endtask |
| |
| // clk gen |
| initial begin |
| // start driving clk only after the first por reset assertion. The fork/join means that we'll |
| // wait a whole number of clock periods, which means it's possible for the clock to synchronise |
| // with the "expected" timestamps. |
| bit done; |
| fork |
| begin |
| wait_for_reset(.wait_posedge(1'b0)); |
| |
| // Wait a short time after reset before starting to drive the clock. |
| #1ps; |
| o_clk = 1'b0; |
| |
| done = 1'b1; |
| end |
| while (!done) #(clk_period_ps * 1ps); |
| join |
| |
| // If there might be multiple clocks in the system, wait another (randomised) short time to |
| // desynchronise. |
| if (!sole_clock) #($urandom_range(0, clk_period_ps) * 1ps); |
| |
| forever begin |
| if (recompute) begin |
| clk_hi_ps = clk_period_ps * duty_cycle / 100; |
| clk_lo_ps = clk_period_ps - clk_hi_ps; |
| clk_hi_modified_ps = clk_hi_ps; |
| clk_lo_modified_ps = clk_lo_ps; |
| recompute = 1'b0; |
| end |
| if (clk_freq_scaling_pc && clk_freq_scaling_chance_pc) apply_freq_scaling(); |
| if (jitter_chance_pc) apply_jitter(); |
| #(clk_lo_modified_ps * 1ps); |
| if (!clk_gate) o_clk = 1'b1; |
| #(clk_hi_modified_ps * 1ps); |
| o_clk = 1'b0; |
| end |
| end |
| |
| assign clk = drive_clk ? o_clk : 1'bz; |
| assign rst_n = drive_rst_n ? o_rst_n : 1'bz; |
| |
| endinterface |