路科v0,第八周,激励随机,层次结构设计

//`timescale 1ns/10ps

package rt_test_pkg;




class rt_packet;
  rand bit[3:0] src;//输入数据(din)的通道
  rand bit[3:0] dst;//输出数据(dout)的通道
  rand bit[7:0] data[];//发送的数据
	constraint pkt_cstr {
		src inside {[0:15]};
		dst inside {[0:15]};
		soft data.size() inside {[0:31]};
	};
  function new();
  endfunction

  function void set_members(bit[3:0] src, bit[3:0] dst, bit[7:0] data[]);
    this.src = src;
    this.dst = dst;
    this.data = data;
  endfunction

  function string sprint();
    sprint = {sprint,$sformatf("src =%0d,\n",src)};
    sprint = {sprint,$sformatf("dst =%0d,\n",dst)};
    sprint = {sprint,$sformatf("data len =%0d,\n",data.size())};
    foreach(data[i])
      sprint = {sprint,$sformatf("data[%d] ='h%0x,\n",i,data[i])};
  endfunction

  function bit compare(rt_packet p);//比较两个pkt,比较他们的dst(输出通道)以及data
    if(this.dst==p.dst && this.data==p.data) begin
      compare = 1;
    end
    else begin
      compare = 0;
    end
  endfunction
endclass



//用于发生激励
class rt_stimulator;
  virtual rt_io io;
	mailbox #(rt_packet) pkts;
	mailbox #(rt_packet) ch_pkts[16];
  int src_chal_status[int];

	function new();
		//pkts = new();
		foreach(ch_pkts[i])
			ch_pkts[i] = new(1);
	endfunction
  
	task distribute_pkt();
		rt_packet p;
		forever begin
			pkts.get(p);
			ch_pkts[p.src].put(p);
		end
	endtask


	//复位动作
  task drive_reset_proc();
    forever begin
      @(negedge io.rstn);//检测到复位信号后,产生一个复位动作
      io.din <= 0;
      io.frame_n <= '1;
      io.valid_n <= '1;
    end
  endtask

	//发送一个packet
  //saddr-in,daddr-out,
  task drive_a_chal_proc(rt_packet p);//发送一个packet
    for(int i = 0;i<4;i++) begin//发送四位的daddr(dst),即那个数据通道
      @(posedge io.clk);
      io.din[p.src] <= p.dst[i];
      io.valid_n[p.src] <= 1'b0;//暂时先不开启输入?
      io.frame_n[p.src] <= 1'b0;
    end

    //drive pad phase
    for(int i = 0;i<5;i++) begin//按照时序,发送五个空拍
      @(posedge io.clk);
      io.din[p.src] <= 1'b1;
      io.valid_n[p.src] <= 1'b1;
      io.frame_n[p.src] <= 1'b0;
    end

    //drive data phase
    foreach(p.data[id]) begin//每个data为一个字节,每个结构体里的data可能有多个字节的数据
      for(int i = 0;i<8;i++) begin//开始发送一个字节
        @(posedge io.clk);
        io.din[p.src] <= p.data[id][i];
        io.valid_n[p.src] <= 1'b0;
        if(id == p.data.size()-1 && i == 7)
          io.frame_n[p.src] <= 1'b1;//数据发送完毕
        else
          io.frame_n[p.src] <= 1'b0;//else继续发送
      end
    end
    
    @(posedge io.clk);//结束动作
    io.din[0] <= 1'b0;
    io.valid_n[p.src] <= 1'b1;
    io.frame_n[p.src] <= 1'b1;
    $display("@%0t [stimulaor] finished \n%s",$time,p.sprint());
  endtask


  //等待src(输入通道)可用(-1),src_chal_status是一个关联数组,存放了src(输入)是否被占用的状态
  task wait_src_chanl_avail(rt_packet p);
    if(!src_chal_status.exists(p.src))
      src_chal_status[p.src] = p.dst;
    else if(src_chal_status[p.src] >= 0)
      wait(src_chal_status[p.src] == -1);
  endtask


  //将某一输入通道置为空闲
  function void set_src_chanl_avail(rt_packet p);
    src_chal_status[p.src] = -1;
  endfunction


	task drive_chanl_proc();
    @(negedge io.rstn);
    repeat(10) @(posedge io.clk);
		foreach(ch_pkts[i]) begin
			automatic int id = i;
			automatic rt_packet p;
			fork
				forever begin
					ch_pkts[id].get(p);
     			drive_a_chal_proc(p);//发送数据
				end
			join_none
		end
	endtask


  task run();
    fork
      drive_reset_proc();//出发复位信号
			distribute_pkt();
      drive_chanl_proc();//出发drive线程
    join_none
  endtask


endclass



class rt_monitor;//定义监视器模块

  virtual rt_io io;
  //注 rt_packet_t是前面定义的结构体,用于储存数据信息,包括dut的输入通道信息,输出通道信息,以及data
  rt_packet in_pkts[16][$];//定义16个队列,每个队列用于存放一个某一通道的结构体信息
  rt_packet out_pkts[16][$];

  task automatic moni_chal_in(bit[3:0] id);//定义输入数据的monitor
    rt_packet pkt;//一个结构体,用于储存一次检测的信息
    forever begin
      pkt = new();
      pkt.src = id;//id是监测的通道id。即pkt.src(输入通道)
      @(negedge io.frame_n[id]);//检测frame_n下降沿,数据开始发送
      for(int i=0;i<4;i++) begin
        @(posedge io.clk);//每个时钟上升沿接收1bit数据,按照时序,现在接受的是四位的地址数据(这个地址数据其实是dout的输出通道数)
        pkt.dst[i] = io.din[id];
      end
      repeat(5) @(negedge io.clk);//按照时序,这里有五个空的时钟,不关心他的值
      do
        begin
          pkt.data = new[pkt.data.size+1] (pkt.data);//这里没明白,可能是分配新的内存空间
          for(int i=0;i<8;i++) begin
            @(negedge io.clk);//八个时钟上升沿,接收一个字节的data
            pkt.data[pkt.data.size-1][i] = io.din[id];
          end
        end
      while(!io.frame_n[id]);//一直重复上面这个过程(接受一字节的data),直到io.frame_n置1,因为按照时序,数据发送结束后,io.frame_n会拉高
      in_pkts[id].push_back(pkt);
      $display("@%0t [monitor-in ]%d train finished\n%s ", $time, id, pkt.sprint());
    end
  endtask

  task automatic moni_chal_out(bit[3:0] id);
    rt_packet pkt;
    forever begin
      pkt = new();
      pkt.src = 0;//dut的输出中,无从得知一个数据的src是哪来的,这里直接定义为0
      pkt.dst = id;//监测的通道id
      @(negedge io.frameo_n[id]);//io.frameo_n[id]下降沿表明dut要开始输出数据了(参见时序图)
      do
        begin
          pkt.data = new[pkt.data.size+1](pkt.data);
          for(int i=0;i<8;i++) begin//dut的输出仅输出data,
            @(negedge io.clk iff !io.valido_n[id]);//dut输出时会将valido拉低,因此这里的判断是:时钟上升沿 并且 valido为低电平
            pkt.data[pkt.data.size-1][i] = io.dout[id];
          end
        end
      while(io.frameo_n[id] == 0);//这里与din的监测逻辑一样,dut的dout输出结束后,会将frameo_n拉高
      out_pkts[id].push_back(pkt);
      $display("@%0t [monitor-out]%d train finished\n%s",$time,id,pkt.sprint());
    end
  endtask

  task moni_chanl();
    foreach(in_pkts[i]) begin//in_pkts是16个队列,对应每一个通道,这里对每一个通道都进行监测
      automatic int chanl_id = i;
      fork//出发监测线程,(且不等待他结束)
          moni_chal_in(chanl_id);//这一个task里面有forever,一直对dut进行监测
          moni_chal_out(chanl_id);
      join_none
    end
  endtask

  task run();
    fork
      moni_chanl();
    join_none
  endtask

endclass



//用于生成数据的模块
class rt_generator;
  //rt_packet pkts[$];
	mailbox #(rt_packet) pkts;
	
	function new();
		pkts = new();
	endfunction

  task put_pkt(input rt_packet p);
		pkts.put(p);
  endtask

  task get_pkt(output rt_packet p);
		pkts.get(p);
  endtask

  function void gen_pkt(int src = -1, int dst = -1);
  endfunction

  task run();
  endtask

endclass



class check;

  int unsigned compared_counter;
  int unsigned error_counter;

  rt_packet expct_out_pkts[16][$];
  rt_monitor moni;

  function new();
    compared_counter = 0;
    error_counter = 0;
  endfunction

	//模拟dut行为
  task do_routing(bit[3:0] id);
    rt_packet pkt;
    forever begin
      wait(moni.in_pkts[id].size() > 0);//监视器发现数据
      pkt = moni.in_pkts[id].pop_front();//得到监视器输入的数据
      expct_out_pkts[pkt.dst].push_back(pkt);//模拟dut行为,pkt.dst为某一通道
    end
  endtask


  task do_compare(bit[3:0] id);//混乱
    rt_packet exp_pkt,act_pkt;
    forever begin
      wait(moni.out_pkts[id].size()>0 && expct_out_pkts[id].size()>0);
      exp_pkt = expct_out_pkts[id].pop_front();//从两个fifo(邮箱)里各取出一个数据(packet)
      act_pkt = moni.out_pkts[id].pop_front();
      if(exp_pkt.compare(act_pkt)) begin//进行对比
        $display("[check] chanl-%d compare successed with data= \n%s\n",id,act_pkt.sprint());
      end
      else begin
        error_counter++;
        $display("[check] chanl-%d compare failure with expct/actual= \n%s\n%s \n",id,exp_pkt.sprint(),act_pkt.sprint());
      end
      compared_counter++;
    end
  endtask


  function void report(string name);
    $display("[report]");
    $display("TOTAL COMPARED %d times",compared_counter);
    if(error_counter == 0) begin
      $display("TEST %s PASSED!",name);
    end
    else begin
      $display("TEST %s FAILED!",name);
      $display("TOTAL ERROE %d times",error_counter);
    end 
  endfunction


  function bit check_data_buffer();
    check_data_buffer = 1;
    foreach(expct_out_pkts[id]) begin
      if(expct_out_pkts[id].size() != 0) begin
        $display("expct_out_pkts[%d] still has %d data",id,expct_out_pkts[id].size());
      end
    end
    foreach(moni.out_pkts[id]) begin
      if(moni.out_pkts[id].size() != 0) begin
        $display("moni.out_pkts[%d] still has %d data",id,moni.out_pkts[id].size());
      end
    end
  endfunction


  task run();
    foreach(expct_out_pkts[i]) begin
      automatic int chanl_id = i;
      fork
        do_routing(chanl_id);//一直监测in_monitor邮箱是否有数据,有的话,获取并模拟dut
        do_compare(chanl_id);//一直监测in_monitior邮箱与ecpect是否有数据,有的话,进行比较
      join_none
    end
  endtask
endclass



//顶层环境
//这个环境里例化了数据产生器,激励,监视器,check
class rt_env;
  rt_generator gen;
  rt_stimulator stim;
  rt_monitor monitor;
  check chk;

  function new(virtual rt_io io);
    //build stage
    stim = new();
    gen = new();
    monitor = new();
    chk = new();
    //connect stage
    stim.io = io;
    monitor.io = io;
    chk.moni = monitor;
		stim.pkts = gen.pkts;
  endfunction

  function void report(string name);
    chk.report(name);
  endfunction

  task run();//顶层环境里,run左右模块,即出发所有线程,然后不断产生数据,并输入激励器
    rt_packet p;//一个结构体句柄
    fork
      //run stage
      stim.run();//激励器有个16通道的fifo(邮箱),始终从里面取数据,并发送至dut
      gen.run();//他有一个16通道fifo,与激励器的fifo相连接,不断将产生的数据送入这个fifo,数据的产生由子类负责
      monitor.run();//监视器链接dut与stim的interface,始终检测输入输出信号,检测到就记录下来
      chk.run();//chk例化一个监视器,并将句柄与env的监视器链接,监视器检测到信号后,模拟dut并进行数据比较
    join_none
  endtask
endclass


//基础测试,继承于顶层环境
//添加了报告功能,数据发送完成后,出发报告线程
class rt_base_test;
  rt_env env;
  bit gen_trans_done = 0;
  int unsigned wait_trans_time_us = 20;
  string name;

  function new(virtual rt_io io,string name = "rt_base_test");
    env = new(io);
    this.name = name;
  endfunction

  function void set_gen_trans_done(bit done);
    gen_trans_done = done;
  endfunction

  task report(string name);
    wait(gen_trans_done == 1);
    #(wait_trans_time_us * 1us);
    env.report(name);
    $finish();
  endtask

  task run();
    $display("%s is start",name);
    fork
      env.run();//启动env线程,
      report(name);//gen_trans_done==1后触发report线程,(trans_done==1只能说明数据产生)等待一段时间(等待其他线程),之后再报告
                   //gen线程完成后,设置gen_trans_done==1,gen由子类负责,也即gen_trans_done由子类负责
    join_none
  endtask
endclass


//测试一个数据,继承于基础测试模块,基础测试模块已经出发了所有的线程,
//这个模块了主要用于产生具体的数据
class rt_single_chanl_test extends rt_base_test;

  function new(virtual rt_io io,string name = "rt_single_chanl_test");
    super.new(io,name);
  endfunction

  task run();
    rt_packet p;
    super.run();
    p = new();//实例化一个p
		p.randomize();//p随机化产生数据
    env.gen.put_pkt(p);//将数据送入fifo,其他的线程会检测到,随后整个环境开始运转
    set_gen_trans_done(1);//数据发送完成,set_gen_trans_done,等待一些时间后,会形成报告
  endtask
endclass


class rt_two_chanl_test extends rt_base_test;
  function new(virtual rt_io io,string name = "rt_two_chanl_test");
    super.new(io,name);
  endfunction
endclass


class rt_two_ch_same_chout_test extends rt_two_chanl_test;

  function new(virtual rt_io io,string name = "rt_two_ch_same_chout_test");
    super.new(io,name);
  endfunction
endclass


class rt_multi_chanl_test extends rt_base_test;

	rand bit[0:3] p_count;
	constraint cstr_p_count {p_count inside {[1:16]};};

  function new(virtual rt_io io,string name = "rt_multi_chanl_test");
    super.new(io,name);
  endfunction
  
  task run();
    rt_packet p;
    super.run();
		randomize(p_count);
		$display("p num = %d",p_count);
		for(int i = 0;i<p_count;i++) begin//这里与single_test类似,只不过这里产生多个数据
    	p = new();
			p.randomize();
    	env.gen.put_pkt(p);
		end
    set_gen_trans_done(1);
  endtask
endclass


class rt_full_chanl_test extends rt_multi_chanl_test;
  function new(virtual rt_io io,string name = "rt_full_chanl_test");
    super.new(io,name);
  endfunction
endclass




endpackage//rt_test_pkg




interface rt_io;
  logic clk;
  logic rstn;
  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



module tb1;//这里就相当于一个顶层设计了

	import rt_test_pkg::*;

  var bit clk,rstn;

  initial begin  //generate clk
    forever #5 clk = ~clk;
  end
  initial begin  //generate rstn
    #5 rstn <= 1;
    #10 rstn <= 0;
    #10 rstn <= 1;
  end
  rt_io io();//例化一个接口
  assign io.clk = clk;
  assign io.rstn = rstn;
  router dut(
  .reset_n(io.rstn), 
  .clock(io.clk),
  .frame_n(io.frame_n),
  .valid_n(io.valid_n),
  .din(io.din),
  .dout(io.dout),
  .busy_n(io.busy_n),
  .valido_n(io.valido_n),
  .frameo_n(io.frameo_n)
  );


  rt_single_chanl_test single_chanl_test;
  rt_multi_chanl_test multi_chanl_test;

	rt_base_test tests[string];

  initial begin:instance_inital_proc
		string name;

    single_chanl_test = new(io);
    multi_chanl_test = new(io);
		//name = "single_chanl_test";
		name = "multi_chanl_test";
		case(name)
			"single_chanl_test":single_chanl_test.run();
			"multi_chanl_test":multi_chanl_test.run();
			default:$fatal("error test name %d is invalid, please specify a valid name",name);
		endcase
  end//initial



endmodule
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值