{"id":16583,"date":"2021-10-01T14:37:33","date_gmt":"2021-10-01T18:37:33","guid":{"rendered":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/?p=16583"},"modified":"2026-03-27T08:52:16","modified_gmt":"2026-03-27T12:52:16","slug":"tlm-1-0-in-pyuvm","status":"publish","type":"post","link":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/2021\/10\/01\/tlm-1-0-in-pyuvm\/","title":{"rendered":"TLM 1.0 in pyuvm"},"content":{"rendered":"\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>This blog post is part of a continuing series discussing Python as a verification language.  You can find links to all the previous blog posts on the <a href=\"https:\/\/verificationacademy.com\/topics\/python\/verification-learns-a-new-language_an-ieee-18002-python-implementation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Python for Verification Series<\/a> page. <\/p>\n<\/blockquote>\n\n\n\n<p>Our previous blog post, <a href=\"https:\/\/verificationacademy.com\/topics\/python\/verification-learns-a-new-language_an-ieee-18002-python-implementation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Python and the UVM<\/a> introduced the new version of <strong>pyuvm<\/strong> that builds upon <strong>cocotb<\/strong>. We focused on the differences between Python and SystemVerilog and how those differences affect the way one writes UVM tests in Python. In this post we will continue examining <strong>pyuvm<\/strong> by looking at its implementation of TLM 1.0.<\/p>\n\n\n\n<p>The developers of the SystemVerilog UVM took on the challenge of implementing SystemC transaction-level modeling using SystemVerilog. SystemVerilog at that time had no concept of interface classes and so the UVM got a set of port and export classes that fulfilled the behavior of interface classes. Port classes such as <code>blocking_put_port<\/code> defined the behavior the port demanded (a <code>put()<\/code> task) and the export classes, such as <code>uvm_tlm_fifo.blocking_put_export<\/code> fulfilled the demand.<\/p>\n\n\n\n<p><strong>pyuvm<\/strong> simplifies and refactors the UVM TLM system to take advantage of the fact that Python has multiple inheritance and no typing. It did not need the <code>*_imp<\/code> classes to create classes that provided implementations of tasks such as <code>put()<\/code> and <code>get()<\/code> and so they\u2019ve been removed. The lack of typing means a lack of parameterized ports, exports, and <code>uvm_tlm_fifos<\/code>. Instances of these classes can pass any object.<\/p>\n\n\n\n<p>We\u2019ll examine <strong>pyuvm<\/strong>\u2019s implementation TLM 1.0 using simple producer\/consumer examples. First we\u2019ll handle blocking operations<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Blocking operations<\/h2>\n\n\n\n<p>Blocking operations ensure synchronization between coroutines by blocking when they are unable to put data into a port or get data out of a port.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Blocking put<\/h3>\n\n\n\n<p>Here is a blocking producer component. The component instantiates a <code>uvm_put_port<\/code> in its <code>build_phase()<\/code>. Then it uses the port to send the numbers from <code>0<\/code> to <code>2<\/code>. Notice that it uses the <code>await<\/code> keyword to call <code>put()<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class BlockingProducer(uvm_component):\n    def build_phase(self):\n        self.pp = uvm_put_port(\"pp\", self)\n\n    async def run_phase(self):\n        self.raise_objection()\n        for nn in range(3):\n            await self.pp.put(nn)\n            print(f\"Put {nn}\", end=\" \")\n        self.drop_objection()<\/code><\/pre>\n\n\n\n<p>The <code>await<\/code> keyword is a nice syntactic feature of coroutines. SystemVerilog has no obvious tag to say that a call is a task call or a function call, other than only the functions can return a value.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Blocking get<\/h3>\n\n\n\n<p>The blocking consumer also uses <code>await<\/code> but it places it on the right side of the <code>=<\/code> to show that it is awaiting the returned data:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class BlockingConsumer(uvm_component):\n    def build_phase(self):\n        self.gp = uvm_get_port(\"gp\", self)\n\n    async def run_phase(self):\n        while True:\n            nn = await self.gp.get()\n            print(f\"Got {nn}\", end=\" \")<\/code><\/pre>\n\n\n\n<p>The above code highlights a difference between SystemVerilog and Python. Whereas SystemVerilog can only return a value from a time-consuming task through the argument list, Python can <em>only<\/em> return values through the <code>return<\/code> statement. So <code>get()<\/code> has no arguments, which is different than SystemVerilog, and it returns the value through the <code>await<\/code> statement. This has implications in the non-blocking operations.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Non-blocking operations<\/h2>\n\n\n\n<p>Implementing non-blocking TLM operations in Python raised some design questions because of Python\u2019s ability to raise exceptions. As we saw in the blog post <a href=\"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/2021\/03\/22\/cocotb-bus-functional-models\/\">cocotb Bus Functional Models<\/a>, Python uses exceptions to implement non-blocking queue operations. Here we see a loop on the negative edge of clock that reads the <code>driver_queue<\/code> without blocking and catches the <code>QueueEmpty<\/code> exception to handle an empty queue.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>        while True:\n            await FallingEdge(self.dut.clk)\n            if self.dut.start.value == 0 and self.dut.done.value == 0:\n                try:\n                    (aa, bb, op) = self.driver_queue.get_nowait()\n                    self.dut.A = aa\n                    self.dut.B = bb\n                    self.dut.op = op\n                    self.dut.start = 1\n                except QueueEmpty:\n                    pass\n            elif self.dut.start == 1:\n                if self.dut.done.value == 1:\n                    self.dut.start = 0<\/code><\/pre>\n\n\n\n<p>Since SystemVerilog has no exception mechanism, the UVM developers needed the nonblocking version of <code>put<\/code> and <code>get<\/code> to return a bit that told us whether the operation had succeeded.<\/p>\n\n\n\n<p>Given the choice between following Python style and the UVM spec, I chose to follow the spec as closely as possible. As we\u2019ll see, this resulted in an interesting wrinkle that used a different non-SystemVerilog feature.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Non-blocking put<\/h3>\n\n\n\n<p>We\u2019ll now implement the producer using a non-blocking put port. Notice that we instantiated the <code>put_port<\/code> again. This is because the <code>put_port<\/code> provides both blocking and non-blocking put operations. This was easy to implement in Python because of multiple inheritance:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class uvm_put_port(uvm_blocking_put_port, uvm_nonblocking_put_port): \n    ...<\/code><\/pre>\n\n\n\n<p>The <code>NonBlockingProducer<\/code> creates the same numbers and sends them to the consumer, however this time it checks a return variable named <code>success<\/code>. If <code>success<\/code> is <code>True<\/code> then we can move on to the next number, otherwise we await the <code>Timer<\/code> and try again:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class NonBlockingProducer(uvm_component):\n    def build_phase(self):\n        self.pp = uvm_put_port(\"pp\", self)\n\n    async def run_phase(self):\n        self.raise_objection()\n        for nn in range(3):\n            success = False\n            while not success:\n                success = self.pp.try_put(nn)\n                if success:\n                    print(f\"Put {nn}\")\n                else:\n                    sleep_time = random.randint(1, 10)\n                    print(f\"Failed to put {nn}. Sleep {sleep_time}\")\n                    await Timer(sleep_time)\n        await Timer(1)\n        self.drop_objection()<\/code><\/pre>\n\n\n\n<p>We await the <code>Timer<\/code> one more time at the end to let the consumer read the final value.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Non-blocking get<\/h3>\n\n\n\n<p>Implementing a non-blocking get in Python required a bit of innovation. The SystemVerilog non-blocking get returned the value in the argument list and a <code>success<\/code> bit as the return value. We can\u2019t do that in Python. Also, one cannot return a \u201cfailure\u201d value in Python since even <code>None<\/code> could be a valid value in some applications.<\/p>\n\n\n\n<p>The solution is to return a Python tuple. A tuple is an object that contains multiple values. In this case we return <code>(success, value)<\/code> as a tuple pair. We check <code>success<\/code> before using <code>value<\/code>. The <code>NonBlockingConsumer<\/code> uses <code>try_get()<\/code> which returns <code>success<\/code> and <code>nn<\/code>. If <code>success<\/code> is <code>True<\/code> we print <code>nn<\/code>, otherwise we await the <code>Timer<\/code> and try again.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class NonBlockingConsumer(uvm_component):\n    def build_phase(self):\n        self.gp = uvm_get_port(\"gp\", self)\n\n    async def run_phase(self):\n        while True:\n            success = False\n            while not success:\n                success, nn = self.gp.try_get()\n                if success:\n                    print(f\"Got {nn}\")\n                else:\n                    sleep_time = random.randint(1, 10)\n                    print(f\"Failed to get. Sleep {sleep_time}\")\n                    await Timer(sleep_time)<\/code><\/pre>\n\n\n\n<p>Python\u2019s tuple data type has saved the day.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Connecting the producer and consumer<\/h2>\n\n\n\n<p>Up until now we\u2019ve focused on the ways that the <strong>pyuvm<\/strong> TLM is different than SystemVerilog TLM, but much is the same. We instantiate put and get ports the same way and the method names are identical. We also connect ports the same way.<\/p>\n\n\n\n<p>Here is a UVM test that instantiates a <code>BlockingProducer<\/code> and <code>BlockingConsumer<\/code> and connects them using classic UVM function calls. The <code>uvm_tlm_fifo<\/code> class provides a <code>put_export<\/code> and <code>get_export<\/code> that we connect to the ports in the connect phase:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class BlockingFIFOTest(uvm_test):\n    def build_phase(self):\n        self.prod = BlockingProducer(\"prod\", self)\n        self.cons = BlockingConsumer(\"cons\", self)\n        self.fifo = uvm_tlm_fifo(\"fifo\", self)\n\n    def connect_phase(self):\n        self.prod.pp.connect(self.fifo.put_export)\n        self.cons.gp.connect(self.fifo.get_export)\n <\/code><\/pre>\n\n\n\n<p>The big simplification here is that we created the ports and FIFO with no typing information. We could swap producers and consumers in and out of this test with no concern as to what they are producing or consuming.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>In this blog post we examined <strong>pyuvm\u2019s<\/strong> implementation of the UVM TLM 1.0 interface. We saw that Python does not need to worry about the type of the data moving through the system.<\/p>\n\n\n\n<p>We saw how the <code>await<\/code> statement signifies that we are<\/p>\n\n\n\n<p>We also saw how Python\u2019s requirement to return all data using the <code>return<\/code> statement (as opposed to SystemVerilog\u2019s ability to return data through the argument list) required slight modifications to the <code>try_put<\/code> and <code>try_get<\/code> functions.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">New examples repository<\/h2>\n\n\n\n<p>The examples in this, the other blog posts in this series have been tested. You can download the examples from Github at <a href=\"https:\/\/github.com\/raysalemi\/vh_blog_examples\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/raysalemi\/vh_blog_examples<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This blog post is part of a continuing series discussing Python as a verification language. You can find links to&#8230;<\/p>\n","protected":false},"author":74314,"featured_media":16606,"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-16583","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\/10\/tlm.png","_links":{"self":[{"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/posts\/16583","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=16583"}],"version-history":[{"count":5,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/posts\/16583\/revisions"}],"predecessor-version":[{"id":18827,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/posts\/16583\/revisions\/18827"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/media\/16606"}],"wp:attachment":[{"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/media?parent=16583"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/categories?post=16583"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/tags?post=16583"},{"taxonomy":"industry","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/industry?post=16583"},{"taxonomy":"product","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/product?post=16583"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/coauthors?post=16583"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}