SystemC自带example的pkt_switch研习

        pkt_switch,此示例演示了4x4多播螺旋数据包交换机(分组交换,具体关于网络术语可以自行学习吧,这里只是学习SystemC的记录,日后若在架构演进自理的所谓异构融合前章 1进过程中涉及到NOC了,估计会专门讨论的).

SystemC自带example的系列:

SystemC自带example的pipe研习

 

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的深度也会起到类似效果。

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

clyfk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值