SystemVerilog课程笔记(五)


1. 构建generator

stimulator只发送数据,构建generator产生数据发送给stimulator
数组与队列用法见[[SystemVerilog学习笔记(五)]]

1.1改造

定义put_pkt函数,将结构体p放入队列pkts里面

function void put_pkt(input rt_packet_t p);
pkts.push_back(p);
endfunction

定义get_pkt任务,将p从队列pkts里面取出

task get_pkt(output rt_packet_t p);
wait(pkts.size() >0 ) //队列不为空
p = pkts.pop_front();
endtask

数据发送过程

## 1.2改造后的tb.sv ```systemverilog typedef struct{ bit [3:0] src; bit [3:0] dst; bit [7:0] data []; }rt_packet_t;//定义结构体,结构体成员为发送端口,目标端口,发送数据

//********************************** stimulator **********************************//
module rt_stimulator(
input clock
,input reset_n
,output logic [15:0] din
,output logic [15:0] frame_n
,output logic [15:0] valid_n
,input [15:0] dout
,input [15:0] valido_n
,input [15:0] busy_n
,input [15:0] frameo_n
);
//for debug purpose from waveform //定义检测状态的变量
typedef enum {DRV_RESET,DRV_IDLE,DRV_ADDR,DRV_PAD,DRV_DATA} drv_state_t;
drv_state_t dbg_state;
byte unsigned dbg_din_chnl0_data;

//receive p from generator
rt_packet_t pkts[$];//定义stimulator中的的队列okts
function void put_pkt(input rt_packet_t p);
pkts.push_back(p); //将generator中传过来的p放入stimulator的pkts中(在pkts队列尾插入p)
endfunction

//reset阶段,复位时,reset_n为低电平,frame_n和valid_n为高电平
initial begin : drive_reset_proc
drive_reset();
end

task drive_reset();
@(negedge reset_n);
dbg_state <= DRV_RESET;
din <= 0;
frame_n <= ‘1;//等效16’hFFFF
valid_n <= ‘1;
endtask

// drive chaannel 0 - chanel 15 (din[15:0])定义数据输出地址及数据
bit [3:0] daddr;
byte unsigned data[];

initial begin : drive_chnl0_proc
rt_packet_t p;
@(negedge reset_n);
repeat(10) @(posedge clock);//延迟10个时钟周期
forever begin
wait(pkts.size()>0);
p = pkts.pop_front(); //将p从队列pkts里面取出
drive_chnl(p.src,p.dst,p.data);//从P中拿到发送端口,目标端口,发送数据
end
//drive_chnl(0,3,’{8’h33,8’h77});//name mapping
//drive_chnl(0,5,’{8’h55,8’h66});//name mapping
end
initial begin : drive_chnl3_proc
@(negedge reset_n);
repeat(10) @(posedge clock);//延迟10个时钟周期
drive_chnl(3,6,’{8’h77,8’h88,8’h22});//name mapping
end

task automatic drive_chnl(bit[3:0] saddr, bit [3:0] daddr, byte unsigned data[]);
$display(“[DRV]src_chnl[%0d],dest_chnl[%0d] data trans started”,saddr,daddr);
// drive address phase 输入地址位阶段
for(int i=0;i<4;i++)begin //4 clock
@(posedge clock);
dbg_state <=DRV_ADDR;
din[saddr] <= daddr[i];
valid_n[saddr] <= $urandom_range(0,1); //valid_n在din的地址输入时间段可为任意值x
frame_n[saddr] <= 1’b0; //frame_n需要为低
end
// drive pad phase //隔离阶段
for (int i=0;i<5;i++)begin //5 clock
@(posedge clock);
dbg_state <=DRV_PAD;
din[saddr] <= 1’b1;
valid_n[saddr] <= 1’b1; //valid_n需为高电平
frame_n[saddr] <= 1’b0; //frame_n需为低电平
end
// drive data phase 传输数据阶段
foreach(data[id])begin
for(int i=0;i<8;i++)begin
@(posedge clock);
dbg_state <=DRV_DATA;
dbg_din_chnl0_data <= data[id];
din[saddr] <= data[id][i];
valid_n[saddr] <=1’b0;
frame_n[saddr] <= (id == data.size()-1 && i == 7) ? 1’b1 : 1’b0;//packet最后一位输出数据时frameo_n为高
end
end
// drive idle phase 闲置(没有数据传输)阶段
@(posedge clock);
dbg_state <=DRV_IDLE;
dbg_din_chnl0_data <= 0;
din[saddr] <= 1’b0;
valid_n[saddr] <= 1’b1;
frame_n[saddr] <= 1’b1;
$display(“[DRV]src_chnl[%0d],dest_chnl[%0d] data trans finished”,saddr,daddr);
endtask
endmodule

//********************************** generator **********************************//
module rt_generator; //generator产生数据交给stimulator
rt_packet_t pkts[$]; //定义队列

function void put_pkt(input rt_packet_t p);
pkts.push_back(p); //将p放入队列pkts里面(在pkts队列尾插入p)
endfunction

task get_pkt(output rt_packet_t p);
wait(pkts.size() >0 ) //队列不为空
p = pkts.pop_front(); //将p从队列pkts里面取出,提取队列首元素
endtask

//generate a random packet
function void gen_pkt(int src = -1, int dst = -1);
endfunction
endmodule

//********************************** tb **********************************//
module tb;

bit clk,rstn;
logic [15:0] din, frame_n, valid_n;
logic [15:0] dout, valido_n, busy_n, frameo_n;

// 产生时钟,周期为10ns
initial
forever #5ns clk <= !clk;

// 产生复位信号
initial begin
#2ns rstn <= 1;
#10ns rstn <= 0;
#10ns rstn <= 1;
end

//例化router为DUT
router dut(
.reset_n(rstn),
.clock(clk),
.* //其余端口名称均相同
);

//例化stimulator
rt_stimulator stim(
.reset_n(rstn),
.clock(clk),
.*
);

//例化generator
rt_generator gen();

initial begin:generate_data_proc//产生数据
rt_packet_t p;
p = ‘{0,3,’{8’h33,8’h77}};
gen.put_pkt(p); //调用genarator里面的put_pkt函数
gen.put_pkt(‘{0,5,’{8’h55,8’h66}});
end
initial begin:genarator_to_stimulator_proc//取出genarator中的数据给stimulator
rt_packet_t p;
forever begin
gen.get_pkt(p);
stim.put_pkt(p);
end
end

endmodule

## 1.3仿真结果
同前面一样
<div align=center><img src=" https://cloudpicture-1313887899.cos.ap-chongqing.myqcloud.com/blog_picture/202211051044480.png" width="100%" height="100%"></div>
## 1.4问题
但是如果生成数据采用如下方式,不同source_chnl不能同时发送数据(<span style="background:rgba(240, 200, 0, 0.2)">上一节是因为采用了两个initial begin end语句块才能并行发送</span>)
>gen.put_pkt('{0,3,'{8'h33,8'h77}});	
>gen.put_pkt('{0,5,'{8'h55,8'h66}});
>gen.put_pkt('{3,6,'{8'h77,8'h88,8'h22}});
>//source_chnl0和source_chnl3不能同时发送

仿真结果如下
<div align=center><img src="https://cloudpicture-1313887899.cos.ap-chongqing.myqcloud.com/blog_picture/202211051554321.png " width="100%" height="100%"></div>

# 2. 来自于不同的source chnl可以并行发送
## 2.1改造
**增加要求**:如果两个chnl没有冲突,可以同时发送
**具体为:**
来自于不同的source chnl就可以并行发送
source chnl、destination chnl不一样可以并行发送
**方法:**
* **定义关联数组**
int src_chnl_status[int];  //关联数组

* **定义判断src_chnl是否空闲的任务**
task判断src chnl是否被占用,是否需要等待。src_chnl_status关联数组里面不含p.src,(表示未占用任何dest_chnl,一开始不满足)。反之,如果含有,表示当前正在使用src_chnl,对应dest_chnl就是p.dst(p.src端口在向p.dst端口发送数据)。如果在给0,1,2...dest_chnl发送数据(被占用),需要等待,,否则不需要等待。直到src_chnl_status[p.src] == -1(自定义的dest_chnl端口数之外任意的数,表示未占用任何dest_chnl)
>task automatic wait_src_chnl_avail(rt_packet_t p);
    if(!src_chnl_status.exists(p.src))//src_chnl_status关联数组里面不含p.src,(表示未占用任何dest_chnl,一开始不满足)
        src_chnl_status[p.src] = p.dst;//表示当前正在使用src_chnl,对应dest_chnl就是p.dst(p.src端口在向p.dst端口发送数据)
    else if(src_chnl_status[p.src] >= 0)//如果在给0,1,2...dest_chnl发送数据(被占用),需要等待,,否则不需要等待
        wait(src_chnl_status[p.src] == -1);//直到src_chnl_status[p.src] == -1(自定义的dest_chnl端口数之外任意的数,表示未占用任何dest_chnl)
endtask

* **采用fork join none后台线程,判断src chnl是否被占用,是否需要等待**
>fork//后台触发线程,触发线程在后台运行,继续执行剩下内容
        begin	
            wait_src_chnl_avail(p);//判断src chnl是否被占用,是否需要等待
            drive_chnl(p.src, p.dst, p.data);//从P中拿到发送端口,目标端口,发送数据
            set_src_chnl_avail(p);
        end
    join_none

* 然后把src_chnl置位
>function automatic set_src_chnl_avail(rt_packet_t p);
    src_chnl_status[p.src] = -1;//如果chnl发送完后,把src_chnl置回来(自定义的dest_chnl端口数之外任意的数,表示未占用任何dest_chnl)
endfunction

## 2.2改造后的tb.sv
```systemverilog
typedef struct{
  bit [3:0] src;
  bit [3:0] dst;
  bit [7:0] data [];
}rt_packet_t;//定义结构体,结构体成员为发送端口,目标端口,发送数据

//********************************** stimulator **********************************//
module rt_stimulator(
  input clock
  ,input reset_n
  ,output logic [15:0] din
  ,output logic [15:0] frame_n
  ,output logic [15:0] valid_n
  ,input [15:0] dout
  ,input [15:0] valido_n
  ,input [15:0] busy_n
  ,input [15:0] frameo_n
);
//for debug purpose from waveform	//定义检测状态的变量
  typedef enum {DRV_RESET,DRV_IDLE,DRV_ADDR,DRV_PAD,DRV_DATA} drv_state_t;
  drv_state_t dbg_state;  
  byte unsigned dbg_din_chnl0_data; 

    int src_chnl_status[int];  //关联数组,后面int:src_chnl的number;前面int:dest_chnl的Number ??
    
//receive p from generator
  rt_packet_t pkts[$];//定义stimulator中的的队列okts
  function void put_pkt(input rt_packet_t p);
    pkts.push_back(p);	//将generator中传过来的p放入stimulator的pkts中(在pkts队列尾插入p)
  endfunction

//reset阶段,复位时,reset_n为低电平,frame_n和valid_n为高电平
initial begin : drive_reset_proc 
  drive_reset();
end

task drive_reset();
  @(negedge reset_n);
  dbg_state <= DRV_RESET;
  din <= 0;
  frame_n <= '1;//等效16'hFFFF
  valid_n <= '1;
endtask

// 发送数据
bit [3:0] daddr;
byte unsigned data[];

initial begin : drive_chnl_proc
    //rt_packet_t p;
  @(negedge reset_n);
  repeat(10) @(posedge clock);//延迟10个时钟周期
  forever begin	
    automatic rt_packet_t p;//声明一个动态的
    wait(pkts.size()>0);
    p = pkts.pop_front();	//将p从队列pkts里面取出
    
    fork//后台触发线程,触发线程在后台运行,继续执行剩下内容
        begin	
            wait_src_chnl_avail(p);//判断src chnl是否被占用,是否需要等待
            drive_chnl(p.src, p.dst, p.data);//从P中拿到发送端口,目标端口,发送数据
            set_src_chnl_avail(p);
        end
    join_none
    
    end
    //drive_chnl(0,3,'{8'h33,8'h77});//name mapping
    //drive_chnl(0,5,'{8'h55,8'h66});//name mapping
end

task automatic wait_src_chnl_avail(rt_packet_t p);//判断src chnl是否被占用,是否需要等待
    if(!src_chnl_status.exists(p.src))//src_chnl_status关联数组里面不含p.src,(表示未占用任何dest_chnl,一开始不满足)
        src_chnl_status[p.src] = p.dst;//表示当前正在使用src_chnl,对应dest_chnl就是p.dst(p.src端口在向p.dst端口发送数据)
    else if(src_chnl_status[p.src] >= 0)//如果在给0,1,2...dest_chnl发送数据(被占用),需要等待,,否则不需要等待
        wait(src_chnl_status[p.src] == -1);//直到src_chnl_status[p.src] == -1(自定义的dest_chnl端口数之外任意的数,表示未占用任何dest_chnl)
endtask

function automatic set_src_chnl_avail(rt_packet_t p);
    src_chnl_status[p.src] = -1;//如果chnl发送完后,把src_chnl置回来(自定义的dest_chnl端口数之外任意的数,表示未占用任何dest_chnl)
endfunction

task automatic drive_chnl(bit[3:0] saddr, bit [3:0] daddr, byte unsigned data[]);
  $display("@%0t:[DRV]src_chnl[%0d],dest_chnl[%0d] data trans started",$time,saddr,daddr);
    // drive address phase 输入地址位阶段
for(int i=0;i<4;i++)begin  //4 clock
  @(posedge clock);
  dbg_state <=DRV_ADDR;	
  din[saddr] <= daddr[i];
  valid_n[saddr] <= $urandom_range(0,1);	//valid_n在din的地址输入时间段可为任意值x
  frame_n[saddr] <= 1'b0;	//frame_n需要为低
end
    // drive pad phase //隔离阶段
  for (int i=0;i<5;i++)begin  //5 clock
    @(posedge clock);
    dbg_state <=DRV_PAD;
    din[saddr] <= 1'b1;
    valid_n[saddr] <= 1'b1;	//valid_n需为高电平
    frame_n[saddr] <= 1'b0; //frame_n需为低电平
  end
    // drive data phase 传输数据阶段
  foreach(data[id])begin
    for(int i=0;i<8;i++)begin
     @(posedge clock);
      dbg_state <=DRV_DATA;
      dbg_din_chnl0_data <= data[id];
      din[saddr] <= data[id][i];
      valid_n[saddr] <=1'b0;
      frame_n[saddr] <= (id == data.size()-1 && i == 7) ? 1'b1 : 1'b0;//packet最后一位输出数据时frameo_n为高
    end
  end
// drive idle phase 闲置(没有数据传输)阶段
  @(posedge clock);
  dbg_state <=DRV_IDLE;
  dbg_din_chnl0_data <= 0;
  din[saddr] <= 1'b0;
  valid_n[saddr] <= 1'b1;
  frame_n[saddr] <= 1'b1;
  $display("@%0t:[DRV]src_chnl[%0d],dest_chnl[%0d] data trans finished",$time,saddr,daddr);
endtask
endmodule

//********************************** generator **********************************//
module rt_generator;	//generator产生数据交给stimulator
  rt_packet_t pkts[$];	//定义队列

  function void put_pkt(input rt_packet_t p);
    pkts.push_back(p);	//将p放入队列pkts里面(在pkts队列尾插入p)
  endfunction
  
  task get_pkt(output rt_packet_t p);
    wait(pkts.size() >0 )	//队列不为空
      p = pkts.pop_front();	//将p从队列pkts里面取出,提取队列首元素
  endtask

  //generate a random packet
  function void gen_pkt(int src = -1, int dst = -1);
  endfunction
endmodule

//********************************** tb **********************************//
module tb;

bit clk,rstn;
logic [15:0] din, frame_n, valid_n;
logic [15:0] dout, valido_n, busy_n, frameo_n;

// 产生时钟,周期为10ns
initial 
    forever #5ns clk <= !clk;

// 产生复位信号
  initial begin
    #2ns rstn <= 1;
    #10ns rstn <= 0;
    #10ns rstn <= 1;
  end

//例化router为DUT
router dut(           
  .reset_n(rstn),
  .clock(clk),
  .*  //其余端口名称均相同
);

//例化stimulator
rt_stimulator stim(
  .reset_n(rstn),
  .clock(clk),
  .*
);

//例化generator
rt_generator gen();

initial begin:generate_data_proc//产生数据
    rt_packet_t p;
    gen.put_pkt('{0,3,'{8'h33,8'h77}});	//调用genarator里面的put_pkt函数
    gen.put_pkt('{0,5,'{8'h55,8'h66}});
    gen.put_pkt('{3,6,'{8'h77,8'h88,8'h22}});
    gen.put_pkt('{4,7,'{8'haa,8'hcc,8'h33}});		
end
initial begin:genarator_to_stimulator_proc//取出genarator中的数据给stimulator
    rt_packet_t p;
    forever begin
        gen.get_pkt(p);
        stim.put_pkt(p);
    end
end

endmodule 

2.3仿真结果


3. 增加接口(interface)

interface用法见[[SystemVerilog学习笔记(三)]]

3.1改进

增加接口模块

interface rt_interface();
logic clock;
logic reset_n;
logic [15:0] din;
logic [15:0] frame_n;
logic [15:0] valid_n;
logic [15:0] dout;
logic [15:0] valido_n;
logic [15:0] busy_n;
logic [15:0] frameo_n;
endinterface

3.2改造后的tb.sv

typedef struct{
  bit [3:0] src;
  bit [3:0] dst;
  bit [7:0] data [];
}rt_packet_t;//定义结构体,结构体成员为发送端口,目标端口,发送数据

interface rt_interface();
    logic clock;
    logic reset_n;
    logic [15:0] din;
    logic [15:0] frame_n;
    logic [15:0] valid_n;
    logic [15:0] dout;
    logic [15:0] valido_n;
    logic [15:0] busy_n;
    logic [15:0] frameo_n;
endinterface
//********************************** stimulator **********************************//
module rt_stimulator(rt_interface intf);
//for debug purpose from waveform	//定义检测状态的变量
  typedef enum {DRV_RESET,DRV_IDLE,DRV_ADDR,DRV_PAD,DRV_DATA} drv_state_t;
  drv_state_t dbg_state;  
  byte unsigned dbg_din_chnl0_data; 

    int src_chnl_status[int];  //关联数组,后面int:src_chnl的number;前面int:dest_chnl的Number ??
    
//receive p from generator
  rt_packet_t pkts[$];//定义stimulator中的的队列okts
  function void put_pkt(input rt_packet_t p);
    pkts.push_back(p);	//将generator中传过来的p放入stimulator的pkts中(在pkts队列尾插入p)
  endfunction

//reset阶段,复位时,reset_n为低电平,frame_n和valid_n为高电平
initial begin : drive_reset_proc 
  drive_reset();
end

task drive_reset();
  @(negedge intf.reset_n);
  dbg_state <= DRV_RESET;
  intf.din <= 0;
  intf.frame_n <= '1;//等效16'hFFFF
  intf.valid_n <= '1;
endtask

// 发送数据
bit [3:0] daddr;
byte unsigned data[];

initial begin : drive_chnl_proc
    //rt_packet_t p;
  @(negedge intf.reset_n);
  repeat(10) @(posedge intf.clock);//延迟10个时钟周期
  forever begin	
    automatic rt_packet_t p;//声明一个动态的
    wait(pkts.size()>0);
    p = pkts.pop_front();	//将p从队列pkts里面取出
    
    fork//后台触发线程,触发线程在后台运行,继续执行剩下内容
        begin	
            wait_src_chnl_avail(p);//判断src chnl是否被占用,是否需要等待
            drive_chnl(p.src, p.dst, p.data);//从P中拿到发送端口,目标端口,发送数据
            set_src_chnl_avail(p);
        end
    join_none
    
    end
    //drive_chnl(0,3,'{8'h33,8'h77});//name mapping
    //drive_chnl(0,5,'{8'h55,8'h66});//name mapping
end

task automatic wait_src_chnl_avail(rt_packet_t p);//判断src chnl是否被占用,是否需要等待
    if(!src_chnl_status.exists(p.src))//src_chnl_status关联数组里面不含p.src,(表示未占用任何dest_chnl,一开始不满足)
        src_chnl_status[p.src] = p.dst;//表示当前正在使用src_chnl,对应dest_chnl就是p.dst(p.src端口在向p.dst端口发送数据)
    else if(src_chnl_status[p.src] >= 0)//如果在给0,1,2...dest_chnl发送数据(被占用),需要等待,,否则不需要等待
        wait(src_chnl_status[p.src] == -1);//直到src_chnl_status[p.src] == -1(自定义的dest_chnl端口数之外任意的数,表示未占用任何dest_chnl)
endtask

function automatic set_src_chnl_avail(rt_packet_t p);
    src_chnl_status[p.src] = -1;//如果chnl发送完后,把src_chnl置回来(自定义的dest_chnl端口数之外任意的数,表示未占用任何dest_chnl)
endfunction

task automatic drive_chnl(bit[3:0] saddr, bit [3:0] daddr, byte unsigned data[]);
  $display("@%0t:[DRV]src_chnl[%0d],dest_chnl[%0d] data trans started",$time,saddr,daddr);
    // drive address phase 输入地址位阶段
for(int i=0;i<4;i++)begin  //4 clock
  @(posedge intf.clock);
  dbg_state <=DRV_ADDR;	
  intf.din[saddr] <= daddr[i];
  intf.valid_n[saddr] <= $urandom_range(0,1);	//valid_n在din的地址输入时间段可为任意值x
  intf.frame_n[saddr] <= 1'b0;	//frame_n需要为低
end
    // drive pad phase //隔离阶段
  for (int i=0;i<5;i++)begin  //5 clock
    @(posedge intf.clock);
    dbg_state <=DRV_PAD;
    intf.din[saddr] <= 1'b1;
    intf.valid_n[saddr] <= 1'b1;	//valid_n需为高电平
    intf.frame_n[saddr] <= 1'b0; //frame_n需为低电平
  end
    // drive data phase 传输数据阶段
  foreach(data[id])begin
    for(int i=0;i<8;i++)begin
     @(posedge intf.clock);
      dbg_state <=DRV_DATA;
      dbg_din_chnl0_data <= data[id];
      intf.din[saddr] <= data[id][i];
      intf.valid_n[saddr] <=1'b0;
      intf.frame_n[saddr] <= (id == data.size()-1 && i == 7) ? 1'b1 : 1'b0;//packet最后一位输出数据时frameo_n为高
    end
  end
// drive idle phase 闲置(没有数据传输)阶段
  @(posedge intf.clock);
  dbg_state <=DRV_IDLE;
  dbg_din_chnl0_data <= 0;
  intf.din[saddr] <= 1'b0;
  intf.valid_n[saddr] <= 1'b1;
  intf.frame_n[saddr] <= 1'b1;
  $display("@%0t:[DRV]src_chnl[%0d],dest_chnl[%0d] data trans finished",$time,saddr,daddr);
endtask
endmodule

//********************************** generator **********************************//
module rt_generator;	//generator产生数据交给stimulator
  rt_packet_t pkts[$];	//定义队列

  function void put_pkt(input rt_packet_t p);
    pkts.push_back(p);	//将p放入队列pkts里面(在pkts队列尾插入p)
  endfunction
  
  task get_pkt(output rt_packet_t p);
    wait(pkts.size() >0 )	//队列不为空
      p = pkts.pop_front();	//将p从队列pkts里面取出,提取队列首元素
  endtask

  //generate a random packet
  function void gen_pkt(int src = -1, int dst = -1);
  endfunction
endmodule

//********************************** tb **********************************//
module tb;

bit clk,rstn;
logic [15:0] din, frame_n, valid_n;
logic [15:0] dout, valido_n, busy_n, frameo_n;

// 产生时钟,周期为10ns
initial 
    forever #5ns clk <= !clk;

// 产生复位信号
  initial begin
    #2ns rstn <= 1;
    #10ns rstn <= 0;
    #10ns rstn <= 1;
  end

//例化router为DUT
router dut(           
  .reset_n(rstn),
  .clock(clk),
  .frame_n(intf.frame_n),
  .valid_n(intf.valid_n),
  .dout(intf.dout),
  .busy_n(intf.busy_n),
  .valido_n(intf.valido_n),
  .frameo_n(intf.frameo_n)
);

rt_interface intf();//例化接口
    assign intf.reset_n = rstn;
    assign intf.clock = clk;
    
//例化stimulator
rt_stimulator stim(intf);

//例化generator
rt_generator gen();
    
initial begin:generate_data_proc//产生数据
    rt_packet_t p;
    gen.put_pkt('{0,3,'{8'h33,8'h77}});	//调用genarator里面的put_pkt函数
    gen.put_pkt('{0,5,'{8'h55,8'h66}});
    gen.put_pkt('{3,6,'{8'h77,8'h88,8'h22}});
    gen.put_pkt('{4,7,'{8'haa,8'hcc,8'h33}});		
end
initial begin:genarator_to_stimulator_proc//取出genarator中的数据给stimulator
    rt_packet_t p;
    forever begin
        gen.get_pkt(p);
        stim.put_pkt(p);
    end
end

endmodule 

3.3仿真结果

只增加了接口,仿真结果未变

硬件层次结构如下
# 4. 初步TestBench概览

5. 增加monitor

5.1 改造

增加组件monitor,监测输入数据和输出数据
监测数据输入的task:

task automatic mon_chnl_in (bit[3:0]id);//监测数据输入的任务
    rt_packet_t pkt;	//定义结构体变量
    forever begin
    //clear content for the same struct variable。清除pkt
    pkt.data.delete();
    pkt.src = id;	//第id个输入端口
    //monitor specific channel-in data and put it into the queue
    // monitor address phase
    @(negedge intf.frame_n[id]);//监测frame_n下降沿(frame_n由时钟驱动)
    $display ("@%0t: [MON] src_chn1[%0d] & dest_chn1[%0d] data trans started",$time,pkt.src,pkt.dst);
    for(int i=0; i<4; i++)begin
        @(negedge intf.clock);		//frame_n下降沿后监测4个clk negedge
        pkt.dst[i]= intf.din[id];
        end
    //pass pad phase 不考虑pad阶段是否满足协议要求
    repeat(5) @(negedge intf.clock);
    do begin
        pkt.data = new [pkt.data.size + 1](pkt.data);//创建动态数组并复制pkt.data
        for ( int i=0; i<8; i++) begin
            @(negedge intf.clock); //在8个clk negedge监测8bit数据
            pkt.data[pkt.data.size-1][i] = intf.din[id];
        end
    end while(!intf.frame_n[id]);
    in_pkts[id].push_back(pkt);	//将monitor拿到的数据放入in_pkts
    $display("@%0t: [MON] src_chn1[%0d] & dest_chn1[%0d] data trans [%0p] finished",$time,pkt.src,pkt.dst,pkt.data);
    end
endtask

监测数据输入的task:

task automatic mon_chnl_out(bit[ 3:0]id);//监测数据输出的任务
    rt_packet_t pkt;
    forever begin
        //clear content for the same struct variable
        pkt.data.delete();
        pkt.src = 0;
        pkt.dst = id;	
        @(negedge intf.frameo_n[id]);
        $display( "@%0t: [MON] CH_OUT dest_chn1[%0d] data trans started",$time,pkt.dst);
        do begin
        pkt.data = new [ pkt.data.size + 1](pkt.data);
        for(int i=0;i<8; i++) begin
            @(negedge intf.clock iff !intf.valido_n[id]);//clock与valido_n信号同时为低
            pkt.data[pkt.data.size-1][i]= intf.dout [id];
        end
    end while(!intf.frameo_n[id]);
    out_pkts[id].push_back(pkt);
    $display("@%0t: [MON] CH_OUT dest_chn1[%0d] data trans [%0p] finished",$time,pkt.dst,pkt.data);
    //monitor specific channel-out data and put it into the queue
    end
endtask

5.2 改造后的tb.sv

typedef struct{
  bit [3:0] src;
  bit [3:0] dst;
  bit [7:0] data [];
}rt_packet_t;//定义结构体,结构体成员为发送端口,目标端口,发送数据

interface rt_interface();
    logic clock;
    logic reset_n;
    logic [15:0] din;
    logic [15:0] frame_n;
    logic [15:0] valid_n;
    logic [15:0] dout;
    logic [15:0] valido_n;
    logic [15:0] busy_n;
    logic [15:0] frameo_n;
endinterface
//********************************** stimulator **********************************//
module rt_stimulator(
    rt_interface intf
);
//for debug purpose from waveform	//定义检测状态的变量
  typedef enum {DRV_RESET,DRV_IDLE,DRV_ADDR,DRV_PAD,DRV_DATA} drv_state_t;
  drv_state_t dbg_state;  
  byte unsigned dbg_din_chnl0_data; 
  int src_chnl_status[int];  //关联数组,后面int:src_chnl的number;前面int:dest_chnl的Number ??
    
//generator传送p给stimulator
  rt_packet_t pkts[$];//定义stimulator中的的队列pkts
  function void put_pkt(input rt_packet_t p);
    pkts.push_back(p);	//将generator中传过来的p放入stimulator的pkts中(在pkts队列尾插入p)
  endfunction

//reset阶段,复位时,reset_n为低电平,frame_n和valid_n为高电平
initial begin : drive_reset_proc 
  drive_reset();
end
task drive_reset();
  @(negedge intf.reset_n);
  dbg_state <= DRV_RESET;
  intf.din <= 0;
  intf.frame_n <= '1;//等效16'hFFFF
  intf.valid_n <= '1;
endtask

// 发送数据
initial begin : drive_chnl_proc
    //rt_packet_t p;
  @(negedge intf.reset_n);
  repeat(10) @(posedge intf.clock);//延迟10个时钟周期
  forever begin	
    automatic rt_packet_t p;//声明一个动态的
    wait(pkts.size()>0);
    p = pkts.pop_front();	//将p从队列pkts里面取出
    fork//后台触发线程,触发线程在后台运行,继续执行剩下内容
        begin	
            wait_src_chnl_avail(p);//判断src chnl是否被占用,是否需要等待
            drive_chnl(p.src, p.dst, p.data);//从P中拿到发送端口,目标端口,发送数据
            set_src_chnl_avail(p);
        end
    join_none
    end
end

task automatic wait_src_chnl_avail(rt_packet_t p);//判断src chnl是否被占用,是否需要等待
    if(!src_chnl_status.exists(p.src))//src_chnl_status关联数组里面不含p.src,(表示未占用任何dest_chnl,一开始不满足)
        src_chnl_status[p.src] = p.dst;//表示当前正在使用src_chnl,对应dest_chnl就是p.dst(p.src端口在向p.dst端口发送数据)
    else if(src_chnl_status[p.src] >= 0)//如果在给0,1,2...dest_chnl发送数据(被占用),需要等待,,否则不需要等待
        wait(src_chnl_status[p.src] == -1);//直到src_chnl_status[p.src] == -1(自定义的dest_chnl端口数之外任意的数,表示未占用任何dest_chnl)
endtask

function automatic set_src_chnl_avail(rt_packet_t p);
    src_chnl_status[p.src] = -1;//如果chnl发送完后,把src_chnl置回来(自定义的dest_chnl端口数之外任意的数,表示未占用任何dest_chnl)
endfunction

task automatic drive_chnl(bit[3:0] saddr, bit [3:0] daddr, byte unsigned data[]);
  $display("@%0t: [DRV]src_chnl[%0d],dest_chnl[%0d] data trans started",$time,saddr,daddr);
    // drive address phase 输入地址位阶段
for(int i=0;i<4;i++)begin  //4 clock
  @(posedge intf.clock);
  dbg_state <=DRV_ADDR;	
  intf.din[saddr] <= daddr[i];
  intf.valid_n[saddr] <= $urandom_range(0,1);	//valid_n在din的地址输入时间段可为任意值x
  intf.frame_n[saddr] <= 1'b0;	//frame_n需要为低
end
    // drive pad phase //隔离阶段
  for (int i=0;i<5;i++)begin  //5 clock
    @(posedge intf.clock);
    dbg_state <=DRV_PAD;
    intf.din[saddr] <= 1'b1;
    intf.valid_n[saddr] <= 1'b1;	//valid_n需为高电平
    intf.frame_n[saddr] <= 1'b0; //frame_n需为低电平
  end
    // drive data phase 传输数据阶段
  foreach(data[id])begin
    for(int i=0;i<8;i++)begin
     @(posedge intf.clock);
      dbg_state <=DRV_DATA;
      dbg_din_chnl0_data <= data[id];
      intf.din[saddr] <= data[id][i];
      intf.valid_n[saddr] <=1'b0;
      intf.frame_n[saddr] <= (id == data.size()-1 && i == 7) ? 1'b1 : 1'b0;//packet最后一位输出数据时frameo_n为高
    end
  end
// drive idle phase 闲置(没有数据传输)阶段
  @(posedge intf.clock);
  dbg_state <=DRV_IDLE;
  dbg_din_chnl0_data <= 0;
  intf.din[saddr] <= 1'b0;
  intf.valid_n[saddr] <= 1'b1;
  intf.frame_n[saddr] <= 1'b1;
  $display("@%0t: [DRV]src_chnl[%0d],dest_chnl[%0d] data trans finished",$time,saddr,daddr);
endtask
endmodule

//********************************** generator **********************************//
module rt_generator;	//generator产生数据交给stimulator
  rt_packet_t pkts[$];	//定义队列

  function void put_pkt(input rt_packet_t p);
    pkts.push_back(p);	//将p放入队列pkts里面(在pkts队列尾插入p)
  endfunction
  
  task get_pkt(output rt_packet_t p);
    wait(pkts.size() >0 )	//队列不为空
      p = pkts.pop_front();	//将p从队列pkts里面取出,提取队列首元素
  endtask

  //generate a random packet
  function void gen_pkt(int src = -1, int dst = -1);
  endfunction
endmodule
//********************************** monitor **********************************//
module rt_monitor(rt_interface intf);
    rt_packet_t in_pkts[16][$];
    rt_packet_t out_pkts[16][$];
    initial begin : mon_chnl_proc
        foreach(in_pkts[i]) begin
            automatic int chid = i;
            fork
            mon_chnl_in(chid);//每个输入端口均调用mon_chnl_in任务,监测数据输入
            mon_chnl_out(chid);//每个输出端口均调用mon_chnl_out任务,监测数据输入
            join_none
        end
    end
    
task automatic mon_chnl_in (bit[3:0]id);//监测数据输入的任务
    rt_packet_t pkt;	//定义结构体变量
    forever begin
    //clear content for the same struct variable。清除pkt
    pkt.data.delete();
    pkt.src = id;	//第id个输入端口
    //monitor specific channel-in data and put it into the queue
    // monitor address phase
    @(negedge intf.frame_n[id]);//监测frame_n下降沿(frame_n由时钟驱动)
        for(int i=0; i<4; i++)begin
        @(negedge intf.clock);		//frame_n下降沿后监测4个clk negedge
        pkt.dst[i]= intf.din[id];
        end
  $display ("@%0t: [MON] src_chn1[%0d] & dest_chn1[%0d] data trans started",$time,pkt.src,pkt.dst);
    //pass pad phase 不考虑pad阶段是否满足协议要求
    repeat(5) @(negedge intf.clock);
    do begin
        pkt.data = new [pkt.data.size + 1](pkt.data);//创建动态数组并复制pkt.data
        for ( int i=0; i<8; i++) begin
            @(negedge intf.clock); //在8个clk negedge监测8bit数据
            pkt.data[pkt.data.size-1][i] = intf.din[id];
        end
    end while(!intf.frame_n[id]);
    in_pkts[id].push_back(pkt);	//将monitor拿到的数据放入in_pkts
    $display("@%0t: [MON] src_chn1[%0d] & dest_chn1[%0d] data trans [%0p] finished",$time,pkt.src,pkt.dst,pkt.data);
    end
endtask

task automatic mon_chnl_out(bit[ 3:0]id);//监测数据输出的任务
    rt_packet_t pkt;
    forever begin
        //clear content for the same struct variable
        pkt.data.delete();
        pkt.src = 0;
        pkt.dst = id;	
        @(negedge intf.frameo_n[id]);
        $display( "@%0t: [MON] CH_OUT dest_chn1[%0d] data trans started",$time,pkt.dst);
        do begin
        pkt.data = new [ pkt.data.size + 1](pkt.data);
        for(int i=0;i<8; i++) begin
            @(negedge intf.clock iff !intf.valido_n[id]);//clock与valido_n信号同时为低
            pkt.data[pkt.data.size-1][i]= intf.dout [id];
        end
    end while(!intf.frameo_n[id]);
    out_pkts[id].push_back(pkt);
    $display("@%0t: [MON] CH_OUT dest_chn1[%0d] data trans [%0p] finished",$time,pkt.dst,pkt.data);
    //monitor specific channel-out data and put it into the queue
    end
endtask

endmodule

//********************************** tb **********************************//
module tb;

bit clk,rstn;
logic [15:0] din, frame_n, valid_n;
logic [15:0] dout, valido_n, busy_n, frameo_n;

// 产生时钟,周期为10ns
initial 
    forever #5ns clk <= !clk;

// 产生复位信号
  initial begin
    #2ns rstn <= 1;
    #10ns rstn <= 0;
    #10ns rstn <= 1;
  end

//例化router为DUT
router dut(           
  .reset_n(rstn),
  .clock(clk),
  .frame_n(intf.frame_n),
  .valid_n(intf.valid_n),
  .din(intf.din),
  .dout(intf.dout),
  .busy_n(intf.busy_n),
  .valido_n(intf.valido_n),
  .frameo_n(intf.frameo_n)
);

rt_interface intf();//例化接口
    assign intf.reset_n = rstn;
    assign intf.clock = clk;
    
//例化stimulator
rt_stimulator stim(intf);
//例化Monitor
rt_monitor mon(intf);
//例化generator
rt_generator gen();
    
initial begin:generate_data_proc//产生数据
    rt_packet_t p;
    gen.put_pkt('{0,3,'{8'h33,8'h77}});	//调用genarator里面的put_pkt函数
    gen.put_pkt('{0,5,'{8'h55,8'h66}});
    gen.put_pkt('{3,6,'{8'h77,8'h88,8'h22}});
    gen.put_pkt('{4,7,'{8'haa,8'hcc,8'h33}});		
end
initial begin:genarator_to_stimulator_proc//取出genarator中的数据给stimulator
    rt_packet_t p;
    forever begin
        gen.get_pkt(p);
        stim.put_pkt(p);
    end
end

endmodule 

5.3 仿真结果

6. 初步TestBench概览


文章作者: DPH
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 DPH !
  目录