pkt_switch,此示例演示了4x4多播螺旋数据包交换机(分组交换,具体关于网络术语可以自行学习吧,这里只是学习SystemC的记录,日后若在架构演进自理的所谓异构融合前章 1进过程中涉及到NOC了,估计会专门讨论的).
SystemC自带example的系列:
1 pkt_switch的基本介绍
该交换机使用移位寄存器的自路由环,以流水线方式将信元从一个端口传输到另一个端口,解决了输出争用问题并有效地处理多播信元。输入和输出端口各有深度4的fifo缓冲区。
每个输入端口都连接到一个发送方进程。每个输出端口都连接到一个接收器进程。在实例化过程中,发送方和接收方进程被赋予不同的id号。
发送方进程,将随机值写入数据,并将其发送给四个接收器中的一个或多个。每个数据包还包含一个发送者id字段。发送方进程以随机间隔发送数据包,时间间隔从1到4个单位不等。
每当数据包到达时,接收器进程就被激活。然后它显示数据包的内容和接收者id。
从上面基本的描述大致可知道有几个模块了:fifo、发送、接受和交换网络,再加上一个顶层完成互联。当然,还有一个重要的结构,就是包格式:
在SystemC中使用了结构体来定义这个格式的包,8bit的数据+4bit的源id+4bit的目的地址:
struct pkt {
sc_int<8> data;
sc_int<4> id;
bool dest0;
bool dest1;
bool dest2;
bool dest3;
inline bool operator == (const pkt& rhs) const {
return (rhs.data == data && rhs.id == id && rhs.dest0 == dest0 && rhs.dest1 == dest1 && rhs.dest2 == dest2 && rhs.dest3 == dest3);
}
};
2 fifo的分析
深度4的fifo缓冲结构,宽度自然为ptk格式的包,使用移位寄存器以流水线方式将信元进行缓冲和端口间的传输。
struct fifo {
pkt regs[4];
bool full;
bool empty;
sc_uint<3> pntr;
// constructor
fifo(){
full = false;
empty = true;
pntr = 0;
}
// methods
void pkt_in(const pkt& data_pkt);
pkt pkt_out();
};
void fifo::pkt_in(const pkt& data_pkt)
{
regs[pntr++] = data_pkt; empty = false;
if (pntr == 4) full = true;
}
pkt fifo::pkt_out()
{
pkt temp;
temp = regs[0];
if (--pntr == 0) empty = true;
else {
regs[0] = regs[1];
regs[1] = regs[2];、
regs[2] = regs[3];
}
return(temp);
}
寄存器的定义上看,体现了pkg的宽度和4的深度,包含了空满标志和读写指针以及in与out的方式。构造函数初始化了空满标志和读写指针。满标志在读写指针处于4的位置,空标志在读写指针处于0的位置,非空时需要移位寄存器。
3 发送方
发送方进程,将随机值写入数据,每个数据包还包含一个发送者id字段。发送方进程以随机间隔发送数据包,时间间隔从2到4个单位不等。并将其发送给四个接收器中的一个或多个。
struct sender: sc_module {
sc_out<pkt> pkt_out;
sc_in<sc_int<4> > source_id;
sc_in_clk CLK;
SC_CTOR(sender)
{
SC_CTHREAD(entry, CLK.pos());
}
void entry();
};
void sender:: entry()
{
pkt pkt_data;
sc_uint<4> dest;
srand((unsigned)time(NULL));
wait(8);
while(true) {
/generate an 8-bit random value for data//
pkt_data.data = rand()%255;
stamp the sender's id
pkt_data.id = source_id.read();
/send it to 1 or more(<=4) destinations//
dest = rand()%15 + 1;
pkt_data.dest0 = dest[0];
pkt_data.dest1 = dest[1];
pkt_data.dest2 = dest[2];
pkt_data.dest3 = dest[3];
pkt_out.write(pkt_data);
cout << ".........................." << endl;
cout << "New Packet Sent" << endl;
cout << "Destination Addresses: ";
if (dest[0]) cout << 1 << " " ;
if (dest[1]) cout << 2 << " ";
if (dest[2]) cout << 3 << " ";
if (dest[3]) cout << 4 << " ";
cout << endl;
cout << "Packet Value: " << (int)pkt_data.data << endl;
cout << "Sender ID: " << (int)source_id.read() + 1 << endl;
cout << ".........................." << endl;
wait();
/wait for 1 to 3 clock periods/
wait(1+(rand()%3));
}
}
这里使用了进程SC_CTHREAD,这个是最基础的模块,也算是一类方法进程。在SystemC中,SC_METHOD()、SC_THREAD()、SC_CTHREAD()是三类最基础的模拟并发的模块。
SC_METHOD()内部是不能被挂起的,一旦运行便会运行到底,仿真引擎根据动态敏感列表不断的调用SC_METHOD,类似与verilog中的always块。其中的变量在其每次被调用时都会被声明和初始化,如果希望保存SC_METHOD中的值 , 那么就需要定义局部变量。
SC_THREAD()内部是可以被挂起的,仿真引擎也只会启动其一次,一旦被启动就完全控制仿真过程,直到其自己将控制返回给仿真器。SC_THREAD有两种返回控制给仿真器的方法,一种是简单退出如 return, 这意味着永远结束该过程,因此常常在SC_THREAD中使用含 有wait语句的无限循环。另一种返回控制的方法是用wait来挂起过程,有时候wait并不是被直接使用的, 比如sc_fifo的阻塞读和写在FIFO分别为空或满的时候将隐式的激活wailt。SC_THREAD依靠wait来挂起自身,当wait被执行时,当前SC_ THREAD过程的状态将被保存,同时仿真内核得到控制权, 接着启动另一个准备好的过程。当被挂起的过程重新被启动时,它将从wait后的语句开始执行。
SC_CTHREAD()是SC_THREAD()变形。
4 接收方
每当数据包到达时,接收器进程就被激活。然后它显示数据包的内容以及发送者和接收者id。这里注意一下,使用的是SC_METHOD的方法,一旦运行便会运行到底,仿真引擎根据动态敏感列表不断的调用SC_METHOD,其中的变量在其每次被调用时都会被声明和初始化,如果希望保存SC_METHOD中的值 , 那么就需要定义局部变量。
struct receiver: sc_module {
sc_in<pkt> pkt_in;
sc_in<sc_int<4> > sink_id;
int first;
SC_CTOR(receiver) {
SC_METHOD(entry);
dont_initialize();
sensitive << pkt_in;
first = 1;
}
void entry();
};
void receiver:: entry()
{
pkt temp_val;
// Ignore the first packet arriving on start-on
if (first == 1) {
first = 0;
}
else {
temp_val = pkt_in.read();
cout << " .........................." << endl;
cout << " New Packet Received" << endl;
cout << " Receiver ID: " << (int)sink_id.read() + 1 << endl;
cout << " Packet Value: " << (int)temp_val.data << endl;
cout << " Sender ID: " << (int)temp_val.id + 1 << endl;
cout << " .........................." << endl;
}
}
5 交换网络
先来看看网络的控制时钟的产生,SC_METHOD的方法,一旦运行便会运行到底,仿真引擎根据动态敏感列表不断的调用SC_METHOD,其中的变量在其每次被调用时都会被声明和初始化,这里因为变量使用了static关键字,只会声明和初始化一次,后续每次时钟上升沿来临时将阻塞赋值,先是赋值给switch_cntrl,而后自己取反。
struct switch_clk: sc_module {
sc_out<bool> switch_cntrl;
sc_in_clk CLK;
SC_CTOR(switch_clk) {
SC_METHOD(entry);
dont_initialize();
sensitive << CLK.pos();
}
void entry();
};
void switch_clk::entry()
{
static bool var_switch_cntrl = false;
{
switch_cntrl = var_switch_cntrl;
var_switch_cntrl = !var_switch_cntrl;
}
}
该交换网络的4输入和4输出端口各有深度4的fifo缓冲区,示例中使用了已经定义过的fifo声明并定义了4个in与4个输入端对应、4个out与4个输出端对应。
使用的移位寄存器的自路由环,以流水线方式将信元从一个端口传输到另一个端口,示例中4个R移位寄存器的声明和定义。
关于端口上的event()方法,看看SystemC库实现的源码:
// was there a value changed event?
bool event() const
{ return (*this)->event(); }
struct switch_reg {
pkt val;
bool free;
// constructor
switch_reg() {
free = true;
}
};
struct mcast_pkt_switch : sc_module {
sc_in<bool> switch_cntrl;
sc_in<pkt> in0;
sc_in<pkt> in1;
sc_in<pkt> in2;
sc_in<pkt> in3;
sc_out<pkt> out0;
sc_out<pkt> out1;
sc_out<pkt> out2;
sc_out<pkt> out3;
SC_CTOR(mcast_pkt_switch) {
SC_THREAD(entry);
sensitive << in0;
sensitive << in1;
sensitive << in2;
sensitive << in3;
sensitive << switch_cntrl.pos();
}
void entry();
};
void mcast_pkt_switch :: entry()
{
wait();
// declarations
switch_reg R0;
switch_reg R1;
switch_reg R2;
switch_reg R3;
switch_reg temp;
int sim_count;
int pkt_count;
int drop_count;
fifo q0_in;
fifo q1_in;
fifo q2_in;
fifo q3_in;
fifo q0_out;
fifo q1_out;
fifo q2_out;
fifo q3_out;
// FILE *result;
// initialization
pkt_count = 0; drop_count = 0; sim_count = 0;
q0_in.pntr = 0; q1_in.pntr = 0; q2_in.pntr = 0; q3_in.pntr = 0;
q0_out.pntr = 0; q1_out.pntr = 0; q2_out.pntr = 0; q3_out.pntr = 0;
q0_in.full = false; q1_in.full = false; q2_in.full = false; q3_in.full = false;
q0_in.empty = true; q1_in.empty = true; q2_in.empty = true; q3_in.empty = true;
q0_out.full = false; q1_out.full = false; q2_out.full = false; q3_out.full = false;
q0_out.empty = true; q1_out.empty = true; q2_out.empty = true; q3_out.empty = true;
R0.free = true; R1.free = true; R2.free = true; R3.free = true;
wait();
// functionality
while( sim_count++ < SIM_NUM ) {
wait();
/read input packets//
if (in0.event()) {
pkt_count++;
if (q0_in.full == true)
drop_count++;
else
q0_in.pkt_in(in0.read());
};
if (in1.event()) {
pkt_count++;
if (q1_in.full == true)
drop_count++;
else
q1_in.pkt_in(in1.read());
};
if (in2.event()) {
pkt_count++;
if (q2_in.full == true)
drop_count++;
else
q2_in.pkt_in(in2.read());
};
if (in3.event()) {
pkt_count++;
if (q3_in.full == true)
drop_count++;
else
q3_in.pkt_in(in3.read());
};
/move the packets from fifo to shift register ring/
if((!q0_in.empty) && R0.free) {
R0.val = q0_in.pkt_out();
R0.free = false;
}
if((!q1_in.empty) && R1.free) {
R1.val = q1_in.pkt_out();
R1.free = false;
}
if((!q2_in.empty) && R2.free) {
R2.val = q2_in.pkt_out();
R2.free = false;
}
if((!q3_in.empty) && R3.free) {
R3.val = q3_in.pkt_out();
R3.free = false;
}
if((bool)switch_cntrl && switch_cntrl.event()){
/shift the channel registers /
temp = R0; R0 = R1; R1 = R2; R2 = R3;R3 = temp;
/write the register values to output fifos
if ((!R0.free) && (R0.val.dest0) && (!q0_out.full)){
q0_out.pkt_in(R0.val);
R0.val.dest0 = false;
if (!(R0.val.dest0|R0.val.dest1|R0.val.dest2|R0.val.dest3)) R0.free = true;
}
if ((!R1.free) && (R1.val.dest1) && (!q1_out.full)){
q1_out.pkt_in(R1.val);
R1.val.dest1 = false;
if (!(R1.val.dest1|R1.val.dest1|R1.val.dest2|R1.val.dest3)) R1.free = true;
}
if ((!R2.free) && (R2.val.dest2) && (!q2_out.full)){
q2_out.pkt_in(R2.val);
R2.val.dest2 = false;
if (!(R2.val.dest2|R2.val.dest1|R2.val.dest2|R2.val.dest3)) R2.free = true;
}
if ((!R3.free) && (R3.val.dest3) && (!q3_out.full)){
q3_out.pkt_in(R3.val);
R3.val.dest3 = false;
if (!(R3.val.dest3|R3.val.dest1|R3.val.dest2|R3.val.dest3)) R3.free = true;
}
/write the packets out//
if (!q0_out.empty) out0.write(q0_out.pkt_out());
if (!q1_out.empty) out1.write(q1_out.pkt_out());
if (!q2_out.empty) out2.write(q2_out.pkt_out());
if (!q3_out.empty) out3.write(q3_out.pkt_out());
}
}
sc_stop();
}
6 顶层互联
互联模块控制了仿真的开始,但是结束是在交换网络中控制的,这里将对上面的部分模块进行波形上的分析。
#include "systemc.h"
#include "pkt.h"
#include "switch_clk.h"
#include "sender.h"
#include "receiver.h"
#include "switch.h"
int sc_main(int, char *[])
{
sc_signal<pkt> pkt_in0;
sc_signal<pkt> pkt_in1;
sc_signal<pkt> pkt_in2;
sc_signal<pkt> pkt_in3;
sc_signal<pkt> pkt_out0;
sc_signal<pkt> pkt_out1;
sc_signal<pkt> pkt_out2;
sc_signal<pkt> pkt_out3;
sc_signal<sc_int<4> > id0, id1, id2, id3;
sc_signal<bool> switch_cntrl;
sc_clock clock1("CLOCK1", 75, SC_NS, 0.5, 0.0, SC_NS);
sc_clock clock2("CLOCK2", 30, SC_NS, 0.5, 10.0, SC_NS);
// Module instiatiations follow
// Note that modules can be connected by hooking up ports
// to signals by name or by using a positional notation
sender sender0("SENDER0");
// hooking up signals to ports by name
sender0.pkt_out(pkt_in0); sender0.source_id(id0); sender0.CLK(clock1);
sender sender1("SENDER1");
// hooking up signals to ports by position
sender1(pkt_in1, id1, clock1);
sender sender2("SENDER2");
// hooking up signals to ports by name
sender2.pkt_out(pkt_in2); sender2.source_id(id2); sender2.CLK(clock1);
sender sender3("SENDER3");
// hooking up signals to ports by position
sender3( pkt_in3, id3, clock1 );
switch_clk switch_clk1("SWITCH_CLK");
// hooking up signals to ports by name
switch_clk1.switch_cntrl(switch_cntrl);
switch_clk1.CLK(clock2);
mcast_pkt_switch switch1("SWITCH");
// hooking up signals to ports by name
switch1.switch_cntrl(switch_cntrl);
switch1.in0(pkt_in0); switch1.in1(pkt_in1); switch1.in2(pkt_in2); switch1.in3(pkt_in3);
switch1.out0(pkt_out0); switch1.out1(pkt_out1); switch1.out2(pkt_out2); switch1.out3(pkt_out3);
receiver receiver0("RECEIVER0");
// hooking up signals to ports by name
receiver0.pkt_in(pkt_out0); receiver0.sink_id(id0);
receiver receiver1("RECEIVER1");
// hooking up signals to ports by position
receiver1( pkt_out1, id1 );
receiver receiver2("RECEIVER2");
// hooking up signals to ports by name
receiver2.pkt_in(pkt_out2); receiver2.sink_id(id2);
receiver receiver3("RECEIVER3");
// hooking up signals to ports by position
receiver3( pkt_out3, id3 );
sc_trace_file *fp; // Create VCD file
fp=sc_create_vcd_trace_file("wave");// open(fp), create wave.vcd file
fp->set_time_unit(1, SC_NS); // set tracing resolution to ns
sc_trace(fp,clock1,"CLOCK1");
sc_trace(fp,clock2,"CLOCK2");
sc_trace(fp,switch_cntrl,"switch_cntrl");
sc_trace(fp,pkt_in0,"pkt_in0");
sc_trace(fp,pkt_in1,"pkt_in1");
sc_trace(fp,pkt_in2,"pkt_in2");
sc_trace(fp,pkt_in3,"pkt_in3");
sc_trace(fp,pkt_out0,"pkt_out0");
sc_trace(fp,pkt_out1,"pkt_out1");
sc_trace(fp,pkt_out2,"pkt_out2");
sc_trace(fp,pkt_out3,"pkt_out3");
sc_trace(fp,id0,"id0");
sc_trace(fp,id1,"id1");
sc_trace(fp,id2,"id2");
sc_trace(fp,id3,"id3");
sc_start(0, SC_NS);
id0.write(sc_int<4>(0));
id0.write(sc_int<4>(1));
id0.write(sc_int<4>(2));
id0.write(sc_int<4>(3));
sc_start();
return 0;
}
6.1 仿真控制上的时钟
这里新的语法就是sc_clock,下面是文件sc_clock.h的定义:
sc_core::sc_clock::sc_clock | ( | const char * | name_, | |
double | period_v_, | |||
sc_time_unit | period_tu_, | |||
double | duty_cycle_, | |||
double | start_time_v_, | |||
sc_time_unit | start_time_tu_, | |||
bool | posedge_first_ = true | |||
) |
sc_clock clock1("CLOCK1", 75, SC_NS, 0.5, 0.0, SC_NS);创建时钟CLOCK1,其周期为75ns,占空比50%,第一个跳边沿出现在0ns之后且在第一个跳边沿之后的初始值为真(1)。
sc_clock clock2("CLOCK2", 30, SC_NS, 0.5, 10.0, SC_NS);创建时钟CLOCK2,其周期为30ns,占空比50%,第一个跳边沿出现在10ns之后且在第一个跳边沿之后的初始值为真(1)。
在SystemC中,如果没有指定周期,默认1个时间单位,而默认的时间单位为1ns。时钟的波形见下图:
6.2 发送和4输入
实例化4个发送,分别对应4个输入端口。从波形来看,皆是等待8个节拍开始初始化数据的,以随机时间间隔从2到4个单位不等发送数据包,并将其发送给四个接收器中的一个或多个。
这里就不一一的截图了,终端打印的信息可以参考一下,试着把两个时钟频率修改一下看一下丢包率,当然fifo的深度也会起到类似效果。