PlanV: Enabling UVM Support in Verilator for RISC-V Verification

PlanV is a two-year-old startup in the RISC-V domain based in Munich. This blog discusses the progress made under the OSVISE project, which aims to enable UVM support in Verilator to assist in getting open-source RISV-V verification tools to an industrial standard.

In this blog, we discuss an important aspect of UVM verification: support for constraint random in Verilator. Most constraint random features were supported after PR #4947, but if-else constraints were not. We began investigating this issue before it was supported, and Antmicro’s solution emerged while we were preparing our PR. In this post, we want to share what we’ve learned during this process.

Why Constraint Random Supporting Is Important

CRT (Constraint Random Test) is a critical and commonly used term in today’s digital verification field, which forms the backbone of most verification cases in the industrial sector today.

CRT focuses on generating constrained random stimuli. The objective is to ensure that these stimuli are not only random but also adhere to specific constraints. Compared to the direct approach, CRT can achieve higher functional coverage more efficiently, significantly reducing time and effort, as illustrated in Fig.1.

Fig.1 Constrained Random Approach compares with Directed Approach (cited from Most of the times Verification is too easy and too much valuable with Gems like CRT- Constrained Random Tests and CDV-Coverage Driven Verification )

Therefore, supporting constraint random in Verilator is highly meaningful and a necessary step towards enabling UVM verification in Verilator.

Constraint random in Verilator

The PR #4947 in Verilator addresses the basic constraint random issue. The general approach involves converting constraints into AstNodeFTask , formatting them into SMT-LIB ( Satisfiability Modulo Theories Library) format, and allowing the verilated C++ file to call an external solver to obtain solutions that meet the constraints.

Considering a SystemVerilog constraint definition like:

how will it be processed and solved in Verilator flow?

This SV code goes through a series of parsing steps, generating an AST (Abstract Syntax Tree). If you run verilator -dump-tree-json , you’ll see a file named **_001_cells.tree.json . This file represents the initial AST. The Verilator source files in the src/ folder, such as link and width , continuously process this initial AST, generating 006, 007, 009, 012 .json file (shown in Fig.2)… The handling of randomize statements occurs between 12_width and 14_randomize. This is where the core processing for supporting randomization takes place.

Fig.2 dumped json files in Verilator

The process starts with the RandomizeMarkVisitor , which marks all nodes related to rand and constraint . Following this, the RandomizeVisitor and ConstraintExprVisitor process the nodes. The two main functions involved in ConstraintExprVisitor are emitSMT() and emitFormat() . In simple terms, they convert the nodes into SMT-LIB format, which is eventually translated into a string as an input of the hard method, shown in Fig.5.

Please refer to Fig.3, part of the 12_width.tree.json file, Fig.4, part of the 14_randomize.tree.json file, Fig.5, part of the verilated C++ file, to have a basic view of how processing goes.

Fig.3 12_width.tree.json

Fig.4 14_randomize.tree.json

Fig.5 classname_Vclpkg_DepSet.cpp

We can see that the current handling of constraints mainly involves converting them into a task, **_setup_constraint . Inside this task, there are two AstCMethodHard : write_var and hard . The hard method contains the actual content of the constraint in SMT-LIB format. Then, during simulation, the verilated C++ file will call functions from include/verilated_random.cpp/.h in the Verilator source code. These functions call a subprocess, invoke the external SMT-LIB solver, solve the constraints, and obtain the randomized values after applying the constraints.

At this point, the SV code has been correctly processed, and the solutions that meet the constraints have been obtained. However, the 4947 commit ( Constrained randomization with popen and external solvers by kozdra · Pull Request #4947 · verilator/verilator ) also mentions that some features, such as conditional constraints and obj.randomize() with {…} , are still not yet supported.

Conditional Constraints

So, what is conditional constraint? According to this blog ( SystemVerilog Implication Constraint ), conditional constraints in SystemVerilog can be divided into two types: implication and if-else. We created a testcase to test both types with SV code like this:

We ran the simulation with Verilator and found that while implication constraints are recognized and handled correctly, the issue lies with the AstConstraintIf node in if-else constraints. Implication constraint will be recognized as a AstLogIf node and emitSMT() to SMT-LIB format (=> %l %r) but there is no extra AstConstraintIf process in src/V3Randomize.cpp .

Without additional handling, if-else constraints are not supported.

Our Solution to support if-else constraint in Verilator

The AstConstraintIf node inherits from AstNodeIf and has three child nodes: condp , thensp , and elsesp . Using a simple example, if(a) {b} else {c} , where condp is a, thensp is b, and elsesp is c, we see that condp is an AstNodeExpr , while thensp and
elsesp are AstNode types. Our approach involves handling thensp and elsesp in the basic-constraint-handling way like mentioned before and processing condp specifically by adding the ite character (the if-else character in SMT-LIB format, formatted as (ite condp thensp elsesp) , where condp is a bool type).

We need to add an extra ConstraintIfVisitor for the Astconstraintif node to implement the above processing. Additionally, when RandomizeVisitor processes constraint nodes, it must include cases where the nodep->itemsp() is a AstConstraintIf . Consequently, the hard function in the verilated C++ file will have three inputs: condp with ite , thensp , and elsesp , shown in Fig 6.

Fig.6 three inputs in hard function

Next, we need to modify include/verilated_random , particularly the hard function, allowing it template to accept multiple inputs and hard function should sort the input strings according to the ite identifier.

With these changes, simple if-else constraints are supported. However, more complex if-else scenarios present issues, especially when thensp and elsesp contain another AstConstraintIf layer.

For example:

Handling these cases requires recursive processing of thensp and elsesp nodes in the RandomizeVisitor (shown in Fig 7) and in the hard function (shown in Fig 8) when dealing with ite -identified inputs.

Fig.7 if-if and else-if recursion in RandomizeVisitor

Fig.8 recursion process in hard function

This resolves the if-else constraint support issue. We prepared a few tests to verify and demonstrate its functionality. If you feel interest, welcome to check our code in this repo ( GitHub – planvtech/verilator at Support_if se_constraint_different_from_#5245 ).

The above is the portion we completed on July 9th and the record of commits and the code is also available in this repo ( GitHub – YilouW ang/Randomization_Enable_4_Verilator-planV- ). We found that Verilator released the support for conditional constraints on July 10th with a similar but better solution. Next, let’s have a look at their solution.

Verilator’s Solution (contributed by antmicro)

The solution from Verilator ( Add support for conditional constraints by kozdra · Pull Request #5245 · verilator/verilator ) adds ite handling not through “special processing after marking”, but by adding an emitSMT() method in src/V3AstNodeExpr.h ’s AstNodeCond class, returning (ite %l, %r, %t) . To mark rand variables under AstConstraintIf nodes, visit(AstConstraintIf* nodep) was added to s rc/V3Randomize.cpp's RandomizeMarkVisitor .

Unlike our separate ConstraintIfVisitor , they extended the existing ConstraintExprVisitor , adjusting emitSMT() to handle three inputs (templating it). In visit(AstConstraintIf* nodep) , it processes as AstCond if all three nodes exist or as logif otherwise.

Their recursive handling is in ConstraintExprVisitor , recursing thensp and elsesp nodes, which re-enters visit(AstConstraintIf* nodep) if a child node is a AstConstraintIf . Thus, the hard function generation remains within ConstraintExprVisitor , simplifying code by removing the need for template and recursive handling in include/verilated_random ’s hard function.

Conclusion

Overall, Verilator’s method is simpler, more concise, and better aligned with the existing code structure. By comparing these approaches, we hope to provide insights into developing Verilator’s internal code. We continue to learn and grow, aiming to share more knowledge and contribute to the open-source verification community. Let’s look forward to our next step!

PlanV is passionate about contributing to the open-source ecosystem making it industrial grade. We are committed to getting our hands in the tools, learning and developing. PlanV can help you with your digital verification and associated tasks. Call out if you need help!