<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://bitsbytesgates.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://bitsbytesgates.com/" rel="alternate" type="text/html" /><updated>2026-02-16T21:36:14+00:00</updated><id>https://bitsbytesgates.com/feed.xml</id><title type="html">Bits, Bytes, and Gates</title><subtitle>There&apos;s oh so much fun to be had. At the leading edge,  at the bleeding edge, at the confluence of bits, bytes, and gates.</subtitle><entry><title type="html">Better Coverage Analysis with AI</title><link href="https://bitsbytesgates.com/eda,/ucis,/coverage/2026/02/15/BetterCoverageAnalysisWithAI.html" rel="alternate" type="text/html" title="Better Coverage Analysis with AI" /><published>2026-02-15T00:00:00+00:00</published><updated>2026-02-15T00:00:00+00:00</updated><id>https://bitsbytesgates.com/eda,/ucis,/coverage/2026/02/15/BetterCoverageAnalysisWithAI</id><content type="html" xml:base="https://bitsbytesgates.com/eda,/ucis,/coverage/2026/02/15/BetterCoverageAnalysisWithAI.html"><![CDATA[<p>One near-term outcome of AI’s <a href="https://bitsbytesgates.com/eda/2026/02/07/OpenSourceEDA_in_AI_Era.html">impact on open source EDA</a> 
is that I’ve been revisiting and enhancing some of my older projects. <a href="https://fvutils.github.io/pyucis/">PyUCIS</a>, a set of tools for working with 
verification coverage, is one of those. Over the next few posts, we’ll look at the impact
AI is having on both EDA developers and users via this project.</p>

<!--more-->

<p align="center">
<img src="https://bitsbytesgates.com/imgs/2026/02/coverage_tui.png" />
</p>

<h1 id="verification-and-coverage">Verification and Coverage</h1>
<p>Coverage is a key metric we use to evaluate how well-verified a design is. We collect 
and analyze code coverage to identify areas of the design that aren’t exercised. We
use assertion and functional coverage to ensure that key conditions are exercised. Combined
with the right meta-data, such as test name, we can derive higher-level information such as
a small set of tests that will exercise a design change.</p>

<p>Many design verification tools and frameworks support capturing and working with coverage 
metrics. For example, <a href="https://cocotb-coverage.readthedocs.io/en/latest/">cocotb-coverage</a> and 
<a href="https://fvutils.github.io/pyucis/">AVL</a> each support capturing functional 
coverage data, saving it to a file, and performing post-processing. <a href="https://www.veripool.org/verilator/">Verilator</a> 
supports capturing code and assertion coverage, and the major EDA companies have solutions as well.</p>

<p>But, all of these are independent. How can I view and analyze Verilator code coverage and AVL functional coverage
together?</p>

<h1 id="standards-ucis">Standards: UCIS</h1>

<p>A unique aspect of verification coverage is that we have are larger set of metrics than software
projects typically use. Software projects tend to focus on code coverage, while design verification
is also interested in functional coverage, FSM coverage, and assertion coverage.</p>

<p>Back in 2012, the Accellera EDA standards body released <a href="https://www.accellera.org/downloads/standards/ucis">UCIS</a>,
the Unified Coverage Interoperability Standard. UCIS defines a data model and an API for interacting with 
all the types of coverage data that verification engineers work with. It also defines an XML interchange
format supported by some tools and the <a href="https://github.com/accellera-official/fc4sc">FC4SC</a> library that
implements functional coverage for SystemC.</p>

<p>Having a common API enables the classic separation of concerns that other common APIs, such as 
<a href="https://microsoft.github.io/language-server-protocol/">LSP</a> and <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a> 
enable. Instead of developing analysis capabilities for each and every coverage format, we can 
create tools that use the UCIS API, and converters to map coverage formats into the UCIS data model.</p>

<h1 id="open-source-pyucis">Open Source: PyUCIS</h1>

<p>I initially created the <a href="https://fvutils.github.io/pyucis">PyUCIS</a> project because I needed a way to work with coverage data
captured by the <a href="https://fvutils.github.io/pyvsc/">PyVSC</a> library that I was also working on. At the time, I focused
on support for functional coverage (coverpoints, cross-coverage, etc) since that was the data PyVSC produced.</p>

<p>As I noted in the introduction, AI-driven productivity gains have me revisiting projects like PyUCIS 
to add new features and provide more-comprehensive support. But, also, to ensure that the libraries
are properly AI-enabled for users.  We’ll cover some of these new feature areas in future posts, but 
this post focuses on AI enablement.</p>

<h1 id="three-levels-of-ai-enablement">Three Levels of AI Enablement</h1>
<p>I’ve gradually come to apply a three-level approach for enabling a tool for AI agent access
that reflect successively-deeper levels of integration with the tool.</p>

<h2 id="ai-friendly-cli">AI-Friendly CLI</h2>
<p>The first level of integration is via the command-line interface (CLI). AI agents, such 
as Copilot, Codex, and Claude Code, excel at using command-line tools. Best practices 
for enhancing the CLI to be AI-friendly include:</p>
<ul>
  <li>Providing granular data-manipulation commands with good help messages</li>
  <li>Providing JSON output</li>
  <li>Providing a pointer to a SKILL.md file</li>
</ul>

<p>In the case of PyUCIS, our primary interest is in enabling an AI agent to analyze 
coverage data, so we focus most of our efforts on the <code class="language-plaintext highlighter-rouge">show</code> sub-commands.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>usage: ucis show [-h]
                 ...

positional arguments:
    summary             Display overall coverage summary
    gaps                Display coverage gaps (uncovered bins and coverpoints)
    covergroups         Display detailed covergroup information
    bins                Display bin-level coverage details
    tests               Display test execution information
    hierarchy           Display design hierarchy structure
    metrics             Display coverage metrics and statistics
    compare             Compare coverage between two databases
    hotspots            Identify coverage hotspots and high-value targets
    code-coverage       Display code coverage with support for LCOV/Cobertura formats
    assertions          Display assertion coverage information
    toggle              Display toggle coverage information

options:
  -h, --help            show this help message and exit
</code></pre></div></div>

<p>LLMs often work more effectively with JSON data vs unstructured 
(or, arbitrarily-structured) text. JSON data has a regular structure and 
associates meta-data with various elements of the output (eg ‘name’, ‘id’, etc). 
In addition, an LLM can leverage tools like <code class="language-plaintext highlighter-rouge">jq</code> to efficiently slice and
dice it. Consequently, it’s a very good practice to support JSON output. 
Enabling this with a specific switch is fine, since most humans won’t want
to see JSON.</p>

<p><a href="https://agentskills.io/home">Agent Skills</a> is an emerging standard for providing
instructions to agents.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>======================================================================
PyUCIS AgentSkills Information
======================================================================

Skill Definition: /home/.../ucis/share/SKILL.md

Note for LLM Agents:
  This file contains detailed information about PyUCIS capabilities,
  usage patterns, and best practices. LLM agents should reference
  this skill to better understand how to work with UCIS coverage
  databases and leverage PyUCIS tools effectively.

For more information, visit: https://agentskills.io
======================================================================
</code></pre></div></div>

<p>A <code class="language-plaintext highlighter-rouge">skill</code> for a tool provides examples and more in-depth
instructions on tool workflows. I like to advertise how to find the 
tool’s <code class="language-plaintext highlighter-rouge">skill</code> in the help message, since AI agents often check this first.</p>

<h2 id="model-context-protocol">Model Context Protocol</h2>
<p>The Model Context Protocol (<a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a>) 
is a JSON RPC-based communication protocol that allows AI agents to run external operations. 
MCP excels in situations where operation setup times are long. For example, loading a waveform
database often takes significant time. The overhead of doing so every time the Agent wishes
to look at a waveform value is prohibitive. A waveform <code class="language-plaintext highlighter-rouge">MCP server</code>, such as 
<a href="https://fvutils.github.io/pywellen-mcp">pywellen-mcp</a>, loads the waveform database once and
allows the Agent to perform many queries.</p>

<p>PyUCIS provides a <a href="https://fvutils.github.io/pyucis/mcp_server.html">MCP server</a> with a 
range of tools, including:</p>

<h3 id="database-operations">Database Operations</h3>
<ul>
  <li>open_database: Load UCIS databases in XML, YAML, or UCIS binary formats</li>
  <li>close_database: Clean up database resources</li>
  <li>list_databases: List all currently open databases</li>
  <li>get_database_info: Retrieve database metadata and statistics</li>
</ul>

<h3 id="coverage-analysis-tools">Coverage Analysis Tools</h3>
<ul>
  <li>get_coverage_summary: Overall coverage statistics by type (statement, branch, etc.)</li>
  <li>get_coverage_gaps: Identify uncovered or low-coverage items with configurable thresholds</li>
  <li>get_covergroups: Retrieve covergroup details with optional bin information</li>
  <li>get_bins: Detailed bin-level coverage with advanced filtering capabilities</li>
  <li>get_tests: Test execution information and results</li>
  <li>get_hierarchy: Navigate and explore the design hierarchy</li>
  <li>get_metrics: Advanced coverage metrics and analysis</li>
</ul>

<h3 id="advanced-features">Advanced Features</h3>
<ul>
  <li>compare_databases: Compare two databases for regression analysis and coverage deltas</li>
  <li>get_hotspots: Identify high-value coverage targets for optimization</li>
  <li>get_code_coverage: Export code coverage in multiple formats (LCOV, Cobertura, JaCoCo, Clover)</li>
  <li>get_assertions: SVA/PSL assertion coverage details</li>
  <li>get_toggle_coverage: Signal toggle coverage information</li>
</ul>

<p>Given the speed of the PyUCIS SQLite database, I’ll be interested in point at which
using the MCP server becomes beneficial.</p>

<h2 id="api">API</h2>
<p>An API provides an AI agent the most-detailed access to a tool’s capabilities.
Both the difficulty of producing an API and the difficult of an AI agent using it
depends heavily on the tool’s implementation language. For example, Python is often
fairly easy on both counts, since producing an API typically means documenting functions
that we’ve already created to implement the tool, and AI agents can simply load the 
module and go. On the other hand, a compiled languages like C/C++ will likely require
explicit action to define and expose an API, and AI agents will need to use the language
toolchain to compile, link, and run with the API.</p>

<h1 id="conclusion-and-next-steps">Conclusion and Next Steps</h1>
<p>Design verification heavily relies on good coverage data to assess completeness, 
and AI can play a critical role in analysis. PyUCIS implements the Accellera UCIS
API, and supports AI agents via the CLI and a built-in MCP server. 
As previously mentioned, you can look at the <a href="https://github.com/fvutils/pyucis/tree/master/examples/ai_assisted_workflow">AI-assisted workflow example</a> 
in the PyUCIS repository to get ideas of how to work with coverage data using
the PyUCIS library and AI agents. Next time, we’ll
shift focus to look at some of the other new and improved features in PyUCIS that
AI agents are helping to rapidly develop.</p>]]></content><author><name></name></author><category term="EDA," /><category term="UCIS," /><category term="Coverage" /><summary type="html"><![CDATA[One near-term outcome of AI’s impact on open source EDA is that I’ve been revisiting and enhancing some of my older projects. PyUCIS, a set of tools for working with verification coverage, is one of those. Over the next few posts, we’ll look at the impact AI is having on both EDA developers and users via this project.]]></summary></entry><entry><title type="html">Open Source EDA in the AI Era</title><link href="https://bitsbytesgates.com/eda/2026/02/07/OpenSourceEDA_in_AI_Era.html" rel="alternate" type="text/html" title="Open Source EDA in the AI Era" /><published>2026-02-07T00:00:00+00:00</published><updated>2026-02-07T00:00:00+00:00</updated><id>https://bitsbytesgates.com/eda/2026/02/07/OpenSourceEDA_in_AI_Era</id><content type="html" xml:base="https://bitsbytesgates.com/eda/2026/02/07/OpenSourceEDA_in_AI_Era.html"><![CDATA[<p>AI is changing the world in many ways. This is particularly visible in the 
commercial software-development space, where AI is often credited with 
<a href="https://techcrunch.com/2025/04/29/microsoft-ceo-says-up-to-30-of-the-companys-code-was-written-by-ai/">developing significant portions of new code</a>. 
But, what about the impact on open source software and, much more specifically, open source
Electronic Design Automation (EDA) software? What follows are some observations
on how open source EDA developers, users, and contributors can leverage AI based
on my own experience. In short, I see a strong potential for AI to spark a new
era of open source EDA growth and discovery if we as user, developers,
and contributors leverage it well.</p>

<!--more-->

<h1 id="eda-software-and-domain-expertise">EDA Software and Domain Expertise</h1>

<p>Electronic Design Automation (EDA) software is a unique market. Like 
any other complex software, developing it requires strong software engineering 
skills. But, developing good EDA software also requires deep knowledge of the
silicon engineering domain.</p>

<p>In my experience with commercial EDA, these two roles are generally handled
by different people. Software engineering is handled by teams of software
developers that, thinking simplistically, need to know very little about
silicon engineering. Silicon engineering expertise is often represented 
by the product engineering role. This role is responsible for understanding
the domain, and the user’s perspective on it, and translating that into
requirements that the software engineering team can implement. 
In practice, of course, each of these disciplines has some cross-over. 
EDA software engineers have varying degrees of expertise in silicon
engineering, and product engineers have varying degrees of software
engineering expertise.</p>

<h1 id="ai-and-open-source-eda">AI and Open Source EDA</h1>

<p>An open source EDA developer typically doesn’t have the luxury of focusing 
on a single domain. Open source EDA developers often start from a vision – whether 
that’s simply the existence of open source tools for a given portion of the 
silicon engineering workflow, or a vision of a new way to approach a 
part of that workflow. But, very quickly, the developer must pivot and focus
of the nuts and bolts of realizing this vision in code.</p>

<p>My experience, increasingly so over the last 4-6 months, has been that 
AI allows me to focus much more energy on the Product Engineering aspects
of open source EDA, while delegating much of the software engineering 
aspects.</p>

<h1 id="ai-and-open-source-eda-developers">AI and Open Source EDA Developers</h1>

<p>If you’re an open source EDA developer, AI enables you to spend a greater
portion of your time in Product Engineer mode. With good requirements and
review (all key Product Engineer skills), the bulk of software engineering
tasks can be delegated to an agent. Here are a few things to consider as 
you look for AI opportunities in your project.</p>

<h2 id="develop-nice-to-have-features">Develop ‘Nice to Have’ Features</h2>
<p>Being an open source developer fosters a mentality of scarcity, if your
experience is anything like mine. Ruthless prioritization is the only
way to ensure that something with sufficiently-broad application can
be produced by a single developer. ‘Nice to have’ features must be 
deferred – potentially forever.</p>

<p>AI nicely inverts this equation. When the primary cost of a new feature
is specification, it becomes much easier to justify adding helpful 
features: better error handling, more output formats, etc.</p>

<h2 id="more-communication">More Communication</h2>
<p>Communication tasks such as documentation, project websites, 
and project news is a task that often gets de-prioritized. After all,
we need to get key features developed!</p>

<p>AI-created content has come a long ways. One can argue it still has a 
ways to go, but it’s often quite good for technical documentation. It 
works well in incremental mode to document features as they’re developed, 
but also to improve documentation for an existing codebase. While we 
should always be cautious about passing off AI-generated ‘slop’ as 
useful documentation, my current experience is that AI-generated docs are
far better than no documentation at all. I’m also finding that providing
a little structure and guidance goes a long way in getting better results.</p>

<h2 id="consider-the-agent-experience">Consider the Agent Experience</h2>
<p>As a developer, it’s key to consider how your user will experience your 
project. Increasingly, you can assume that your users will be using an 
AI agent of some form to work with your project. This means that your
project should expose usage information to make it easy for an agent 
effectively use your project.</p>

<p>One approach that I’ve found effective is to reference a <code class="language-plaintext highlighter-rouge">skill.md</code> file
for the tool from the help message. For example, here’s the help output
from <a href="https://dv-flow.github.io">DV Flow Manager</a>, a workflow automation tool I work with.</p>

<pre>
usage: dfm [-h] [--log-level {NONE,INFO,DEBUG}] [-D NAME=VALUE] [-P FILE_OR_JSON]
           {graph,run,show,cache,validate,context,agent,util} ...

DV Flow Manager (dfm) - A dataflow-based build system

positional arguments:
  {graph,run,show,cache,validate,context,agent,util}
    graph               Generates the graph of a task
    run                 run a flow
    show                Display and search packages, tasks, types, and tags
    cache               Cache management commands
    validate            Validate flow.yaml/flow.dv files for errors and warnings
    context             Output comprehensive project context for LLM agent consumption
    agent               Launch an AI assistant with DV Flow context
    util                Internal utility command

For LLM agents: See the skill file at: /home/mballance/.../share/skill.md
</pre>

<p>AI agents often check a tool’s help message to better-understand its operation. Including
a path to the tool’s “AI Skill” file provides the agent with a pointer to more in-depth
information.</p>

<p>If your project produces human-consumable output, such as reports, you should
have an option to produce JSON output as well. While JSON isn’t particularly easy 
for humans to read, agents work well with its regular structure.</p>

<h2 id="test-test-test">Test, Test, Test</h2>
<p>We all know that tests are good and important. But, often, we settle for a few
basic tests so we can spend more time developing new features.</p>

<p>Here, again, AI can help. But, also, a good test suite is critical to getting
good results with AI. Just like a human developer, an agent makes mistakes and
a robust test suite catches those errors before they accidentally get committed.</p>

<h1 id="ai-and-open-source-eda-users">AI and Open Source EDA Users</h1>

<p>As an Open Source EDA user, AI provides you tools to make better use of the 
available software. Don’t hesitate to have your agent look at the source
for software that you’re using. If you’re trying to get a case working, 
ask it to analyze the EDA software source alongside your code and understand
what needs to change in your code.</p>

<p>If you hit a bug, use AI to create a reduced testcase in conjunction with
the EDA software’s source.</p>

<p>Now, one personal request as an open source developer: please don’t just
paste the AI-generated output into an Issue and click submit. The AI-created
analysis is important, of course. Please include it in your Issue report.
But, it’s likely that the AI-generated analysis is missing critical details
about your usecase and intent. Including that critical user perspective 
helps to ensure that your usecase is well-supported beyond just fixing
a point issue.</p>

<h1 id="ai-and-open-source-eda-contributors">AI and Open Source EDA Contributors</h1>

<p>If you’re trying to move from open source EDA user to open source EDA 
contributor, welcome! AI is definitely a help here as well.</p>

<p>Any complex codebase is difficult to get started with, and open source
EDA is no different. AI offers strong benefits in helping to more-rapidly
find key components of the codebase.</p>

<p>A key thing to bear in mind is that LLMs are tools, not oracles. Using 
AI isn’t going to magically turn you into an expert in a given project.
And, all the best practices of contributing to an open source project
still apply: ask questions, search issues, ask for feedback on 
contributions. That said, AI can bring you up to speed on a project 
far faster than you could on your own.</p>

<p>Set your sights a bit more broadly when it comes to reference materials.
Reading an entire standard (SystemVerilog, SystemRDL, PSS) is daunting
as a human, but easy with an agent. Use an agent to help explore 
implementation alternatives and refine requirements.</p>

<h1 id="ai-and-open-source-eda-sponsors">AI and Open Source EDA Sponsors</h1>

<p>Sponsoring open source software development and maintenance has always been
a bit tricky – especially for small projects. Large projects with an organization
and, often, a team of paid software developers are somewhat easy: donate
to the foundation, possibly participate in a steering committee, etc. In this case,
there is a clear link between supporting the project financially and the project
being able to scale. Smaller projects are much harder. Developers will have a 
‘day job’ with contract clauses prohibiting moonlighting. But, even without
this obstacle, paying the developer won’t really help the project scale because
it won’t lead to more people working on the project.</p>

<p>While it’s still early, AI suggests a new way to support small open-source projects.
AI access is typically paid by usage, coarsely denominated in Tokens. Open source
developers with an effective AI utilization strategy can easily scale a project
based on access to a larger number of tokens each month.</p>

<p>If you’re a would-be sponsor of Open Source EDA software, be on the lookout 
for organizations and platforms that allow you to contribute AI agent
access (LLM tokens) to your favorite projects.</p>

<h1 id="conclusion">Conclusion</h1>

<p>AI has the potential to scale open source EDA software efforts in ways that 
other technology advances simply have not. In short, allowing us as 
open source EDA users, contributors, and developers to focus on envisioning
the workflows that we want to have, and delegating the bulk of implementation
tasks to AI assistants. This new era of open source EDA
promises new, more productive, approaches to silicon engineering challenges,
easier-to-use tools, and new funding models to help the ecosystem
scale. The future of open source EDA in the AI era is bright, and I’m 
excited to be here for this phase of the journey.</p>

<h2 id="references">References</h2>
<ul>
  <li><a href="https://agentskills.io/home">Anthropic Skills</a></li>
</ul>]]></content><author><name></name></author><category term="EDA" /><summary type="html"><![CDATA[AI is changing the world in many ways. This is particularly visible in the commercial software-development space, where AI is often credited with developing significant portions of new code. But, what about the impact on open source software and, much more specifically, open source Electronic Design Automation (EDA) software? What follows are some observations on how open source EDA developers, users, and contributors can leverage AI based on my own experience. In short, I see a strong potential for AI to spark a new era of open source EDA growth and discovery if we as user, developers, and contributors leverage it well.]]></summary></entry><entry><title type="html">Getting Started with Executable Specs</title><link href="https://bitsbytesgates.com/zuspec/2026/02/01/ModelingExample_GettingStarted.html" rel="alternate" type="text/html" title="Getting Started with Executable Specs" /><published>2026-02-01T00:00:00+00:00</published><updated>2026-02-01T00:00:00+00:00</updated><id>https://bitsbytesgates.com/zuspec/2026/02/01/ModelingExample_GettingStarted</id><content type="html" xml:base="https://bitsbytesgates.com/zuspec/2026/02/01/ModelingExample_GettingStarted.html"><![CDATA[<p>Hardware engineers spend their lives working with specifications, both
natual language (“paper”) and executable. We’ve been looking at 
characterizing the interfaces and internal implementation of executable
specifications – models. But, enough theory. Let’s look at specifications
and specification refinement through an example. Throughout the process,
we can see how the <a href="https://github.com/zuspec/">Zuspec</a> ecosystem helps to 
speed and simplify the modeling process, as well as increasing our ability 
to reuse models.</p>

<!--more-->

<h1 id="example-vehicle-dma-engine">Example Vehicle: DMA Engine</h1>

<p>A DMA engine is one of my favorite examples to use. It’s relatively simple,
yet highlights several key aspects of a range of hardware devices:</p>
<ul>
  <li>It is controlled via a software interface (mmio)</li>
  <li>It has characteristics of other bulk data movers, such as accelerators and high-speed communications interfaces</li>
  <li>Its operation often involves arbitration and resource contention</li>
</ul>

<p><img /></p>

<p>Let’s walk through the process of developing a series of executable and
natural-language specifications that will allow us to refine a high-level
natural-language specification for a DMA device to a synthesizable
executable specification. I’ll store all of the models in the
<a href="https://github.com/zuspec/zuspec-example-dma/">zuspec-example-dma</a> 
repo so you can follow along if you’re interested.</p>

<h1 id="dma-high-level-specification">DMA High-Level Specification</h1>

<p>Let’s start with a simple natural-language spec for what we want. You can find 
the file here: <a href="https://github.com/zuspec/zuspec-example-dma/blob/main/docs/spec/01_highlevel_spec.md">01_highlevel_spec.md</a></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># DMA: High-Level Requirements

The DMA engine supports fast memory transfer between two 
memory regions. It is intended for use with both storage
and memory-mapped I/O devices.

## Transfer Requirements: General
- Each transfer shall have an associated priority (0..15)
- Each transfer shall have non-overlapping source and destination addresses
- Each transfer shall specifies the total number of bytes to transfer

## Transfer Requirements: Device
In addition to the requirements above:
- Each transfer shall specify an access size (eg 1, 2, 4, 8)
- Each transfer shall specify a chunk size, denominated in &lt;accesses&gt;
- The total transfer size is in bytes, and must be a multiple of &lt;access-size&gt;
- The source and destination addresses shall be aligned to &lt;access-size&gt;
- Each transfer specifies whether the source/dest addresses are incremented
- Device I/O is generally controlled by requests from the device itself
  - Request a chunk -&gt; DMA engine performs &lt;chunk-size&gt; &lt;access-size&gt; accesses

## Transfer Requirements: Chaining
- It shall be possible to specify lists of both general and device transfers
</code></pre></div></div>

<p>So, these are quite high-level. They focus on the required functionality, while
spending little time on how we might implement this.</p>

<p>Already, though, we might want to do some experiments with different executable
specifications that implement the natural language one above.</p>

<h1 id="dma-algorithmic-model">DMA Algorithmic Model</h1>

<p>Our first step is to define an algorithmic model with interfaces that satisfy
the requirements above. Let’s start with the Logical Interface, since that is 
often how we phrase high-level requirements.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="n">zuspec.dataclasses</span> <span class="k">as</span> <span class="n">zdc</span>
<span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Protocol</span>

<span class="nd">@zdc.dataclass</span>
<span class="k">class</span> <span class="nc">MemCpy</span><span class="p">(</span><span class="n">zdc</span><span class="p">.</span><span class="n">Struct</span><span class="p">):</span>
    <span class="n">src</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">uptr</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">field</span><span class="p">()</span>
    <span class="n">dst</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">uptr</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">field</span><span class="p">()</span>
    <span class="n">sz</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">u32</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">field</span><span class="p">()</span>

<span class="nd">@zdc.dataclass</span>
<span class="k">class</span> <span class="nc">DevCpy</span><span class="p">(</span><span class="n">MemCpy</span><span class="p">):</span>
    <span class="n">acc_sz</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">u8</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">field</span><span class="p">()</span>
    <span class="n">chk_sz</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">u32</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">field</span><span class="p">()</span>
    <span class="n">inc_src</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">field</span><span class="p">()</span>
    <span class="n">inc_dst</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">field</span><span class="p">()</span>


<span class="k">class</span> <span class="nc">DmaOp</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">memcpy</span><span class="p">(</span>
            <span class="n">self</span><span class="p">,</span>
            <span class="n">src</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">uptr</span><span class="p">,</span>
            <span class="n">dst</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">uptr</span><span class="p">,</span>
            <span class="n">sz</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">u32</span><span class="p">,</span>
            <span class="n">pri</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">i32</span> <span class="o">=</span> <span class="mi">0</span><span class="p">):</span>
        <span class="p">...</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">memcpy_chain</span><span class="p">(</span>
            <span class="n">self</span><span class="p">,</span>
            <span class="n">xfers</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">MemCpy</span><span class="p">],</span>
            <span class="n">pri</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">i32</span> <span class="o">=</span> <span class="mi">0</span><span class="p">):</span>
        <span class="p">...</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">devcpy</span><span class="p">(</span>
            <span class="n">self</span><span class="p">,</span>
            <span class="n">src</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">uptr</span><span class="p">,</span>
            <span class="n">dst</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">uptr</span><span class="p">,</span>
            <span class="n">sz</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">u32</span><span class="p">,</span>
            <span class="n">acc_sz</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">u8</span><span class="p">,</span>
            <span class="n">chk_sz</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">u32</span><span class="p">,</span>
            <span class="n">inc_src</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
            <span class="n">inc_dst</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
            <span class="n">pri</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">i32</span> <span class="o">=</span> <span class="mi">0</span><span class="p">):</span>
        <span class="p">...</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">devcpy_chain</span><span class="p">(</span>
            <span class="n">self</span><span class="p">,</span>
            <span class="n">xfers</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">DevCpy</span><span class="p">],</span>
            <span class="n">pri</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">i32</span> <span class="o">=</span> <span class="mi">0</span><span class="p">):</span>
        <span class="p">...</span></code></pre></figure>

<p>The API definition above captures how software would interact with the
DMA engine. But, we also care about how the DMA engine accesses memory 
in the environment, and how devices in the environment request service
from the DMA engine.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">MemoryOp</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
    <span class="s">"""Memory interface for DMA to access system memory."""</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">read</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">addr</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">u64</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">zdc</span><span class="p">.</span><span class="n">u64</span><span class="p">:</span>
        <span class="s">"""Read a word from memory.

        Args:
            addr: Memory address (word-aligned)

        Returns:
            Data value read
        """</span>
        <span class="p">...</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">write</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">addr</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">u64</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">u64</span><span class="p">,</span> <span class="n">size</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">i8</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
        <span class="s">"""Write a word to memory.

        Args:
            addr: Memory address (word-aligned)
            data: Data value to write
        """</span>
        <span class="p">...</span></code></pre></figure>

<p><a href="https://github.com/zuspec/zuspec-example-dma/blob/main/src/org/zuspec/example/dma/mem.py">mem.py</a> 
defines a memory-access interface that the DMA model implementation will
use to access memory in the system.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">ReqOp</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">def</span> <span class="nf">req_transfer</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="nb">id</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">i32</span><span class="p">):</span>
        <span class="s">"""Request a transfer"""</span>
        <span class="p">...</span></code></pre></figure>

<p><a href="https://github.com/zuspec/zuspec-example-dma/blob/main/src/org/zuspec/example/dma/req.py">req.py</a>
defines the API used by devices to request data transfers from the DMA engine.</p>

<p>In total, we’ve define the key operations used to interact with the DMA engine
in less than 100 lines of code. Now, to do something with these operations.</p>

<h1 id="algorithmic-model-implementation">Algorithmic Model Implementation</h1>
<p>Now that we’ve defined our abstract interfaces, we can create an <em>algorithmic</em> 
implementation of the DMA engine.</p>

<p>You can find the algorithmic implementation of the DMA engine model here:
<a href="https://github.com/zuspec/zuspec-example-dma/blob/main/src/org/zuspec/example/dma/impl/op_op_alg.py">op_op_alg.py</a>.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="nd">@zdc.dataclass</span>
<span class="k">class</span> <span class="nc">DmaOpOpAlg</span><span class="p">(</span><span class="n">DmaOp</span><span class="p">,</span> <span class="n">ReqOp</span><span class="p">,</span> <span class="n">zdc</span><span class="p">.</span><span class="n">Component</span><span class="p">):</span>
    <span class="s">"""DMA engine with no fixed channels. Uses MemoryOp for memory access
    and implements ReqOp for device transfer request control."""</span>
    
    <span class="n">mem</span><span class="p">:</span> <span class="n">MemoryOp</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">port</span><span class="p">()</span></code></pre></figure>

<p>The interface to the model is shown above. Because we’ve deliberately been
abstract about the device’s structure – for example, how many channels
it contains – the model simply implements both the DmaOp and ReqOp 
interfaces.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python">    <span class="k">async</span> <span class="k">def</span> <span class="nf">memcpy</span><span class="p">(</span>
            <span class="n">self</span><span class="p">,</span>
            <span class="n">src</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">uptr</span><span class="p">,</span>
            <span class="n">dst</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">uptr</span><span class="p">,</span>
            <span class="n">sz</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">u32</span><span class="p">,</span>
            <span class="n">pri</span><span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">i32</span> <span class="o">=</span> <span class="mi">0</span><span class="p">):</span>
        <span class="s">"""Copy memory from src to dst.
        
        Performs narrow accesses until 8-byte aligned, then wide accesses.
        """</span>
        <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="n">_mem_l</span><span class="p">.</span><span class="nf">acquire</span><span class="p">()</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">remaining</span> <span class="o">=</span> <span class="n">sz</span>
            <span class="k">while</span> <span class="n">remaining</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
                <span class="c1"># Determine access size based on alignment and remaining bytes
</span>                <span class="c1"># Use the largest power-of-2 size that is both aligned and fits
</span>                <span class="n">align</span> <span class="o">=</span> <span class="n">src</span> <span class="o">&amp;</span> <span class="mh">0x7</span>  <span class="c1"># Low 3 bits give alignment
</span>                <span class="k">if</span> <span class="n">align</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">remaining</span> <span class="o">&gt;=</span> <span class="mi">8</span><span class="p">:</span>
                    <span class="n">xfer_sz</span> <span class="o">=</span> <span class="mi">8</span>
                <span class="nf">elif </span><span class="p">(</span><span class="n">align</span> <span class="o">&amp;</span> <span class="mh">0x3</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">remaining</span> <span class="o">&gt;=</span> <span class="mi">4</span><span class="p">:</span>
                    <span class="n">xfer_sz</span> <span class="o">=</span> <span class="mi">4</span>
                <span class="nf">elif </span><span class="p">(</span><span class="n">align</span> <span class="o">&amp;</span> <span class="mh">0x1</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">remaining</span> <span class="o">&gt;=</span> <span class="mi">2</span><span class="p">:</span>
                    <span class="n">xfer_sz</span> <span class="o">=</span> <span class="mi">2</span>
                <span class="k">else</span><span class="p">:</span>
                    <span class="n">xfer_sz</span> <span class="o">=</span> <span class="mi">1</span>
                
                <span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="n">mem</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">src</span><span class="p">)</span>
                <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="n">mem</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">dst</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">xfer_sz</span><span class="p">)</span>
                <span class="n">src</span> <span class="o">+=</span> <span class="n">xfer_sz</span>
                <span class="n">dst</span> <span class="o">+=</span> <span class="n">xfer_sz</span>
                <span class="n">remaining</span> <span class="o">-=</span> <span class="n">xfer_sz</span>
        <span class="k">finally</span><span class="p">:</span>
            <span class="n">self</span><span class="p">.</span><span class="n">_mem_l</span><span class="p">.</span><span class="nf">release</span><span class="p">()</span></code></pre></figure>

<p>The implementation of the ‘memcpy’ operation is shown above. The implementation
is simple and brief, aside from a little complexity with respect to aligning 
the address. The total model implementation is ~150 lines of Python code, 
which is just about 6x the length of the original high-level spec.</p>

<h1 id="tests-and-test-fixture">Tests and Test Fixture</h1>

<p>Now that we have a behavioral model, we need a way to exercise it. Fortunately,
we have a couple of tools that simplify the process. <a href="https://docs.pytest.org/en/stable/">pytest</a> is used as the
testing framework, and AI assistants are proving to be incredibly capable at
creating test fixtures and tests from the behavioral model that we created.</p>

<p>You can find the tests in <a href="https://github.com/zuspec/zuspec-example-dma/blob/main/tests/unit/test_op_op_alg.py">test_op_op_alg.py</a>.
The vast majority of the code was created using an AI assistant: copilot cli and Sonnet 4.5 in this case.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">test_memcpy_basic</span><span class="p">():</span>
    <span class="s">"""Test basic memcpy operation."""</span>
    <span class="nf">print</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">=== Test: Basic memcpy ==="</span><span class="p">)</span>

    <span class="nd">@zdc.dataclass</span>
    <span class="k">class</span> <span class="nc">Top</span><span class="p">(</span><span class="n">zdc</span><span class="p">.</span><span class="n">Component</span><span class="p">):</span>
        <span class="n">fixture</span><span class="p">:</span> <span class="n">DmaTestFixture</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">field</span><span class="p">()</span>

        <span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
            <span class="c1"># Initialize source memory
</span>            <span class="n">data</span> <span class="o">=</span> <span class="p">[</span><span class="mh">0x10</span><span class="p">,</span> <span class="mh">0x20</span><span class="p">,</span> <span class="mh">0x30</span><span class="p">,</span> <span class="mh">0x40</span><span class="p">]</span>
            <span class="n">self</span><span class="p">.</span><span class="n">fixture</span><span class="p">.</span><span class="nf">init_memory</span><span class="p">(</span><span class="mh">0x1000</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span>

            <span class="c1"># Perform memcpy
</span>            <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="n">fixture</span><span class="p">.</span><span class="n">dma</span><span class="p">.</span><span class="nf">memcpy</span><span class="p">(</span>
                <span class="n">src</span><span class="o">=</span><span class="mh">0x1000</span><span class="p">,</span>
                <span class="n">dst</span><span class="o">=</span><span class="mh">0x2000</span><span class="p">,</span>
                <span class="n">sz</span><span class="o">=</span><span class="mi">32</span>  <span class="c1"># 4 words * 8 bytes
</span>            <span class="p">)</span>

            <span class="c1"># Verify destination
</span>            <span class="n">result</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">fixture</span><span class="p">.</span><span class="nf">read_memory</span><span class="p">(</span><span class="mh">0x2000</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
            <span class="k">assert</span> <span class="n">result</span> <span class="o">==</span> <span class="n">data</span><span class="p">,</span> <span class="sa">f</span><span class="s">"Data mismatch: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s"> != </span><span class="si">{</span><span class="n">data</span><span class="si">}</span><span class="s">"</span>

            <span class="nf">print</span><span class="p">(</span><span class="s">"  Basic memcpy test PASSED"</span><span class="p">)</span>

    <span class="n">t</span> <span class="o">=</span> <span class="nc">Top</span><span class="p">()</span>
    <span class="n">asyncio</span><span class="p">.</span><span class="nf">run</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="nf">run</span><span class="p">())</span>
    <span class="n">t</span><span class="p">.</span><span class="nf">shutdown</span><span class="p">()</span></code></pre></figure>

<p>A basic transfer test is shown above, showing how the ‘memcpy’ operation is 
used, and how results are checked.</p>

<h1 id="conclusions-and-next-steps">Conclusions and Next Steps</h1>
<p>Engineers spend significant time working with natural-language and executable
specifications. Zuspec simplifies the process of creating and testing 
executable specifications (models) at multiple abstraction levels, which
enables greater use of executable models to help refine our natural-language
specifications.</p>

<p>Algorithmic modeling of this style is done today – and, often, in Python. 
The difference with Zuspec is that the models are designed to be reusable
for multiple purposes. Over the next few posts, we’ll look at how we continue
to refine, reuse, and retarget our DMA model.</p>

<h1 id="resources">Resources</h1>
<ul>
  <li><a href="">zuspec-example-dma</a></li>
</ul>]]></content><author><name></name></author><category term="Zuspec" /><summary type="html"><![CDATA[Hardware engineers spend their lives working with specifications, both natual language (“paper”) and executable. We’ve been looking at characterizing the interfaces and internal implementation of executable specifications – models. But, enough theory. Let’s look at specifications and specification refinement through an example. Throughout the process, we can see how the Zuspec ecosystem helps to speed and simplify the modeling process, as well as increasing our ability to reuse models.]]></summary></entry><entry><title type="html">Relating Hardware Model Abstractions</title><link href="https://bitsbytesgates.com/zuspec/2026/01/18/RelatingModelAbstractions.html" rel="alternate" type="text/html" title="Relating Hardware Model Abstractions" /><published>2026-01-18T00:00:00+00:00</published><updated>2026-01-18T00:00:00+00:00</updated><id>https://bitsbytesgates.com/zuspec/2026/01/18/RelatingModelAbstractions</id><content type="html" xml:base="https://bitsbytesgates.com/zuspec/2026/01/18/RelatingModelAbstractions.html"><![CDATA[<p align="center">
<img src="https://bitsbytesgates.com/imgs/2026/01/design_abstraction.png" />
</p>

<p>The most difficult transition in hardware modeling is the transition from
natural-language to executable specification. It is during this transition
that the ambiguities inherent in natural-language descriptions are resolved.
Maximizing value from a multi-abstraction hardware modeling strategy requires
minimizing the number of times a natural-language spec is converted to executable.
Zuspec defines interface abstraction levels that provide a basis for comparing
implementations with different abstraction levels.</p>

<!--more-->

<h1 id="connecting-modeling-abstractions">Connecting Modeling Abstractions</h1>

<p align="center">
<img src="https://bitsbytesgates.com/imgs/2026/01/spec_model_relationship.png" />
</p>

<p>In the previous post, we looked at several internal-implementation abstraction
levels. These help us explore the architecture and micro-architecture of the
design with different cost and performance tradeoffs. However, the internal 
implementation doesn’t provide a good basis for comparing models. 
In essence, this means that 
the natural-language to executable spec translation happens for each model.
This raises the cost of modeling, but also increases the risk that each model
implements a slightly different interpretation of the original spec.</p>

<p align="center">
<img src="https://bitsbytesgates.com/imgs/2026/01/spec_model_model_relationship.png" />
</p>

<p>Ideally, we want some basis for mechanically comparing two models with different
implementations such that we can easily establish whether they are equivalent 
according to that basis. Each
model still needs to incorporate abstraction-specific details from the spec, but 
isn’t a full from-spec implementation.</p>

<h1 id="breaking-down-device-abstraction">Breaking Down Device Abstraction</h1>

<p align="center">
<img src="https://bitsbytesgates.com/imgs/2026/01/design_abstraction.png" />
</p>

<p>Zuspec uses interface abstraction levels as this basis of comparison. 
The first thing to note is that there are two levels of interface: logical and physical.</p>

<p>The same set of interface abstractions apply to logical and physical interfaces, so
let’s explore the abstraction levels first.</p>

<h2 id="abstraction-scenario">Abstraction: Scenario</h2>
<p>A scenario-level interface captures rules about how a device may be used. The 
<a href="https://www.accellera.org/downloads/standards/portable-stimulus">Portable Test and Stimulus (PSS) standard</a> 
is likely the best-known example of a scenario-level interface abstraction.
PSS uses scheduling rules and constraints to specify data, data-flow, and 
resource utilization rules. The rules of a scenario-level description assist
in automating test creation and simplifying the integration of content 
from multiple IPs.</p>

<h2 id="abstraction-operation">Abstraction: Operation</h2>
<p>An operation-level interface captures the device interface in terms of 
operations that the device can perform. Think of the API of a software driver
for a device. An operation-level interface lets us exercise key functions
of a device without worrying too much about the details.</p>

<h2 id="abstraction-mmio">Abstraction: MMIO</h2>
<p>A memory-mapped I/O (MMIO) interface uses memory-mapped registers and 
interrupts as the device interface.</p>

<h2 id="abstraction-tlm">Abstraction: TLM</h2>
<p>A transaction-level modeling (TLM) interface uses packet-like messages. 
The <a href="https://www.accellera.org/images/downloads/standards/systemc/TLM_2_0_LRM.pdf">TLM 1.0 and 2.0 standards</a> 
provide good examples of this level of modeling. TLM is also heavily
used in <a href="https://www.accellera.org/downloads/standards/uvm">UVM</a> testbench 
environments.</p>

<h2 id="abstraction-protocol">Abstraction: Protocol</h2>
<p>Protocol-level interfaces are the familiar signal-level interfaces used
in register-transfer level (RTL) designs.</p>

<h2 id="physical-interfaces">Physical Interfaces</h2>

<p>We’re all familiar with physical device interfaces. In RTL, these are the Protocol-level
interfaces with signal-level ports at the boundary of the device. Every hardware model has physical interfaces,
and these typically</p>

<h2 id="logical-interfaces">Logical Interfaces</h2>

<p>A logical interface is typically a software interface. Software, firmware, and 
test environments interact with a device via logical interfaces. A key attribute
of a logical interface is that it is independent of the physical interface 
and can be virtualized.</p>

<h2 id="useful-combinations">Useful Combinations</h2>

<p>The combination of logical and physical interface abstraction levels, combined with
internal implementation abstraction level, provides a flexible way to characterize
a given model. The table below summarizes how the abstraction levels apply to
logical and physical interfaces.</p>

<table>
  <thead>
    <tr>
      <th>Abstraction</th>
      <th>Logical</th>
      <th>Physical</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Scenario</td>
      <td>Yes</td>
      <td>Maybe</td>
    </tr>
    <tr>
      <td>Operation</td>
      <td>Yes</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>MMIO</td>
      <td>Yes</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>TLM</td>
      <td>Maybe</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>Protocol</td>
      <td>No</td>
      <td>Yes</td>
    </tr>
  </tbody>
</table>

<h1 id="interface-roles">Interface Roles</h1>

<p align="center">
<img src="https://bitsbytesgates.com/imgs/2026/01/interface_roles.png" />
</p>

<p>In addition to abstraction level and interface type, interface roles are also worth
considering when characterizing a device.</p>
<ul>
  <li>Initiator interfaces make requests to targets. Think: memory interface on a RISC-V core</li>
  <li>Target interfaces respond to requests from an initiator. Think: register interface on a UART</li>
  <li>Monitor interfaces passively view interaction between initiator and target</li>
</ul>

<h1 id="relating-interface-abstractions">Relating Interface Abstractions</h1>

<p align="center">
<img src="https://bitsbytesgates.com/imgs/2026/01/abstractors.png" />
</p>

<p>Abstractors, a term used by the <a href="https://www.accellera.org/downloads/standards/ip-xact">IP-XACT</a>
standard, provide a mechanism for bridging abstraction levels in a reusable way. An abstractor
has two or more physical interfaces with different abstraction levels and different roles. 
Specific kinds of abstractors often go by names like bus functional model or transactor.</p>

<p>With the right set of abstractors, we can provide multiple views of the same model implementation.
For example, an algorithmic implementation of a device with MMIO physical interfaces can 
be used as a stand-in for the RTL implementation of the device with protocol-level interfaces 
in a verification testbench simply by adding the right protocol transactors from a library.</p>

<h1 id="next-steps">Next Steps</h1>
<p>Interface abstraction levels and interface types (logical, physical) enable 
model reuse and provide a basis for comparing models with very different
internal implementations. While there are many legal combinations of logical interface,
physical interface, and internal implementation, there are a few key combinations. 
In the next post, we’ll start to look at how some of these key combinations, and
how they are captured and evaluated in Zuspec.</p>]]></content><author><name></name></author><category term="Zuspec" /><summary type="html"><![CDATA[The most difficult transition in hardware modeling is the transition from natural-language to executable specification. It is during this transition that the ambiguities inherent in natural-language descriptions are resolved. Maximizing value from a multi-abstraction hardware modeling strategy requires minimizing the number of times a natural-language spec is converted to executable. Zuspec defines interface abstraction levels that provide a basis for comparing implementations with different abstraction levels.]]></summary></entry><entry><title type="html">Shifting Left with Hardware Model Abstractions</title><link href="https://bitsbytesgates.com/zuspec/2026/01/11/ShiftingLeftWithModelingAbstractions.html" rel="alternate" type="text/html" title="Shifting Left with Hardware Model Abstractions" /><published>2026-01-11T00:00:00+00:00</published><updated>2026-01-11T00:00:00+00:00</updated><id>https://bitsbytesgates.com/zuspec/2026/01/11/ShiftingLeftWithModelingAbstractions</id><content type="html" xml:base="https://bitsbytesgates.com/zuspec/2026/01/11/ShiftingLeftWithModelingAbstractions.html"><![CDATA[<p>“Shift-Left” is a strategy to reorganize processes to enable more parallelism 
by adjusting dependencies. Hardware model abstractions provide a key tool to
shifting tasks left in the silicon design process.</p>

<!--more-->

<h1 id="a-simplified-silicon-design-process">A simplified silicon design process</h1>

<p align="center">
<img src="https://bitsbytesgates.com/imgs/2026/01/simplified_si_design_process.png" />
</p>

<p>The gantt chart above illustrates a simplified silicon design process. It
deliberately makes no attempt to assign accurate relative times to any step
in the process. The key challenge is that starting most steps is gated by
the previous step either completing, or reaching some fairly advanced state
of readiness. For example, the DV team can’t really start verifying the 
design until RTL is available. They can study the spec and, perhaps even,
write some tests speculatively while the RTL is under development. But, 
validating assumptions must wait until RTL exists.</p>

<p>Note that these tasks are grouped by the design-model abstraction
level, with most activity centering around a register-transfer-level (RTL) 
model of the design. This poses several challenges, but one of the biggest
is that the abstraction difference between Spec and RTL is enormous.</p>

<h1 id="a-closer-look">A closer look</h1>

<p>While the diagram above is straightforward, it hides some useful details
about what is actually happening during each of the steps above.</p>

<div class="mermaid">
gantt
    title Elaborated Silicon Design Process
    dateFormat YYYY-MM-DD
    axisFormat x
    Start            :start, 2000-01-12, 1d
    section Spec
        Spec Development :spec_devel, after start, 24d
        Arch Choices      :arch, after start, 12d
    section Algorithmic
        Statistical Model :static_validate, after arch, 12d
        Dynamic Model     :dynamic_validate, after arch, 12d
        Spec Ready        :spec_ready, after dynamic_validate, 2d
    section Cycle Accurate
        Reference Model      :ref_model, after bring_up, 16d
    section RTL
        Impl Structure       :rtl_structure, after spec_ready, 8d
        Impl Sub-IP          :rtl_subip, after rtl_structure, 8d
        Sub-IP Integ         :rtl_integ, after rtl_subip, 8d
        RTL Ready            :rtl_ready, after rtl_integ, 2d
        Bring-up             :bring_up, after rtl_ready, 8d
        Spec Cov             :spec_coverage, after bring_up, 8d
        Impl Cov             :impl_coverage, after spec_coverage, 8d
        RTL Verified         :rtl_verified, after impl_coverage, 2d
        Develop Firmware     :dev_firmware, after rtl_verified, 24dj
</div>

<p>Let’s walk through to look at the changes. First, you’ll notice that 
there are now four abstraction levels:</p>
<ul>
  <li><strong>Spec</strong> - Typically a natural-language description</li>
  <li><strong>Algorithmic</strong> - A behavioral model with limited timing fidelity</li>
  <li><strong>Cycle Accurate</strong> - A behavioral (non-synthesizable) model that reflects the 
intended micro-architecture of the design</li>
  <li><strong>RTL</strong> - Synthesizable model</li>
</ul>

<h2 id="spec-development">Spec Development</h2>

<p>During spec development, it’s natural to use some high-level models to
validate assumptions. These models are captured at a high level of
abstraction, and support one of two methods of evaluation. A model
intended for dynamic evaluation will be behavioral, and supports 
activities such as running existing software workloads. A statistical
model (e.g. an Excel sheet) enables what-if analysis by adjusting 
control parameters. These are typically two distinct models because 
of the two different evaluation approaches.</p>

<h2 id="rtl-implementation">RTL Implementation</h2>

<p>The RTL development process is also not a monolith. Generally, you could
think of a process by which the design is partitioned into independent
sub-IP that can be implemented by different engineers. Once the design
is partitioned, engineers can work in parallel to implement and test
their assigned sub-IP. Finally, sub-IPs are integrated back into the
overall structure of the design.</p>

<h2 id="design-verification">Design Verification</h2>

<p>The design verification process is also step-wise, typically starting
with basic bring-up tests to ensure that simple operations, such as 
register accesses, make it past the interfaces and properly interact 
with the design. Verification then proceeds to exercise key device
usescases and, finally, exercise key implementation details.</p>

<p>The DV team will often create some type of reference model or predictor
to use in checking results from the design. Sometimes this will be 
cycle accurate, but it might also be at an algorithmic level of abstraction.</p>

<p>Looking in more detail, it becomes clear that modeling at different abstarction
levels is being done. But, too often, these models are only used within a specific
silo. There are many reasons for this, including the language(s) used to implement
models and integration challenges.</p>

<h1 id="shifting-left-with-model-reuse">Shifting Left with Model Reuse</h1>

<p>The Zuspec ‘bet’ is that we can reorganize the silicon development process by making models
easier to create, easier to reuse and transform, and easier to integrate. Future posts will
go into more depth on how Zuspec enables this. For now, let’s look at how different modeling
abstractions are used across the silicon development process.</p>

<div class="mermaid">
gantt
    title Ideal Design Implementation Process
    dateFormat YYYY-MM-DD
    axisFormat x
    Start            :start, 2000-01-12, 1d
    section Spec
        Spec Development :spec_devel, after start, 24d
        Arch Choices      :arch, after start, 12d
    section Algorithmic
        Alg Model            :alg_model, after arch, 12d
        Spec Ready           :spec_ready, after alg_model, 2d
    section Cycle Accurate
        CA Model (Structure) :ca_model, after spec_ready, 16d
    section RTL
        Impl Sub-IP          :rtl_subip, after ca_model, 8d
        Sub-IP Integ         :rtl_integ, after rtl_subip, 8d
        RTL Ready            :rtl_ready, after rtl_integ, 2d
        Spec Cov             :spec_coverage, after spec_ready, 8d
        Impl Cov             :impl_coverage, after ca_model, 8d
        Bring-up             :bring_up, after rtl_ready, 8d
        RTL Verified         :rtl_verified, after bring_up, 2d
        Develop Firmware     :dev_firmware, after spec_ready, 24d
</div>

<p>Let’s look at some of the key points:</p>
<ul>
  <li>A single algorithmic model is used to serve both dynamic (simulation) and
static/formal evaluation during the spec development process.
    <ul>
      <li>The DV team can use this algorithmic model, with a few modifications, as a 
substitute for RTL while they setup the testbench environment and write 
tests that exercise the specified design behavior.</li>
      <li>The Firmware team can also get started writing firmware using the 
algorithmic model.</li>
    </ul>
  </li>
  <li>The RTL implementation team develops a cycle-accurate model of the design
as part of the process of partitioning the design.
    <ul>
      <li>The RTL implementation team selectively replaces sub-IPs within the 
cycle-accurate model to test a particular sub-IP’s implementation together
with the more-abstract remainder of the design model.</li>
      <li>The DV team can move to running their tests against the more-accurate 
model. This helps to highlight different interpretations of the spec 
much earlier.</li>
      <li>The DV team can also run their tests against various configurations
of the hybrid cycle accurate / RTL model of the device to begin 
exercising sub-IPs prior to availability of the fully-integrated RTL.
Doing this can also enable tests that target implementation coverage 
to be developed incrementally as the relevant sub-IPs are ready.</li>
    </ul>
  </li>
  <li>Once the RTL is ready, the DV team proceeds to run bring-up activities. 
Final issues are much easier to track down because the tests are known
to run against the cycle-accurate model and various combinations of
RTL sub-IPs.</li>
</ul>

<p>So, how much time will we save? That, of course, heavily depends on the 
relative size of the tasks shown above, as well as on the cost
of creating the set of models used above. But, the savings should be
significant.</p>

<h1 id="next-stes">Next Stes</h1>

<p>We’ve looked at several implementation abstraction levels for device models. 
In the next post, we’ll dig into interface abstraction levels and see how
these enable incremental refinement of models.</p>]]></content><author><name></name></author><category term="Zuspec" /><summary type="html"><![CDATA[“Shift-Left” is a strategy to reorganize processes to enable more parallelism by adjusting dependencies. Hardware model abstractions provide a key tool to shifting tasks left in the silicon design process.]]></summary></entry><entry><title type="html">The Best of a Language and a Class Library</title><link href="https://bitsbytesgates.com/zuspec/2026/01/04/BestOfLanguageAndClassLib.html" rel="alternate" type="text/html" title="The Best of a Language and a Class Library" /><published>2026-01-04T00:00:00+00:00</published><updated>2026-01-04T00:00:00+00:00</updated><id>https://bitsbytesgates.com/zuspec/2026/01/04/BestOfLanguageAndClassLib</id><content type="html" xml:base="https://bitsbytesgates.com/zuspec/2026/01/04/BestOfLanguageAndClassLib.html"><![CDATA[<p>In hardware design and verification, we’re used to working with domain-specific
languages (DSLs), such as SystemVerilog, VHDL, and PSS, as well as class libraries, such as
UVM, SystemC, and CHISEL. We use these DSLs and class libraries to capture key 
semantics of hardware design ; each have their costs and benefits. A language, of course,
offers ultimate flexibility with significant implementation cost. Class libraries reduce
that implementation cost significantly by leveraging the capabilities of a host language,
but often lack expressive capability and portability. The <a href="https://github.com/zuspec">Zuspec</a> 
project that I’ve been working on takes a different approach, with the goal of getting the 
benefits (and avoiding most drawbacks) of both approaches.</p>

<!--more-->

<h1 id="dsl-or-class-library">DSL or Class Library?</h1>
<p>In my experience, the use of hardware modeling varies widely across project teams. At the
extremes, some attempt to maintain a consistent set of models across various abstraction 
levels, while others focus on producing RTL for their piece of the design and create 
models on an as-needed basis to achieve that task. While these approaches seem very 
different there are points of intersection. For example, both teams will likely have a 
predictor model for use in verification. Both teams will likely find that model creation
is time consuming and, depending on their choice of modeling language, both may have 
challenges integrating a predictor model into their verification environment.</p>

<p>Modeling language is one of the first choices to be made when creating a hardware model.
In general, there are two choices: select a domain-specific language such as SystemVerilog, 
or select a class library, such as SystemC, that is implemented in terms of a 
general-purpose language.</p>

<p>Both of these approaches have benefits and drawbacks.</p>

<h3 id="benefits">Benefits</h3>
<ul>
  <li>Language
    <ul>
      <li>Full flexibility to have domain-specific features</li>
      <li>Clear boundaries between what is the ‘language’ and the rest of the</li>
      <li>Full flexibility to process a model</li>
    </ul>
  </li>
  <li>Class Library
    <ul>
      <li>Leverage existing tools (compilers, editors, linters, debuggers) for the base language</li>
      <li>Leverage existing expertise in the base language</li>
      <li>Easily expand the class library to add new capabilities</li>
    </ul>
  </li>
</ul>

<h3 id="drawbacks">Drawbacks</h3>
<ul>
  <li>Language
    <ul>
      <li>Expensive to implement and add new features</li>
      <li>Must convince users to learn and become proficient in the language</li>
      <li>Interoperability with existing languages and tools can be a challenge</li>
    </ul>
  </li>
  <li>Class Library
    <ul>
      <li>Existing tools don’t comprehend domain-specifics encoded by the library</li>
      <li>The need to work with base-language compilers limits how the model can be processed</li>
      <li>The base language often limits how easily/naturally domain-specific features can be described</li>
    </ul>
  </li>
</ul>

<p>We currently use a variety of domain-specific languages across the design and verification
process. Ideally, we could apply a modeling approach that retains the key benefits of 
both approaches much more broadly.</p>

<h1 id="zuspec-a-dsl--class-library-hybrid">Zuspec: A DSL / Class Library Hybrid</h1>

<p><a href="https://github.com/zuspec/">Zuspec</a> is a Python class library with a twist. 
A model described with Zuspec dataclasses is completely valid Python syntax, and can 
be validated with existing Python static checkers.  But, due to some key Python 
capabilities, a Zuspec model can also be processed as if it were a domain-specific language.</p>

<p>Python offers benefits both to the Zuspec users (ie the author of a Zuspec model) and 
to tool implementors. For users, large portions of the existing tool ecosystem can
be leveraged natively with a Zuspec description. Because the Zuspec library adheres
to Python type rules, content-assist and navigation features in integrated developement
environments, such as <a href="https://code.visualstudio.com/">VSCode</a>, work properly. 
For the same reason, static type checkers can help detect issues, such as incorrect 
use of functions, early.</p>

<p>But, what’s even more attractive about Python is that it provides parsing infrastructure
that helps Zuspec construct an intermediate-representation (IR) data model that captures
details that would impossible for a pure class library to capture. This IR also 
allows Zuspec to map a Zuspec model to a variety of implementations.</p>

<p>Let’s look at a simple example:</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python">    <span class="nd">@zdc.dataclass</span>
    <span class="k">class</span> <span class="nc">Prod</span><span class="p">(</span><span class="n">zdc</span><span class="p">.</span><span class="n">Component</span><span class="p">):</span>
        <span class="n">p</span> <span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">PutIF</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">port</span><span class="p">()</span>

        <span class="k">async</span> <span class="k">def</span> <span class="nf">_send</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
            <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">16</span><span class="p">):</span>
                <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="n">p</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
                <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="nf">wait</span><span class="p">(</span><span class="n">zdc</span><span class="p">.</span><span class="n">Time</span><span class="p">.</span><span class="nf">ns</span><span class="p">(</span><span class="mi">10</span><span class="p">))</span>

    <span class="nd">@zdc.dataclass</span>
    <span class="k">class</span> <span class="nc">Cons</span><span class="p">(</span><span class="n">zdc</span><span class="p">.</span><span class="n">Component</span><span class="p">):</span>
        <span class="n">c</span> <span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">GetIF</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">port</span><span class="p">()</span>

        <span class="nd">@zdc.process</span>
        <span class="k">async</span> <span class="k">def</span> <span class="nf">_recv</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
            <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
                <span class="n">i</span> <span class="o">=</span> <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="n">c</span><span class="p">.</span><span class="nf">get</span><span class="p">()</span>
                <span class="nf">print</span><span class="p">(</span><span class="s">"Received %d"</span> <span class="o">%</span> <span class="n">i</span><span class="p">)</span>

    <span class="nd">@zdc.dataclass</span>
    <span class="k">class</span> <span class="nc">Top</span><span class="p">(</span><span class="n">zdc</span><span class="p">.</span><span class="n">Component</span><span class="p">):</span>
        <span class="n">p</span> <span class="p">:</span> <span class="n">Prod</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">inst</span><span class="p">()</span>
        <span class="n">c</span> <span class="p">:</span> <span class="n">Cons</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">inst</span><span class="p">()</span>
        <span class="n">ch</span> <span class="p">:</span> <span class="n">zdc</span><span class="p">.</span><span class="n">Channel</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="n">zdc</span><span class="p">.</span><span class="nf">inst</span><span class="p">()</span>

        <span class="k">def</span> <span class="nf">__bind__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span> <span class="nf">return </span><span class="p">(</span>
            <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">p</span><span class="p">.</span><span class="n">p</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">ch</span><span class="p">.</span><span class="n">put</span><span class="p">),</span>
            <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">c</span><span class="p">.</span><span class="n">c</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">ch</span><span class="p">.</span><span class="n">get</span><span class="p">)</span>
        <span class="p">)</span>

    <span class="n">t</span> <span class="o">=</span> <span class="nc">Top</span><span class="p">()</span>

    <span class="n">asyncio</span><span class="p">.</span><span class="nf">run</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">p</span><span class="p">.</span><span class="nf">_send</span><span class="p">())</span></code></pre></figure>

<p>This is a simple producer-consumer model. The producer and consumer 
communicate via a channel, and the testbench is responsible for activating
the producer. As a side-note, a comparable case in SystemC is roughly twice
as many lines of code, so there are already some measurable efficiencies.</p>

<p>Staying inside Python early in the model-development process is attractive 
due to the fast turnaround time and access to existing Python libraries. 
Any Zuspec model can be run directly in Python and can access all Python 
language and library features.</p>

<p>If you look closely at the description above, you might find yourself 
wondering how it executes. For example, how are ports connected? This is 
where some of the ‘declarative’ aspects of this description come into play.
Despite the description looking and behaving like a class library, there is
still some “magic” behind the scenes. In the case of port connections, the
Zuspec library takes the user’s bind specification and determines how to
properly connect ports and channels. And, of course, there are many other
cases where Zuspec allows the user to specify <em>what</em> is desired and have
the library determine <em>how</em> that intent is implemented.</p>

<h1 id="beyond-pure-python">Beyond Pure Python</h1>

<p>There are quite a few projects that seek to translate Python to a more-performant
implementation. Python ahead-of-time (AOT) compilers typically work with a Python
script (and it dependent libraries) as a whole. Zuspec looks at the world 
differently.</p>

<h2 id="identifying-the-model-boundary">Identifying the Model Boundary</h2>
<p>Zuspec uses types defined in zuspec.dataclasses to identify the boundary of
a model. For example, in the example above, the ‘Top’ class defines such
a boundary. Tools that map a Zuspec description to a non-Python implementation
operate on such boundaries.</p>

<p align="center">
<img src="https://bitsbytesgates.com/imgs/2026/01/zuspec_diagram_2.png" />
</p>

<h2 id="pure-python-vs-retargetable">Pure Python vs Retargetable</h2>

<p>The other place where Zuspec is a bit different is in defining ‘Profiles’
for content. This has significant similarities to the SystemVerilog “synthesizable” 
subset. The first choice is whether a model is <em>Retargetable</em> or not. 
<em>Retargetable</em> models can be mapped to non-Python implementations.</p>

<p>In order to be <em>retargetable</em>, a model can only contain elements of Zuspec-recognized
types. The ‘Top’ class above matches this criteria. That said, as long as running 
in Python is sufficient, a Zuspec model is free to use any Python construct.</p>

<p>Checking <em>Profile</em> compliance is another place where the Python ecosystem helps.
Zuspec implements a plug-in to the <a href="https://flake8.pycqa.org/en/latest/">flake8</a> 
linter that allows Zuspec to check profile compliance along with other Python
rules that flake8 checks. This allows Zuspec-specific checks to be performed
on-the-fly as code is developed, providing much faster feedback to the developer 
(or LLM, as is often the case today).</p>

<p>The diagram above shows several options for how a <em>Retargetable</em> Zuspec model 
might be implemented. Several of these targets, such as SystemVerilog RTL,
have their own <em>Profile</em> that further restricts available features.</p>

<h1 id="conclusions-and-next-steps">Conclusions and Next Steps</h1>
<p>Zuspec is showing early promise in simplifying hardware model creation, and allowing
those models to be reused and retargeted to a variety of environments. Next time,
we’ll look at modeling abstraction-level methodology, and how this helps humans (and LLMs)
to more-effectively discuss and implement the hardware models they care about.</p>

<h2 id="references">References</h2>
<ul>
  <li><a href="https://bitsbytesgates.com/zuspec/2025/09/22/Zuspec_PythonicModelDrivenHardwareDevelopment.html">Zuspec: Pythonic Model-Driven Hardware Development</a></li>
</ul>]]></content><author><name></name></author><category term="Zuspec" /><summary type="html"><![CDATA[In hardware design and verification, we’re used to working with domain-specific languages (DSLs), such as SystemVerilog, VHDL, and PSS, as well as class libraries, such as UVM, SystemC, and CHISEL. We use these DSLs and class libraries to capture key semantics of hardware design ; each have their costs and benefits. A language, of course, offers ultimate flexibility with significant implementation cost. Class libraries reduce that implementation cost significantly by leveraging the capabilities of a host language, but often lack expressive capability and portability. The Zuspec project that I’ve been working on takes a different approach, with the goal of getting the benefits (and avoiding most drawbacks) of both approaches.]]></summary></entry><entry><title type="html">Embracing UVM for FOSSi Design Verification</title><link href="https://bitsbytesgates.com/fossidv/2025/11/28/EmbracingUvmFossiDV.html" rel="alternate" type="text/html" title="Embracing UVM for FOSSi Design Verification" /><published>2025-11-28T00:00:00+00:00</published><updated>2025-11-28T00:00:00+00:00</updated><id>https://bitsbytesgates.com/fossidv/2025/11/28/EmbracingUvmFossiDV</id><content type="html" xml:base="https://bitsbytesgates.com/fossidv/2025/11/28/EmbracingUvmFossiDV.html"><![CDATA[<p>Open source communities emphasize reuse and collaboration centered around
open and standard data formats. While open source communities do exist in 
cases where closed-source tools provide the sole implementation of a standard,
these communities have significant challenges compared to communities with 
access to open-source tools. The Free and Open Source 
Silicon (FOSSi) community <a href="https://antmicro.com/blog/2025/10/support-for-upstream-uvm-2017-in-verilator/">recently gained the ability</a> 
to run SystemVerilog/UVM code on the Verilator open-source simulator, bringing significant new opportunities
to share reusable testbench components across flows running on open- and closed-source
tool flows.</p>

<!--more-->

<h1 id="the-value-of-consistency">The Value of Consistency</h1>
<p>There has always been significant cross-over between open source and closed-source
development in the software world. Students and hobbyists gain critical skills 
working with open-source tools, with the understanding that the same languages, 
methodologies, and tools are used in environments where the work product may 
not be open source. Companies that produce closed-source software often contribute 
to open-source libraries and tools that they use internally, and benefit from a 
larger community of users and developers.</p>

<p>By and large, this holds true in the FOSSi community when it comes to hardware 
design. SystemVerilog and VHDL are used to capture hardware designs, with full
open- and closed-source tool stacks to simulate and synthesize. Innovative
methodologies and tools, such as CHISEL and Amaranth HDL, increase productivity
while still connecting to existing tools using standard hardware description languages (HDLs).</p>

<p>Support for standard languages and a standard design methodology 
has been critical in fostering a sizable ecosystem of available and reusable 
open-source hardware designs. Because these open-source hardware components 
use the same standard interchange formats used for closed-source development,
the technical aspects of integrating an open-source component into a closed-source 
design are straightforward.</p>

<h1 id="fossi-and-functional-verification">FOSSi and Functional Verification</h1>
<p>Until recently, the picture was quite different for functional verification. 
Functional verification is the systematic process of confirming that the
implementation of a hardware design matches the intended functionality, as 
described by the functional specification. Due to tool capabilities, what
is common when developing with closed-source tools differs significantly
from what is common when developing with open-source tools.</p>

<p align="center">
<img src="https://bitsbytesgates.com/imgs/2025/11/open_vs_closed_dv_flow.png" />
</p>

<p>Across the industry, <a href="https://verificationacademy.com/topics/planning-measurement-and-analysis/wrg-industry-data-and-trends/2024-siemens-eda-and-wilson-research-group-ic-asic-functional-verification-trend-report/">SystemVerilog/UVM</a> 
is the most-prevalent infrastructure used to create verification environments.
Until recently, only closed-source tools could run SystemVerilog/UVM testbench environments. 
Thus, content intended to run with a pure open-source tool stack had to 
use a different testbench methodology.</p>

<h1 id="toward-a-portable-verification-methodology">Toward a Portable Verification Methodology</h1>
<p>Ideally, as a community, we can have the best of both worlds: the ability
to use prevalent industry libraries and methodologies with open- and 
closed-source tool chains, and the ability to add new technologies and
techniques on top.</p>

<p align="center">
<img src="https://bitsbytesgates.com/imgs/2025/11/portable_dv_flow.png" />
</p>

<p>A portable verification stack allows students and hobbyists to hone their skills 
in dominant industry practices, while also enabling them to explore emerging new verification 
approaches and technologies. A portable verification stack also makes it 
easier for industrial users to incorporate new technologies into their 
existing SystemVerilog/UVM environments.</p>

<p>Leveraging SV/UVM in a portable verification flow has other highly-desirable
side effects as well. Having more open-source verification collateral written
in SystemVerilog will help to increase Verilator’s support for the language, 
and will result in increased testing of Verilator due to the prevalence of 
continuous-integration flows used for open source.</p>

<h1 id="why-focus-on-verification-ip">Why Focus on Verification IP?</h1>

<p>Protocol Verification IP (VIP) is the nexus of the various flows shown above
for good reason.  Hardware designs interact with the outside world via their interfaces. 
Using standard interfaces vastly simplifies the task of integrating designs. Standard
interface protocols range from quite simple to incredibly complex. Most importantly,
the most critical aspects to verify in a hardware design are typically not the
implementation of the interface protocols. And, what’s more, the way our tests
wish to interact with the design is typically at the software level – for example,
memory reads and writes – not at the detailed level of the interface protocol.</p>

<p>That’s where protocol Verification IP comes into the picture. Just like other 
reusable design IP, protocol verification IPs are pre-verified components of
the testbench environment that exist to translate between the software-level 
activity of our tests and the detailed signal-level implementation of a standard
protocol.</p>

<p>Verification IP allow us to start testing the unique aspects of our design
more quickly, since we can interact with the ‘test’ side of the VIP at a high
level, with confidence that the VIP will translate to the details of the standard
protocol. Being able to use UVM in our verification flow gives us access
to a much wider set of reusable verification IP.</p>

<h1 id="conclusion">Conclusion</h1>
<p>Support for SystemVerilog/UVM in open-source tools opens up new possibilities for 
sharing within the FOSSi community and with the closed-source silicon development 
industry. It allows students and professional hobbyists to hone their skills with
common industrial practice using open source tools and collateral. And, it enables
far better distributed development by allowing all contributors to access the same
continuous integration (CI) flow.</p>

<p>We’ll look at an architecture for modular multi-language/multi-environment UVM 
protocol VIP in a future series of posts. But, next week, we’ll spend some time looking at 
the suite of PyHDL-IF examples and how to run them.</p>

<h1 id="references">References</h1>
<ul>
  <li><a href="https://antmicro.com/blog/2025/10/support-for-upstream-uvm-2017-in-verilator/">Support for upstream UVM 2017 in Verilator</a></li>
  <li><a href="https://www.chisel-lang.org/">CHISEL</a></li>
  <li><a href="https://github.com/amaranth-lang">Amaranth HDL</a></li>
  <li><a href="https://verificationacademy.com/topics/planning-measurement-and-analysis/wrg-industry-data-and-trends/2024-siemens-eda-and-wilson-research-group-ic-asic-functional-verification-trend-report/">2024 Wilson Research Group Functional Verification Study</a></li>
  <li><a href="https://www.veripool.org/verilator/">Verilator</a></li>
  <li><a href="https://www.accellera.org/downloads/standards/uvm">UVM</a></li>
</ul>]]></content><author><name></name></author><category term="FOSSiDV" /><summary type="html"><![CDATA[Open source communities emphasize reuse and collaboration centered around open and standard data formats. While open source communities do exist in cases where closed-source tools provide the sole implementation of a standard, these communities have significant challenges compared to communities with access to open-source tools. The Free and Open Source Silicon (FOSSi) community recently gained the ability to run SystemVerilog/UVM code on the Verilator open-source simulator, bringing significant new opportunities to share reusable testbench components across flows running on open- and closed-source tool flows.]]></summary></entry><entry><title type="html">Introducing SV/UVM to Python Development Tools</title><link href="https://bitsbytesgates.com/pythonuvm/2025/11/23/UvmWithPython__DevTools.html" rel="alternate" type="text/html" title="Introducing SV/UVM to Python Development Tools" /><published>2025-11-23T00:00:00+00:00</published><updated>2025-11-23T00:00:00+00:00</updated><id>https://bitsbytesgates.com/pythonuvm/2025/11/23/UvmWithPython__DevTools</id><content type="html" xml:base="https://bitsbytesgates.com/pythonuvm/2025/11/23/UvmWithPython__DevTools.html"><![CDATA[<p>Over the past few posts, we’ve examined the details of dynamically interacting
with a SystemVerilog/UVM environment from Python. We can access the 
component hierarchy of a UVM testbench, read and write the value of sequence-item
fields, and run sequences from Python – all without recompiling our UVM testbench
or needing to generate any testbench-specific code. In this
post, we’ll see how we can generate a Python view of key user-defined SystemVerilog 
classes in the UVM testbench. The reason? To better support the operation of Python
development tools.</p>

<!--more-->

<h1 id="feeding-python-development-tools">Feeding Python Development Tools</h1>

<p>Python has a rich ecosystem of developer tools. There are IDE plug-ins that 
assist developers in navigating around a codebase, and provide context-aware 
editing capabilities.  There are static-checking tools (MyPy, Flake8, etc) that 
identify coding mistakes before execution, saving iteration time. And, of course, 
there are a collection of AI assistants (Copilot, Cline, Codex, Claude, etc) that 
have proven very adept at writing Python code. The common factor with all of these 
tools is that they all operate on Python source.</p>

<p>We’ve gotten this far without needing to generate any testbench-specific Python
or SystemVerilog code to implement a general-purpose Python integration with
a UVM testbench. Fortunately, the same dynamically-discovered data that enables ease 
of integration can be used to generate the Python source that enables our 
development tools.</p>

<h1 id="discovering-available-types">Discovering Available Types</h1>

<p>PyHDL-IF already uses the vast majority of the data required to generate a Python 
view of user-defined SV/UVM classes to implement the runtime integration 
between Python and SystemVerilog – specifically, identifying and accessing 
named component instances and fields registered with the UVM library. 
The one piece that we’re missing is a list
of all the classes registered with the UVM factory.</p>

<p>Unfortunately, the UVM library doesn’t make this information available via
a standard API. The good news is that there is a workaround. The UVM factory
provides a <code class="language-plaintext highlighter-rouge">print</code> function that reports the names of registered classes via
the UVM report infrastructure. Using a custom message handler, we can intercept
and save this catalog of available choices. You can find the relevant 
code in <a href="https://github.com/fvutils/pyhdl-if/blob/main/src/hdl_if/share/uvm/pyhdl_uvm_object_rgy.svh">src/hdl_if/share/uvm/pyhdl_uvm_object_rgy.svh</a>.</p>

<figure class="highlight"><pre><code class="language-verilog" data-lang="verilog"><span class="cm">/** 
 * Implements a report catcher to allow capturing the 
 * list of object typenames printed by the factory
 */</span>     
<span class="kt">class</span> <span class="n">factory_print_catcher</span> <span class="k">extends</span> <span class="n">uvm_report_catcher</span><span class="p">;</span>
    <span class="kt">string</span>  <span class="n">factory_print</span><span class="p">;</span>

    <span class="k">function</span> <span class="k">new</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="o">=</span><span class="s">"factory_print_catcher"</span><span class="p">);</span>
        <span class="k">super</span><span class="p">.</span><span class="k">new</span><span class="p">(</span><span class="n">name</span><span class="p">);</span>
    <span class="k">endfunction</span>

    <span class="k">function</span> <span class="n">action_e</span> <span class="n">catch</span><span class="p">();</span>
        <span class="n">factory_print</span> <span class="o">=</span> <span class="n">get_message</span><span class="p">();</span>

        <span class="c1">// Suppress the message</span>
        <span class="k">return</span> <span class="n">CAUGHT</span><span class="p">;</span>
    <span class="k">endfunction</span>
<span class="k">endclass</span>

<span class="kt">class</span> <span class="n">pyhdl_uvm_object_rgy</span><span class="p">;</span>
    <span class="c1">// ...</span>
    <span class="kt">virtual</span> <span class="k">function</span> <span class="kt">string</span> <span class="mi">_</span><span class="n">get_type_dump</span><span class="p">();</span>
        <span class="n">factory_print_catcher</span> <span class="n">catcher</span> <span class="o">=</span> <span class="k">new</span><span class="p">;</span>
        <span class="n">uvm_factory</span> <span class="n">factory</span> <span class="o">=</span> <span class="n">uvm_factory</span><span class="o">::</span><span class="n">get</span><span class="p">();</span>

        <span class="c1">// Attach our custom report catcher so we can </span>
        <span class="c1">// save the message printed by factory.print()</span>
        <span class="n">uvm_report_cb</span><span class="o">::</span><span class="n">add</span><span class="p">(</span><span class="k">null</span><span class="p">,</span> <span class="n">catcher</span><span class="p">);</span>

        <span class="n">factory</span><span class="p">.</span><span class="n">print</span><span class="p">();</span>

        <span class="n">uvm_report_cb</span><span class="o">::</span><span class="n">delete</span><span class="p">(</span><span class="k">null</span><span class="p">,</span> <span class="n">catcher</span><span class="p">);</span>

        <span class="k">return</span> <span class="n">catcher</span><span class="p">.</span><span class="n">factory_print</span><span class="p">;</span>
    <span class="k">endfunction</span>

    <span class="c1">// ...</span>
<span class="k">endclass</span></code></pre></figure>

<h1 id="the-pyhdl_uvm_pygen-uvm-test">The pyhdl_uvm_pygen UVM Test</h1>

<p>We need to run the simulator in order to load and execute code from the UVM
testbench. In a UVM environment, the UVM test is the center of executing
test behavior, so it makes sense to provide a UVM test that handles discovering
the available user-defined UVM classes and generating a Python view. The 
PyHDL-IF library provides the <code class="language-plaintext highlighter-rouge">pyhdl_uvm_pygen</code> test for this purpose.</p>

<p>While the <code class="language-plaintext highlighter-rouge">pyhdl_uvm_pygen</code> test is the entrypoint, the task of discovering
available classes, processing them, and generating Python is all implemented
in Python.</p>

<h1 id="example">Example</h1>
<p>While all the details of <em>how</em> we extract information from SV/UVM classes is 
interesting, pragmatic users will be much more interested in <em>applying</em> the
workflow and using the result.</p>

<p>Let’s look at an example, which you can find in <a href="https://github.com/fvutils/pyhdl-if/tree/main/examples/uvm/pygen">examples/uvm/pygen</a>.</p>

<p>This example consists of a simple UVM environment with a memory-oriented sequence 
item, shown below:</p>

<figure class="highlight"><pre><code class="language-verilog" data-lang="verilog">    <span class="kt">class</span> <span class="n">seq_item</span> <span class="k">extends</span> <span class="n">uvm_sequence_item</span><span class="p">;</span>
        <span class="kt">bit</span>              <span class="n">ctrl_addr_page</span><span class="p">;</span>
        <span class="kt">bit</span><span class="p">[</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>         <span class="n">addr_page</span><span class="p">;</span>

        <span class="k">rand</span> <span class="kt">bit</span> <span class="p">[</span><span class="mi">7</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>   <span class="n">addr</span><span class="p">;</span>
        <span class="k">rand</span> <span class="kt">bit</span>         <span class="nb">write</span><span class="p">;</span> <span class="c1">// 1=write, 0=read</span>
        <span class="k">rand</span> <span class="kt">bit</span> <span class="p">[</span><span class="mi">31</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>  <span class="n">data</span><span class="p">;</span>
        <span class="k">rand</span> <span class="kt">bit</span> <span class="p">[</span><span class="mi">3</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>   <span class="n">tid</span><span class="p">;</span>

        <span class="c1">// ...</span>

        <span class="cp">`uvm_object_utils_begin</span><span class="p">(</span><span class="n">seq_item</span><span class="p">)</span>
            <span class="cp">`uvm_field_int</span><span class="p">(</span><span class="n">ctrl_addr_page</span><span class="p">,</span> <span class="n">UVM_ALL_ON</span><span class="p">)</span>
            <span class="cp">`uvm_field_int</span><span class="p">(</span><span class="n">addr_page</span><span class="p">,</span> <span class="n">UVM_ALL_ON</span><span class="p">)</span>
            <span class="cp">`uvm_field_int</span><span class="p">(</span><span class="n">addr</span> <span class="p">,</span> <span class="n">UVM_ALL_ON</span><span class="p">)</span>
            <span class="cp">`uvm_field_int</span><span class="p">(</span><span class="nb">write</span><span class="p">,</span> <span class="n">UVM_ALL_ON</span><span class="p">)</span>
            <span class="cp">`uvm_field_int</span><span class="p">(</span><span class="n">data</span> <span class="p">,</span> <span class="n">UVM_ALL_ON</span><span class="p">)</span>
            <span class="cp">`uvm_field_int</span><span class="p">(</span><span class="n">tid</span>  <span class="p">,</span> <span class="n">UVM_ALL_ON</span><span class="p">)</span>
        <span class="cp">`uvm_object_utils_end</span>

        <span class="c1">// ...</span>
    <span class="k">endclass</span></code></pre></figure>

<p>Our build/run-flow specification (flow.yaml) specifies how to build
and run tests. This example also launches the PyHDL-IF-provided 
UVM test that generates Python classes. Note how the task parameters
specify a SystemVerilog plusarg (+pyhdl.outdir) to specify the destination directory
for the generated Python classes.</p>

<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml">  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">sim-run</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s2">"</span><span class="s">hdlsim.$.SimRun"</span>
    <span class="na">needs</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">sim-img</span>
    <span class="pi">-</span> <span class="s">pyhdl-if.DpiLib</span>
    <span class="na">with</span><span class="pi">:</span>
      <span class="na">plusargs</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">UVM_TESTNAME=pyhdl_uvm_pygen</span>
      <span class="pi">-</span> <span class="s">pyhdl.python=$/../../packages/python/bin/python</span>
      <span class="pi">-</span> <span class="s">pyhdl.debug=0</span>
      <span class="pi">-</span> <span class="s">pygen.debug=1</span>
      <span class="pi">-</span> <span class="s">pyhdl.outdir=$/env_classes/env</span></code></pre></figure>

<p>Running the <code class="language-plaintext highlighter-rouge">sim-run</code> task runs the <code class="language-plaintext highlighter-rouge">pyhdl_uvm_pygen</code> UVM test, which
generates Python classes in the example directory. The class corresponding
to the sequence item is shown below. You can find the full code in
<a href="https://github.com/fvutils/pyhdl-if/blob/main/examples/uvm/pygen/env_classes/env/seq_item.py">examples/uvm/pygen/env_classes/env/seq_item.py</a>.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="nd">@dc.dataclass</span><span class="p">(</span><span class="n">kw_only</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">seq_item_fields</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="n">ctrl_addr_page</span> <span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">dc</span><span class="p">.</span><span class="nf">field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
    <span class="n">addr_page</span> <span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">dc</span><span class="p">.</span><span class="nf">field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
    <span class="n">addr</span> <span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">dc</span><span class="p">.</span><span class="nf">field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
    <span class="n">write</span> <span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">dc</span><span class="p">.</span><span class="nf">field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
    <span class="n">data</span> <span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">dc</span><span class="p">.</span><span class="nf">field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
    <span class="n">tid</span> <span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">dc</span><span class="p">.</span><span class="nf">field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>

<span class="nd">@dc.dataclass</span>
<span class="k">class</span> <span class="nc">seq_item</span><span class="p">(</span><span class="n">uvm_object</span><span class="p">,</span> <span class="n">seq_item_fields</span><span class="p">):</span>
    <span class="k">pass</span></code></pre></figure>

<p>The class fields are declared in a pure-data class (seq_item_fields) to make 
it easier to use just the data aspect of the class for unit testing.</p>

<p>Once generated, these Python classes that mirror the user-defined 
SystemVerilog/UVM classes can be supplied to all of our standard Python 
development tools, allowing these tools to check and provide help working
with the Python interface to our SystemVerilog/UVM testbench.</p>

<h1 id="conclusion">Conclusion</h1>
<p>The PyHDL-IF library provides an easy-to-use integration between Python and
a SystemVerilog/UVM testbench environment. And, by generating a Python view
of the SystemVerilog classes, supports Python development tools in providing
a productive developer experience.</p>

<p>We’ll look at other features of the PyHDL-IF library and its support for SV/UVM
in future posts. But, more immediately, we’ll be looking at how recent changes in
the open-source EDA ecosystem are changing what’s possible in a verification 
flow that supports both open-source and closed-source tools.</p>

<h1 id="references">References</h1>
<ul>
  <li><a href="https://github.com/fvutils/pyhdl-if/">PyHDL-IF</a></li>
  <li><a href="https://mypy-lang.org/">MyPy</a></li>
  <li><a href="https://flake8.pycqa.org/en/latest/">Flake8</a></li>
</ul>]]></content><author><name></name></author><category term="PythonUVM" /><summary type="html"><![CDATA[Over the past few posts, we’ve examined the details of dynamically interacting with a SystemVerilog/UVM environment from Python. We can access the component hierarchy of a UVM testbench, read and write the value of sequence-item fields, and run sequences from Python – all without recompiling our UVM testbench or needing to generate any testbench-specific code. In this post, we’ll see how we can generate a Python view of key user-defined SystemVerilog classes in the UVM testbench. The reason? To better support the operation of Python development tools.]]></summary></entry><entry><title type="html">Working with Analysis Ports</title><link href="https://bitsbytesgates.com/pythonuvm/2025/11/16/UvmWithPython__AnalysisPorts.html" rel="alternate" type="text/html" title="Working with Analysis Ports" /><published>2025-11-16T00:00:00+00:00</published><updated>2025-11-16T00:00:00+00:00</updated><id>https://bitsbytesgates.com/pythonuvm/2025/11/16/UvmWithPython__AnalysisPorts</id><content type="html" xml:base="https://bitsbytesgates.com/pythonuvm/2025/11/16/UvmWithPython__AnalysisPorts.html"><![CDATA[<p>For the most part, UVM and SystemVerilog make interactions with a 
dynamically-typed language surprisingly easy. There are a few exceptions
where the developer of the SystemVerilog UVM code must step in to enable
access to certain testbench elements. Analysis ports are one case in point.
Fortunately, there is a relatively non-invasive approach to allow Python
to dynamically interact with UVM analysis ports. Let’s look more closely at 
the details.</p>

<h1 id="working-with-analysis-ports">Working with Analysis Ports</h1>

<p>UVM testbench environments use analysis ports extensively. An <em>analysis port</em> 
publishes data to zero or more listeners, and is used to route transactions 
from interface monitors to scoreboards and other analysis components.</p>

<p>Connecting to analysis ports from Python allows scoreboards and other analysis
components to be implemented in Python. There are two technical requirements
that enable Python to receive transactions published by analysis ports:</p>
<ul>
  <li>Be able to identify a uvm_analysis_port as a distinct type (vs, say, a uvm_object or uvm_component).</li>
  <li>Be able to add a new listener to the analysis port</li>
</ul>

<h1 id="challenges-of-dynamically-using-analysis-ports">Challenges of Dynamically Using Analysis Ports</h1>
<p>Each of these technical requirements poses its own challenge.</p>

<div class="mermaid" align="center">
classDiagram
  uvm_analysis_port_base &lt;|-- uvm_analysis_port
  uvm_tlm_if_base &lt;|-- uvm_analysis_port_base
  uvm_component &lt;|-- uvm_tlm_if_base
  class uvm_analysis_port["uvm_analysis_port #(T)"]
  class uvm_analysis_port_base["uvm_analysis_port_base #(uvm_tlm_if_base #(T,T))"]
  class uvm_tlm_if_base["uvm_tlm_if_base #(T,T)"]
  class uvm_component["uvm_component"]
</div>

<p>SystemVerilog is a statically-typed language that provides minimal 
<em>introspection</em> tools for looking at the internals of user-defined 
classes. This is especially true when it comes to templated types. In 
order to ask whether a given object <em>is an</em> instance of uvm_analysis_port,
we need to ask about a specific specialization of that type – for example,
“uvm_analysis_port #(my_special_transaction)”. As you might expect, this
poses some challenges because the PyHDL-IF library only knows about concrete
types defined by the UVM library, and doesn’t know anything about
the transaction types used within a user’s testbench. As a consequence,
when the PyHDL-IF library looks at an analysis port instance, 
it just sees a <code class="language-plaintext highlighter-rouge">uvm_component</code> instance.</p>

<p>One possible approach to this challenge is to have the user register each 
of the transaction types that they might want to use with analysis ports 
with the PyHDL-IF library. 
For example:</p>

<figure class="highlight"><pre><code class="language-verilog" data-lang="verilog"><span class="kt">class</span> <span class="n">my_transaction</span> <span class="k">extends</span> <span class="n">uvm_sequence_item</span><span class="p">;</span>
  <span class="c1">// ...</span>
<span class="k">endclass</span>

<span class="cp">`pyhdl_uvm_transaction_utils</span><span class="p">(</span><span class="n">my_transaction</span><span class="p">)</span></code></pre></figure>

<p>This would allow the PyHDL-IF library to identify analysis-port instances,
but wouldn’t help with the second challenge of working with 
analysis ports.</p>

<p>The second challenge comes when we want to add a new listener to an 
analysis port. This must be done during <code class="language-plaintext highlighter-rouge">connect_phase</code>, and requires that 
a properly-specialized <code class="language-plaintext highlighter-rouge">uvm_analysis_imp #(T)</code> class instance was 
previously created during the build phase.</p>

<h1 id="making-analysis-ports-visibile">Making Analysis Ports Visibile</h1>

<p>While both of these challenges can be overcome independently, doing so 
would require the user to make two independent sets of changes. 
And, more troubling, wouldn’t provide a good way for VIP developers 
to hide this complexity from users. 
At minimum, the testbench developer would always need to pre-create
<code class="language-plaintext highlighter-rouge">uvm_analysis_imp</code> instances for each analysis port to which they might
subscribe.</p>

<p>Instead, PyHDL-IF provides two classes that supports two paths for making
analysis ports visible and accessible from Python:</p>
<ul>
  <li><em>pyhdl_uvm_analysis_port</em> – An alternative analysis port implementation intended for use by VIP authors</li>
  <li><em>pyhdl_uvm_analysis_imp</em> – An analysis port listener intended to make existing analysis ports available to PyHDL-IF</li>
</ul>

<h2 id="example">Example</h2>

<p>Let’s take a look at an example to understand how analysis ports are made 
accessible to the PyHDL-IF library. The uvm/seq_item_scoreboard example
shows how to make analysis ports accessible as well as how to receive
transactions from analysis ports.</p>

<figure class="highlight"><pre><code class="language-verilog" data-lang="verilog">  <span class="c1">// Producer component with two analysis ports</span>
  <span class="kt">class</span> <span class="n">dual_producer</span> <span class="k">extends</span> <span class="n">uvm_component</span><span class="p">;</span>
    <span class="cp">`uvm_component_utils</span><span class="p">(</span><span class="n">dual_producer</span><span class="p">)</span>

    <span class="n">pyhdl_uvm_analysis_port</span> <span class="p">#(</span><span class="n">seq_item_a</span><span class="p">)</span> <span class="n">ap_a</span><span class="p">;</span>
    <span class="n">uvm_analysis_port</span> <span class="p">#(</span><span class="n">seq_item_b</span><span class="p">)</span> <span class="n">ap_b</span><span class="p">;</span>

    <span class="k">function</span> <span class="k">new</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="n">uvm_component</span> <span class="n">parent</span><span class="p">);</span>
      <span class="k">super</span><span class="p">.</span><span class="k">new</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">parent</span><span class="p">);</span>
    <span class="k">endfunction</span>

    <span class="k">function</span> <span class="kt">void</span> <span class="n">build_phase</span><span class="p">(</span><span class="n">uvm_phase</span> <span class="n">phase</span><span class="p">);</span>
      <span class="k">super</span><span class="p">.</span><span class="n">build_phase</span><span class="p">(</span><span class="n">phase</span><span class="p">);</span>
      <span class="n">ap_a</span> <span class="o">=</span> <span class="k">new</span><span class="p">(</span><span class="s">"ap_a"</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span>
      <span class="n">ap_b</span> <span class="o">=</span> <span class="k">new</span><span class="p">(</span><span class="s">"ap_b"</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span>
    <span class="k">endfunction</span></code></pre></figure>

<p>The code snippet above shows how the pyhdl_uvm_analysis_port can be used
instead of uvm_analysis_port to make an analysis port available to the
PyHDL-IF library. The API of this class is identical to uvm_analysis_port,
making this a good choice for VIP authors that want to automatically make 
analysis ports accessible.</p>

<figure class="highlight"><pre><code class="language-verilog" data-lang="verilog">  <span class="c1">// Environment tying producer to scoreboard</span>
  <span class="kt">class</span> <span class="n">my_env</span> <span class="k">extends</span> <span class="n">uvm_env</span><span class="p">;</span>
    <span class="cp">`uvm_component_utils</span><span class="p">(</span><span class="n">my_env</span><span class="p">)</span>

    <span class="c1">// ...</span>
    <span class="n">pyhdl_uvm_analysis_imp</span> <span class="p">#(</span><span class="n">seq_item_b</span><span class="p">)</span>    <span class="n">ap_b_proxy</span><span class="p">;</span>
    <span class="c1">// ...</span>

    <span class="k">function</span> <span class="kt">void</span> <span class="n">build_phase</span><span class="p">(</span><span class="n">uvm_phase</span> <span class="n">phase</span><span class="p">);</span>
      <span class="k">super</span><span class="p">.</span><span class="n">build_phase</span><span class="p">(</span><span class="n">phase</span><span class="p">);</span>
      <span class="c1">// ...</span>
      <span class="n">ap_b_proxy</span> <span class="o">=</span> <span class="k">new</span><span class="p">(</span><span class="s">"ap_b_proxy"</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span>
    <span class="k">endfunction</span>

    <span class="k">function</span> <span class="kt">void</span> <span class="n">connect_phase</span><span class="p">(</span><span class="n">uvm_phase</span> <span class="n">phase</span><span class="p">);</span>
      <span class="k">super</span><span class="p">.</span><span class="n">connect_phase</span><span class="p">(</span><span class="n">phase</span><span class="p">);</span>
      <span class="c1">// ...</span>
      <span class="n">prod</span><span class="p">.</span><span class="n">ap_b</span><span class="p">.</span><span class="n">connect</span><span class="p">(</span><span class="n">ap_b_proxy</span><span class="p">.</span><span class="n">analysis_export</span><span class="p">);</span>
    <span class="k">endfunction</span>
  <span class="k">endclass</span></code></pre></figure>

<p>Note that <code class="language-plaintext highlighter-rouge">ap_b</code> uses a normal uvm_analysis_port in the VIP. We can 
make this analysis port accessible to the PyHDL-IF library by connecting
an instance of <code class="language-plaintext highlighter-rouge">pyhdl_uvm_analysis_imp</code> to it. While this could be done
anywhere, it’s often done in the environment (as shown above).</p>

<p>The <code class="language-plaintext highlighter-rouge">pyhdl_uvm_analysis_imp</code> instance is connected to the analysis port 
in the same way that any other analysis port subscriber does. Doing 
this allows Python code to receive transactions published by the 
analysis port.</p>

<p>Both <code class="language-plaintext highlighter-rouge">pyhdl_uvm_analysis_port</code> and <code class="language-plaintext highlighter-rouge">pyhdl_uvm_analysis_imp</code> have a <code class="language-plaintext highlighter-rouge">proxy</code>
field inside with a <code class="language-plaintext highlighter-rouge">add_listener</code> method that the Python environment 
uses to register a listener. Let’s look at the Python environment now.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">PyComp</span><span class="p">(</span><span class="n">uvm_component_impl</span><span class="p">):</span>

    <span class="k">def</span> <span class="nf">build_phase</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">phase</span><span class="p">):</span>
        <span class="nf">print</span><span class="p">(</span><span class="s">"build_phase"</span><span class="p">,</span> <span class="n">flush</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">connect_phase</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">phase</span><span class="p">):</span>
        <span class="nf">print</span><span class="p">(</span><span class="s">"connect_phase"</span><span class="p">,</span> <span class="n">flush</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
        <span class="n">env</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">proxy</span><span class="p">.</span><span class="nf">get_parent</span><span class="p">()</span>
        <span class="n">env</span><span class="p">.</span><span class="n">prod</span><span class="p">.</span><span class="n">ap_a</span><span class="p">.</span><span class="n">proxy</span><span class="p">.</span><span class="nf">add_listener</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">write_a</span><span class="p">)</span>
        <span class="n">env</span><span class="p">.</span><span class="n">ap_b_proxy</span><span class="p">.</span><span class="n">proxy</span><span class="p">.</span><span class="nf">add_listener</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">write_b</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">write_a</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">t</span><span class="p">):</span>
        <span class="nf">print</span><span class="p">(</span><span class="s">"write_a %0s"</span> <span class="o">%</span> <span class="nf">str</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="nf">pack</span><span class="p">()),</span> <span class="n">flush</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">write_b</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">t</span><span class="p">):</span>
        <span class="nf">print</span><span class="p">(</span><span class="s">"write_b %0s"</span> <span class="o">%</span> <span class="nf">str</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="nf">pack</span><span class="p">()),</span> <span class="n">flush</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span></code></pre></figure>

<p>In this case, the component proxy is a child instance of the <code class="language-plaintext highlighter-rouge">env</code> class
shown above. Consequently, to connect to the analysis ports, we first
need to get a handle to the instance of the <code class="language-plaintext highlighter-rouge">env</code> class (our parent).
After that, we need to access the analysis port <code class="language-plaintext highlighter-rouge">proxy</code> field inside
the <code class="language-plaintext highlighter-rouge">pyhdl_uvm_analysis_port</code> and <code class="language-plaintext highlighter-rouge">pyhdl_uvm_analysis_imp</code> instances.
We can pass any callable Python method or object to the <code class="language-plaintext highlighter-rouge">add_listener</code>
method. In this case, we simply register two class methods. When simulation
runs, <code class="language-plaintext highlighter-rouge">write_a</code> and <code class="language-plaintext highlighter-rouge">write_b</code> will be called whenever the analysis port
that they monitor publishes a transaction.</p>

<h1 id="conclusions-and-next-steps">Conclusions and Next Steps</h1>
<p>The PyHDL-IF library allows analysis ports to be made visible and accessible 
from Python with a small one-time investment. Verification IP (VIP) 
developers can implement this support, allowing all users to benefit. 
Testbench developers can also perform this work for VIP that isn’t pre-instrumented. 
The result is that scoreboards and other analysis components can easily be developed in Python.</p>

<p>Thus far in the series, we’ve seen how to run Python 
behavior from SystemVerilog. We’ve seen how to interact with user-defined 
UVM class fields, and we’ve now seen how to subscribe to analysis ports. 
These technical capabilities make it easy for Python to dynamically interoperate 
with an existing SystemVerilog/UVM testbench. But, thus far, these 
capabilities don’t do much to support Python development tools. In the next
post, we’ll see how the PyHDL-IF library provides a bridge from SystemVerilog/UVM
to help Python development tools understand what is present in the UVM environment
and make us more productive developing Python testbench components.</p>

<h2 id="references">References</h2>
<ul>
  <li>PyHDL-IF library - <a href="https://github.com/fvutils/pyhdl-if">https://github.com/fvutils/pyhdl-if</a></li>
  <li>Analysis port example - <a href="https://github.com/fvutils/pyhdl-if/tree/main/examples/uvm/seq_item_scoreboard">https://github.com/fvutils/pyhdl-if/tree/main/examples/uvm/seq_item_scoreboard</a></li>
</ul>]]></content><author><name></name></author><category term="PythonUVM" /><summary type="html"><![CDATA[For the most part, UVM and SystemVerilog make interactions with a dynamically-typed language surprisingly easy. There are a few exceptions where the developer of the SystemVerilog UVM code must step in to enable access to certain testbench elements. Analysis ports are one case in point. Fortunately, there is a relatively non-invasive approach to allow Python to dynamically interact with UVM analysis ports. Let’s look more closely at the details. Working with Analysis Ports UVM testbench environments use analysis ports extensively. An analysis port publishes data to zero or more listeners, and is used to route transactions from interface monitors to scoreboards and other analysis components. Connecting to analysis ports from Python allows scoreboards and other analysis components to be implemented in Python. There are two technical requirements that enable Python to receive transactions published by analysis ports: Be able to identify a uvm_analysis_port as a distinct type (vs, say, a uvm_object or uvm_component). Be able to add a new listener to the analysis port Challenges of Dynamically Using Analysis Ports Each of these technical requirements poses its own challenge. classDiagram uvm_analysis_port_base &lt;|-- uvm_analysis_port uvm_tlm_if_base &lt;|-- uvm_analysis_port_base uvm_component &lt;|-- uvm_tlm_if_base class uvm_analysis_port["uvm_analysis_port #(T)"] class uvm_analysis_port_base["uvm_analysis_port_base #(uvm_tlm_if_base #(T,T))"] class uvm_tlm_if_base["uvm_tlm_if_base #(T,T)"] class uvm_component["uvm_component"] SystemVerilog is a statically-typed language that provides minimal introspection tools for looking at the internals of user-defined classes. This is especially true when it comes to templated types. In order to ask whether a given object is an instance of uvm_analysis_port, we need to ask about a specific specialization of that type – for example, “uvm_analysis_port #(my_special_transaction)”. As you might expect, this poses some challenges because the PyHDL-IF library only knows about concrete types defined by the UVM library, and doesn’t know anything about the transaction types used within a user’s testbench. As a consequence, when the PyHDL-IF library looks at an analysis port instance, it just sees a uvm_component instance. One possible approach to this challenge is to have the user register each of the transaction types that they might want to use with analysis ports with the PyHDL-IF library. For example: class my_transaction extends uvm_sequence_item; // ... endclass `pyhdl_uvm_transaction_utils(my_transaction) This would allow the PyHDL-IF library to identify analysis-port instances, but wouldn’t help with the second challenge of working with analysis ports. The second challenge comes when we want to add a new listener to an analysis port. This must be done during connect_phase, and requires that a properly-specialized uvm_analysis_imp #(T) class instance was previously created during the build phase. Making Analysis Ports Visibile While both of these challenges can be overcome independently, doing so would require the user to make two independent sets of changes. And, more troubling, wouldn’t provide a good way for VIP developers to hide this complexity from users. At minimum, the testbench developer would always need to pre-create uvm_analysis_imp instances for each analysis port to which they might subscribe. Instead, PyHDL-IF provides two classes that supports two paths for making analysis ports visible and accessible from Python: pyhdl_uvm_analysis_port – An alternative analysis port implementation intended for use by VIP authors pyhdl_uvm_analysis_imp – An analysis port listener intended to make existing analysis ports available to PyHDL-IF Example Let’s take a look at an example to understand how analysis ports are made accessible to the PyHDL-IF library. The uvm/seq_item_scoreboard example shows how to make analysis ports accessible as well as how to receive transactions from analysis ports. // Producer component with two analysis ports class dual_producer extends uvm_component; `uvm_component_utils(dual_producer) pyhdl_uvm_analysis_port #(seq_item_a) ap_a; uvm_analysis_port #(seq_item_b) ap_b; function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); ap_a = new("ap_a", this); ap_b = new("ap_b", this); endfunction The code snippet above shows how the pyhdl_uvm_analysis_port can be used instead of uvm_analysis_port to make an analysis port available to the PyHDL-IF library. The API of this class is identical to uvm_analysis_port, making this a good choice for VIP authors that want to automatically make analysis ports accessible. // Environment tying producer to scoreboard class my_env extends uvm_env; `uvm_component_utils(my_env) // ... pyhdl_uvm_analysis_imp #(seq_item_b) ap_b_proxy; // ... function void build_phase(uvm_phase phase); super.build_phase(phase); // ... ap_b_proxy = new("ap_b_proxy", this); endfunction function void connect_phase(uvm_phase phase); super.connect_phase(phase); // ... prod.ap_b.connect(ap_b_proxy.analysis_export); endfunction endclass Note that ap_b uses a normal uvm_analysis_port in the VIP. We can make this analysis port accessible to the PyHDL-IF library by connecting an instance of pyhdl_uvm_analysis_imp to it. While this could be done anywhere, it’s often done in the environment (as shown above). The pyhdl_uvm_analysis_imp instance is connected to the analysis port in the same way that any other analysis port subscriber does. Doing this allows Python code to receive transactions published by the analysis port. Both pyhdl_uvm_analysis_port and pyhdl_uvm_analysis_imp have a proxy field inside with a add_listener method that the Python environment uses to register a listener. Let’s look at the Python environment now. class PyComp(uvm_component_impl): def build_phase(self, phase): print("build_phase", flush=True) def connect_phase(self, phase): print("connect_phase", flush=True) env = self.proxy.get_parent() env.prod.ap_a.proxy.add_listener(self.write_a) env.ap_b_proxy.proxy.add_listener(self.write_b) def write_a(self, t): print("write_a %0s" % str(t.pack()), flush=True) def write_b(self, t): print("write_b %0s" % str(t.pack()), flush=True) In this case, the component proxy is a child instance of the env class shown above. Consequently, to connect to the analysis ports, we first need to get a handle to the instance of the env class (our parent). After that, we need to access the analysis port proxy field inside the pyhdl_uvm_analysis_port and pyhdl_uvm_analysis_imp instances. We can pass any callable Python method or object to the add_listener method. In this case, we simply register two class methods. When simulation runs, write_a and write_b will be called whenever the analysis port that they monitor publishes a transaction. Conclusions and Next Steps The PyHDL-IF library allows analysis ports to be made visible and accessible from Python with a small one-time investment. Verification IP (VIP) developers can implement this support, allowing all users to benefit. Testbench developers can also perform this work for VIP that isn’t pre-instrumented. The result is that scoreboards and other analysis components can easily be developed in Python. Thus far in the series, we’ve seen how to run Python behavior from SystemVerilog. We’ve seen how to interact with user-defined UVM class fields, and we’ve now seen how to subscribe to analysis ports. These technical capabilities make it easy for Python to dynamically interoperate with an existing SystemVerilog/UVM testbench. But, thus far, these capabilities don’t do much to support Python development tools. In the next post, we’ll see how the PyHDL-IF library provides a bridge from SystemVerilog/UVM to help Python development tools understand what is present in the UVM environment and make us more productive developing Python testbench components. References PyHDL-IF library - https://github.com/fvutils/pyhdl-if Analysis port example - https://github.com/fvutils/pyhdl-if/tree/main/examples/uvm/seq_item_scoreboard]]></summary></entry><entry><title type="html">Accessing User-Defined SV Data from Python</title><link href="https://bitsbytesgates.com/pythonuvm/2025/11/08/UvmWithPython__UserDefinedData.html" rel="alternate" type="text/html" title="Accessing User-Defined SV Data from Python" /><published>2025-11-08T00:00:00+00:00</published><updated>2025-11-08T00:00:00+00:00</updated><id>https://bitsbytesgates.com/pythonuvm/2025/11/08/UvmWithPython__UserDefinedData</id><content type="html" xml:base="https://bitsbytesgates.com/pythonuvm/2025/11/08/UvmWithPython__UserDefinedData.html"><![CDATA[<p>We’ve seen how the <a href="http://github.com/fvutils/pyhdl-if">PyHDL-IF</a> library makes it simple to implement UVM sequences 
and components in Python, and how Python can call back into SystemVerilog. But,
a significant part of a testbench deals with user-defined data, ranging from 
named UVM instances, transaction fields in sequence items and control knobs on 
sequences. Fortunately, UVM provides features that <a href="http://github.com/fvutils/pyhdl-if">PyHDL-IF</a> uses to provide 
dynamic and Pythonic access to user-defined data in UVM.</p>

<!--more-->

<h1 id="two-kinds-of-user-defined-data">Two Kinds of User-Defined Data</h1>

<p>There are two key kinds of user-defined data that we care about in our UVM 
environment: named UVM instances, and data fields. In both of these cases,
a SystemVerilog environment typically references the named field directly.</p>

<figure class="highlight"><pre><code class="language-verilog" data-lang="verilog">    <span class="k">task</span> <span class="n">body</span><span class="p">();</span>
      <span class="n">uvm_status_e</span>   <span class="n">status</span><span class="p">;</span>
      <span class="n">uvm_reg_data_t</span> <span class="n">rd</span><span class="p">;</span>

      <span class="c1">// Program CTRL / CLKDIV / SS</span>
      <span class="n">m_env</span><span class="p">.</span><span class="n">m_reg</span><span class="p">.</span><span class="n">CTRL</span><span class="p">.</span><span class="n">enable</span><span class="p">.</span><span class="nb">write</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
      <span class="n">m_env</span><span class="p">.</span><span class="n">m_reg</span><span class="p">.</span><span class="n">CTRL</span><span class="p">.</span><span class="n">master</span><span class="p">.</span><span class="nb">write</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
      <span class="n">m_env</span><span class="p">.</span><span class="n">m_reg</span><span class="p">.</span><span class="n">CLKDIV</span><span class="p">.</span><span class="n">div</span><span class="p">.</span><span class="nb">write</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="mh">16'h0004</span><span class="p">);</span>
      <span class="n">m_env</span><span class="p">.</span><span class="n">m_reg</span><span class="p">.</span><span class="n">SS</span><span class="p">.</span><span class="n">ss_mask</span><span class="p">.</span><span class="nb">write</span><span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="mh">4'h1</span><span class="p">);</span>

      <span class="c1">// ...</span>
    <span class="k">endtask</span></code></pre></figure>

<p>An example of using named instance fields, using registers, is shown
above.  Our goal is to be able to access these fields just as easily in 
Python, as well as plain data fields.</p>

<h2 id="named-uvm-instances">Named UVM Instances</h2>

<p>Named UVM instances are UVM objects registered with the UVM library using a name.
User code typically accesses these objects via the SystemVerilog instance 
fields, but they can also be obtained using UVM API methods. Fortunately, 
Python provides specific functionality to enable us to do this.</p>

<figure class="highlight"><pre><code class="language-verilog" data-lang="verilog">  <span class="kt">class</span> <span class="n">spi_reg_block</span> <span class="k">extends</span> <span class="n">uvm_reg_block</span><span class="p">;</span>
    <span class="cp">`uvm_object_utils</span><span class="p">(</span><span class="n">spi_reg_block</span><span class="p">)</span>

    <span class="k">rand</span> <span class="n">reg_CTRL</span>   <span class="n">CTRL</span><span class="p">;</span>
    <span class="k">rand</span> <span class="n">reg_STATUS</span> <span class="n">STATUS</span><span class="p">;</span>
    <span class="c1">// ...</span>

    <span class="k">function</span> <span class="k">new</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="o">=</span><span class="s">"spi_reg_block"</span><span class="p">);</span>
      <span class="k">super</span><span class="p">.</span><span class="k">new</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">UVM_NO_COVERAGE</span><span class="p">);</span>
    <span class="k">endfunction</span>

    <span class="kt">virtual</span> <span class="k">function</span> <span class="kt">void</span> <span class="n">build</span><span class="p">();</span>
      <span class="c1">// ...</span>

      <span class="n">CTRL</span>   <span class="o">=</span> <span class="n">reg_CTRL</span>  <span class="o">::</span><span class="n">type_id</span><span class="o">::</span><span class="n">create</span><span class="p">(</span><span class="s">"CTRL"</span><span class="p">);</span>
      <span class="n">STATUS</span> <span class="o">=</span> <span class="n">reg_STATUS</span><span class="o">::</span><span class="n">type_id</span><span class="o">::</span><span class="n">create</span><span class="p">(</span><span class="s">"STATUS"</span><span class="p">);</span>
      <span class="c1">// ...</span>

      <span class="n">CTRL</span>  <span class="p">.</span><span class="n">configure</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="k">null</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span>
      <span class="n">STATUS</span><span class="p">.</span><span class="n">configure</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="k">null</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span>
      <span class="c1">// ...</span>
    <span class="k">endfunction</span>

  <span class="k">endclass</span></code></pre></figure>

<p>Registers and register fields provide a good example of UVM named 
instances. As shown above, register blocks (and registers) usually
provide named SystemVerilog fields to represent registers and fields.
But, the <code class="language-plaintext highlighter-rouge">configure</code> method also registers the object with the UVM
API such that it can be found by name. In the case of the register
block, the <code class="language-plaintext highlighter-rouge">get_registers</code> function returns a list of register objects.</p>

<p>The Python <code class="language-plaintext highlighter-rouge">__getattr__</code> method allows us to access UVM named instances
from Python just as easily as in SystemVerilog.</p>

<figure class="highlight"><pre><code class="language-verilog" data-lang="verilog"><span class="o">@</span><span class="n">api</span>
<span class="kt">class</span> <span class="n">uvm_reg_block</span><span class="p">(</span><span class="n">uvm_object</span><span class="p">)</span><span class="o">:</span>

    <span class="n">def</span> <span class="mi">__</span><span class="n">init__</span><span class="p">(</span><span class="n">self</span><span class="p">)</span><span class="o">:</span>
        <span class="n">self</span><span class="mf">._</span><span class="n">reg_m</span> <span class="o">=</span> <span class="n">None</span>

    <span class="p">#</span> <span class="p">...</span>
    <span class="o">@</span><span class="n">imp</span>
    <span class="n">def</span> <span class="n">get_registers</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">uvm_reg_p</span><span class="p">]</span><span class="o">:</span>
        <span class="n">raise</span> <span class="n">NotImplementedError</span><span class="p">()</span>
    
    <span class="n">def</span> <span class="mi">__</span><span class="n">getattr__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span><span class="o">:</span>
        <span class="k">if</span> <span class="n">self</span><span class="mf">._</span><span class="n">reg_m</span> <span class="n">is</span> <span class="n">None</span><span class="o">:</span>
            <span class="n">regs</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">get_registers</span><span class="p">()</span>
            <span class="n">self</span><span class="mf">._</span><span class="n">reg_m</span> <span class="o">=</span> <span class="o">{}</span>
            <span class="k">for</span> <span class="n">r</span> <span class="n">in</span> <span class="n">regs</span><span class="o">:</span>
                <span class="n">self</span><span class="mf">._</span><span class="n">reg_m</span><span class="p">[</span><span class="n">r</span><span class="p">.</span><span class="n">get_name</span><span class="p">()]</span> <span class="o">=</span> <span class="n">r</span>
        <span class="k">if</span> <span class="n">name</span> <span class="n">in</span> <span class="n">self</span><span class="mf">._</span><span class="n">reg_m</span><span class="p">.</span><span class="n">keys</span><span class="p">()</span><span class="o">:</span>
            <span class="k">return</span> <span class="n">self</span><span class="mf">._</span><span class="n">reg_m</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
        <span class="nl">else:</span>
            <span class="n">raise</span> <span class="n">AttributeError</span><span class="p">(</span><span class="s">"No register %s in block %s"</span> <span class="o">%</span> <span class="p">(</span>
                <span class="n">name</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">get_name</span><span class="p">()))</span></code></pre></figure>

<p>The Python code snippet above shows how this is implemented for the 
<code class="language-plaintext highlighter-rouge">uvm_reg_block</code> class. Python calls the <code class="language-plaintext highlighter-rouge">__getattr__</code> class method
any time an unknown class attribute is referenced. In a <code class="language-plaintext highlighter-rouge">uvm_reg_block</code>
class, we build a map of the named register fields and return the
proper one if it is available.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">PyRegSeq</span><span class="p">(</span><span class="n">uvm_sequence_impl</span><span class="p">):</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">body</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="c1"># Obtain the register model from the sequencer
</span>        <span class="n">seqr</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">proxy</span><span class="p">.</span><span class="n">m_sequencer</span>
        <span class="n">_</span><span class="p">,</span><span class="n">spi_regs</span> <span class="o">=</span> <span class="n">seqr</span><span class="p">.</span><span class="nf">get_config_object</span><span class="p">(</span><span class="s">"spi_regs"</span><span class="p">,</span> <span class="bp">False</span><span class="p">)</span>

        <span class="nf">print</span><span class="p">(</span><span class="s">"spi_regs: %s"</span> <span class="o">%</span> <span class="nf">str</span><span class="p">(</span><span class="n">spi_regs</span><span class="p">),</span> <span class="n">flush</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

        <span class="n">spi_regs</span><span class="p">.</span><span class="n">CTRL</span><span class="p">.</span><span class="n">enable</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
        <span class="n">spi_regs</span><span class="p">.</span><span class="n">CTRL</span><span class="p">.</span><span class="n">master</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
        <span class="k">await</span> <span class="n">spi_regs</span><span class="p">.</span><span class="n">CTRL</span><span class="p">.</span><span class="nf">update</span><span class="p">()</span>

        <span class="n">spi_regs</span><span class="p">.</span><span class="n">CLKDIV</span><span class="p">.</span><span class="n">div</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="mh">0x4</span><span class="p">)</span>
        <span class="n">spi_regs</span><span class="p">.</span><span class="n">SS</span><span class="p">.</span><span class="n">ss_mask</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="mh">0x1</span><span class="p">)</span>
        <span class="k">await</span> <span class="n">spi_regs</span><span class="p">.</span><span class="n">CLKDIV</span><span class="p">.</span><span class="nf">update</span><span class="p">()</span>
        <span class="k">await</span> <span class="n">spi_regs</span><span class="p">.</span><span class="n">SS</span><span class="p">.</span><span class="nf">update</span><span class="p">()</span>

        <span class="k">await</span> <span class="n">spi_regs</span><span class="p">.</span><span class="n">TXDATA</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="mh">0xA5</span><span class="p">)</span></code></pre></figure>

<p>Connecting <code class="language-plaintext highlighter-rouge">__getattr__</code> to a search of named UVM fields enables a 
Python-implemented UVM sequence to access registers and register fields
directly, as shown above.</p>

<h2 id="plain-data-fields">Plain-Data Fields</h2>

<p>Accessing UVM named-instance fields is useful in some cases, but there
are many other cases where the fields are not named instances. UVM 
sequence-item fields are a great example. Python code may wish to
both read and write the value of these fields. While the path to accessing
the value of these fields is a bit less direct, UVM absolutely provides
a path for doing so.</p>

<figure class="highlight"><pre><code class="language-verilog" data-lang="verilog"><span class="kt">class</span> <span class="n">seq_item</span> <span class="k">extends</span> <span class="n">uvm_sequence_item</span><span class="p">;</span>
    <span class="kt">bit</span>              <span class="n">ctrl_addr_page</span><span class="p">;</span>
    <span class="kt">bit</span><span class="p">[</span><span class="mi">1</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>         <span class="n">addr_page</span><span class="p">;</span>

    <span class="k">rand</span> <span class="kt">bit</span> <span class="p">[</span><span class="mi">7</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>   <span class="n">addr</span><span class="p">;</span>
    <span class="k">rand</span> <span class="kt">bit</span>         <span class="nb">write</span><span class="p">;</span> <span class="c1">// 1=write, 0=read</span>
    <span class="k">rand</span> <span class="kt">bit</span> <span class="p">[</span><span class="mi">31</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>  <span class="n">data</span><span class="p">;</span>
    <span class="k">rand</span> <span class="kt">bit</span> <span class="p">[</span><span class="mi">3</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span>   <span class="n">tid</span><span class="p">;</span>

    <span class="c1">// ...</span>

    <span class="cp">`uvm_object_utils_begin</span><span class="p">(</span><span class="n">seq_item</span><span class="p">)</span>
        <span class="cp">`uvm_field_int</span><span class="p">(</span><span class="n">ctrl_addr_page</span><span class="p">,</span> <span class="n">UVM_ALL_ON</span><span class="p">)</span>
        <span class="cp">`uvm_field_int</span><span class="p">(</span><span class="n">addr_page</span><span class="p">,</span> <span class="n">UVM_ALL_ON</span><span class="p">)</span>
        <span class="cp">`uvm_field_int</span><span class="p">(</span><span class="n">addr</span> <span class="p">,</span> <span class="n">UVM_ALL_ON</span><span class="p">)</span>
        <span class="cp">`uvm_field_int</span><span class="p">(</span><span class="nb">write</span><span class="p">,</span> <span class="n">UVM_ALL_ON</span><span class="p">)</span>
        <span class="cp">`uvm_field_int</span><span class="p">(</span><span class="n">data</span> <span class="p">,</span> <span class="n">UVM_ALL_ON</span><span class="p">)</span>
        <span class="cp">`uvm_field_int</span><span class="p">(</span><span class="n">tid</span>  <span class="p">,</span> <span class="n">UVM_ALL_ON</span><span class="p">)</span>
    <span class="cp">`uvm_object_utils_end</span>
    <span class="c1">// ...</span>
<span class="k">endclass</span></code></pre></figure>

<p>The code snippet above shows a typical UVM sequence item with random fields.
This sequence item uses the UVM field macros (eg uvm_field_int) to register
the user-defined fields with the UVM library. This enables the UVM library
to automatically implement several pieces of functionality, such as 
comparing the value of two objects and copying the field values of an object.
Users can also implement these functions by hand-implementing methods such 
as <code class="language-plaintext highlighter-rouge">do_copy</code> and <code class="language-plaintext highlighter-rouge">do_compare</code>, but that results in quite a bit of hand-created
code and an increased risk of introducing errors.</p>

<p>There are three key elements of functionality that the <a href="http://github.com/fvutils/pyhdl-if">PyHDL-IF</a> library uses
to enable user-defined data fields to be accessed from Python:</p>
<ul>
  <li><strong>sprint</strong> – Converts the class and its fields to a string representation</li>
  <li><strong>pack_int</strong> – Packs the field values to an array of unsigned ints</li>
  <li><strong>unpack_int</strong> – Sets the field values from an array of unsigned ints</li>
</ul>

<p>Used together, these three methods provide the <a href="http://github.com/fvutils/pyhdl-if">PyHDL-IF</a> library everything
it needs to allow Python to easily get and set the value of user-defined
fields. If you’re interested in the details of how it works, see the
<code class="language-plaintext highlighter-rouge">mk</code> method of the <a href="https://github.com/fvutils/pyhdl-if/blob/main/src/hdl_if/uvm/wrap/object_rgy.py">uvm_object_rgy</a>
class.</p>

<p>The short version is that type information is built each time a new type of 
UVM object is passed from SystemVerilog to Python. The field names and
types are extracted using the string representation provided by <code class="language-plaintext highlighter-rouge">sprint</code>.
The field names and types are used to dynamically define a Python type.
When Python code calls the <code class="language-plaintext highlighter-rouge">pack</code> method, an instance of this Python 
type is constructed with field values provided by packing the SystemVerilog 
field values. Setting the value of the SystemVerilog fields is done 
by calling <code class="language-plaintext highlighter-rouge">unpack</code> in Python. Let’s see how it is used.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">PyRandSeq</span><span class="p">(</span><span class="n">uvm_sequence_impl</span><span class="p">):</span>
    <span class="k">async</span> <span class="k">def</span> <span class="nf">body</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="c1"># ...
</span>
        <span class="c1"># Now, exercise each page in turn 
</span>        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
            <span class="n">req</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">proxy</span><span class="p">.</span><span class="nf">create_req</span><span class="p">()</span>
            
            <span class="c1"># Get the current values
</span>            <span class="n">val</span> <span class="o">=</span> <span class="n">req</span><span class="p">.</span><span class="nf">pack</span><span class="p">()</span>
            <span class="n">val</span><span class="p">.</span><span class="n">ctrl_addr_page</span> <span class="o">=</span> <span class="mi">1</span>
            <span class="n">val</span><span class="p">.</span><span class="n">addr_page</span> <span class="o">=</span> <span class="n">i</span>

            <span class="c1"># Set the field values
</span>            <span class="n">req</span><span class="p">.</span><span class="nf">unpack</span><span class="p">(</span><span class="n">val</span><span class="p">)</span>

            <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="n">proxy</span><span class="p">.</span><span class="nf">start_item</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>
            <span class="c1"># Randomize with control knobs
</span>            <span class="n">req</span><span class="p">.</span><span class="nf">randomize</span><span class="p">()</span>
            <span class="k">await</span> <span class="n">self</span><span class="p">.</span><span class="n">proxy</span><span class="p">.</span><span class="nf">finish_item</span><span class="p">(</span><span class="n">req</span><span class="p">)</span></code></pre></figure>

<p>The example above shows how to use both the <code class="language-plaintext highlighter-rouge">pack</code> and <code class="language-plaintext highlighter-rouge">unpack</code> methods to 
control randomization from Python using randomization control knobs. In this
case, we want to control the address “page” – its upper bits. To do this,
the sequence creates an instance of the request object, then calls <code class="language-plaintext highlighter-rouge">pack</code>
to obtain a Python object containing the field values of the SystemVerilog
sequence item. After setting the desired address page, the <code class="language-plaintext highlighter-rouge">unpack</code> 
method is called to set the value of the SystemVerilog fields. Finally, 
the standard <code class="language-plaintext highlighter-rouge">start_item</code>, <code class="language-plaintext highlighter-rouge">randomize</code>, <code class="language-plaintext highlighter-rouge">finish_item</code> sequence of calls
is made to execute the sequence item on the driver.</p>

<h1 id="conclusion">Conclusion</h1>
<p>The <a href="http://github.com/fvutils/pyhdl-if">PyHDL-IF</a> library allows Python code to easily access both named UVM
instance fields and data fields. This significantly enhances the type 
of test and analysis behavior that can easily be implemented in Python.</p>

<p>Thus far in this series of posts, we’ve seen how to launch Python behavior
from SystemVerilog, interact with UVM APIs, and access fields declared in 
SystemVerilog UVM classes. Next, we’ll take a look at UVM analysis ports.
Analysis ports are critical for obtaining transactions for scoreboards and
other analysis components. Working with them in a generic fashion is tricky.
Next time, we’ll see how the <a href="http://github.com/fvutils/pyhdl-if">PyHDL-IF</a> library makes them easily accessible
from Python.</p>

<h1 id="references">References</h1>
<ul>
  <li>PyHDL-IF library: <a href="https://github.com/fvutils/pyhdl-if">https://github.com/fvutils/pyhdl-if</a></li>
  <li>Example: spi_reg_seq: <a href="https://github.com/fvutils/pyhdl-if/tree/main/examples/uvm/spi_reg_seq">https://github.com/fvutils/pyhdl-if/tree/main/examples/uvm/spi_reg_seq</a></li>
  <li>Example: sequence-item knobs: <a href="https://github.com/fvutils/pyhdl-if/tree/main/examples/uvm/sequence_item_knobs">https://github.com/fvutils/pyhdl-if/tree/main/examples/uvm/sequence_item_knobs</a></li>
</ul>]]></content><author><name></name></author><category term="PythonUVM" /><summary type="html"><![CDATA[We’ve seen how the PyHDL-IF library makes it simple to implement UVM sequences and components in Python, and how Python can call back into SystemVerilog. But, a significant part of a testbench deals with user-defined data, ranging from named UVM instances, transaction fields in sequence items and control knobs on sequences. Fortunately, UVM provides features that PyHDL-IF uses to provide dynamic and Pythonic access to user-defined data in UVM.]]></summary></entry></feed>