Verification in the trenches: Creating your verification components using VMM1.2
Posted by Ambar Sarkar on 25th December 2009
Dr. Ambar Sarkar, Chief Verification Technologist, Paradigm Works Inc.
Ever wonder why it is hard to mix and match verification components from different sources and have them play nicely with the one you created? You want all of these components to execute in sync with each other through the phases of their construction, configuration, shutdown, etc. For example, if the AXI slave transactor is executing its reset phase while the PCIe stimulus generator is sending in DMA read requests to the AXI interface, you have a problem. Often, you end up adding dedicated code or using explicit synchronization objects such as events to get the right coordination. Wouldn’t it be nice if this synchronization came automatically?
This is where the vmm_unit base class introduced in VMM1.2 comes in. The basic idea is to derive your verification component from this predefined class and you are guaranteed that the verification environment will automatically synchronize its execution with the others. While there is much offered by the vmm_unit class, the following statement summarizes its real benefit:
vmm_unit class comes with a rich set of built in synchronization points.
These synchronization points are represented as predefined tasks or functions called phases. The verification engineer provides the actual implementation of these phases. The environment makes sure that all the objects derived from the vmm_unit class get their phases called in a well defined order, so that once an object moves into a phase, it is guaranteed that all its siblings have completed the previous phase. For example, once a component enters reset, you know every other associated component is either being reset or about to enter the reset state.
Couple of things to note, however. First, you do not have to provide implementation for each and every phase. If you do not define them, the default action is that this object will wait for the others to finish this phase before moving to the next one. Second, you can override, replace, or even add your own phases to introduce a finer or different synchronization scheme altogether.
So how does the implementation end up looking like? Here is a snippet from something that I coded recently for a reusable module-level verification environment. Note that I only defined a few of the phases of my own and used the default implementation for the others.
| Predefined phase | Sample Code Snippet |
| build_ph() | … // Create various functional components of this environment pwr_hi = new(“subenv”, “PWR_HI”, this); pwr_pi = new(“subenv”,”_PWR_PI”, this); // Instantiate a consensus manager cm = new(this, {this.get_object_name(), “_CM”}, pwr_port); …. |
| configure_ph() | // If someone built me as a sub-environmnet, take appropriate action if (is_subenv) begin // Disable the host interface driver … |
| connect_ph() | // Connect the components as needed pwr_hi.chk.ana_port.tlm_bind(sb.pwr_hi_sb_chk_ap); pwr_pi.has_generator) pwr_pi.gen.ana_port.tlm_bind(sb.pwr_pi_sb_post_ap); |
| start_of_sim_ph() | // Put a diagnostic message, otherwise leave empty `vmm_verbose(log, “Starting simulation”); |
| reset_ph() | // Power_cycle pwr_port.dck.reset <= 0; @(pwr_port.dck); pwr_port.dck.reset <= 1; repeat (10) @(pwr_port.dck); pwr_port.dck.reset <= 0; repeat (2) @(pwr_port.dck); |
| training_ph() | // Leave as default |
| config_dut_ph() | // Sw initialization sequence // … |
| start_ph() | // Leave as default |
| start_of_test_ph() | // Leave as default |
| run() | // All you need is to wait for the consensus manager to agree to shut down cm.wait_for_end_t(); |
| shutdown_ph() | // Leave as default |
| cleanup() | // Leave as default |
| report() | // Dump the final scoreboard status sb.report(); |
| destruct() | // Leave as default |
The key is to make sure that your code is partitioned into the appropriate phases, as shown above. Also note that a bunch of the phases were left alone to their default implementation.
Okay, one minor detail. You do not directly derive from the vmm_unit base class. Instead, two classes, vmm_xactor and vmm_group have been provided. Both are derived from vmm_unit, so you have all the support for synchronization. vmm_xactor should be used as the base class for defining your individual transactors, whereas vmm_group should be used as the base class for components that put together several others into a single entity such as the top-level environment or a top-level interface vip.
Of course, the correctness of your synchronization will still depend on what you end up implementing in the body of the pre-defined phases. The names of the predefined phases give a hint. So you are still subject to what the other components implemented in their corresponding phases. Do follow the spirit of what each phase is supposed to do. Do not connect your components in build_ph() phase even if the test passes. Do so in connect_ph(). A small price to pay for most cases, IMHO.
Do note that there often are scenarios where some components need to be synchronized separately from others. For example, in a PCIe based SOC, what if you need the OCP interface to be up and running before you bring your PCIe interface out of reset? In this case, you definitely do not want the OCP and the PCIe VIPs to run their configure and reset phases in lock-step with each other. This is where advanced synchronization features such as vmm_timelines come to play, but that’s a topic for the next post. Stay tuned.
This article is the 3rd in the Verification in the trenches series. Hope you found this article useful. If you would like to hear about any other related topic, please comment or drop me a line at ambar.sarkar@paradigm-works.com. Also, if you are starting out fresh, please check out the free VMM1.2 environment generator at http://resourceworks.paradigm-works.com/svftg/vmm .
Posted in Phasing, Structural Components | No Comments »




