This article introduces how to write synthesizable SystemC code and the test bench.
A typical organization of the project is:
.
|-- synthesizable/
| |-- design.h
| |-- design.cpp
|-- testbench/
| |-- tb_design.h
| |-- tb_design.cpp
|-- main.cpp
|-- Makefile
The templates of each source file are shown in the following sections.
#ifndef DESIGN_H_
#define DESIGN_H_
#include "systemc.h"
// define
// typedef
SC_MODULE(design) {
// Module port
sc_in_clk clk;
sc_in<bool> rst;
sc_in<sc_uint<8> > idata;
sc_out<sc_uint<8> > odata;
// Signal variable
// Data variable
// Member function
void foo();
// Method process
void main();
// Module constructor
SC_CTOR(design) {
SC_CTHREAD(main, clk.pos());
reset_signal_is(rst, true);
}
~design() {}
};
#endif#include "design.h"
// Constants
void
design::main() {
// Reset code
// Reset internal variables
// Reset outputs
wait();
while(true) {
// Read inputs
// Algorithm code
// Write outputs
wait();
}
// No statement is allowed after the loop
}
void
design::foo() {
// function description
}#ifndef TB_DESIGN_H_
#define TB_DESIGN_H_
#include "systemc.h"
// define
// define input and output file names
#define DIR "testbench/"
#define INFILE DIR"idata.txt"
#define OUTFILE DIR"odata.txt"
#define OUTFILE_GOLDEN DIR"odata_golden.txt"
#define DIFF DIR"diff.txt"
// typedef
SC_MODULE(tb_design) {
// Module port
sc_in_clk clk;
sc_out<bool> rst;
sc_in<sc_uint<8> > odata;
sc_out<sc_uint<8> > idata;
// Signal variable
// Data variable
FILE *infile, *outfile, *outfile_golden, *diff;
// Member function
void time_stamp();
void compare();
// Method process
void send();
void recv();
// Module constructor
SC_CTOR(tb_design) {
SC_CTHREAD(send, clk.pos());
reset_signal_is(rst, true);
SC_CTHREAD(recv, clk.pos());
reset_signal_is(rst, true);
}
~tb_design() {}
};
#endif#include "tb_design.h"
void
tb_design::send() {
// Check input file
infile = fopen(INFILE, "r");
if (infile == NULL) {
cout << "Could not open " << INFILE << endl;
sc_stop();
exit(-1);
}
unsigned int idata_t;
if (rst)
wait();
while (true) {
// Start testing
time_stamp();
cout << "Testing starts" << endl;
while (fscanf(infile, "%u", &idata_t) != EOF) {
// Send data to input ports
idata.write(idata_t);
wait();
}
time_stamp();
cout << "Testing ends" << endl;
// The testing ends
fclose(infile);
fclose(outfile);
time_stamp();
cout << "Start comparing results " << endl;
compare();
sc_stop();
wait();
}
// No statement is allowed after the loop
}
void
tb_design::recv() {
// Check output file
outfile = fopen(OUTFILE, "wt");
if (!outfile) {
cout << "Could not open " << OUTFILE << endl;
sc_stop();
exit(-1);
}
int odata_t;
if (rst)
wait();
// Receiving the data
while (true) {
odata_t = odata.read().to_int();
fprintf(outfile, "%d\n", odata_t);
wait();
}
// No statement is allowed after the loop
}
void
tb_design::compare() {
// Open output file
outfile = fopen(OUTFILE, "rt");
if (!outfile) {
cout << "[Compare] Could not open " << OUTFILE << endl;
sc_stop();
exit(-1);
}
// Open output golden file
outfile_golden = fopen(OUTFILE_GOLDEN, "rt");
if (!outfile_golden) {
cout << "[Compare] Could not open " << OUTFILE_GOLDEN << endl;
sc_stop();
exit(-1);
}
// Open diff file
diff = fopen(DIFF, "wt");
if (!diff) {
cout << "[Compare] Could not open " << DIFF << endl;
sc_stop();
exit(-1);
}
// Start comparing
int line = 1;
int errors = 0;
int data_out, data_out_golden;
while (fscanf(outfile_golden, "%d", &data_out_golden) != EOF) {
fscanf(outfile, "%d", &data_out);
if (data_out != data_out_golden) {
cout << "Output mismatch [line: " << line << "] ";
cout << "Golden: " << data_out_golden << " -- ";
cout << "Output: " << data_out << endl;
fprintf(diff, "[Line: %d] Golden: %d -- Output: %d\n",
line, data_out_golden, data_out);
errors ++;
}
line ++;
}
time_stamp();
if (errors == 0)
cout << "Simulation SUCCESSED" << endl;
else
cout << "Simulation FAILED, " << errors << " mismatches found" << endl;
fclose(outfile);
fclose(outfile_golden);
fclose(diff);
}
void
tb_design::time_stamp() {
cout << "@" << sc_time_stamp() << " ";
}#include "design.h"
#include "tb_design.h"
SC_MODULE(SYSTEM) {
// Module declarations
design *design0;
tb_design *tb_design0;
// Local signal declarations
sc_clock clk_sig;
sc_signal<bool> rst_sig;
sc_signal<sc_uint<8> > idata_sig;
sc_signal<sc_uint<8> > odata_sig;
SC_CTOR(SYSTEM): clk_sig("clk_sig", 25, SC_NS, 0.5, 12.5, SC_NS, true) {
// Module instance signal connections
tb_design0 = new tb_design("tb_design0");
tb_design0->clk(clk_sig);
tb_design0->rst(rst_sig);
tb_design0->idata(idata_sig);
tb_design0->odata(odata_sig);
design0 = new design("design0");
design0->clk(clk_sig);
design0->rst(rst_sig);
design0->idata(idata_sig);
design0->odata(odata_sig);
}
// Destructor
~SYSTEM() {
delete tb_design0;
delete design0;
}
};
SYSTEM *top = NULL;
int sc_main(int argc, char *argv[]) {
top = new SYSTEM("top");
#ifdef WAVE_DUMP
sc_trace_file* trace_file = sc_create_vcd_trace_file("trace_behav");
sc_trace(trace_file, top->clk_sig, "clk");
sc_trace(trace_file, top->rst_sig, "rst");
sc_trace(trace_file, top->idata_sig, "idata");
sc_trace(trace_file, top->odata_sig, "odata");
#endif
top->rst_sig.write(1);
sc_start(100, SC_NS);
top->rst_sig.write(0);
cout << "Simulation starts" << endl;
sc_start();
#ifdef WAVE_DUMP
sc_close_vcd_trace_file(trace_file);
#endif
return 0;
}# SystemC installation path
SYSTEMC_HOME ?= /eda/cwb/cyber_61/LINUX/osci
# SystemC headers
SYSTEMC_INC_DIR = $(SYSTEMC_HOME)/include
# SystemC library
SYSTEMC_LIB_DIR = $(SYSTEMC_HOME)/lib-linux64
SYSTEMC_LIBS = $(SYSTEMC_LIB_DIR) -lsystemc
# Running libraries
RUNTIME_LIBS = -Wl,-rpath,$(SYSTEMC_LIB_DIR)
# Includes and Libraries
INCS = -I $(SYSTEMC_INC_DIR)
INCS += -I synthesizable/ -I testbench/
LIBS = $(SYSTEMC_LIBS) $(RUNTIME_LIBS) -lm
# -lstdc++
# Compiler
CC = g++
# Compiler flags
CFLAGS = -g -Wall
# -pedantic -Wno-long-long -Werror
# Linker flags
LDFLAGS = -L $(LIBS)
#============================================================
TARGET := main
SRCS := $(wildcard *.cpp) $(wildcard **/*.cpp)
#============================================================
OBJS := $(SRCS:.cpp=.o)
DEPS := $(OBJS:.o=.d)
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $@ $(LDFLAGS)
wave: $(OBJS)
$(CC) $^ -o $(TARGET) $(LDFLAGS)
# headers dependencies
-include $(DEPS)
wave: CFLAGS += -DWAVE_DUMP
# -c: not to run the linker
%.o: %.cpp
$(CC) $(CFLAGS) -MD $(INCS) -c $< -o $@
.PHONY: clean
clean:
-rm -f $(TARGET) $(OBJS) $(DEPS)
deepclean:
-rm -f $(TARGET) $(OBJS) $(DEPS)
-rm -f *.vcd
-rm -f testbench/odata.txt testbench/diff.txtVerification
- Hierarchical test environment
- Top level test structure
- Instance of FIR module (DUT)
- Instance of testbench module
- Connectivity
- Testbench module
- Stimulus thread
- Sink thread
- Top level test structure
// top_module.h
sc_in <sc_uint<1> > i_vld; // Valid
sc_out<sc_uint<1> > i_rdy; // Ready
sc_out<sc_uint<1> > o_vld; // Valid
sc_in <sc_uint<1> > o_rdy; // Ready// top_module.cpp
// Reading
i_rdy.write(1); // Is ready to receive inputs
do { // Wait until the inputs are valid
wait();
} while (!i_vld.read());
// Start reading
// Writing
o_vld.write(1); // Outputs are valid
do { // Wait until the next block is ready to receive the outputs
wait();
} while (!o_rdy.read());sc_time clock_period;
sc_clock *clk_p = DCAST<sc_clock*>(clk.get_interface());
clock_period = clk_p->period();FILE *outfp;
char output_file[256];
sprintf(output_file, "./output.dat"); // Naming the file
outfp = fopen(output_file, "w");
if (outfp == NULL)
{
printf("Couldn't open output.data file for writing.\n");
exit(0);
}sc_time start_time[n], end_time[n]; // n is number of IOs
// In source()
start_time[i] = sc_time_stamp();
// In sink()
end_time[i] = sc_time_stamp();
double total_cycles = 0.0;
total_cycles += (end_time[i] - start_time[i]) / clock_period;
double average_latency = total_cycles / n;How fast can we put data into the design.
double total_throughput = (start_time[n-1] - start_time[0]) / clock_period;
double average_throughput = total_throughput / (n - 1);At the end of source().
wait(10000); // A reasonable large number
printf("Hanging simulation stopped by TB source thread. Please check DUT module.\n");
sc_stop();#include <systemc.h>
SC_MODULE(and2)
{
sc_in<DT> a;
sc_in<DT> b;
sc_out<DT> f;
void func()
{
f.write(a.read() & b.read());
}
SC_CTOR(and2)
{
SC_METHOD(func);
sensitive << a << b;
}
};#include <systemc.h>
SC_MODULE(and2)
{
sc_in<bool> clk;
sc_in<sc_uint<1> > a;
sc_in<sc_uint<1> > b;
sc_out<sc_uint<1> > f;
void func()
{
f.write(a.read() & b.read());
}
SC_CTOR(and2)
{
SC_METHOD(func);
sensitive << clk.pos();
}
};-
SystemC uses functions to read from
sc_in<>or write tosc_out<>.read().write()
-
SystemC has bit-accurate versions of the integer data type
- Data types have a fixed width
- Unlike C
inttype, always 32 bits
-
Unsigned and signed
sc_uint<N>
- A thread is a function made to act like a hardware process
- Run concurrently
- Sensitive to signals, clock edges or fixed amount of simulation time
- Not called by the user, always active
- SystemC supports three kinds of threads
- SC_METHOD()
- SC_THREAD()
- SC_CTHREAD()
- Executes once every sensitivity event
- Runs continuously
- Analogous to a Verilog
@alwaysblock - Synthesizable
- Useful for combinational expressions or simple sequential logic (e.g. a counter that can be executed in one clock cycle)
- Runs once at start of simulation, then suspends itself when done
- Can contain an infinite loop to execute code at a fixed rate of time
- Similar to a Verilog
@initialblock - NOT synthesizable
- Useful in test benches to describe clocks or initial startup signal sequences
- Means clocked thread
- Runs continuously
- References a clock edge
- Synthesizable
- Can take one or more clock cycles to execute a single iteration
- Used in 99% of all high-level behavioral designs
- Not limited to one cycle
- Can contain continuous loops
- Can contain large blocks of code with operations or control
- Great for behavioral synthesis
sc_in<bool> clk;
sc_in<bool> rst;
sc_in<sc_int<16> > idata;
sc_out<bool> odata;Note: sc_inout is not synthesizable in CWB.
In hierarchical modules, use signals to communicate between the ports of instantiated modules. Use internal signals for peer-to-peer communication between processes within the same modules.
sc_signal<signal_type> signal_name;Note: Always use sc_signal for interprocess communication.
Inside a module, you can define data member variables of any synthesizable SystemC or C++ type. These variables can be used for internal storage in the module.
int count_val;
sc_int<8> mem[1024];You can declare member functions in a module that are not processes. This type of member function is not registered as a process in the module's constructor. It can be called from a process.
SystemC processes are declared in the module body and registered as processes inside the constructor of the module.
// process declaration
void my_method_proc();
// module constructor
SC_CTOR(my_module) {
// register process
SC_METHOD(my_method_proc);
// define sensitivity list
sensitive << a << b << c; // stream declaration
sensitive(d); // function declaration
}Note: To eliminate the risk of pre- and post-synthesis simulation mismatches, include all the inputs to the combinational logic process in the sensitivity list of the method process.
Defining an edge-sensitive process.
sensitive_pos(clock);
sensitive_neg << b << reset;For best synthesis, use appropriate data types and bit-widths so unnecessary hardware is not built during RTL synthesis.
- For a single-bit variable, use the native C++ type
bool. - For variables with a width of 64 bits or less, use
sc_intorsc_uintdata types. Usesc_intfor logic operations. - For variables larger than 64 bits, use
sc_bigintorsc_biguint. - Use
sc_logicorsc_lvonly when you need to model three-state signals or buses. Avoid comparison withXandZvalues since such comparisons are not synthesizable. - Use native C++ integer types for loop counters.
Commonly used data types
sc_uint<n>,sc_int<n>boolint,unsigned intchar,unsigned charstruct
Bit vector data type operators
- Bitwise:
&(and),|(or),^(xor),~(not) - Bitwise:
<<(shift left),>>(shift right) - Bit selection:
[x] - Part selection:
range(x, y) - Concatenation:
(x, y) - Reduction:
and_reduce(),or_reduce(),xor_reduce() - Type conversion:
to_uint(),to_int()