| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| class pattgen_base_vseq extends cip_base_vseq #( |
| .RAL_T (pattgen_reg_block), |
| .CFG_T (pattgen_env_cfg), |
| .COV_T (pattgen_env_cov), |
| .VIRTUAL_SEQUENCER_T (pattgen_virtual_sequencer) |
| ); |
| `uvm_object_utils(pattgen_base_vseq) |
| `uvm_object_new |
| |
| // variables |
| uint num_pattern_req = 0; |
| uint num_pattern_gen = 0; |
| // channel config |
| rand pattgen_channel_cfg channel_cfg[NUM_PATTGEN_CHANNELS-1:0]; |
| |
| // indicate channels are setup before enabled |
| bit [NUM_PATTGEN_CHANNELS-1:0] channel_setup = 'h0; |
| bit [NUM_PATTGEN_CHANNELS-1:0] channel_start = 'h0; |
| // this one-hot vector is used for round-robin arbitrating the accesses to the shared registers |
| // this vector is initialized to 1 (the channel 0 is granted by default) |
| bit [NUM_PATTGEN_CHANNELS-1:0] channel_grant = 'h1; |
| |
| // random variables |
| rand uint num_runs; |
| rand uint b2b_pattern_dly; |
| rand uint clear_intr_dly; |
| // if start_all_channels bit is set: both channels can start simmultaneously |
| rand bit start_all_channels; |
| rand bit do_error_injected; |
| |
| // constraints |
| constraint num_trans_c { |
| num_trans inside {[cfg.seq_cfg.pattgen_min_num_trans : cfg.seq_cfg.pattgen_max_num_trans]}; |
| } |
| constraint num_runs_c { |
| num_runs inside {[cfg.seq_cfg.pattgen_min_num_runs : cfg.seq_cfg.pattgen_max_num_runs]}; |
| } |
| constraint start_all_channels_c { |
| start_all_channels dist { |
| 1'b1 :/ cfg.seq_cfg.pattgen_sync_channels_pct, |
| 1'b0 :/ (100 - cfg.seq_cfg.pattgen_sync_channels_pct) |
| }; |
| } |
| constraint do_error_injected_c { |
| do_error_injected dist { |
| 1'b1:/ cfg.seq_cfg.error_injected_pct, |
| 1'b0:/ (100 - cfg.seq_cfg.error_injected_pct) |
| }; |
| } |
| constraint b2b_pattern_dly_c { |
| b2b_pattern_dly inside {[cfg.seq_cfg.pattgen_min_dly : cfg.seq_cfg.pattgen_max_dly]}; |
| } |
| constraint clear_intr_dly_c { |
| clear_intr_dly inside {[cfg.seq_cfg.pattgen_min_dly : cfg.seq_cfg.pattgen_max_dly]}; |
| } |
| |
| virtual task pre_start(); |
| cfg.m_pattgen_agent_cfg.en_monitor = cfg.en_scb; |
| `uvm_info(`gfn, $sformatf("\n--> %s monitor and scoreboard", |
| cfg.en_scb ? "enable" : "disable"), UVM_DEBUG) |
| num_runs.rand_mode(0); |
| // env_cfg must be reset after vseq completion |
| cfg.seq_cfg.error_injected_enb = 1'b0; |
| cfg.seq_cfg.pattgen_min_prediv = 0; |
| cfg.seq_cfg.pattgen_min_len = 0; |
| cfg.seq_cfg.pattgen_min_reps = 0; |
| super.pre_start(); |
| endtask : pre_start |
| |
| // setup basic pattgen features |
| virtual task initialize_dut(); |
| csr_wr(.ptr(ral.intr_enable), .value({TL_DW{1'b1}})); |
| `uvm_info(`gfn, "\n call pattgen_init", UVM_DEBUG) |
| endtask : initialize_dut |
| |
| // TODO: consider optimize the base_vseq to make each channel |
| // cfg, start, stop independently with semaphore (PR #4040) |
| virtual task body(); |
| `uvm_info(`gfn, "\n--> start of sequence", UVM_DEBUG) |
| `uvm_info(`gfn, $sformatf("\n--> total required patterns %0d", num_trans), UVM_DEBUG) |
| |
| initialize_dut(); |
| while (num_pattern_req < num_trans || // not send all pattern configs |
| num_pattern_gen < num_trans || // not get all pattern done interrupts |
| channel_start) begin // at least one channel is running |
| wait(cfg.clk_rst_vif.rst_n); // wait until reset is de-asserted |
| fork |
| // all channels are completely independent (programm, start w/ or wo/ sync, and stop) |
| setup_pattgen_channel_0(); |
| setup_pattgen_channel_1(); |
| start_pattgen_channels(); |
| stop_pattgen_channels(); |
| join |
| end |
| `uvm_info(`gfn, "\n--> end of sequence", UVM_DEBUG) |
| endtask : body |
| |
| virtual task setup_pattgen_channel_0(); |
| if (num_pattern_req < num_trans && |
| channel_grant[0] && // ch0 setup is granted |
| !channel_setup[0] && // ch0 has not been programmed |
| !channel_start[1]) begin // ch1 is not under start (avoid re-programming regs) |
| wait_for_channel_ready(Channel0); |
| channel_cfg[0] = get_random_channel_config(.channel(0)); |
| ral.size.len_ch0.set(channel_cfg[0].len); |
| ral.size.reps_ch0.set(channel_cfg[0].reps); |
| csr_update(ral.size); |
| csr_wr(.ptr(ral.prediv_ch0), .value(channel_cfg[0].prediv)); |
| csr_wr(.ptr(ral.data_ch0[0]), .value(channel_cfg[0].data[31:0])); |
| csr_wr(.ptr(ral.data_ch0[1]), .value(channel_cfg[0].data[63:32])); |
| ral.ctrl.polarity_ch0.set(channel_cfg[0].polarity); |
| update_pattgen_agent_cfg(.channel(0)); |
| csr_update(ral.ctrl); |
| channel_setup[0] = 1'b1; |
| void'(right_rotation(channel_grant)); |
| `uvm_info(`gfn, "\n channel 0: programmed", UVM_DEBUG) |
| end |
| endtask : setup_pattgen_channel_0 |
| |
| virtual task setup_pattgen_channel_1(); |
| if (num_pattern_req < num_trans && |
| channel_grant[1] && // ch1 setup is granted |
| !channel_setup[1] && // ch1 has not been programmed |
| !channel_start[0]) begin // ch0 is not under start (avoid re-programming regs) |
| wait_for_channel_ready(Channel1); |
| channel_cfg[1] = get_random_channel_config(.channel(1)); |
| ral.size.len_ch1.set(channel_cfg[1].len); |
| ral.size.reps_ch1.set(channel_cfg[1].reps); |
| csr_update(ral.size); |
| csr_wr(.ptr(ral.prediv_ch1), .value(channel_cfg[1].prediv)); |
| csr_wr(.ptr(ral.data_ch1[0]), .value(channel_cfg[1].data[31:0])); |
| csr_wr(.ptr(ral.data_ch1[1]), .value(channel_cfg[1].data[63:32])); |
| ral.ctrl.polarity_ch1.set(channel_cfg[1].polarity); |
| update_pattgen_agent_cfg(.channel(1)); |
| csr_update(ral.ctrl); |
| channel_setup[1] = 1'b1; |
| void'(right_rotation(channel_grant)); |
| `uvm_info(`gfn, "\n channel 1: programmed", UVM_DEBUG) |
| end |
| endtask : setup_pattgen_channel_1 |
| |
| virtual task start_pattgen_channels(); |
| if (num_pattern_req < num_trans) begin |
| `DV_CHECK_MEMBER_RANDOMIZE_FATAL(start_all_channels) |
| // if start_all_channels is set, then activate both channels if they are setup and not busy |
| if (start_all_channels) begin |
| // once start_all_channels is set, temporaly disable its randomization to sync all channels |
| start_all_channels.rand_mode(0); |
| if (channel_setup[0] && !channel_start[0] && channel_setup[1] && !channel_start[1]) begin |
| `DV_CHECK_MEMBER_RANDOMIZE_FATAL(b2b_pattern_dly) |
| cfg.clk_rst_vif.wait_clks(b2b_pattern_dly); |
| control_channels(AllChannels, Enable); |
| {channel_start[0], channel_start[1]} = {1'b1, 1'b1}; |
| `uvm_info(`gfn, "\n all channels: activated", UVM_DEBUG) |
| // re-enable start_all_channels's randomization after all channels have been started |
| start_all_channels.rand_mode(1); |
| end |
| end else begin |
| // otherwise, activate channels independently |
| for (uint i = 0; i < NUM_PATTGEN_CHANNELS; i++) begin |
| if (channel_setup[i] && !channel_start[i]) begin |
| channel_select_e ch_select; |
| `downcast(ch_select, i+1) |
| `DV_CHECK_MEMBER_RANDOMIZE_FATAL(b2b_pattern_dly) |
| cfg.clk_rst_vif.wait_clks(b2b_pattern_dly); |
| control_channels(ch_select, Enable); |
| channel_start[i]= 1'b1; |
| `uvm_info(`gfn, $sformatf("\n channel %0d: activated", i), UVM_DEBUG) |
| end |
| end |
| end |
| end |
| endtask : start_pattgen_channels |
| |
| // this task allows channels to be stopped independently/simultaneously |
| virtual task stop_pattgen_channels(); |
| bit error_injected; |
| bit [NUM_PATTGEN_CHANNELS-1:0] intr_status; |
| bit [NUM_PATTGEN_CHANNELS-1:0] channel_stop; |
| |
| if (channel_start) begin |
| error_injected = 1'b0; |
| get_interrupt_states(intr_status); |
| //-------------------------------------------------------------------------------- |
| // inject error |
| //-------------------------------------------------------------------------------- |
| // ch_start intr_status ch_stop description |
| //-------------------------------------------------------------------------------- |
| // 01 (CH0) 01 01 CH0 finished CH1 not started |
| // 01 (CH0) 00 01 CH0 error injected* CH1 not started |
| // 10 (CH1) 10 10 CH0 not started CH1 finished |
| // 10 (CH1) 00 10 CH0 not started CH1 error injected* |
| // 11 (CH1) 01 01 CH0 finished CH1 error injected* |
| // 11 (CH1) 10 10 CH0 error injected* CH1 finished |
| // 11 (CH1) 11 11 CH0 finished CH1 finished |
| // 11 (CH1) 00 11 CH0 error injected CH1 error injected* |
| channel_stop = intr_status; // default setting |
| if (cfg.seq_cfg.error_injected_enb) begin |
| `DV_CHECK_MEMBER_RANDOMIZE_FATAL(do_error_injected) |
| if (do_error_injected) begin |
| if (intr_status != channel_start) begin |
| channel_stop = channel_start; |
| error_injected = 1'b1; |
| cfg.m_pattgen_agent_cfg.error_injected = channel_stop; |
| `uvm_info(`gfn, $sformatf("\n error inject: intr_status %b ch_stop %b ch_start %b", |
| intr_status, channel_stop, channel_start), UVM_DEBUG) |
| end |
| end |
| end |
| |
| case (channel_stop) |
| Channel0: begin |
| if (!error_injected) cfg.m_pattgen_agent_cfg.channel_done = Channel0; |
| clear_interrupts(Channel0, error_injected); |
| control_channels(Channel0, Disable); |
| if (error_injected) cfg.m_pattgen_agent_cfg.error_injected[0] = 1'b0; |
| num_pattern_gen++; |
| {channel_setup[0], channel_start[0]} = {1'b0, 1'b0}; |
| `uvm_info(`gfn, $sformatf("\n channel 0: %s %0d/%0d", |
| error_injected ? "error" : "completed", num_pattern_gen, num_trans), UVM_DEBUG) |
| end |
| Channel1: begin |
| if (!error_injected) cfg.m_pattgen_agent_cfg.channel_done = Channel1; |
| clear_interrupts(Channel1, error_injected); |
| control_channels(Channel1, Disable); |
| if (error_injected) cfg.m_pattgen_agent_cfg.error_injected[1] = 1'b0; |
| num_pattern_gen++; |
| {channel_setup[1], channel_start[1]} = {1'b0, 1'b0}; |
| `uvm_info(`gfn, $sformatf("\n channel 1: %s %0d/%0d", |
| error_injected ? "error" : "completed", num_pattern_gen, num_trans), UVM_DEBUG) |
| end |
| AllChannels: begin |
| if (!error_injected) cfg.m_pattgen_agent_cfg.channel_done = AllChannels; |
| clear_interrupts(AllChannels, error_injected); |
| control_channels(AllChannels, Disable); |
| if (error_injected) begin |
| cfg.m_pattgen_agent_cfg.error_injected = NoChannels; |
| `uvm_info(`gfn, $sformatf("\n update m_pattgen_agent_cfg.error_injected"), UVM_DEBUG) |
| end |
| num_pattern_gen += 2; |
| channel_setup = {NUM_PATTGEN_CHANNELS{1'b0}}; |
| channel_start = {NUM_PATTGEN_CHANNELS{1'b0}}; |
| `uvm_info(`gfn, $sformatf("\n all channels: %s %0d/%0d", |
| error_injected ? "error" : "completed", num_pattern_gen, num_trans), UVM_DEBUG) |
| end |
| endcase |
| cfg.m_pattgen_agent_cfg.channel_done = NoChannels; |
| end |
| endtask : stop_pattgen_channels |
| |
| virtual task wait_for_channel_ready(channel_select_e ch_select); |
| case (ch_select) |
| Channel0: begin |
| csr_spinwait(.ptr(ral.ctrl.enable_ch0), .exp_data(1'b0)); |
| `uvm_info(`gfn, $sformatf("\n channel 0 is ready for programmed"), UVM_DEBUG) |
| end |
| Channel1: begin |
| csr_spinwait(.ptr(ral.ctrl.enable_ch1), .exp_data(1'b0)); |
| `uvm_info(`gfn, $sformatf("\n channel 1 is ready for programmed"), UVM_DEBUG) |
| end |
| endcase |
| endtask : wait_for_channel_ready |
| |
| // this task allows channels to be activated or stopped independently/simultaneously |
| virtual task control_channels(channel_select_e ch_select, channel_status_e status); |
| // wait for no register access on bus before enable channels to avoid the race condition |
| // (i.e. concurrent access to update ctrl.polarity on the other channel) |
| wait_no_outstanding_access(); |
| case (ch_select) |
| Channel0: begin |
| ral.ctrl.enable_ch0.set(status); |
| if (status == Enable) begin |
| num_pattern_req++; |
| `uvm_info(`gfn, $sformatf("\n channel 0: request %0d/%0d\n%s", |
| num_pattern_req, num_trans, channel_cfg[0].convert2string()), UVM_DEBUG) |
| end |
| end |
| Channel1: begin |
| ral.ctrl.enable_ch1.set(status); |
| if (status == Enable) begin |
| num_pattern_req++; |
| `uvm_info(`gfn, $sformatf("\n channel 1: request %0d/%0d\n%s", |
| num_pattern_req, num_trans, channel_cfg[1].convert2string()), UVM_DEBUG) |
| end |
| end |
| AllChannels: begin |
| ral.ctrl.enable_ch0.set(status); |
| ral.ctrl.enable_ch1.set(status); |
| if (status == Enable) begin |
| num_pattern_req += 2; |
| `uvm_info(`gfn, $sformatf("\n sync channel 0: request %0d/%0d\n%s", |
| num_pattern_req - 1, num_trans, channel_cfg[0].convert2string()), UVM_DEBUG) |
| `uvm_info(`gfn, $sformatf("\n sync channel 1: request %0d/%0d\n%s", |
| num_pattern_req, num_trans, channel_cfg[0].convert2string()), UVM_DEBUG) |
| end |
| end |
| endcase |
| if (status == Enable) begin |
| `uvm_info(`gfn, $sformatf("\n requested %0d/%0d patterns", |
| num_pattern_req, num_trans), UVM_DEBUG) |
| end else begin |
| `uvm_info(`gfn, $sformatf("\n received %0d/%0d patterns", |
| num_pattern_gen, num_trans), UVM_DEBUG) |
| end |
| csr_update(ral.ctrl); |
| endtask : control_channels |
| |
| virtual task get_interrupt_states(output bit[NUM_PATTGEN_CHANNELS-1:0] intr_bits); |
| bit [TL_DW-1:0] intr_state; |
| csr_rd(.ptr(ral.intr_state), .value(intr_state)); |
| intr_bits = NUM_PATTGEN_CHANNELS'(intr_state); |
| `uvm_info(`gfn, $sformatf("\n intr_state %b", intr_bits), UVM_DEBUG) |
| endtask : get_interrupt_states |
| |
| // this task allows the interrupts of channels to be cleared independently/simultaneously |
| virtual task clear_interrupts(channel_select_e ch_select, bit error = 1'b0); |
| bit [TL_DW-1:0] intr_clear ='h0; |
| |
| if (!error) begin |
| case (ch_select) |
| Channel0: intr_clear = 1 << DoneCh0; |
| Channel1: intr_clear = 1 << DoneCh1; |
| AllChannels: intr_clear = (1 << DoneCh1) | (1 << DoneCh0); |
| default: `uvm_fatal(`gfn, " invalid argument") |
| endcase |
| `DV_CHECK_MEMBER_RANDOMIZE_FATAL(clear_intr_dly) |
| cfg.clk_rst_vif.wait_clks(clear_intr_dly); |
| csr_wr(.ptr(ral.intr_state), .value(intr_clear)); |
| end else begin |
| `uvm_info(`gfn, $sformatf("\n channel error, no clear interrupts %b", intr_clear), UVM_DEBUG) |
| end |
| endtask : clear_interrupts |
| |
| // this function randomizes the channel config |
| virtual function pattgen_channel_cfg get_random_channel_config(uint channel); |
| pattgen_channel_cfg ch_cfg; |
| ch_cfg = pattgen_channel_cfg::type_id::create($sformatf("channel_cfg_%0d", channel)); |
| `DV_CHECK_RANDOMIZE_WITH_FATAL(ch_cfg, |
| ch_cfg.polarity dist { |
| 1'b0 :/ cfg.seq_cfg.pattgen_low_polarity_pct, |
| 1'b1 :/ (100 - cfg.seq_cfg.pattgen_low_polarity_pct) |
| }; |
| ch_cfg.data[31:0] dist { |
| DataMax :/ cfg.seq_cfg.data_top_pct, |
| DataMin :/ cfg.seq_cfg.data_bottom_pct, |
| [DataMin + 1:DataMax - 1] :/ cfg.seq_cfg.data_middle_pct |
| }; |
| ch_cfg.data[63:32] dist { |
| DataMax :/ cfg.seq_cfg.data_top_pct, |
| DataMin :/ cfg.seq_cfg.data_bottom_pct, |
| [DataMin:DataMax] :/ cfg.seq_cfg.data_middle_pct |
| }; |
| ch_cfg.prediv dist { |
| cfg.seq_cfg.pattgen_max_prediv :/ cfg.seq_cfg.data_top_pct, |
| cfg.seq_cfg.pattgen_min_prediv :/ cfg.seq_cfg.data_bottom_pct, |
| [cfg.seq_cfg.pattgen_min_prediv + 1 : |
| cfg.seq_cfg.pattgen_max_prediv - 1] :/ cfg.seq_cfg.data_middle_pct |
| }; |
| ch_cfg.reps dist { |
| cfg.seq_cfg.pattgen_max_reps :/ cfg.seq_cfg.data_top_pct, |
| cfg.seq_cfg.pattgen_min_reps :/ cfg.seq_cfg.data_bottom_pct, |
| [cfg.seq_cfg.pattgen_min_reps:cfg.seq_cfg.pattgen_max_reps] :/ cfg.seq_cfg.data_middle_pct |
| }; |
| ch_cfg.len dist { |
| cfg.seq_cfg.pattgen_max_len :/ cfg.seq_cfg.data_top_pct, |
| cfg.seq_cfg.pattgen_min_len :/ cfg.seq_cfg.data_bottom_pct, |
| [cfg.seq_cfg.pattgen_min_len:cfg.seq_cfg.pattgen_max_len] :/ cfg.seq_cfg.data_middle_pct |
| }; |
| ) |
| return ch_cfg; |
| endfunction : get_random_channel_config |
| |
| // this function allows update the pattgen_agent_config |
| virtual function void update_pattgen_agent_cfg(int channel); |
| cfg.m_pattgen_agent_cfg.polarity[channel] = channel_cfg[channel].polarity; |
| // see the specification document, the effective values of prediv, len, and reps |
| // are incremented from the coresponding register values |
| cfg.m_pattgen_agent_cfg.length[channel] = (channel_cfg[channel].len + 1) * |
| (channel_cfg[channel].reps + 1); |
| `uvm_info(`gfn, $sformatf("\n--> pattgen_agent_cfg: channel %0d, polarity %0b, length %0d", |
| channel, cfg.m_pattgen_agent_cfg.polarity[channel], |
| cfg.m_pattgen_agent_cfg.length[channel]), UVM_DEBUG) |
| endfunction : update_pattgen_agent_cfg |
| |
| // right rotate a one-hot vector |
| virtual function right_rotation(ref bit [NUM_PATTGEN_CHANNELS-1:0] x); |
| x = {x[NUM_PATTGEN_CHANNELS-2:0], x[NUM_PATTGEN_CHANNELS-1]}; |
| endfunction : right_rotation |
| |
| task wait_host_for_idle(); |
| csr_spinwait(.ptr(ral.ctrl.enable_ch0), .exp_data(1'b0)); |
| csr_spinwait(.ptr(ral.ctrl.enable_ch1), .exp_data(1'b0)); |
| csr_spinwait(.ptr(ral.intr_state.done_ch0), .exp_data(1'b0)); |
| csr_spinwait(.ptr(ral.intr_state.done_ch1), .exp_data(1'b0)); |
| endtask : wait_host_for_idle |
| |
| endclass : pattgen_base_vseq |