Verification Learns a New Language Introducing Python UVM (pyuvm) © Siemens 2021 | DVClub Europe 2021 | Siemens EDA
Verification Learns a New
Language
Introducing Python UVM (pyuvm)
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
The SystemVerilog Assumption
A structured testbench is only software making
function calls through a proxy
The UVM assumes the "software" is written in
SystemVerilog
Testbench "software" running on a simulator
engine is slow and leads to unacceleratable
testbenches.
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
This could be Python!
How does Python drive the simulator? One approach: cocotb
Cocotb
• COroutine COsimulation TB
• https://github.com/cocotb/
Connects Python to most simulators through VPI and VHPI.
There are also DPI-based approaches
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
Unique Python Elements No typing and other exceptions
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
Everything in Python is an Object
No typing issues
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
Typing leads to parameters
SystemVerilog
uvm_put_port #(txn_a) ff;
ff = new("ff",this);
txn_b bb;
bb = new("bb")
ff.put(bb);
^^^ Syntax Error
Python
ff=uvm_put_port("ff",this);
bb = TxnB("bb")
ff.put(bb);
Parameters lead to pain
unsigned short AA;
unsigned float BB;
AA = BB;
^^^^ lost data
AA
BB BB BB BB
AA
Typed Language copies data
AA = BB
AA = 6000
BB = 1600.00
BB
AA 6000
1600.00
BB
AA 6000
1600.00
Python copies handles
int
float
Forgiveness Instead of Permission
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
• Duck Typing
• If it walks like a
duck
• talks like a duck
• walks like a duck…
int ii;
uvm_get_port#(duck) duck_p;
ii = duck_p.get();
Permission Denied! Syntax Error
pp.put(100)
ii = duck_p.get()
ii.migrate()
-------------------------------------------------
AttributeError
<ipython> in <module>
7 pp.put(11)
8 ii = duck_p.get()
----> 9 ii.migrate()
AttributeError: 'int' object has no attribute 'migrate' Clearly not a duck.
pyuvm Implementation
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
Introducing pyuvm
UVM 1.2 IEEE Specification
Rules for Implementation
• Implement common features
• Ignore rarely-used features
• Follow spelling in spec
• uvm_test vs UvmTest
• Don’t reimplement Python
• copy.deepcopy() vs. do_copy()
• __str__() vs convert2string()
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
% pip install pyuvm
from pyuvm import *
Available on pypi.org
Equivalent to import uvm_pkg::*
class AluTest(uvm_test):
def run_phase(self):
self.raise_objection()
seqr = ConfigDB().get(self, "", "SEQR")
seq = AluSeq("seq")
seq.start(seqr)
time.sleep(1)
self.drop_objection()
uvm_root().run_test("AluTest")
Real UVM Behavior
Your UVM on Python
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
class AluAgent(uvm_agent):
def build_phase(self):
super().build_phase()
if self.active():
self.driver = Driver.create("driver", self)
try:
self.is_monitor = ConfigDB().get(self, "", "is_monitor")
except UVMConfigItemNotFound:
self.is_monitor = True
if self.is_monitor:
self.cmd_mon = Monitor("cmd_mon", self, "get_cmd")
self.result_mon = Monitor("result_mon", self, "get_result")
UVM Underscore Class Names
No macros. Automatically in the factory
No phase arguments
Simplified factory access
Simplified
configDB access
Python exceptions simplify code
1.2 Notes
5 Base Classes uvm_object does not capture transaction timing information
6 Reporting Classes Leverages logging, controlled using UVM hierarchy
8 Factory Classes All uvm_void classes automatically registered
9 Phasing Simplified to only common phases. Supports objection system
12 UVM TLM Interfaces Fully implemented
13 Predefined Component Classes Implements uvm_component with hierarchy, uvm_root
singleton,run_test(), simplified ConfigDB, uvm_driver, etc
14
15
Sequences, sequencer,
sequence_item
Refactored sequencer functionality leveraging Python language
capabilities. Simpler and more direct implementation
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
Implemented Sections
Ignored UVM Features
Custom UVM Phasing
• Only runs with common UVM phases
uvm_resource_db
• Implements uvm_config_db interface to
uvm_resource_db
_imp classes
• Unneeded because of multiple inheritance
Report handlers
• Leverage Python logging instead
Synchronization classes
Container Classes
Recording classes
Transaction recording
Policy classes
Register layer and model
• Good open-source project
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
Using the Factory without Incanations
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
function void build_phase(uvm_phase phase);
driver_h = driver::type_id::create("driver_h",this);
coverage_h = coverage::type_id::create ("coverage_h",this);
scoreboard_h = scoreboard::type_id::create("scoreboard_h",this);
SystemVerilog UVM
Python UVM
def build_phase(self):
self.driver = Driver.create("driver", self)
self.coverage = Coverage.create("coverage", self)
self.scoreboard = Scoreboard.create("scoreboard", self)
Simplified singleton implementation. No type parameters
• No uvm_resource_db
• Directly implement uvm_config_db interface
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
The Python ConfigDB
config_db#(uvm_sequencer)::set(null, "*", "SEQR", seqr)
if(!uvm_config_db #(uvm_sequencer)::get(this, "","SEQR",seqr))
`uvm_fatal("Could not find sequencer") # Error out if not there
ConfigDB().set(None, "*", "SEQR",self.seqr)
self.seqr = ConfigDB().get(self, "", "SEQR") # Automatically errors out
SystemVerilog UVM
Python UVM
Threads in Python
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
SystemVerilog barbarously
kills thread without warning
Python requires mechanism
for threads to put their affairs
order
Blocking get() functions
must exit.
class UVMQueue(queue.Queue):
#### In __init__()
self.end_while_predicate = ObjectionHandler().run_phase_complete
self.sleep_time = 0.1
####
def get(self, block=True, timeout=None):
if not block or timeout is not None:
try:
return super().get(block, timeout)
except queue.Empty:
raise
else: # create block that can die
while not self.end_while_predicate():
try:
datum = super().get(block=True,timeout=self.sleep_time)
return datum
except queue.Empty:
pass
sys.exit() # Kill thread if it's time to die
Phasing Refactored
Only implement UVM "common phases"
with default phase progression
• Common phases defined in UVM 1.2 Spec
Section 9.8.1
traverse runs phases top down and bottom
up.
Custom phases not implemented
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
uvm_common_phases = [
uvm_build_phase,
uvm_connect_phase,
uvm_end_of_elaboration_phase,
uvm_start_of_simulation_phase,
uvm_run_phase,
uvm_extract_phase,
uvm_check_phase,
uvm_report_phase,
uvm_final_phase]
class uvm_topdown_phase(uvm_phase):
@classmethod
def traverse(cls, comp):
...
class uvm_build_phase(uvm_topdown_phase, common_phase):
...
Python Logging replaces UVM Reporting
Loggers generate messages
Logging levels filter messages
Logging handlers output messages
• Screen, files, html, etc
Logging formatters format messages
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
Level Numeric
value
CRITICAL 50
ERROR 40
WARNING 30
INFO 20
DEBUG 10
FIFO_DEBUG 5
NOTSET 0
pyuvm addition
Using Logging
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
class LogComp(uvm_component):
def run_phase(self):
self.raise_objection()
self.logger.debug("This is debug")
self.logger.info("This is info")
self.logger.warning("This is warning")
self.logger.error("This is error")
self.logger.critical("This is critical")
self.logger.log(FIFO_DEBUG, "This is a FIFO message")
self.drop_objection()
DEBUG: <src_file>(6)[uvm_test_top.comp]: This is debug
INFO: <src_file>(7)[uvm_test_top.comp]: This is info
WARNING: <src_file>(8)[uvm_test_top.comp]: This is warning
ERROR: <src_file>(9)[uvm_test_top.comp]: This is error
CRITICAL: <src_file>(10)[uvm_test_top.comp]: This is critical
FIFO_DEBUG: <src_file>(11)[uvm_test_top.comp]: This is a FIFO message
Every component has self.logger
pyuvm formatter looks like UVM format (no message_id)
Controlling Logging
uvm_report_object
• set_logging_level(logging_level)
• add_logging_handler(handler)
• StreamHandler—Prints to sys.stdout and sys.stderr
• FileHandler—Writes to a File
• NullHandler—NOP for libraries.
• set_formatter_on_handlers(formatter)
uvm_component
• set_logging_level_hier(logging_level)
• add_logging_handler_hier(handler)
• set_formatter_on_handlers_hier(formatter)
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
As with SystemVerilog call logging control in end_of_elaboration_phase
Example of Python UVM The TinyALU Cocotb Testbench
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
TinyALU Example
Python testbench developed with no simulator
Simulator testbench developed with cocotb
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
Cocotb Proxy with BFMS
Python and Simulation Have Same Proxy Interface
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
class CocotbProxy:
def __init__(self, dut, label):
self.dut = dut
ConfigDB().set(None, "*", label, self)
self.driver_queue = UVMQueue(maxsize=1)
self.cmd_mon_queue = UVMQueue(maxsize=0)
+++
class PythonProxy(uvm_component):
def __init__(self, name, parent, label):
super().__init__(name, parent)
ConfigDB().set(None, "*", label, self)
+++ proxy.send_op(aa, bb, op)
cmd = proxy.get_cmd()
result = proxy.get_result()
Blocking API
maxsize = 0 means infinite size
Monitor
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
class Monitor(uvm_component):
def __init__(self, name, parent, method_name):
super().__init__(name, parent)
self.method_name = method_name
def build_phase(self):
self.ap = uvm_analysis_port("ap", self)
def connect_phase(self):
self.proxy = self.cdb_get("PROXY")
def run_phase(self):
while not ObjectionHandler().run_phase_complete():
get_method = getattr(self.proxy, self.method_name)
datum = get_method()
self.ap.write(datum)
def build_phase(self):
self.cmd_mon = Monitor("cmd_mon", self, "get_cmd")
self.rslt_mon = Monitor("rslt_mon", self, "get_result")
Python allows us to
pass the name of
the monitoring
function as a string.
Cocotb Results Monitor BFM
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
async def result_mon_bfm(self):
prev_done = 0
while True:
await FallingEdge(self.dut.clk)
try:
done = int(self.dut.done)
except ValueError:
done = 0
if done == 1 and prev_done == 0:
self.result_mon_queue.put_nowait(int(self.dut.result.value))
prev_done = done
def get_result(self):
return self.result_mon_queue.get() A blocking get
Functional Coverage
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
class Coverage(uvm_subscriber):
def end_of_elaboration_phase(self):
self.cvg = set()
def write(self, cmd):
self.cvg.add(cmd.op)
def check_phase(self):
if len(set(Ops) - self.cvg) > 0:
self.logger.error(f"Functional coverage error. Missed: {set(Ops)-self.cvg}")
No covergroup in Python. So, create set of all ops and set of seen ops.
Then subtract to find missed coverage
Sequence Item
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
class AluSeqItem(uvm_sequence_item):
<snip __init__>
def __eq__(self, other):
same = self.A == other.A and self.B == other.B and self.op == other.op
return same
def __str__(self):
return f"{self.get_name()} : A: 0x{self.A:02x} OP: {self.op.name} "
f"({self.op.value}) B: 0x{self.B:02x}"
def randomize(self):
self.A = random.randint(0,255)
self.B = random.randint(0,255)
self.op = random.choice(list(Ops))
Here we replace do_compare() and convert2sring() with Python equivalents
Base ALUTest
Instantiate the environment
Set logging level to DEBUG
Raise and drop objections and run sequence
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
class AluTest(uvm_test):
def build_phase(self):
self.env = AluEnv.create("env", self)
def end_of_elaboration_phase(self):
self.set_logging_level_hier(logging.DEBUG)
def run_phase(self):
self.raise_objection()
seqr = ConfigDB().get(self, "", "SEQR")
seq = AluSeq("seq")
seq.start(seqr)
time.sleep(1)
self.drop_objection()
Python and Cocotb Tests Extend AluTest
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
class PythonAluTest(AluTest):
def build_phase(self):
_ = PythonProxy("model_proxy", self, "PROXY")
super().build_phase()
class CocotbAluTest(AluTest):
def final_phase(self):
cocotb_proxy = self.cdb_get("PROXY")
cocotb_proxy.done.set()
• Instantiate a Python Proxy
• Store it at "PROXY"
• Notify cocotb that the test is
finished using the proxy
if __name__ == "__main__":
uvm_root().run_test("PythonAluTest") % python tinyalu_uvm.py
Cocotb Test
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
@cocotb.test()
async def test_alu(dut):
clock = Clock(dut.clk, 2, units="us")
cocotb.fork(clock.start())
proxy = CocotbProxy(dut, "PROXY")
await proxy.reset()
cocotb.fork(proxy.driver_bfm())
cocotb.fork(proxy.cmd_mon_bfm())
cocotb.fork(proxy.result_mon_bfm())
await FallingEdge(dut.clk)
test_thread = threading.Thread(target=run_uvm_test,
args=("CocotbAluTest",), name="run_test")
test_thread.start()
await proxy.done.wait()
await FallingEdge(dut.clk)
cocotb runs test
cocotb drives clock
Instantiate cocotb proxy
stores itself in ConfigDB()
Use cocotb.fork to launch
BFMS
Wait for them to start
Launch UVM test in thread
Wait for thread to notify of
completion and wait one clock
to let it finish
Run the sim with Cocotb
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
0.00ns INFO cocotb __init__.py:229 in _initialise_testbench Seeding
Python random module with 1610296863
0.00ns INFO cocotb.regression regression.py:127 in __init__
Found test tinyalu_cocotb.test_alu
0.00ns INFO cocotb.regression regression.py:459 in _start_test
Running test 1/1: test_alu
0.00ns INFO cocotb.test.test_alu.0x7fe84e6e4730 decorators.py:255 in _advance
Starting test: "test_alu"
Description: None
INFO: tinyalu_uvm.py(204)[uvm_test_top.env.agent.sb_h]: Test passed: A: 4e OP: ALUOps.XOR (3) B: 71 = 003f
INFO: tinyalu_uvm.py(204)[uvm_test_top.env.agent.sb_h]: Test passed: A: 1d OP: ALUOps.XOR (3) B: 66 = 007b
INFO: tinyalu_uvm.py(204)[uvm_test_top.env.agent.sb_h]: Test passed: A: b8 OP: ALUOps.ADD (1) B: d4 = 018c
INFO: tinyalu_uvm.py(204)[uvm_test_top.env.agent.sb_h]: Test passed: A: 28 OP: ALUOps.AND (2) B: b6 = 0020
INFO: tinyalu_uvm.py(204)[uvm_test_top.env.agent.sb_h]: Test passed: A: c6 OP: ALUOps.XOR (3) B: 99 = 005f
INFO: tinyalu_uvm.py(204)[uvm_test_top.env.agent.sb_h]: Test passed: A: 84 OP: ALUOps.ADD (1) B: 5b = 00df
INFO: tinyalu_uvm.py(204)[uvm_test_top.env.agent.sb_h]: Test passed: A: 72 OP: ALUOps.MUL (4) B: 98 = 43b0
Next Steps
Available on pypi.org
• % pip install pyuvm
Open Source Project on Github:
• https://github.com/pyuvm/pyuvm
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA
Contact Published by Siemens 2021
Ray Salemi
Aerospace and Defense and FPGA Solutions Manager
Division (IC Verification Solutions)
E-mail [email protected]
© Siemens 2021 | DVClub Europe 2021 | Siemens EDA