When we must verify a highly computational RTL, we may deal with complicated mathematical functions and algorithms. Implementing and debugging an RTL model can be tricky and time consuming. In such cases modeling using Octave/Matlab, C/C++ or SystemC can be a good alternative.
In this article we present a “Hello world!” example to illustrate how to use SystemVerilog as the verification language and Octave as the numerical modeling language. This is a simplified version of the Algorithm Verification with SystemVerilog and OpenSource presentation held at DVCon Europe.
First of all, we must install Octave’s main package and it’s development package.
Then we create a testbench and connect it to Octave as shown in the image below:

We need the C++ layer because Octave libraries are C++ libraries.
The SystemVerilog Layer
The SystemVerilog layer contains the implementation of an UVM component and imports a DPI-C function. By using the import DPI-C construct we can call from SystemVerilog a function implemented in C.
import "DPI-C" function void c_hello_world();
class amiq_hello_world extends uvm_component;
...
function void sv_hello_world();
`uvm_info("HELLO_WORLD", "Hello world from SV!", UVM_NONE);
// Call the C layer hello world
c_hello_world();
endfunction
...
endclass
The C Layer
The C layer forwards the call to C++. When mixing C and C++ code the extern “C” construct must be used. In this layer C – C++ type casting may be required.
extern "C" {
void c_hello_world() {
printf("[HELLO_WORLD] Hello world from C!\n");
// Call C++ layer hello world
cpp_hello_world();
}
}
The C++ Layer
The C++ layer forwards the call to Octave. In this layer C++ – Octave type casting may be required.
// In order to access Octave libraries and to use Octave specific data types,
// 3 header files must be included into the C++ layer:
// Main C++ Octave library
#include <octave/octave.h>
// For octave_main()
#include <octave/oct.h>
// For access to virtual terminal support
#include <octave/parse.h>
// Hello world example - C++ function
void cpp_hello_world() {
cout << "[HELLO_WORLD] Hello world from C++!" << endl;
// Input parameters list for octave custom function
octave_value_list oct_in_list;
// Load Octave hello world function
load_fcn_from_file("octave_hello_world.m", "", "", "octave_hello_world", true);
// Call Octave layer hello world
feval("octave_hello_world", oct_in_list, 1);
}
The Octave Layer
The Octave layer contains the mathematical function – in this case only a message will be printed.
% Hello world example
function octave_hello_world ();
disp("[HELLO_WORLD] Hello world from Octave!");
end
Octave Initialization
The Octave interpreter must be initialized at the beginning of the simulation by calling octave_main():
// SystemVerilog
import "DPI-C" function void initialize_octave();
// Hello world environment
class amiq_hello_world extends uvm_component;
...
virtual task run_phase(uvm_phase phase);
...
// Initialize the Octave interpreter
initialize_octave();
...
endtask
...
endclass
// C++
void initialize_octave_cpp() {
// Declare a string vector used to pass arguments to octave_main function
string_vector argv(2);
// Set the first argument to "embedded"
argv(0) = "embedded";
// Set verbosity to quiet
argv(1) = "-q";
// Call octave_main() to initialize the interpreter
octave_main(2, argv.c_str_vec(), 1);
}
Compiling
We must create a shared object that will be passed to the simulator. The mkoctfile utility helps us:
$: mkoctfile -link-stand-alone -v my_cpp_code.cpp
The mkoctfile output is the g++ command that creates the shared object:
g++ -c -fPIC -I/usr/include/octave-3.4.3/octave/.. -I/usr/include/octave-3.4.3/octave -I/usr/include/freetype2 -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic my_ccp_code.cpp -o my_ccp_code.o
g++ -shared -Wl,-Bsymbolic -o my_ccp_code.oct my_ccp_code.o -link-stand-alone -L/usr/lib64/octave/3.4.3 -L/usr/lib64 -loctinterp -loctave -lcruft -L/usr/lib64/atlas -llapack -L/usr/lib64/atlas -lf77blas -latlas -lfftw3 -lfftw3f -lm -L/usr/lib/gcc/x86_64-redhat-linux/4.4.6 -L/usr/lib/gcc/x86_64-redhat-linux/4.4.6/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/4.4.6/../../.. -lgfortranbegin -lgfortran -lm
Running
Simulations can be run using any of the 3 major EDA vendors simulators (irun, vlog/vsim and vcs). For vsim the shared library must be included at run time and for irun and vcs at compile time. You can find some invocation examples here amiq_hello_world_demo.sh.
Note: If you experience issues related to the octave_main() function, remove the macro call “OCTINTERP_API” from “octave.h” header file (usually located in “/usr/include/”). This macro is relevant only for Microsoft’s Visual C compiler. If you create a new file with the macro call removed, you must include it before including the Octave libraries.
Results
The results should look like this:
UVM_INFO @ 0 [HELLO_WORLD] Hello world from SV!
[HELLO_WORLD] Hello world from C!
[HELLO_WORLD] Hello world from C++!
[HELLO_WORLD] Hello world from Octave!
Source Code
The source files for the “Hello world!” example above, as well as more elaborate ones can be found on AMIQ’s github amiq_sv_octave project repository. There are three packages that you can download:
- amiq_hello_world for code and scripts in this tutorial
- amiq_dsp_algorithms for DSP algorithms in both SystemVerilog and Octave, including tests
- amiq_dsp_algorithms_fp for a fixed point implementation of the amiq_dsp_algorithms package using SystemVerilog and SystemC
The slides from this presentation can be viewed here.
Andrea Masi
June 23rd, 2016 09:18:12