Cross-language Thread Synchronization Between SystemC and SystemVerilog via DPI-C
In a mixed-simulation environment where a SystemC reference model must be integrated into a SystemVerilog UVM framework, the SystemC component often runs as a persistent thread rather than a simple request-response module. To achieve this, the simulation must launch concurrent threads in both languages and allow the SystemVerilog side to control a synchronization flag (lock) accessible by the SystemC process.
SystemC Implementation
The SystemC module defines a thread that runs continuously. It uses DPI-C imports to interact with the SV simulation time and check the status of a lock.
#include "systemc.h"
#include "svdpi.h"
#include "svdpi_src.h"
#include <iostream>
using namespace std;
extern "C" {
void advance_time(int cycles);
bool check_lock_status();
}
SC_MODULE(WorkerModule) {
SC_CTOR(WorkerModule) {
SC_THREAD(processing_loop);
}
void processing_loop() {
while (true) {
cout << "SystemC worker active" << endl;
advance_time(5);
// Poll lock status with a delay to allow simulation time to progress
while(check_lock_status()) {
advance_time(1);
}
}
}
};
extern "C" {
void log_message(char* msg) {
printf("SC_Log: %s\n", msg);
}
int sc_top(int argc, char* argv[]) {
WorkerModule worker("worker_inst");
cout << "SystemC module initialized" << endl;
sc_start();
return 0;
}
void sc_entry_point(int argc, svOpenArrayHandle argv_handle) {
char* main_args[argc];
for (int i = 0; i < argc; i++) {
char ** ptr = (char **) svGetArrElemPtr(argv_handle, i);
main_args[i] = *ptr;
}
sc_top(argc, main_args);
}
}
SystemVerilog Testbench
The SV testbench uses DPI-C to export tasks and functions that the SystemC side can call, effectively creating a shared control mechanism.
module top_tb;
import "DPI-C" context task sc_entry_point(int argc, string argv[]);
import "DPI-C" function void log_message(string msg);
export "DPI-C" task advance_time;
export "DPI-C" function check_lock_status;
bit shared_lock = 1;
task advance_time(input int cycles);
repeat(cycles) begin
#1;
end
endtask
function bit check_lock_status();
return shared_lock;
endfunction
string app_args[5];
initial begin
app_args[0] = "./sim";
app_args[1] = "-c";
app_args[2] = "config.hex";
app_args[3] = "-m";
app_args[4] = "fast";
fork
begin
// SV Thread: Toggle lock periodically
forever begin
$display("SV Monitor running at time %0t", $time);
shared_lock = ~shared_lock;
$display("Lock state changed to %0b", shared_lock);
#10;
end
end
begin
// Launch SystemC World
sc_entry_point(5, app_args);
end
join_none
end
endmodule
Simulation Output
The output demonstrates that the SystemC thread executes when the lock is released (0) and pauses when the lock is engaged (1), while the SV thread continues to run and toggle the lock.
SV Monitor running at time 378240
Lock state changed to 0
SystemC worker active
SystemC worker active
SV Monitor running at time 378250
Lock state changed to 1
SV Monitor running at time 378260
Lock state changed to 0
SystemC worker active
SystemC worker active
SV Monitor running at time 378270
Lock state changed to 1
A critical issue to avoid is simulation deadlock. If the SystemC polling loop (while(check_lock_status())) does not contain a time-consuming call like advance_time(1), the simulation time will never advance. This prevents the SV thread from executing and releasing the lock, causing both sides to hang. Insreting a delay in the polling loop ensures the simulator yields control back to SV, allowing the lock to be updated.