The Hidden Feature of vr_ad: Gray Zone Comparison
Verifying RTL updatable registers (e.g. status registers) can be very difficult due to factors like slight desynchronization between model and DUT or different sampling points for the register value. Register reads in the out-of-synch window produce false errors which are a nightmare to handle in the verification environment. Luckily, the vr_ad library comes with a build-in feature to tackle this problem, called “Gray Zone Comparison”.
In this post we will see how this feature works and how we can fine tune it.
1. Device Under Test
Let’s assume that we need to verify a DUT with two registers accessible via an APB interface – one for control, and one for status. The status register contains only a counter field, which increments every 8 clock cycles, when enabled.
Name | Offset | Bits | |||||||||||||||||||||||||||||||
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ||
CTRL | 0xF000 | reserved | EN | ||||||||||||||||||||||||||||||
STATUS | 0xF004 | reserved | COUNTER |
The timing diagram bellow shows how the counter is incremented:
Due to some internal logic (FSMs, pipeline registers, etc), reading STATUS register can have a wait state of up to 5 clock cycles and the return value can be sampled anywhere in this interval.
2. Gray Zone Comparison Explained
Let’s see how the uncertainty introduced by the unknown sampling point of the returned read value reflects in the check performed by the vr_ad library.
For simplicity, let’s consider that our model of STATUS.COUNTER is perfectly aligned with the one from the DUT.
In the image below we have three APB reads from the STATUS register and the value of the STATUS.COUNTER probed inside the DUT.
The important thing to notice in the above image is the stability of the STATUS.COUNTER throughout each APB read access and the read value returned by the DUT:
- For access #1, STATUS.COUNTER is stable throughout the read access and the DUT returns the stable value: ‘h3
- For access #2, STATUS.COUNTER changes throughout the read access and the DUT returns the new value: ‘h8
- For access #3, STATUS.COUNTER changes throughout the read access and the DUT returns the old value: ‘hA
We know that when we connect the vr_ad register model to the APB monitor the check of the read value is done at the end of the APB transfer, when we call compare_and_update() method. So we can deduce that the expected values for each of the three accesses are:
- For access #1 the expected value is ‘h3
- For access #2 the expected value is ‘h8
- For access #3 the expected value is ‘hB
Based on the above statement, it is reasonable to assume that vr_ad will trigger an error for access #3 as the check will use as expected value the model data from that point in time (‘hB), but the DUT returns the previous value (‘hA).
However, this is NOT the case as vr_ad will happily inform us that even the third access is OK from its point of view. This is the making of the Gray Zone Comparison feature in vr_ad, and this is enabled by default.
With Gray Zone Comparison, vr_ad defines a gray zone as a time window, and the comparison will accept as correct values for the register either the value from the beginning of the window or the value of the register from the end of the window.
2.1. Start of the Gray Zone
To mark the start of the Gray Zone one must call method reg_being_read() from the vr_ad_reg_file:
reg_being_read(addr : vr_ad_addr_t, data : vr_ad_data_t)
The job of this method is to add in a list called regs_being_read an address-data pair used as an accepted read value when compare_and_update() is called next time.
In the default implementation, this is called from vr_ad_sequence_driver just before vr_ad_execute_op() TCM is executed.
2.2. End of the Gray Zone
To mark the end of the Gray Zone one must call method reg_ended_being_read() from vr_ad_reg_file:
reg_ended_being_read(addr : vr_ad_addr_t)
The job of this method is to remove from list regs_being_read the first entry which matches the address used as an argument for this method.
3. The Issues of the Default Implementation
There are several issues with the default implementation of the Gray Zone Comparison but vr_ad provides enough flexibility to easily solve them.
3.1. Gray Zone Defined by the Driver
In the default implementation, the start and end of the gray zone are defined by the vr_ad sequence driver. This has two major drawbacks:
- This will not work if the user access the registers via physical sequences and not the default vr_ad API (e.g. call of reg.write() or reg.read())
- This will not work if the IP environment is reused at system level and the register access interface (e.g. APB) is suddenly driven by some other RTL module (e.g. CPU) and not by the active part of the IP environment.
One more robust approach is to let the monitor define the start and end of the gray zone.
First, we need to disable in vr_ad the automatic call of the reg_being_read() method. This is easily controlled like so:
extend CFS_REG_FILE vr_ad_reg_file { reset() is also { // Set this to FALSE if you want to disable the default // definition of Gray Zone Comparison, the default value is TRUE set_automatic_update_of_read_start(FALSE); }; };
Next, we need to hook up to the analysis port coming from the APB monitor. When we detect the beginning of a read transfer from a register, we sample the register value at that point in time and use it to start the gray zone:
extend cfs_dut_env { write_apb(transfer : acme_apb_mon_transfer) is first { if(transfer.is_active()) { if(transfer.dir == READ) { var relative_addr := transfer.addr - reg_file.get_address(); var reg := reg_file.get_reg_by_address(relative_addr); if(reg != NULL) { // This is the beginning of the custom Gray Zone -> // When the read is first detected on the bus reg_file.reg_being_read(relative_addr, reg.read_reg_val()); }; }; }; }; };
To define the end of the gray zone we need to do some hacking because vr_ad does not provide some control to stop the call of reg_ended_being_read() at the end of vr_ad_execute_op().
First, we need to override this method and make it do nothing:
extend CFS_REG_FILE vr_ad_reg_file { reg_ended_being_read(addr : vr_ad_addr_t) is only { // Do nothing }; };
Next, we need to do the old job of reg_ended_being_read(), which is deleting the entry from regs_being_read, but at the end of the APB transfer, after the comparison is finished. We can take advantage of the fact that regs_being_read is a public field in vr_ad_reg_file:
extend cfs_dut_env { write_apb(transfer : acme_apb_mon_transfer) is also { if(transfer.is_active() == FALSE) { if(transfer.dir == READ) { var relative_addr := transfer.addr - reg_file.get_address(); var reg := reg_file.get_reg_by_address(relative_addr); if(reg != NULL) { // This is the end of the custom Gray Zone -> // When the read ends on the bus var index_to_delete := reg_file.regs_being_read.first_index( it.addr == relative_addr); if(index_to_delete != UNDEF) { reg_file.regs_being_read.delete(index_to_delete); }; }; }; }; }; };
It is important here to make sure that this “is also” definition of write_apb() is called after compare_and_update() is executed.
Defining the gray zone based on the monitor information makes the gray zone window a little bit smaller but it is still effective:
3.2. Gray Zone Messes Up the Register Model
Gray Zone Comparison is a great feature to add some flexibility to the check of the read value but most of the times we don’t want this check to influence our values in the register model.
However, in the default behavior of the vr_ad, when a register is read, the check is performed and the value is updated – hence the compare_and_update() method name. This has the potential of desynchronizing our register model with the DUT, like in the waveforms below:
Fortunately, this automatic update can be easily disabled:
extend CFS_REG_FILE vr_ad_reg_file { reset() is also { // Disable the automatic update of the STATUS register // during the compare_and_update() method status.static_info.do_update_on_compare = FALSE; }; };
This will produce the following behavior:
That’s all there is regarding the Gray Zone Comparison feature – it is a great feature of the vr_ad library which requires just a little bit of fine tuning in order to use it properly.
You can also play around with an example I build to showcase this feature on EDA Playground.
Hope you found this useful 🙂