Handling input to output delay in Monitor sampling

I am verifying a SIPO register. The input bit is available on output after 1 clk cycle. And with clocking block, i have additional 1 clk delay. So there is total 2 clk delay from input to output.
I am yet to code the monitor. I have few questions:

  1. How do i take care of 2 clk delay in monitor while sampling? i.e. i need to start collecting output only after 2 clks but at the same time, need to collect inputs in those 2 clock cycle
  2. If i just add 2 clks delay in the monitor, i might miss out mosi_in input for those 2 clks and moreover the output(sr) of seq_item_collected won’t be of corresponding input .

I am thinking of 2 options(monitor):

  1. Wait for 2 clks to collect valid output only in the beginning. Have 2 buffers for mosi_in(for 2 clks delay in the beginning) in monitor till valid output is available. Later output is sampled on every clock.
  2. Just collect input and output in the seq_item_collecedt on every clock. In scoerboard, make sure that output is compared with its corresponding input(They will be offset by 2 entries). But this may not work with chip_select control etc.

is there any other better way to handle this 2 clk delay? I want input to be sent on every clk, don’t want to lose any input bit and maintain data integrity.

Interface

interface chained_spi_sr_if(input logic clk, sclk, reset );  
  //inputs to RTL
  logic  mosi_in;
  logic  cs_in;
  //RTL output fields
  logic [7:0] sr [NUM_WORDS-1:0];

clocking spi_cb @(posedge sclk);
  default input #1step output #1;
  inout mosi_in;
  //RTL output
  input sr;
endclocking 
endinterface 

DRIVER


virtual task drive();  
  @( spi_vif.spi_cb);
  if(spi_vif.reset ) begin
    spi_vif.spi_cb.mosi_in <= req.mosi_in;
   :
   :
   :
  end //if reset
endtask 

MONITOR

virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    forever begin
      @( spi_vif.spi_cb);   
      if ( spi_vif.reset == 1) begin 
        seq_item_collected.mosi_in    = spi_vif.mosi_in;
        @( spi_vif.spi_cb);
        seq_item_collected.sr = (spi_vif.spi_cb.sr);   
        //TLM fifo   
         trans_collected_port.write(seq_item_collected); 
      end  // if reset   
    end //forever
  endtask : run_phase

In reply to uvmsd:

Or use 2 analysis ports. One to store inputs(output part is not significant) and the other to store outputs(input parts are not significant) only after 2 clocks.
Scoreboard would use these two ports to compare?
I use below code in scoreboard to extract the data from write:

 spi_seq_item mon_pkt_qu1[$];

function void write(spi_seq_item mon_pkt);
  mon_pkt_qu.push_back(mon_pkt);
endfunction : write

How do i differentiate write(in monitor) to two different analysis ports? any links?

In reply to uvmsd:

Using 2 analysis ports (one for input, one for output), it will be no problem. But you need to create different port using `uvm_analysis_imp_decl macro, then you will have 2 different write method to handle input/output trans.


`uvm_analysis_imp_decl(_input)
`uvm_analysis_imp_decl(_output)
class scoreboard extended uvm_socreboard;
   uvm_analysis_imp_input#(spi_seq_item , scoreboard) input_export_m;
   uvm_analysis_imp_output#(spi_seq_item , scoreboard) output_export_m;

   ...

   function void write_input(spi_seq_item trans);
     // Collect input trans
   endfunction : write_input

  function void write_output(spi_seq_item trans);
     // Collect output trans
   endfunction : write_output
endclass

However, if you want to handle 1 transaction including input and output data. There will have the way, all you need to do is saving the transaction until you have enough information.
For example:


class monitor extends uvm_monitor;
  spi_seq_item items[$];
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    fork
      forever begin
        @( spi_vif.spi_cb);   
        if ( spi_vif.reset == 1) begin
          spi_seq_item seq_item_collected = new;
          seq_item_collected.mosi_in = spi_vif.mosi_in;
          items.push_back(seq_item_collected);
        end  // if reset   
      end //forever
      forever begin
        @( spi_vif.spi_cb);   
        if ( spi_vif.reset == 1) begin
          @( spi_vif.spi_cb);
          if(items.size() > 0) begin
            spi_seq_item seq_item_collected = items.pop_front();
            seq_item_collected.sr = (spi_vif.spi_cb.sr); 
            //TLM fifo   
            trans_collected_port.write(seq_item_collected);
          end
          else begin
            `uvm_error("MON", "Unexpected output without input")
          end
        end  // if reset   
      end //forever
    join
  endtask : run_phase

Idea: Save the transaction to a queue when you collect input, restore the transaction from the queue to collect output.

In reply to chris90:

Thanks much Chris Le. A perfect and an elaborate answer!!!
I thought of using buffers but thought it might be bit confusing. So went ahead with 2 analysis ports. I referred to below link though:

I will keep in mind your queue method for future use. Its more efficient.

I have a question:
Since there is a chip_select(active_low) signals which is high for valid input data, does it have to be re-aligned with the data after clocking block? The input data gets delayed by one clock after clocking block because of skew. Moreover output sampling too takes an extra clock with clocking block, right?
Since cs_in(chip_select) is generated in driver, can i just delay or shift it by extra 2 clks to handle clocking block presence(because of input & output skews?) Right now, i see some data mismatch and i am suspecting it to be because of chip_select.

Thanks again!

In reply to uvmsd:

Hi,
I dont understand how to use two analysis ports. In my case I have to compute the output at a particular valid signal but send it to scoreboard after a delay of three clock cycles based another signal flag.
This is how i am currently implementing using the buffer approach but i am curious to know how to implement using two ports instead.


class normal_add_ref_monitor_after extends uvm_monitor;
`uvm_component_utils(normal_add_ref_monitor_after)
  jelly_bean_transaction jb_txm;
   jelly_bean_transaction jb_txm_q[$];
virtual simpleadder_if jb_vi;
uvm_analysis_port#(jelly_bean_transaction) ref_mon_a_port_after;

   function new(string name="normal_add_ref_monitor_after",uvm_component parent);
      super.new(name,parent);
   endfunction :new
   function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      void'(uvm_resource_db#(virtual simpleadder_if)::read_by_name(.scope("ifs"), .name("simpleadder_if"), .val(jb_vi)));
      ref_mon_a_port_after=new("ref_mon_a_port_after",this);
   endfunction:build_phase
   task run_phase(uvm_phase phase);
      jb_txm=jelly_bean_transaction::type_id::create("jb_txm");
      fork
	 forever begin  
	    @(posedge jb_vi.clk)
	    
	      if(jb_vi.valid == 1) begin
	       jelly_bean_transaction jb_txmq = new;
	       
	       jb_txmq.a = jb_vi.a;
 	       jb_txmq.b=jb_vi.b;
	       jb_txmq.c=jb_txmq.a +jb_txmq.b ;
		 // add_mon_ref_add_result();
		 jb_txm_q.push_back(jb_txmq);
		 $display("queue = %p",jb_txm_q);
		 
		 
	       
		 
	      end
	 end
	 forever begin  
	    @(posedge jb_vi.clk)  
	      if(jb_vi.valid_out == 1) begin
		 jelly_bean_transaction jb_txmq = jb_txm_q[0];
		 ref_mon_a_port_after.write(jb_txmq);
		 `uvm_info("input_pop",$sformatf("value: %0d", jb_txm_q.pop_front()),UVM_LOW);
		
	      end
	 end // forever begin
      join
endtask:run_phase
endclass:normal_add_ref_monitor_after

In reply to pallavi rajanna:

Coulyou please use the SV code tags to make your code more readible.

In reply to chr_sue:

Sorry about that. Its changed now :)

In reply to pallavi rajanna:

Thanks.
I do not understand why you are using a queue in your monitor.
THe monitor should publish a transaction when the data are valid. Because the scoreboard does not know anything about clock cycles there should not be any problem. To store analysis data you should use a uvm_tlm_analysis_fifo in the scoreboard. The scoreboard has to process the data in the right order indepemden t on a clock cycles.

In reply to chr_sue:
Monitor code that is shared gets both inputs and make a prediction of the expected result
Without the queue the code would look something like this

task run_phase(uvm_phase phase);
      jb_txm=jelly_bean_transaction::type_id::create("jb_txm");
      fork
	 forever begin  
	    @(posedge jb_vi.clk)
	       jelly_bean_transaction jb_txmq = new;
               if(jb_vi.valid == 1)
	       jb_txmq.a = jb_vi.a;
 	       jb_txmq.b=jb_vi.b;
	       jb_txmq.c=jb_txmq.a +jb_txmq.b ;
               if(jb_vi.valid_out == 1)
               ref_mon_a_port_after.write(jb_txmq);		
	      end
	 end
endtask:run_phase

This does not give the correct results as my inputs when the valid_out=1 is different from the inputs when valid =1 . I need to compute sum when valid=1 but this output is delayed until valid_out =1 . Hence i am computing the sum and storing the results in queue and fetching the stored results only when valid_out =1 .Is there any better way of doing this

In reply to pallavi rajanna:

You could calculate the sum in the scoreboard. In most cases the scoreboard has inside a reference model. If you are doing this in the monitor your are dependent on the clock cycles. This makes your life complicated.