Sunday, September 6, 2015

My Take on SVA Usage with UVM

For verifying complex temporal behavior, SystemVerilog assertions (SVAs) are unmatched. They provide a powerful way to specify signal relationships over time and to validate that these requirements hold. One limitation of SVAs is that they can only be used in static constructs (module, interface or checker). Since modern verification is class based, this leads to segregation between the assertions and the testbench. There have been many papers written about how to bring these two parts of the verification environment closer together, particularly when using UVM.

Let's start our exploration of SVAs with some simple assertions for the Wishbone protocol. To keep it simple, we'll only consider a subset of signals:

The STB_I signal initiates a transfer and in classic Wishbone it's supposed to stay high until it is acknowledged by the slave:

stb_held_until_ack:assertproperty($rose(STB_I)|->STB_IthroughoutACK_O[->1])else$error("STB_I must be held until ACK_O");

The assertion above states that once STB_I goes high, it's supposed to stay high until the first occurrence of ACK_O.

A first step to closer collaboration between the testbench and the SVAs is to integrate assertion messaging with UVM's reporting mechanism. The SVA Bible recommends replacing severity system tasks with calls to their corresponding `uvm_* macros:

stb_held_until_ack:assertproperty(// ...)else`uvm_error("WBSLV","STB_I must be held until ACK_O")

This is a nice idea in theory, but there are more subtle points to consider in practice. The approach works fine when there's only one instance of the interface, but not as well when we have more. For the fail messages for $error(...) the simulator will print the scope where the error happened. This makes it easy to trace the source of a failure. Simple calls to `uvm_error(...) won't do this anymore. This is because `uvm_*(...) calls outside of UVM report objects get forwarded to the topmost node of the hierarchy, uvm_root, making it impossible to distinguish between callers.

To work around this limitation, we can add the scope to the error message ourselves:

stb_held_until_ack:assertproperty(// ...)else`uvm_error("WBSLV",$sformatf("%s\n In scope %m","STB_I must be held until ACK_O"))

The %m format specifier is a placeholder for the hierarchical path of the current scope. Let's add another assertion that checks that all address bits are at valid levels during a transfer:

adr_not_unknown:assertproperty(STB_I|->!$isunknown(ADR_I))else`uvm_error("WBSLV",$sformatf("%s\n In scope %m","ADR_I must be at a known level during a transfer"))

Passing around the scope like this in every assertion can get a bit tedious. It' also makes it difficult to change the format of our messages should we so desire (like printing the scope before the error message). To compact things a bit more, we can wrap the `uvm_error(...) macro with an own macro that handles printing the scope:

We've integrated assertion reporting with UVM, so now we'll see assertion fails contribute to the report at the end of the simulation. In addition to this, it should also open up new possibilities.

Sometimes we want to disable select assertions in certain tests where we are intentionally causing a fail scenario. Such situations could be when we are doing error testing or fault injection (for example for ISO 26262 certification). SystemVerilog provides the $assertoff(...) system task for this.

Ideally, we want to do any kind of disabling from inside our UVM environment, i.e. from our UVM test. Normally we have a reference to the interface supplied to us as a virtual interface:

Trying to call $assertoff(0, vif.stb_held_until_ack) gives different results depending on the simulator, but all of them are disappointing. One one simulator I've seen it throw a fatal run time error, while on another it just silently refused to work.

UVM provides a way of fiddling with report messages. Among others, one thing it allows us to do is to change the severity of certain messages we choose. This is done through a report catcher. We can define our own report catcher that intercepts the error message from the stb_held_untils_ack assertions and demotes them to warnings:

This will mean that all errors for this assertion will get demoted, regardless of where they come from. If we had two instances of the interface and we'd only want to relax one of them, this wouldn't do. We could change the catcher to also match against the message content against the desired scope, but this is too flaky and it's also not tractable (e.g. what if we have 20 instances and we want to ignore the assertion in 10 of them).

This paper shows us how to embed a UVM component inside the interface so that it can participate in the UVM phasing and configuration mechanisms. There's no reason why such a component couldn't also participate in reporting. We can declare a light class that inherits from uvm_component and instantiate it:

This creates a component parallel to the testbench whose name contains the hierarchical path of it's parent interface's instance. Instead of dispatching messages to uvm_root, we could send them through this component. This also has the added benefit that we don't need to specify the scope anymore:

What I don't like about this approach is that it creates multiple tops under uvm_root. Normally we have a UVC for a certain protocol (in our case Wishbone) and the assertions are conceptually part of that UVC, even though they live in the static world. Our goal should be to somehow bring these assertions into the UVC agent. Instead of having the interface's reporter be instantiated under uvm_root, it would be really neat if we could make it a child of the agent. To do this, it has to be created inside the agent instead of getting new-ed in the interface. This is going to be problematic since the reporter class is defined in the interface.

This idea of instantiating classes inside interfaces and referencing them in the UVM hierarchy is suspiciously similar to what we looked at in the previous post on how to achieve interface polymorphism. There I mentioned that the idea came from older papers that favored the idea of abstract BFMs. As luck would have it, one of those papers (namely this one) shows exactly how to make such a BFM a part of the agent.

The first step is to define an abstract class that replaces the interface, a so called proxy:

Notice that we defined a field for the proxy object, but we didn't instantiate it yet. This is will be done in the get_proxy(...) function, where it gets passed the name and the parent. We want to pass this wrapper class to the agent so that it can call this function, effectively passing itself back to the interface and becoming the proxy's parent. We can do this via the config DB:

This way we've separated the act of declaring the proxy from instantiating it. We've let the agent know that the interface exists and asked it to create the proxy as a child component. Now, if we change the `error(...) macro to use the proxy, messages reported from the interface will seem like they originated from inside the agent:

No more parallel hierarchies and no more fiddling with children of uvm_root.

One of the main motivations in the Verilab paper for having an embedded UVM component inside the interface is so that we could use the configuration database to tweak various settings inside it. There's no reason why we couldn't do it now as well. We don't even need the config DB. For example, the Wishbone protocol also defines the so called pipelined mode. In this mode, the STB signal doesn't need to stay high until the transfer is completed. A CYC signal (which we've ignored until now) is supposed to stay asserted from start (STB)to finish (ACK):

interfacevgm_wb_slave_interface(inputbitRST_I,inputbitCLK_I);logicCYC_I;bitm_is_pipelined;cyc_held_until_end:assertproperty($rose(STB_I)|->CYC_I##0(ACK_Oor##1CYC_Ithroughout(!m_is_pipelined&&STB_I||ACK_O)[->1]))else`error("CYC_I must be held until transfer end");// ...endinterface

The m_is_pipelined variable controls the mode we are in. We could control its value from the UVM environment via the proxy. We first need to declare a function inside the abstract proxy class to set this variable's value:

The abstract proxy class advertises to its users that it's authorized to configure the mode of its interface. Inside the interface, this function's implementation will reference the m_is_pipelined variable:

Now we've got the interface fully under our control. If you want to see the complete example in action, you can download it from SourceForge.

Let's take a quick look back and see what we've managed to do. We've achieved much tighter integration between our SVAs defined in the interface (static) and our UVC agent (dynamic). By forwarding fail messages through a child component of the agent we've made it seem like the assertions are instantiated inside the UVC. This proxy component takes the place of the static interface for tasks such as disabling individual assertions (using a report catcher) or configuring various parameters. Now we can tweak SVAs to our heart's desire directly from the UVM testbench.

About

I am a Verification Engineer at Infineon Technologies, where I get the chance to work with both e and SystemVerilog.
I started the Verification Gentleman blog to store solutions to small (and big) problems I've faced in my day to day work. I want to share them with the community in the hope that they may be useful to someone else.