SystemVerilog API
The SystemVerilog API provides three levels of Python API access:
Generated wrapper classes that mirror a Python-defined API
An abstracted class-based convenience API
Direct access to the Python C API
Generated wrappers
The SystemVerilog API generator creates wrapper classes that enable bidirectional communication between Python and SystemVerilog code. This system allows you to define APIs in Python and automatically generate corresponding SystemVerilog interfaces and implementations.
Command Usage
The api-gen-sv command generates SystemVerilog wrapper code from Python
API definitions:
python -m hdl_if api-gen-sv -m my_module -o my_api.svh
Required Parameters:
-m, --module: Python module(s) containing@apidecorated classes (can be specified multiple times)
Optional Parameters:
-o, --output: Output file name (default:hdl_call_if_api.svh)-p, --package: Wrap generated code in a SystemVerilog package-uvm: Generate UVM-friendly interface classes--deprecated: Include legacy class formats
Example:
python -m hdl_if api-gen-sv \
-m my_project.interfaces \
-m my_project.components \
-p my_api_pkg \
-o generated_api.svh
Python API Definition
APIs are defined in Python using decorators to mark classes and methods for SystemVerilog generation.
API Class Decorator
Use the @api decorator to mark a class for SystemVerilog generation:
from hdl_if.decorators import api, imp, exp
@api
class memory_interface:
"""Custom memory interface API"""
pass
Method Decorators
Two decorators control the direction of method calls:
@imp: Implementation methods - Implemented in SystemVerilog, called from Python@exp: Export methods - Implemented in Python, called from SystemVerilog
@api
class memory_interface:
@imp
def write(self, addr: int, data: int) -> bool:
"""Write data to memory (implemented in SV)"""
pass
@imp
def read(self, addr: int) -> int:
"""Read data from memory (implemented in SV)"""
pass
@exp
def on_write_complete(self, addr: int, success: bool) -> None:
"""Callback when write completes (implemented in Python)"""
print(f"Write to {addr}: {'success' if success else 'failed'}")
Type Translation
Python type annotations are automatically translated to SystemVerilog types:
Python Type |
SystemVerilog Type |
Notes |
|---|---|---|
|
|
64-bit signed integer |
|
|
Single bit |
|
|
SystemVerilog string type |
|
|
Floating point number |
|
|
Python list object |
Custom objects |
|
Python object reference |
|
|
Specific width integers |
|
|
8-bit signed integer |
Parameter Directions:
Method parameters become
inputparameters in SystemVerilogReturn values become function return types or
outputparameters for tasksTasks (methods that may block) use
outputparameters for return values
Generated Class Structure
For each @api class, four SystemVerilog components are generated:
``<class>_exp_if``: Interface for SystemVerilog calling Python methods (
@expmethods)``<class>_imp_if``: Interface for Python calling SystemVerilog methods (
@impmethods)``<class>_exp_impl``: Implementation class for SystemVerilog to call Python
``<class>_imp_impl``: Implementation class for Python to call SystemVerilog
Interface Classes
The interface classes define the method signatures:
// Generated for @exp methods
interface class memory_interface_exp_if;
pure virtual function void on_write_complete(
input int addr,
input bit success
);
endclass
// Generated for @imp methods
interface class memory_interface_imp_if;
pure virtual function bit write(
input longint addr,
input longint data
);
pure virtual function longint read(input longint addr);
endclass
Implementation Classes
The implementation classes handle the Python/SystemVerilog communication:
``<class>_exp_impl``: For calling Python from SystemVerilog
class memory_interface_exp_impl implements memory_interface_exp_if;
pyhdl_if::PyObject m_obj;
function new(pyhdl_if::PyObject obj=null, bit create=1, string clsname="memory_interface");
// Constructor logic
endfunction
virtual function void on_write_complete(input int addr, input bit success);
// Calls Python method through PyObject
endfunction
endclass
``<class>_imp_impl``: For calling SystemVerilog from Python
class memory_interface_imp_impl #(type ImpT=memory_interface_imp_if)
implements pyhdl_if::ICallApi;
ImpT m_impl;
function new(ImpT impl, pyhdl_if::PyObject obj=null, bit create=1);
// Constructor logic
endfunction
virtual function pyhdl_if::PyObject invokeFunc(string method, pyhdl_if::PyObject args);
// Dispatches to SystemVerilog implementation
endfunction
endclass
SystemVerilog Implementation Example
To use the generated API, implement the _imp_if interface in SystemVerilog:
class my_memory implements memory_interface_imp_if;
logic [31:0] mem[1024];
virtual function bit write(input longint addr, input longint data);
if (addr < 1024) begin
mem[addr] = data[31:0];
return 1'b1; // Success
end
return 1'b0; // Failure
endfunction
virtual function longint read(input longint addr);
if (addr < 1024) begin
return longint'(mem[addr]);
end
return 0;
endfunction
endclass
SystemVerilog Usage Example
Create and connect the API implementations:
module testbench;
// Create SystemVerilog implementation
my_memory mem_impl;
// Create Python-to-SV bridge
memory_interface_imp_impl #(my_memory) mem_bridge;
// Create SV-to-Python caller
memory_interface_exp_impl py_caller;
initial begin
// Initialize implementations
mem_impl = new();
mem_bridge = new(mem_impl);
py_caller = new();
// Use the API
automatic bit success = mem_bridge.invokeFunc("write", args);
py_caller.on_write_complete(32'h100, success);
end
endmodule
Best Practices
- Object Lifecycle:
Always properly initialize implementation objects before creating bridges
Use the provided constructors to establish Python/SystemVerilog connections
Handle Python GIL (Global Interpreter Lock) properly in custom implementations
- Error Handling:
Check return values from API calls
Handle Python exceptions that may propagate through the interface
Use appropriate SystemVerilog error handling for failed API calls
- Performance:
Minimize frequent calls across the Python/SystemVerilog boundary
Batch operations when possible to reduce overhead
Be aware that each cross-language call has performance implications
Complete Working Example
1. Python API Definition (``my_api.py``):
from hdl_if.decorators import api, imp, exp
@api
class counter_api:
@imp
def increment(self) -> int:
"""Increment counter, return new value"""
pass
@imp
def get_value(self) -> int:
"""Get current counter value"""
pass
@exp
def on_overflow(self, value: int) -> None:
"""Called when counter overflows"""
print(f"Counter overflow at value: {value}")
2. Generate SystemVerilog:
python -m hdl_if api-gen-sv -m my_api -o counter_api.svh
3. SystemVerilog Implementation:
`include "counter_api.svh"
class counter_impl implements counter_api_imp_if;
int counter = 0;
counter_api_exp_impl py_notifier;
function new(counter_api_exp_impl notifier);
py_notifier = notifier;
endfunction
virtual function longint increment();
counter++;
if (counter > 1000) begin
py_notifier.on_overflow(counter);
counter = 0;
end
return longint'(counter);
endfunction
virtual function longint get_value();
return longint'(counter);
endfunction
endclass
4. SystemVerilog Usage:
module test;
counter_impl impl;
counter_api_imp_impl #(counter_impl) bridge;
counter_api_exp_impl py_caller;
initial begin
py_caller = new();
impl = new(py_caller);
bridge = new(impl);
// Test the API
for (int i = 0; i < 1005; i++) begin
automatic longint val = bridge.invokeFunc("increment", null);
$display("Counter: %0d", val);
end
end
endmodule
This example demonstrates the complete flow from Python API definition to SystemVerilog implementation and usage, showing both directions of tcommunication between Python and SystemVerilog. ==================
-
class py_object
Convenience wrapper class for PyObject handles
Subclassed by py_dict, py_list, py_tuple
Public Functions
-
virtual void dispose()
Drops ownership of handle
-
virtual py_object call(py_tuple args = null, py_object kwargs = null)
Calls the object as a Python callable
-
py_iter iter()
Creates an iterator for the given object
-
virtual py_object call_attr(string name, py_tuple args = null, py_object kwargs = null)
Calls a named attribute of the object as a method
-
virtual PyObject borrow()
Returns a borrowed reference to this object (ie just returns the object handle)
-
virtual PyObject steal()
Returns a stolen reference to this object. Specifically, this method increments the ref count, such that stealing a reference doesn’t invalidate this object
-
virtual int to_int()
Obtains the integer value of this object and releases ownership
-
virtual longint to_long()
Obtains the long-int value of this object and releases ownership
-
virtual void to_void()
Disposes of the object
-
virtual string to_str()
Obtains the string value of the object and disposes of the object
-
int as_int()
Obtains the integer value of the object
-
longint as_long()
Obtains the long-int value of the object
-
virtual string as_str()
Obtains the string value of the object
-
virtual void dispose()
-
class py_list : public py_object
Public Functions
-
int size()
Returns the number of elements in the list
-
int size()
Utility Methods
-
py_object py_call_builtin(string name, py_tuple args, py_dict kwargs = null)
Call a built-in function
-
void py_gil_enter()
Acquire the GIL
-
void py_gil_leave()
Release the GIL