systemc调度策略可以使得C++代码很好地模拟RTL的并行执行效果,轻松实现寄存器的功能。
假设有这样一个模块:两个10bit的输入信号,经过一个加法器,计算结果经过寄存器打一拍后输出。以下分别用verilog和systemc来实现,源代码见文章末尾。
Systemc实现:
sc_clock 和sc_in_clock为时钟,前者产生时钟,初始化时需要配置period,默认占空比为50%。
信号绑定时,sc_in/sc_out 必须和sc_signal进行绑定,不能不绑定,也不能sc_in和sc_out进行绑定。如果是嵌套模块,子模块的sc_in/sc_out可以传递给top层的sc_in/sc_out的。
所以,如果想将A模块的sc_in和B模块的sc_out进行绑定,必须再定义一个sc_signal,分别将A的sc_in和B的sc_out绑定到这个sc_signal上。形式为sc_in (sc_signal ) 和sc_out (sc_signal ),不能颠倒为sc_signal (sc_in /sc_out )。
如果make没有报错,执行时发现报以下错误,说明port没绑定。
complete binding failed: port not bound: port In file: …/…/…/…/src/sysc/communication/sc_port.cpp:231
sc_in/sc_out/ sc_signal分别通过read()和write()函数来读信号值和给信号赋值。
产生波形文件使用sc_create_vcd_trace_file,添加dump波形信号使用sc_trace。仿真结束后使用gtkwave打开vcd波形文件。
产生类似与RTL的阻塞赋值方式,在构造函数中固定使用如下方式。
SC_METHOD(AddMethod); sensitive<<m_clk.pos(); dont_initialize();
SC_METHOD的敏感列表都配置成clock的上升沿 (下降沿或双边沿也可以自行配置),一般加上dont_initialize()使其初始化时不执行。
需要特别注意的是,所有sc_in/sc_out/ sc_signal信号,在write新值的当拍,是read不到这个新值的。Write的新值在下一拍才会被所有的method看到(包括write的这个自身method)。我们可以从打印信息中看出,TestTask::PrintResultMethod()的打印结果总是比Adder::AddMethod()的结果晚一拍。
但是,在sc_trace的波形文件中,信号被write新值的那一时刻就能显示在波形上。所以,我们会发现t_in_0、t_in_1和t_result是在同一拍变化的,但是m_test_result_print就比t_in_0、t_in_1晚了一拍。
Verilog实现
写Verilog的测试激励时,需要注意,不能再clock的上升沿的当时刻,更新input0和input1的值,否则会发现result在当拍就更新了,没有延时一拍的效果,此处具体原因等待后续慢慢研究。
verilog 仿真可以使用轻量版工具icarus verilog + gtkwave,使用方法可以参考我之前的博客:
https://blog.youkuaiyun.com/zgcjaxj/article/details/104853081
systemc实现
/*
Original 2020-03-28
README:
This is a example to teach you how to implement a adder with systemc
and how to bind signal, dump waveform,
execute:
g++ -g -Wall -lsystemc -m64 -pthread main.cpp -L/$(your systemc path)/lib-linux64 -I/$(your systemc path)/include -I/$(your systemc path)/src/tlm_utils -o sim
you need particular attention that
1. sc_in/sc_out/ sc_signal write() new value at 0T, can be read() at 1T.
2. for other points, can check my csdn blog
*/
#include <iostream>
#include "systemc.h"
#define ADD_WIDTH 10
#define RESULT_WIDTH 11
using namespace std;
class Adder: public sc_module
{
public:
SC_HAS_PROCESS(Adder);
Adder(const sc_module_name& name)
: sc_module(name)
{
SC_METHOD(AddMethod);
sensitive<<m_clk.pos();//sensitive event list
dont_initialize();
};
public:
void AddMethod();
~Adder() {;}
public:
sc_in_clk m_clk;
sc_in<sc_uint<ADD_WIDTH> > m_input_0;
sc_in<sc_uint<ADD_WIDTH> > m_input_1;
sc_out<sc_uint<RESULT_WIDTH> > m_output_result;
};
void Adder::AddMethod()
{
sc_uint<RESULT_WIDTH> t_result = m_input_0.read() + m_input_1.read();
m_output_result.write(t_result);
cout<<"["<<sc_time_stamp()
<<"] input is "<<dec<< m_input_0.read()
<<" and "<<m_input_1.read()
<<" , result = "<<t_result
<<endl;
}
class TestTask: public sc_module
{
public:
SC_HAS_PROCESS(TestTask);
TestTask(const sc_module_name& name)
: sc_module(name)
{
SC_THREAD(GeneTestThread);
SC_METHOD(PrintResultMethod);
sensitive<<m_clk.pos();
dont_initialize();
};
public:
void GeneTestThread();
void PrintResultMethod();
~TestTask() {;}
public:
sc_in_clk m_clk;
sc_out<sc_uint<ADD_WIDTH> > m_input_0;
sc_out<sc_uint<ADD_WIDTH> > m_input_1;
sc_in <sc_uint<RESULT_WIDTH> > m_output_result;
sc_signal<sc_uint<RESULT_WIDTH> > m_test_result_print;
};
void TestTask::GeneTestThread()
{
//start test here
wait(1,SC_NS);
m_input_0.write(1);
m_input_1.write(2);
wait(1,SC_NS);
m_input_0.write(3);
m_input_1.write(4);
wait(1,SC_NS);
m_input_0.write(5);
m_input_1.write(6);
}
void TestTask::PrintResultMethod()
{
sc_uint<RESULT_WIDTH> t_result = m_output_result.read() ;
m_test_result_print.write(t_result);
cout<<"["<<sc_time_stamp()
<<"] output result is "<<t_result
<<endl;
}
int sc_main(int argc, char** argv)
{
Adder * m_adder = new Adder("Adder");
TestTask * m_test = new TestTask("TestTask");
//initial clock, set period,
sc_clock t_clock("Clock",sc_time(1,SC_NS));
sc_signal<sc_uint<ADD_WIDTH> > t_test_in_0;
sc_signal<sc_uint<ADD_WIDTH> > t_test_in_1;
sc_signal<sc_uint<RESULT_WIDTH> > t_output;
//bind signal
// sc_in/sc_out must bind to sc_signal
m_adder->m_clk(t_clock);
m_adder->m_input_0(t_test_in_0);
m_adder->m_input_1(t_test_in_1);
m_adder->m_output_result(t_output);
m_test->m_clk(t_clock);
m_test->m_input_0(t_test_in_0);
m_test->m_input_1(t_test_in_1);
m_test->m_output_result(t_output);
//add vcd waveform
sc_trace_file *g_trace_file = sc_create_vcd_trace_file("my_trace");
sc_trace(g_trace_file,t_clock,"Clock");
sc_trace(g_trace_file,t_test_in_0,"t_in_0");
sc_trace(g_trace_file,t_test_in_1,"t_in_1");
sc_trace(g_trace_file,t_output,"t_result");
sc_trace(g_trace_file,m_test->m_test_result_print,"m_test_result_print");
sc_start(8,SC_NS);
return 0;
}
verilog实现
`timescale 100ps/10ps
module test_adder;
reg clk;
reg [9:0] t_in_0; //在激励中设置信号值,必须定义为reg类型
reg [9:0] t_in_1;
wire [10:0] t_result; //必须定义为wire类型
Adder test0 (clk,t_in_0, t_in_1, t_result);
//Clock generation
always #5 clk = ~clk; // always #(cycle /2)
initial
begin
clk = 1'b1; //clk begin is high
t_in_0 = 10'd0;
t_in_1 = 10'd0;
#10; // delay 1 ns
@(posedge clk);#1; //此处的 #1 (也就是延时100ps) 必须要加上
//否则会导致t_result当拍就有结果,没有延时1T的效果
t_in_0 = 10'd1;
t_in_1 = 10'd2;
#8;
@(posedge clk);#1;
t_in_0 = 10'd3;
t_in_1 = 10'd4;
#8;
@(posedge clk);#1;
t_in_0 = 10'd5;
t_in_1 = 10'd6;
# 30 ;
$finish(); //finish simulation
end
//dump waveform
initial
begin
$dumpfile("test_adder.vcd"); //这两行主要是给gtkwave这个工具使用的...
$dumpvars(0,test_adder);
end
endmodule
module Adder(clk,t_in_0, t_in_1, t_result);
input clk;
input [9:0] t_in_0; // input和output信号 缺省为wire类型
input [9:0] t_in_1;
output [10:0] t_result;
reg [10:0] t_test;
reg [10:0] t_result; // 阻塞赋值,t_result设置为register类型
always @( posedge clk )
begin
t_result <= t_in_0 + t_in_1; //过程块内赋值的的每个信号必须为reg类型
t_test <= t_result; //阻塞赋值,达到的效果就是寄存器延时1T
end
endmodule