xspcomm is the common data definition and operation interface for picker, including interface read/write, clock, coroutine, SWIG callback function definition, etc. xspcomm is used as a basic component by upper-level applications or libraries such as DUT, MLVP, OVIP. xspcomm requires features of C++20, it is recommended to use g++ version 11 or above, and cmake version greater than or equal to 3.11. When exporting Python interface through SWIG, swig version greater than or equal to 4.2.0 is required.
Compile: Compile using the make command. Enable support for Python and other languages by specifying the BUILD_XSPCOMM_SWIG parameter (currently, the SWIG interface supports Python, JavaScript, Java, Scala, and Go). Upon completion of compilation, the generated files will be located in the build directory:
make BUILD_XSPCOMM_SWIG=python,scala,java,golang
# results
build/python/
└── xspcomm # python module
├── __init__.py
├── _pyxspcomm.so -> _pyxspcomm.so.0.0.1
├── _pyxspcomm.so.0.0.1
├── info.py
└── pyxspcomm.py
build/scala/
└── xspcomm-scala.jar # scala package
build/java/
└── xspcomm-java.jar # java package
build/golang/
└── src # golang module
└── xspcomm
├── golangxspcomm.so -> golangxspcomm.so.0.0.1
├── golangxspcomm.so.0.0.1
└── xspcomm.go
Testing: The make command compiles and executes tests/test_xdata.cpp by default. To run tests/tests_python.py, execute the following commands:
$make BUILD_XSPCOMM_SWIG=python
$make test_python
Packaging and Installation: (python wheel) By default, the so dynamic library and header files are packaged. To package PYTHON, you need to enable BUILD_XSPCOMM_SWIG=python
$pip install pipx
$cd xcomm
$BUILD_XSPCOMM_SWIG=python pipx run build // Packaging
After packaging, a whl installation package will be generated in the dist directory
Install and test
$pip install dist/pyxspcomm-0.0.1-cp310-cp310-manylinux_2_35_x86_64.whl
$python -m xspcomm.info --help # This command is only available when
# BUILD_XSPCOMM_SWIG=python is enabled
Usage: python -m xspcomm.info [option]
--help: print this help
--export: print export cmd of XSPCOMM_ROOT, XSPCOMM_INCLUDE, XSPCOMM_LIB
--version: print python wrapper version
--root: print xcomm root path
--include: print xcomm include path
--lib: print xcomm lib path
--path: print xcomm all path [default]
xspcomm includes basic data types for operating DUT (Design Under Test): XData, XPort, XClock.
- XData represents the IO interface of the circuit (i.e., bound with the circuit pin), and reads and writes the IO interface of the circuit through DPI. It supports writing and reading of 0, 1, Z, X four states. The definition is located in xspcomm/xdaya.h.
Main interfaces and member variables of XData:
Initialization
// Default constructor
XData();
// Re-initialization, width represents the width of the pin
// itype represents the input/output mode of the pin: Input, Output, InOut
// name represents the name of the pin
void ReInit(uint32_t width, IOType itype, std::string name = "");
// Same as ReInit
XData(uint32_t width, IOType itype, std::string name = "");
Bind DPI
void BindDPIRW(xfunction<void, void *> read,
xfunction<void, void *> write);
void BindDPIRW(xfunction<void, void *> read,
xfunction<void, unsigned char> write);
Set pin mode: WriteMode::Imme for immediate write, WriteMode::Rise for write on rising edge, WriteMode::Fall for write on falling edge. XData defaults to write on rising edge.
bool SetWriteMode(WriteMode mode);
Assignment operation
XData a(128, IOType::Input);
a = 0x1234 // Direct assignment int, unsigned int, etc.
a = "0b11011" // Binary assignment through string
a = "0b1101z" // Binary assignment through string, the last bit is set to high impedance
a = "0xfffff" // Octal assignment through string
a = "::fffff" // Direct assignment of string ascii code
a = "X" // Set all 128 bits to undefined state
// Read and write data through vector
uint8 buffer[128/8]
a.GetBits(buffer, sizeof(buffer));
a.SetBits(buffer, sizeof(buffer));
// Read data to buffer or vector
std::vector<unsigned char> vector(buffer)
auto ret = a.GetBytes()
a.SetBytes(vector);
uint32_t x = a.W(); // Convert to uint32
uint64_t x =a.U(); // Convert to uint64
int64_t x = a.S(); // Convert to int64
bool x = a.B(); // Convert to bool
std::string a.String() // Convert to string
XData b(128, IOType::Input);
a = b // Assignment of the same type
Member variables
std::string mName; // XData name
IOType mIOType; // Input/output type
uint32_t mWidth; // Bit width
- XPort is a wrapper for XData, which can operate multiple XData
Main interfaces and member variables of XPort:
Initialization
XPort(std::string prefix = "");
Main methods
int PortCount(); // Number of pins
bool Add(std::string pin, xspcomm::XData &pin_data); // Add pin
bool Del(std::string pin); // Delete pin
bool Connect(XPort &target); // Connect with another port
XPort &NewSubPort(std::string subprefix); // Create subport
xspcomm::XData &operator[](std::string key); // Get pin by key
xspcomm::XData &Get(std::string key,
bool raw_key = false); // Same as above
XPort &Flip(); // Flip all pin values in the port
XPort &AsBiIO(); // Set all pins in the port to BiIO mode
XPort &WriteOnRise(); // Write all pin values through DPI (called on clock's rising edge)
XPort &WriteOnFall(); // Write all pin values through DPI (called on clock's falling edge)
XPort &ReadFresh(xspcomm::WriteMode m); // Refresh pin reading (through DPI)
XPort &SetZero(); // Set all pin values to 0
Member variables
std::string prefix; // Prefix of pin (XData) name
std::map<std::string, xspcomm::XData *> port_list; // Pin list
- XClock is a wrapper for the circuit's clock, used to drive the circuit's clock, and provides corresponding callback entries at the rising or falling edge of the clock
Main interfaces and member variables of XClock:
Initialization
// stepfunc is the circuit advancement method
// provided by DUT backend, such as verilaor's step_eval, etc.
// xfunction<ret, Args...> is functionally equivalent
// to std::function<ret(Args...)>, mainly used for SWIG export
XClock();
XClock(xfunction<int, bool> stepfunc,
std::initializer_list<xspcomm::XData *> clock_pins = {},
std::initializer_list<xspcomm::XPort *> ports = {});
void ReInit(xfunction<int, bool> stepfunc,
std::initializer_list<xspcomm::XData *> clock_pins = {},
std::initializer_list<xspcomm::XPort *> ports = {});
Main methods
void Add(xspcomm::XData &d); // Add clk pin
void Add(xspcomm::XPort &d); // Add port
void Step(int s = 1); // Advance clock
void RunStep(int s = 1); // Same as above
void Reset(); // Reset
// Add rising edge callback
void StepRis(xfunction<void, u_int64_t, void *> func, void *args = nullptr,
std::string desc = "");
// Add falling edge callback
void StepFal(xfunction<void, u_int64_t, void *> func, void *args = nullptr,
std::string desc = "");
// Asynchronous methods
XStep AStep(int i = 1); // Wait for i steps
XCondition ACondition(std::function<bool(void)> checker); // Conditional wait
XNext ANext(int n = 1); // Wait for i scheduling
void eval(); // Drive circuit execution, do not update waveform (only for combinational logic)
void eval_t(); // Drive circuit execution, update waveform (not recommended)
The xspcomm
provides three coroutine methods in the clock
class: AStep(int i = 1)
, ACondition(std::function<bool(void)> checker)
, ANext(int n = 1)
, which can be used for asynchronous programming. Here is an example pseudocode:
#include "xspcomm/xclock"
#include "xspcomm/xcoroutine.h"
...
// Receive data
xcoroutine<bool> send(XClock &clk, XPort &port){
// Wait for the a_valid signal in port to go high
co_await clck.ACondition([&port](){return port["a_valid"]==1;});
// Return result
co_return port["data"] == 0xffff;
}
// Loop receive, print result
xcoroutine<> infinite_loop(XClock &clk, XPort &port){
while(true){
auto ret = co_await send(clk, port);
printf("send result is 0xffff: %s\n", ret ? "Yes":"No");
// Wait for the next clock
co_await clk.AStep();
}
}
int main(){
...
// Start coroutine
infinite_loop(clk, port);
// Push 1000 clocks (coroutines will be scheduled by default during the Step process)
clk.Step(1000);
return 0;
}
The Python module generated by SWIG is named xspcomm
, which includes basic classes such as XData
, XPort
, XClock
. It supports the same asynchronous methods as C++: XClock.AStep
, XClock.ACondition
, XClock.ANext
. The new asynchronous method XClock.RunStep
is added to drive xclock
, which has the same function as XClock.Step
in synchronous mode.
from xspcomm import *
from asyncio import run, create_task
async def test_async():
clk = XClock(lambda a: 0)
clk.StepRis(lambda c : print("lambda ris: ", c))
task = create_task(clk.RunStep(30))
print("test AStep:", clk.clk)
await clk.AStep(3)
print("test ACondition:", clk.clk)
await clk.ACondition(lambda: clk.clk == 20)
print("test cpm:", clk.clk)
await task
if __name__ == "__main__":
print("version: %s" % version())
run(test_async())
This project provides a way to communicate between UVM and Python (C++) based on UVMC.
Taking VCS as an example, the compilation steps of the DUT example are as follows:
...
# 1 Generate python wrapper through swig
$swig -D'MODULE_NAME="tlm_pbsb"' -python -c++ -DUSE_VCS -I${XSP_COMM_INCLUDE} \
-o tlmps.cpp ${XSP_COMM_INCLUDE}/xspcomm/python_tlm_pbsb.i
# 2 Compile the communication module
$SYSCAN -full64 -cflags -DUSE_VCS -cflags -I{PYTHON_INCLUDE} \
-cflags -I${XSP_COMM_INCLUDE} ${XSP_COMM_INCLUDE}/xspcomm/tlm_pbsb.cpp tlmps.cpp
# 3 Compile DUT in slave mode
$VLOGAN -full64 +incdir+common tlm.sv +define+UVM_OBJECT_MUST_HAVE_CONSTRUCTOR
$VCS -e VcsMain -full64 sv_main ${VCS_HOME}/linux64/lib/vcs_tls.o -slave
# 4 Rename
$mv simv _tlm_pbsb.so
$mv simv.daidir _tlm_pbsb.so.daidir
...
After compilation, you can load the DUT through import. For specific examples, please refer to: tests/tlm
The header file xspcomm/xutil.h provides the following basic functionalities:
LogLevel get_log_level() // Get the current log level
void set_log_level(LogLevel val) // Set the current log level
Info(fmt, ...) // Log Info
Warn(fmt, ...) // Log Warn
Error(fmt, ...) // Log Error
Debug(fmt, ...) // Log Debug
debug(c, fmt, ...) // Log Debug, but print log when c is true
Fatal(fmt, ...) // Log Fatal, print log, then exit execution
Assert(c, fmt, ...) // Assertion, print log and exit execution when c is false
// Format string, eg: auto str = sFmt("Hello %s", "World!")
std::string sFmt(const std::string &format, Args... args)
// Convert array to hex (string format)
inline std::string sArrayHex(unsigned char *buff, int size)
// Check if string starts with prefix
inline bool sWith(const std::string &str, const std::string &prefix)
// Check if vector contains a
inline bool contains(std::vector<T> v, T a)
// Convert string to lowercase
inline std::string sLower(std::string input)
// Unit conversion: convert int i to KB, MB, GB
inline std::string FmtSize(u_int64_t s)
// Compiler optimization guidance
likely(cond)
unlikely(cond)
// Get a random number
inline u_int64_t xRandom(u_int64_t a, u_int64_t b)
// Set random seed
inline void XSeed(unsigned int seed)
// Check if the header file version matches the library version
inline bool checkVersion()
// Loop n times, eg: FOR_COUNT(10){i++;}
FOR_COUNT(n)
xinstance.h is only used for instantiating template classes and has no other functions.