| // 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::*; | 
 | `endif | 
 |  | 
 |   bit drive_clk;              // enable clk generation | 
 |   logic o_clk;                // output clk | 
 |  | 
 |   bit drive_rst_n;            // enable rst_n generation | 
 |   logic o_rst_n;              // output rst_n | 
 |  | 
 |   // clk params | 
 |   bit clk_gate      = 1'b0;   // clk gate signal | 
 |   int clk_period_ps = 20_000; // 50MHz default | 
 |   real clk_freq_mhz = 50;     // 50MHz default | 
 |   int duty_cycle    = 50;     // 50% default | 
 |   int max_jitter_ps = 1000;   // 1ns default | 
 |   bit recompute     = 1'b1;   // compute half periods when period/freq/duty are changed | 
 |   int clk_hi_ps;              // half period hi in ps | 
 |   int clk_lo_ps;              // half period lo in ps | 
 |   int jitter_chance_pc = 0;   // jitter chance in percentage on clock edge - disabled by default | 
 |   bit sole_clock = 1'b0;      // if true, this is the only clock in the system | 
 |  | 
 |   // use IfName as a part of msgs to indicate which clk_rst_vif instance | 
 |   string msg_id = {"clk_rst_if::", 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 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 | 
 |  | 
 |   // call this function at t=0 (from tb top) to enable clk and rst_n to be driven | 
 |   function automatic void set_active(bit drive_clk_val = 1'b1, bit drive_rst_n_val = 1'b1); | 
 |     time t = $time; | 
 |     if (t == 0) begin | 
 |       drive_clk = drive_clk_val; | 
 |       drive_rst_n = drive_rst_n_val; | 
 |     end | 
 |     else begin | 
 | `ifdef VERILATOR | 
 |       $error({msg_id, "this function can only be called at t=0"}); | 
 | `else | 
 |       `uvm_fatal(msg_id, "this function can only be called at t=0") | 
 | `endif | 
 |     end | 
 |   endfunction | 
 |  | 
 |   // set the clk period in ns | 
 |   function automatic void set_period_ns(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); | 
 |     if (!(duty inside {[1:99]})) begin | 
 | `ifdef VERILATOR | 
 |       $error({msg_id, $sformatf("duty cycle %0d is not inside [1:99]", duty)}); | 
 | `else | 
 |       `uvm_fatal(msg_id, $sformatf("duty cycle %0d is not inside [1:99]", duty)) | 
 | `endif | 
 |     end | 
 |     duty_cycle = duty; | 
 |     recompute = 1'b1; | 
 |   endfunction | 
 |  | 
 |   // set maximum jitter in ps | 
 |   function automatic void set_max_jitter_ps(int jitter_ps); | 
 |     max_jitter_ps = 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); | 
 |     if (!(jitter_chance inside {[0:100]})) begin | 
 | `ifdef VERILATOR | 
 |       $error({msg_id, $sformatf("jitter_chance %0d is not inside [0:100]", jitter_chance)}); | 
 | `else | 
 |       `uvm_fatal(msg_id, $sformatf("jitter_chance %0d is not inside [0:100]", jitter_chance)) | 
 | `endif | 
 |     end | 
 |     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 | 
 |  | 
 |   // add jitter to clk_hi and clk_lo half periods based on jitter_chance_pc | 
 |   function automatic void add_jitter(); | 
 |     int jitter_ps; | 
 |     if ($urandom_range(1, 100) <= jitter_chance_pc) begin | 
 | `ifndef VERILATOR | 
 |       `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(jitter_ps, | 
 |           jitter_ps inside {[-1*max_jitter_ps:max_jitter_ps]};, "", msg_id) | 
 | `endif | 
 |       clk_hi_ps += jitter_ps; | 
 |     end | 
 |     if ($urandom_range(1, 100) <= jitter_chance_pc) begin | 
 | `ifndef VERILATOR | 
 |       `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(jitter_ps, | 
 |           jitter_ps inside {[-1*max_jitter_ps:max_jitter_ps]};, "", msg_id) | 
 | `endif | 
 |       clk_lo_ps += jitter_ps; | 
 |     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 | 
 |   // TODO make this enum? | 
 |   // rst_n_scheme | 
 |   // 0 - fullly synchronous reset - it is asserted and deasserted on clock edges | 
 |   // 1 - async assert, sync dessert (default) | 
 |   // 2 - async assert, async dessert | 
 |   // 3 - clk gated when reset asserted | 
 |   // Note: for power on reset, please ensure pre_reset_dly_clks is set to 0 | 
 |   // TODO #2338 issue workaround - $urandom call moved from default argument value to function body | 
 |   task automatic apply_reset(int pre_reset_dly_clks   = 0, | 
 |                              integer reset_width_clks = 'x, | 
 |                              int post_reset_dly_clks  = 0, | 
 |                              int rst_n_scheme         = 1); | 
 |     int dly_ps; | 
 |     if ($isunknown(reset_width_clks)) reset_width_clks = $urandom_range(4, 20); | 
 |     dly_ps = $urandom_range(0, clk_period_ps); | 
 |     wait_clks(pre_reset_dly_clks); | 
 |     case (rst_n_scheme) | 
 |       0: begin : sync_assert_deassert | 
 |         o_rst_n <= 1'b0; | 
 |         wait_clks(reset_width_clks); | 
 |         o_rst_n <= 1'b1; | 
 |       end | 
 |       1: begin : async_assert_sync_deassert | 
 |         #(dly_ps * 1ps); | 
 |         o_rst_n <= 1'b0; | 
 |         wait_clks(reset_width_clks); | 
 |         o_rst_n <= 1'b1; | 
 |       end | 
 |       2: begin : async_assert_async_deassert | 
 |         #(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 | 
 | `ifdef VERILATOR | 
 |         $error({msg_id, $sformatf("rst_n_scheme %0d not supported", rst_n_scheme)}); | 
 | `else | 
 |         `uvm_fatal(msg_id, $sformatf("rst_n_scheme %0d not supported", rst_n_scheme)) | 
 | `endif | 
 |       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; | 
 |         recompute = 1'b0; | 
 |       end | 
 |       if (jitter_chance_pc != 0) add_jitter(); | 
 |       #(clk_lo_ps * 1ps); | 
 |       // wiggle output clk if not gated | 
 |       if (!clk_gate) o_clk = 1'b1; | 
 |       #(clk_hi_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 |