Skip to content

Instantly share code, notes, and snippets.

@brabect1
Created December 30, 2018 19:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brabect1/9f8d7d144367b9f3dd77ba2a3b459f1d to your computer and use it in GitHub Desktop.
Save brabect1/9f8d7d144367b9f3dd77ba2a3b459f1d to your computer and use it in GitHub Desktop.
Simple example of using a Verilator model from multiple threads.

Verilator Model Driven by Multiple Threads

Most of the time, a verilated Verilog/SystemVerilog can be driven from a single threaded test. And if not, SystemC may come to rescue.

There can be situations, where a true multithreaded test is needed. An particular example is connecting a debugger to a Verilator model of a CPU. You will need one thread to interface GDB (maybe through other layers such as OpenOCD) and another thread generating a "normal" stimuli to the CPU model (reset, clock, etc.).

This gist provides a simple proof of concept. It uses a simple module with two counters, each clocked from a different clock. The C++ test uses separate threads to drive each clock. Access to the Verilator model class (and any other Verilator resources) is guarded with a mutex.

/*
Copyright 2018 Tomas Brabec
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
module dut(
input logic sys_clk,
input logic othr_clk
);
int sys_cnt = 0;
int othr_cnt = 0;
always @(posedge sys_clk) begin
sys_cnt++;
end
always @(posedge othr_clk) begin
othr_cnt++;
end
always @(othr_cnt) begin
if (othr_cnt > 10) $finish();
end
endmodule
/*
Copyright 2018 Tomas Brabec
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// general includes
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
// Verilator related includes
#include "Vdut.h"
#include <verilated.h>
#if VM_TRACE
#include <verilated_vcd_c.h>
#endif
using namespace std;
struct th_arg {
int id;
pthread_mutex_t* mutex;
Vdut* top;
int* simtime;
};
static pthread_mutex_t mutex;
void* sys_clk(void* arg) {
struct th_arg* a = (th_arg*)arg;
int done = 0;
int* simtime = a->simtime;
int cnt = 0;
pthread_mutex_lock(a->mutex);
cout << "id " << a->id << ": System clock started ..." << endl;
pthread_mutex_unlock(a->mutex);
while (!done) {
cnt++;
pthread_mutex_lock(a->mutex);
if (!Verilated::gotFinish()) {
a->top->sys_clk = 1;
a->top->eval();
(*simtime)++;
a->top->sys_clk = 0;
a->top->eval();
(*simtime)++;
} else {
done = 1;
}
pthread_mutex_unlock(a->mutex);
usleep(10);
}
pthread_mutex_lock(a->mutex);
cout << "id " << a->id << ": Finished ..." << endl;
pthread_mutex_unlock(a->mutex);
pthread_exit(NULL);
}
void* othr_clk(void* arg) {
struct th_arg* a = (th_arg*)arg;
int done = 0;
int* simtime = a->simtime;
int cnt = 0;
pthread_mutex_lock(a->mutex);
cout << "id " << a->id << ": Other clock started ..." << endl;
pthread_mutex_unlock(a->mutex);
while (!done) {
cnt++;
pthread_mutex_lock(a->mutex);
if (!Verilated::gotFinish()) {
cout << "id " << a->id << ": Other clock " << cnt << " ..." << endl;
a->top->othr_clk = 1;
a->top->eval();
(*simtime)++;
a->top->othr_clk = 0;
a->top->eval();
(*simtime)++;
} else {
done = 1;
}
pthread_mutex_unlock(a->mutex);
// unlike in the system clock thread, we stop the other
// clock generator for a while
sleep(1);
}
pthread_mutex_lock(a->mutex);
cout << "id " << a->id << ": Finished ..." << endl;
pthread_mutex_unlock(a->mutex);
pthread_exit(NULL);
}
int main(int argc, char **argv, char **env) {
Verilated::commandArgs(argc, argv);
Vdut* top = new Vdut;
int cnt;
int simtime = 0;
pthread_t threads[2];
struct th_arg targs[2];
pthread_mutex_lock(&mutex);
cout << "Simulation started ..." << endl;
pthread_mutex_unlock(&mutex);
for (long int i = 0 ; i < 2 ; ++i) {
targs[i].id = i;
targs[i].mutex = &mutex;
targs[i].top = top;
targs[i].simtime = &simtime;
int t;
if (i == 0) {
t = pthread_create(&threads[i], NULL, sys_clk, (void*)&targs[i]);
} else {
t = pthread_create(&threads[i], NULL, othr_clk, (void*)&targs[i]);
}
if (t != 0) {
pthread_mutex_lock(&mutex);
cout << "Error in thread creation: " << t << endl;
pthread_mutex_unlock(&mutex);
}
}
for(int i = 0 ; i < 2; ++i) {
void* status;
int t = pthread_join(threads[i], &status);
if (t != 0) {
pthread_mutex_lock(&mutex);
cout << "Error in thread join: " << t << endl;
pthread_mutex_unlock(&mutex);
}
}
pthread_mutex_lock(&mutex);
cout << "simtime=" << simtime << endl;
cout << "sys_cnt=" << top->dut__DOT__sys_cnt << endl;
cout << "othr_cnt=" << top->dut__DOT__othr_cnt << endl;
pthread_mutex_unlock(&mutex);
delete top;
return 0;
}
# Copyright 2018 Tomas Brabec
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
SHELL=bash
vsrc = dut.sv
csrc = indep_clk.cpp
cflags = -lpthread
ldflags = -lpthread
objdir = obj
obj = $(csrc:%.cpp=%)
vflags = $(if $(cflags),-CFLAGS "$(cflags)" ) $(if $(ldflags),-LDFLAGS "$(ldflags)" ) --cc --exe --Mdir $(objdir) \
--top-module dut
vflags_extra = --trace
sflags =
.PHONY: help
help:
@echo -e "Usage:\n\tmake [ooptions] [target] [<variable>=<value>]"
@echo -e "\nTargets:"
@echo -e " help prints this help message"
@echo -e " build creates a testbench binary ($(objdir)/$(obj))"
@echo -e " clean delete all outputs"
@echo -e "\nVariables:"
@echo -e " cflags extra C++ compiler flags"
@echo -e ""
.PHONY: all build
all build: $(objdir)/$(obj)
.PHONY: verilate
verilate: $(objdir)/Vdut.mk
$(objdir)/Vdut.mk: $(vsrc) $(csrc)
verilator $(vflags) $(vflags_extra) -o $(notdir $(obj)) $(csrc) $(vsrc)
$(objdir)/$(obj): $(objdir)/Vdut.mk
make -C $(objdir) -f Vdut.mk
.PHONY: clean
clean:
rm -rf $(objdir) dump.vcd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment