Saturday, November 1, 2014

Using indirect_access(...) in vr_ad

I've been working a lot with vr_ad lately. It has a lot of nice features for modeling registers, but unfortunately not all of them are documented. I'm going to do a longer series of posts based on my recent experiences.

Let's start out small. We've all had the case where accesses to one register of the design affect the values of other registers. This might be best illustrated with a concrete example. Let's say we have a status register defined as follows:

<'
reg_def STATUS EXAMPLE 0x0 {
  reg_fld VALID : uint(bits : 1) : R : 0x0;
  reg_fld DONE  : uint(bits : 1) : R : 0x0;
};
'>

We'll also have a control register:

<'
reg_def CONTROL EXAMPLE 0x4 {
  reg_fld SETVALID : uint(bits : 1) : W : 0x0;
  reg_fld CLRVALID : uint(bits : 1) : W : 0x0;
  reg_fld CLRDONE  : uint(bits : 1) : W : 0x0;
  reg_fld START    : uint(bits : 1) : W : 0x0;
};
'>

The status flags are updated by the device. Using the control register's SETVALID and CLRVALID fields, we can affect the value of the VALID status flag. Whenever our VALID flag is set, our DUT can start crunching data. The operation is triggered by writing the START field of the control register and terminates within 5 clock cycles. The DONE status flag is set by the hardware once the operation completes and can be cleared by writing to the CLRDONE control field.

In the past I implemented this by giving the affecting register pointers to the affected registers and implementing the logic inside the post_access(...) method. While this works, vr_ad provides a neater way of doing it.

What we first need to do is to mark the status register as an observer of the control register:

<'
extend EXAMPLE vr_ad_reg_file {
  add_registers() is also {
    control.attach(status);
  };
};
'>

Now, whenever the control register is accessed, a method called indirect_access(...) is called inside the status register. This method receives the direction of the access, together with the observed vr_ad object (in our case it's a register, but it could just as well be a register file, a memory, etc.). Using this information we can model the evolution of the status flags.

Let's start with modeling the VALID flag. As stated above, writing a '1' to SETVALID will set the flag, while writing a '1' to CLRVALID will clear it. Simultaneously writing '1's to both fields doesn't make sense, so what we'll do in that case is leave the flag unchanged. Let's distil this behavior into a method:

<'
extend STATUS vr_ad_reg {
  model_valid(control : CONTROL vr_ad_reg) is {
    if control.SETVALID == 1 and control.CLRVALID == 0 {
      VALID = 1;
    }
    else if control.CLRVALID == 1 {
      VALID = 0;
    };
  };
};
'>

We'll call this method from within indirect_access(...), whenever a write to CONTROL happens:

<'
extend STATUS vr_ad_reg {
  indirect_access(direction : vr_ad_rw_t, ad_item : vr_ad_base) is {
    if direction == WRITE {
      var control := ad_item.as_a(CONTROL vr_ad_reg);
      assert control != NULL;
      
      model_valid(control);
    };
  };
};
'>

Let's give it a test drive to make sure that everything works. We'll emulate a monitor updating the register model by calling update(...) for writes and compare_and_update(...) for reads. To make things easier, let's wrap these calls into two handy methods to access the registers:

<'
extend sys {
  reg_file : EXAMPLE vr_ad_reg_file;
  
  event clk is @sys.any;
  
  write_control(data : vr_ad_data_t) @clk is {
    reg_file.update(0x4, pack(packing.high, data), {});
    wait [1];
  };
  
  read_status(data : vr_ad_data_t) @clk is {
    compute reg_file.compare_and_update(0x0, pack(packing.high, data));
    wait [1];
  };
};
'>

The data argument we pass to these methods represents the data seen on the bus. When reading the status register, if the data we pass to compare_and_update(...) doesn't match the model's value, an error message will appear and we'll know we've made a mistake.

Let's start by trying to set VALID:

<'
extend sys {
  run() is also {
    start do_test();
  };
  
  do_test() @clk is {
    // set VALID
    write_control(0b1000);
    
    // expect to read VALID
    read_status(0b10);
  };
};
'>

Let's also try to clear VALID:

<'
extend sys {
  do_test() @clk is also {
    // clear VALID
    write_control(0b0100);
    
    // expect to read not VALID
    read_status(0b00);
  };
};
'>

Let's tackle the DONE flag now. We'll need to define a clock event to handle the duration of the operation. Also, our modeling method will have to be a TCM:

<'
extend STATUS vr_ad_reg {
  event clk;
  
  model_done(control : CONTROL vr_ad_reg) @clk is {
    if control.CLRDONE == 1 {
      DONE = 0;
    };
    
    if control.START == 1 and VALID == 1 {
      wait [5];
      DONE = 1;
    };
  };
'>

We'll start this method from indirect_access(...):

<'
extend STATUS vr_ad_reg {
  indirect_access(direction : vr_ad_rw_t, ad_item : vr_ad_base) is {
    if direction == WRITE {
      // ...
      
      start model_done(control);
    };
  };
};
'>

As before, let's test this to make sure it works. First let's set VALID and issue a START command. With an immediate read from the status register we'll only expect to see the VALID flag set. After 5 clock cycle we'll expect to see both VALID and DONE set:

<'
extend sys {
  do_test() @clk is also {
    // set VALID and START
    write_control(0b1001);
    
    // expect to read VALID
    read_status(0b10);
    
    // after 5 clocks, expect to read DONE as well
    for i from 1 to 5 { wait [1] };
    read_status(0b11);
  };
};
'>

After writing CLRDONE, we expect to see the DONE flag cleared:

<'
extend sys {
  do_test @clk is also {
    // clear DONE
    write_control(0b0010);
    
    // expect to read not DONE
    read_status(0b10);
  };
};
'>

And there we have it: a nice, clean way of modeling register interdependencies. This pattern can be extended to however many other registers might depend on the CONTROL register. Best of all, it encapsulates the modeling logic inside the register that is being affected, as opposed to inside the affecting register. This means that we can easily add and remove observer registers as we please, as they are all independent of each other.

You can find the complete code on SourceForge if you want to try it out.

Have fun with your register modeling!

P.S.

Don't forget to subscribe if you don't want to miss out on any future vr_ad related posts. You can also be notified about new posts via email by using the "Subscribe by Email" box.

3 comments:

  1. Nice article! Don't forget vr_ad has lots of examples of modelling different scenarios in the vr_ad/examples/ directory.
    Good to see your simple and effective unit test approach too; I see far too many people trying to figure out complex register modelling in their full testbench where it's (relatively) slow, over constrained and harder to debug.

    ReplyDelete
    Replies
    1. Hi Stephen,

      I know about the examples and I always refer to them, but some of them are not broad enough, IMO. Plus, it's always nice to have some textual explanations to go with the code and I hope others will find it useful. Writing these posts is also a good way for me to better internalize the concepts (if you can't explain it simply, you don't understand it properly) and dig a bit deeper.

      Delete
  2. Great info on vr_ad! I was also using post_access() and did some stuff there. this method is cleaner!

    ReplyDelete