{"id":16507,"date":"2021-09-09T15:45:40","date_gmt":"2021-09-09T19:45:40","guid":{"rendered":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/?p=16507"},"modified":"2026-03-27T08:47:49","modified_gmt":"2026-03-27T12:47:49","slug":"python-and-the-uvm","status":"publish","type":"post","link":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/2021\/09\/09\/python-and-the-uvm\/","title":{"rendered":"Python and the UVM"},"content":{"rendered":"\n<p>In our previous two posts in this series on Python as a verification language, we examined <a href=\"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/2021\/03\/11\/introduction-to-coroutines\/\">Python coroutines<\/a> and using coroutines to create <a href=\"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/2021\/03\/22\/cocotb-bus-functional-models\/\"><strong>cocotb<\/strong> bus functional models<\/a>. Now we are going to look at the next step, the Universal Verification Methodology (UVM) implemented in Python.<\/p>\n\n\n\n<p>The UVM is completely described in the IEEE 1800.2-2020 standard. The standard describes the UVM&#8217;s classes and functions and also describes some of the detail of how it has been implemented in SystemVerilog.<\/p>\n\n\n\n<p>I use the IEEE UVM standard to create <strong>pyuvm<\/strong>, a clean-sheet reimplementation of the UVM from the standard. Refactoring the UVM in Python allowed me to simplify the code and take advantage of Python features such as multiple inheritance that made, for example, the <code>_imp<\/code> classes unnecessary.<\/p>\n\n\n\n<p>The initial release, <strong>pyuvm<\/strong> 1.0 implemented the UVM using Python threads. However, once <strong>cocotb<\/strong> released coroutine versions of Python Queues, it became clear that a <strong>cocotb<\/strong>-based <strong>pyuvm<\/strong> would be much easier to use. We&#8217;ll examine <strong>pyuvm<\/strong> 2.0 in this blog post.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Focusing on differences<\/h1>\n\n\n\n<p>This article assumes that the reader is familiar with the SystemVerilog version of the UVM. It will use an example testbench for the TinyALU as a path through a UVM testbench written in Python.<\/p>\n\n\n\n<p>Python, with its lack of typing and dynamic nature, is easier to write than SystemVerilog, and so I refactored much of the UVM to take advantage of Python&#8217;s features. We&#8217;ll see these changes as we work through the testbench.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">The <strong>cococtb<\/strong> test<\/h1>\n\n\n\n<p>We&#8217;ll start a the top of the testbench with the coroutine that we&#8217;ve tagged with the <code>@cocotb.test()<\/code> decorator:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><span class=\"syntax-all syntax-keyword\">import<\/span> cocotb\n<span class=\"syntax-all syntax-keyword\">from<\/span> pyuvm <span class=\"syntax-all syntax-keyword\">import<\/span> <span class=\"syntax-all syntax-keyword\">*<\/span>\n\n<span class=\"syntax-all syntax-entity\">@cocotb.test<\/span>()\n<span class=\"syntax-all syntax-keyword\">async<\/span> <span class=\"syntax-all syntax-keyword\">def<\/span> <span class=\"syntax-all syntax-entity\">test_alu<\/span>(<span class=\"syntax-all syntax-parameter\">dut<\/span>):\n    bfm <span class=\"syntax-all syntax-keyword\">=<\/span> TinyAluBfm(dut) \n    ConfigDB().set(<span class=\"syntax-all syntax-constant\">None<\/span>, <span class=\"syntax-all syntax-string\">\"*\"<\/span>, <span class=\"syntax-all syntax-string\">\"BFM\"<\/span>, bfm)\n    <span class=\"syntax-all syntax-keyword\">await<\/span> bfm.startup_bfms()\n    <span class=\"syntax-all syntax-keyword\">await<\/span> uvm_root().run_test(<span class=\"syntax-all syntax-string\">\"AluTest\"<\/span>)<\/code><\/pre>\n\n\n\n<p>The first thing to note is the way we imported <strong>pyuvm<\/strong>. When we work in SystemVerilog we write <code>import uvm_pkg::*<\/code> which makes all the UVM classes and functions available in our code without referencing the package. We&#8217;re doing the same here in Python.<\/p>\n\n\n\n<p>The test starts simply enough. We create a <code>bfm<\/code> object that contains the bus functional models we created in the <a href=\"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/2021\/03\/22\/cocotb-bus-functional-models\/\" target=\"_blank\" rel=\"noreferrer noopener\">previous blog post<\/a>. The next line is from <strong>pyuvm<\/strong> and demonstrates the simplicity of writing UVM code in Python: we store the BFM in the configuration database.<\/p>\n\n\n\n<p><code>ConfigDB().set(None, \"*\", \"BFM\", bfm)<\/code><\/p>\n\n\n\n<p>Storing an item in the <code>config_db<\/code> in the SystemVerilog UVM entails using a long incantation with various types and static function calls. Under the hood the <code>uvm_config_db<\/code> interfaces is built upon the <code>uvm_resource_db<\/code> which has a complicated way of storing a variety of data types in a database.<\/p>\n\n\n\n<p><strong>pyuvm<\/strong> does away with all this. Instead of static function calls, it provides a singleton that stores data in the configuration database using the same hierarchy-based control as the SystemVerilog version.<\/p>\n\n\n\n<p>The <code>ConfigDB()<\/code> call looks like an instantiation, but it is actually returning the handle to a singleton. Python doesn&#8217;t require an explicit <code>get()<\/code> static method to implement singletons.<\/p>\n\n\n\n<p>The result is a call to <code>ConfigDB().set()<\/code> without the incantations needed to store values of a specific type. Any object can be stored anywhere in the <strong>pyuvm<\/strong> <code>ConfigDB()<\/code>.<\/p>\n\n\n\n<p>The next thing we do in the test is launch the BFMs using the <code>bfm.startup_bfms()<\/code> coroutine. The coroutines forks off the BFM coroutines as we saw in the <a href=\"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/2021\/03\/22\/cocotb-bus-functional-models\/\">bus functional model blog post.<\/a>  <\/p>\n\n\n\n<p>We see in the last line that the UVM task <code>run_test()<\/code> is now a coroutine. We get a handle to the <code>uvm_root()<\/code> singleton and await <code>run_test()<\/code>, passing the name of the test so the factory can create it.  We&#8217;ll see the AluTest class defined below.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Awaiting Tasks<\/h2>\n\n\n\n<p>The <code>await<\/code> call to <code>run_test()<\/code> demonstrates a nice feature of Python: we call tasks and functions differently.<\/p>\n\n\n\n<p>In SystemVerilog, we call a function that returns <code>void<\/code> and a task the same way. There is nothing to indicate that the call could consume time. Python requires that all time-consuming calls be done using <code>await<\/code>.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">The UVM test: AluTest<\/h1>\n\n\n\n<p>Our call to <code>run_test()<\/code> above took the string <code>AluTest<\/code> as an argument, which means we should look at <code>AluTest<\/code> for more differences between Python UVM and SystemVerilog UVM:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><span class=\"syntax-all syntax-keyword\">class<\/span> <span class=\"syntax-all syntax-entity\">AluTest<\/span>(<span class=\"syntax-all syntax-entity\">uvm_test<\/span>):\n    <span class=\"syntax-all syntax-keyword\">def<\/span> <span class=\"syntax-all syntax-entity\">build_phase<\/span>(<span class=\"syntax-all syntax-parameter\">self<\/span>):\n        <span class=\"syntax-all syntax-constant\">self<\/span>.env <span class=\"syntax-all syntax-keyword\">=<\/span> AluEnv.create(<span class=\"syntax-all syntax-string\">\"env\"<\/span>, <span class=\"syntax-all syntax-constant\">self<\/span>)\n\n    <span class=\"syntax-all syntax-keyword\">async<\/span> <span class=\"syntax-all syntax-keyword\">def<\/span> <span class=\"syntax-all syntax-entity\">run_phase<\/span>(<span class=\"syntax-all syntax-parameter\">self<\/span>):\n        <span class=\"syntax-all syntax-constant\">self<\/span>.raise_objection()\n        seqr <span class=\"syntax-all syntax-keyword\">=<\/span> ConfigDB().get(<span class=\"syntax-all syntax-constant\">self<\/span>, <span class=\"syntax-all syntax-string\">\"\"<\/span>, <span class=\"syntax-all syntax-string\">\"SEQR\"<\/span>)\n        bfm <span class=\"syntax-all syntax-keyword\">=<\/span> ConfigDB().get(<span class=\"syntax-all syntax-constant\">self<\/span>, <span class=\"syntax-all syntax-string\">\"\"<\/span>, <span class=\"syntax-all syntax-string\">\"BFM\"<\/span>)\n        seq <span class=\"syntax-all syntax-keyword\">=<\/span> AluSeq(<span class=\"syntax-all syntax-string\">\"seq\"<\/span>)\n        <span class=\"syntax-all syntax-keyword\">await<\/span> seq.start(seqr)\n        <span class=\"syntax-all syntax-keyword\">await<\/span> ClockCycles(bfm.dut.clk, <span class=\"syntax-all syntax-constant\">50<\/span>)  <span class=\"syntax-all syntax-comment\"># to do last transaction\n<\/span>        <span class=\"syntax-all syntax-constant\">self<\/span>.drop_objection()\n\n    <span class=\"syntax-all syntax-keyword\">def<\/span> <span class=\"syntax-all syntax-entity\">end_of_elaboration_phase<\/span>(<span class=\"syntax-all syntax-parameter\">self<\/span>):\n        <span class=\"syntax-all syntax-constant\">self<\/span>.set_logging_level_hier(logging.<span class=\"syntax-all syntax-constant\">DEBUG<\/span>)<\/code><\/pre>\n\n\n\n<p>The first thing to notice is that <code>Alutest<\/code> extends <code>uvm_test<\/code>. Though Python style prefers camel casing for classes, <strong>pyuvm<\/strong> preserves all the class names from the standard. The exception is when, as with <code>ConfigDB()<\/code> the feature has been completely refactored.<\/p>\n\n\n\n<p>Notice that we don&#8217;t have the <code>\\`uvm_component_utils()<\/code> macro or its equivalent in the code. This is because <strong>pyuvm<\/strong> automatically registers all classes that extend <code>uvm_void<\/code> in the factory.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The <code>build_phase()<\/code> function<\/h2>\n\n\n\n<p>The <code>build_phase()<\/code> function demonstrates that <strong>pyvum<\/strong> implements what the UVM spec calls the <code>common_phases<\/code>. The lack of a <code>uvm_phase phase<\/code> argument in <code>build_phase()<\/code> shows that <strong>pyuvm<\/strong> refactors phasing. It <em>only<\/em> implements the common phases and there is no mechanism to create custom phases, as this feature is rarely used.<\/p>\n\n\n\n<p>The <code>build_phase()<\/code> function creates an environment named <code>self.env<\/code> using the factory. The call to the factory is much simpler than it is in SystemVerilog. We don&#8217;t have the heroic string of static function calls and variables in a SystemVerilog UVM factory incantation. Instead, <code>create<\/code> is simply a static method in all classes that extend <code>uvm_object<\/code>. The factory implements all the override functionality found in the SystemVerilog UVM.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The <code>run_phase()<\/code> coroutine<\/h2>\n\n\n\n<p>In SystemVerilog UVM the <code>run_phase()<\/code> is the only phase implemented as a time-consuming task. The same is true in <strong>pyuvm<\/strong>, as <code>run_phase()<\/code> is implemented as a coroutine.<\/p>\n\n\n\n<p>This means that you must use the <code>async<\/code> keyword to define the <code>run_phase()<\/code>.<\/p>\n\n\n\n<p>There is no <code>phase<\/code> argument, so the <code>raise_objection()<\/code> and <code>drop_objection()<\/code> functions are methods in <code>uvm_component<\/code>. The UVM will run until the every <code>raise_objection<\/code> has been matched by a <code>drop_objection<\/code>.<\/p>\n\n\n\n<p>The <code>run_phase()<\/code> creates a sequence of type <code>AluSeq<\/code> and gets the sequencer out of the <code>ConfigDB()<\/code>. Then it uses <code>await<\/code> to kick off the <code>start()<\/code> task in the sequence.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The <code>end_of_elaboration_phase()<\/code> function<\/h2>\n\n\n\n<p>The <code>end_of_elaboration_phase()<\/code> function runs after the UVM has built the testbench hierarchy and connected all the FIFOs. In this example, we use it to set our logging level.<\/p>\n\n\n\n<p>Notice that <strong>pyuvm<\/strong> does not implement the UVM reporting system, as Python provides the <code>logging<\/code> module. Instead, <strong>pyuvm<\/strong> leverages <code>logging<\/code> and provides hierarchical functions for us to control logging behavior.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Summary<\/h1>\n\n\n\n<p>This blog post introduced <strong>pyuvm<\/strong>, a Python implementation of the UVM built upon <strong>cocotb<\/strong>. While <strong>pyuvm<\/strong> implements all the features we expect from the UVM including the config db and the factory, it refactors elements of the UVM that are easier to use in Python.<\/p>\n\n\n\n<p>You can see the source code for <strong>pyuvm<\/strong> at <a href=\"https:\/\/github.com\/pyuvm\/pyuvm\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/pyuvm\/pyuvm<\/a>.<\/p>\n\n\n\n<p>You can install <strong>pyuvm<\/strong> from pypi.org using <code>pip<\/code>:<\/p>\n\n\n\n<p><code>% pip install pyuvm<\/code><\/p>\n\n\n\n<p>This will also install <strong>cocotb<\/strong><\/p>\n\n\n\n<p>The next blog post in the series will examine logging in more detail, as well as <strong>pyuvm<\/strong>&#8216;s implementation of TLM 1.0.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In our previous two posts in this series on Python as a verification language, we examined Python coroutines and using&#8230;<\/p>\n","protected":false},"author":74314,"featured_media":16532,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spanish_translation":"","french_translation":"","german_translation":"","italian_translation":"","polish_translation":"","japanese_translation":"","chinese_translation":"","footnotes":""},"categories":[984,1,981,10],"tags":[],"industry":[],"product":[],"coauthors":[947],"class_list":["post-16507","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cocotb","category-news","category-pyuvm","category-tips-tricks"],"featured_image_url":"https:\/\/blogs.sw.siemens.com\/wp-content\/uploads\/sites\/54\/2021\/09\/uvmpython.png","_links":{"self":[{"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/posts\/16507","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/users\/74314"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/comments?post=16507"}],"version-history":[{"count":4,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/posts\/16507\/revisions"}],"predecessor-version":[{"id":16582,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/posts\/16507\/revisions\/16582"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/media\/16532"}],"wp:attachment":[{"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/media?parent=16507"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/categories?post=16507"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/tags?post=16507"},{"taxonomy":"industry","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/industry?post=16507"},{"taxonomy":"product","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/product?post=16507"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/coauthors?post=16507"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}