SV学习笔记(八)

本文

芯片验证系列文章主要分为三部分:验证工程师与芯片验证、SV学习笔记、UVM学习笔记。此为 SV学习笔记 部分第八篇,主要介绍一些实验练习中的具体代码解析。

版本 说明
0.1 初版发布

参考

名称 作者 来源
《芯片验证漫游指南》 刘斌 书籍
《SystemVerilog验证》 克里斯.斯皮尔 书籍

专业术语与缩略语

缩写 全称 说明
MCDF multi-channel data formatter 多通道数据整流器

SV入门练习

基本数据类型

  • 有符号无符号、四状态双状态、枚举类型、结构体
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
module data_type;

bit b_signed_vs_unsigned = 1;
bit b_bit_vs_logic = 1;
bit b_enum_type = 1;
bit b_struct_type = 1;


// TODO-1: distinguish signed and unsigned type
initial begin: signed_vs_unsigned
  byte b0;
  bit[7:0] b1;
  wait(b_signed_vs_unsigned == 1); $display("signed_vs_unsigned process block started");
  b0 = 'b1000_0000;
  $display("byte variable b0 = %d", b0);
  b1 = b0;
  $display("bit vector variable b1 = %d", b1);
end


// TODO-2: distinguish bit and logic
initial begin: bit_vs_logic
  bit v1;
  logic v2;
  wait(b_bit_vs_logic == 1); $display("bit_vs_logic process block started");

  v2 = 'b1;
  $display("logic variable v2 = %d", v2);
  v1 = v2;
  $display("bit variable v1 = %d", v1);

  v2 = 'b0;
  $display("logic variable v2 = %d", v2);
  v1 = v2;
  $display("bit variable v1 = %d", v1);

  v2 = 'bx;
  $display("logic variable v2 = %d", v2);
  v1 = v2;
  $display("bit variable v1 = %d", v1);

  v2 = 'bz;
  $display("logic variable v2 = %d", v2);
  v1 = v2;
  $display("bit variable v1 = %d", v1);
end

// TODO-3: enum type
initial begin: enum_type
  typedef enum {IDLE, START, PROC, END} state_t;
  state_t st1, st2;
  wait(b_enum_type == 1); $display("enum_type process block started");
  st1 = IDLE;
  $display("st1 value = %0d (int)", st1);
  $display("st1 value = %s (string)", st1); // implicit conversion
  $display("st1 value = %s (string)", st1.name());

  st2 = state_t'(1);
  $display("st1 value = %0d (int)", st2);
  $display("st1 value = %s (string)", st2.name());
end

// TODO-4: struct type
initial begin: struct_type
  typedef struct {
    bit[7:0] addr;
    bit[31:0] data;
    bit is_write;
    int id;
  } trans_t;
  trans_t t1, t2, t3;
  wait(b_struct_type == 1); $display("struct_type process block started");
  t1 = '{'h10, 'h1122_3344, 'b1, 'h1000};
  $display("t1 data content is %p", t1);


  t2.addr = 'h20;
  t2.data = 'h5566_7788;
  t2.is_write = 'b0;
  t2.id = 'h2000;
  $display("t2 data content is %p", t2);

  t3 = t2;
  t3.data = 'h99AA_BBCC;
  t3.id = 'h3000;
  $display("t3 data content is %p", t3);
  $display("t2 data content is %p", t2);
end

endmodule

字符串类型

  • 构建字符串和字符串拼接 (一般常用$sformatf函数)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
module string_type;

bit b_string_format = 1;
bit b_string_builtin_function = 1;

// TODO-1 understand how to formulate a new string
initial begin: string_format
  string s1, s2, s3, s4;
  wait(b_string_format == 1);$display("string_format process block started");
  s1 = "Welcome";
  s2 = "www.rockeric.com";

  s3 = {s1, " to ", s2}; // concatenation operator '{...}'
  $display("s3 content: %s", s3);

  s4 = $sformatf("%s to %s", s1, s2); // system format function
  $display("s4 content: %s", s4);
end

// TODO-2  understand how s3 is composed with s1 and s2
initial begin: string_builtin_function
  string s1, s2, s3;
  int i1;
  wait(b_string_builtin_function == 1); $display("string_builtin_function process block started");
  s1 = "RockerIC is established in ";
  i1 = 2015;
  s2.itoa(i1); // integer converted to string
  s3 = {s1.len()+s2.len(){" "}}; // try to comment this line and check the result
  for(int i=0; i<s1.len()+s2.len(); i++) begin
    s3[i] = i < s1.len() ? s1[i] : s2[i-s1.len()];
  end
  $display("s3 content: %s", s3);
end

endmodule

数组类型

  • 合并非合并、赋值与循环、动态数组、队列、关联数组
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
module array_type;

bit b_unpacked_vs_packed = 1;
bit b_array_assigment_and_loop = 1;
bit b_dynamic_array = 1;
bit b_queue_use = 1;
bit b_associate_array = 1;

// TODO-1 learn the difference between unpacked and packed data storage and
// assignment
initial begin: unpacked_vs_packed
  bit [7:0] unpacked_word [3:0];
  bit [3:0] [7:0] packed_word;
  wait(b_unpacked_vs_packed == 1); $display("unpacked_vs_packed process block started");

  // legal assignment
  unpacked_word[0] = 10;
  unpacked_word[1] = 32;
  unpacked_word[2] = 54;
  unpacked_word[3] = 76;
  $display("unpacked_word = %p", unpacked_word);

  // legal assignment with '{}
  unpacked_word = '{76, 54, 32, 10};
  $display("unpacked_word = %p", unpacked_word);

  // legal assignment
  packed_word[0] = 10;
  packed_word[1] = 32;
  packed_word[2] = 54;
  packed_word[3] = 76;
  $display("packed_word = %p", packed_word);

  // legal assignment with {} but without '
  packed_word = {76, 54, 32, 10};
  $display("packed_word = %p", packed_word);
  // legal assignment directly like a vector packedt_word[31:0]
  packed_word = (76<<24) + (54<<16) + (32<<8) + 10;
  $display("packed_word = %p", packed_word);

  // illegal assignment
  // packed_word = unpacked_word [X]
  // unpacked_word = packed_word [X]

  // illegal assignment between packed and unpacked array
  foreach(packed_word[i])
    packed_word[i] = unpacked_word[i];

  foreach(unpacked_word[i])
    unpacked_word[i] = packed_word[i];
end

// TODO-2 learn the array assignment and foreach loop indexing method
initial begin: array_assigment_and_loop
  integer sum [4][2]; // 8*4 size array
  wait(b_array_assigment_and_loop == 1); $display("array_assigment_and_loop process block started");
  // concatenation and default value
  sum = '{0:'{'h21, 'h43}, default:'{default:'x}};
  // foreach loop indexing
  foreach(sum[i, j]) begin
    $display("sum[%0d][%0d] = 'h%0x", i, j, sum[i][j]);
  end
end

// TODO-3 learn the dynamic array basics
initial begin: dynamic_array
  int dyn1[], dyn2[];
  wait(b_dynamic_array == 1); $display("dynamic_array process block started");
  dyn1 = '{1, 2, 3, 4};
  $display("dyn1 = %p", dyn1);
  // copp method option-1
  dyn2 = dyn1;
  $display("dyn2 = %p", dyn2);
  $display("dyn2 size is %0d", dyn2.size());
  // copp method option-2
  dyn2 = new[dyn1.size()](dyn1);
  $display("dyn2 = %p", dyn2);
  $display("dyn2 size is %0d", dyn2.size());
  dyn2.delete();
  $display("dyn2 size is %0d", dyn2.size());
end

// TODO-4: learn queue use
initial begin: queue_use
  int que1[$], que2[$];
  wait(b_queue_use == 1); $display("queue_use process block started");
  que1 = {10, 30, 40};
  $display("que1 = %p", que1);
  que2 = que1;
  $display("que2 = %p", que1);
  que1.insert(1, 20);
  $display("que1 = %p", que1);
  que1.delete(3); // delete que1[3]==40
  void'(que1.pop_front()); // pop que[0]==10
  $display("que1 = %p", que1);
  que1.delete();
  $display("que1 = %p", que1);
end


// TODO-5 learn associate array use
initial begin: associate_array
  int id_score1[int], id_score2[int]; // key ID, value SCORE
  wait(b_associate_array == 1); $display("associate_array process block started");
  id_score1[101] = 111;
  id_score1[102] = 222;
  id_score1[103] = 333;
  id_score1[104] = 444;
  id_score1[105] = 555;

  // associate array copy
  id_score2 = id_score1;
  id_score2[101] = 101;
  id_score2[102] = 102;
  id_score2[103] = 103;
  id_score2[104] = 104;
  id_score2[105] = 105;
  foreach(id_score1[id]) begin
    $display("id_score1[%0d] = %0d", id, id_score1[id]);
  end
  foreach(id_score2[id]) begin
    $display("id_score2[%0d] = %0d", id, id_score2[id]);
  end
end

endmodule

接口的定义与例化

  • 接口里也可以定义方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// TODO-1 understand how the interface is defined and instantied
// TODO-2 check how to define methods inside interface and call them internally or externally
// TODO-3 understand how to prepare transactions, drive them and monitor them
module interface_type;
  typedef struct {
    bit[7:0] addr;
    bit[31:0] data;
    bit write;
    int id;
  } trans_t;

  // struct print utility function
  function void trans_print(trans_t t, string name = "trans");
    string s;
    s  = $sformatf("%s struct content is as below \n", name);
    s  = $sformatf("%s\taddr  = 'h%2x \n", s, t.addr);
    s  = $sformatf("%s\tdata  = 'h%8x \n", s, t.data);
    s  = $sformatf("%s\twrite = 'b%0b \n", s, t.write);
    s  = $sformatf("%s\tid    = 'h%8x \n", s, t.id);
    $display("%s", s);
  endfunction

  interface intf1;
    logic [7:0] addr;
    logic [31:0] data;
    logic write;
    int id;

    // transaction drive task
    task drive_trans(trans_t t);
      addr  <= t.addr ;
      data  <= t.data ;
      write <= t.write;
      id    <= t.id   ;
    endtask

    // transaction monitor task
    task mon_trans(output trans_t t);
      t.addr  = addr ;
      t.data  = data ;
      t.write = write;
      t.id    = id   ;
    endtask
  endinterface

  // interface instantiation
  intf1 if1();

  initial begin
    trans_t trans_in[3], trans_mon[3];
    // stimulus preparation
    trans_in = '{'{'h10, 'h1122_3344, 'b1, 'h1000}
                ,'{'h14, 'h5566_7788, 'b0, 'h1001}
                ,'{'h18, 'h99AA_BBCC, 'b1, 'h1002}
                };
    foreach(trans_in[i]) begin
      #10;
      // stimulus drive
      if1.drive_trans(trans_in[i]);
      trans_print(trans_in[i], $sformatf("trans_in[%0d]",i));
      #10;
      // stimulus monitor
      if1.mon_trans(trans_mon[i]);
      trans_print(trans_mon[i], $sformatf("trans_mon[%0d]",i));

      // transaction comparison
      if(trans_in[i] === trans_mon[i])
        $display("trans_in[%0d] === trans_mon[%0d]", i, i);
      else
        $error("trans_in[%0d] !== trans_mon[%0d]", i, i);
    end
  end

endmodule

类的封装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
module class_encapsulation;

bit b_object_instantiation = 1;

  class chnl_trans;
    bit[31:0] data[];
    int ch_id;
    int pkt_id;
    int data_nidles;
    int pkt_nidles;
    bit rsp;
    int obj_id;
    static int global_obj_id = 0;

    function new();
      global_obj_id++;
      obj_id = global_obj_id;
    endfunction

    function chnl_trans clone();
      chnl_trans c = new();
      c.data = this.data;
      c.ch_id = this.ch_id;
      c.pkt_id = this.pkt_id;
      c.data_nidles = this.data_nidles;
      c.pkt_nidles = this.pkt_nidles;
      c.rsp = this.rsp;
      return c;
    endfunction

    function string sprint();
      string s;
      s = {s, $sformatf("obj_id = %0d: \n", this.obj_id)};
      foreach(data[i]) s = {s, $sformatf("data[%0d] = %8x \n", i, this.data[i])};
      s = {s, $sformatf("ch_id = %0d: \n", this.ch_id)};
      s = {s, $sformatf("pkt_id = %0d: \n", this.pkt_id)};
      s = {s, $sformatf("data_nidles = %0d: \n", this.data_nidles)};
      s = {s, $sformatf("pkt_nidles = %0d: \n", this.pkt_nidles)};
      s = {s, $sformatf("rsp = %0d: \n", this.rsp)};
      return s;
    endfunction
  endclass: chnl_trans

// TODO-1 learn the object instantiation
// TODO-2 learn the handle pointting to an object
// TODO-3 learn the class function clone()/sprint()
// TODO-4 compare if t1, t2 and t3 are pointting to the same object?
// TODO-5 check if the t1 pointted object data is exactly the same with the t3
//        pointted object?
// TODO-6 learn how to call STATIC member variable/function, and their
//        difference with local member variable/function
initial begin: object_instantiation
  chnl_trans t1, t2, t3;
  wait(b_object_instantiation == 1); $display("b_object_instantiation process block started");
  t1 = new();
  t1.data = '{1, 2, 3, 4};
  t1.ch_id = 2;
  t1.pkt_id = 100;
  t2 = t1;
  $display("t1 object content is as below:\n%s", t1.sprint());
  $display("t2 object content is as below:\n%s", t2.sprint());
  t3 = t1.clone();
  $display("t3 object content is as below:\n%s", t3.sprint());

  $display("t1 object ID is [%0d]", t1.obj_id);
  $display("t2 object ID is [%0d]", t2.obj_id);
  $display("t3 object ID is [%0d]", t3.obj_id);
  $display("the latest chnl_trans object iD is [%0d]", chnl_trans::global_obj_id);
end

endmodule

类的继承

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
module class_inheritance;

bit b_member_override = 1;

  class trans;
    bit[31:0] data[];
    int pkt_id;
    int data_nidles;
    int pkt_nidles;
    bit rsp;

    function trans clone(trans t = null);
      if(t == null) t = new();
      t.data = data;
      t.pkt_id = pkt_id;
      t.data_nidles = data_nidles;
      t.pkt_nidles = pkt_nidles;
      t.rsp = rsp;
      return t;
    endfunction
  endclass

  class chnl_trans extends trans;
    int ch_id; // new member in child class

    // member function override with
    // same function name, arguments, and return type

    // TODO-1 seperately enable the clone function-1 and function-2, and check
    // if both of them works, and compare which is better, and why?
    // clone function-1
    function trans clone(trans t = null);
      chnl_trans ct;
      if(t == null)
        ct = new();
      else
        void'($cast(ct, t));
      ct.data = data;
      ct.pkt_id = pkt_id;
      ct.data_nidles = data_nidles;
      ct.pkt_nidles = pkt_nidles;
      ct.rsp = rsp;
      ct.ch_id = ch_id; // new member
      return ct;
    endfunction

    // clone function-2
    // function trans clone(trans t = null);
    //   chnl_trans ct;
    //   if(t == null)
    //     ct = new();
    //   else
    //     void'($cast(ct, t));
    //   void'(super.clone(ct));
    //   ct.ch_id = ch_id; // new member
    //   return ct;
    // endfunction
  endclass

  initial begin: member_override
    trans t1, t2;
    chnl_trans ct1, ct2;
    wait(b_member_override == 1); $display("b_member_override process block started");

    ct1 = new();
    ct1.pkt_id = 200;
    ct1.ch_id = 2;
    // t1 pointed to ct1's trans class data base
    t1 = ct1;

    // t2 copied ct1's trans class data base
    t2 = ct1.clone();
    void'($cast(ct2, t2));
    // TODO-2 why could not clone t1(->ct1) to t2?
    // t2 = t1.clone(); // ERROR clone call
    // void'($cast(ct2, t2));

    $display("ct1.pkt_id = %0d, ct1.ch_id = %0d", ct1.pkt_id, ct1.ch_id);

    // TODO-3 uncomment the statement below and consider
    //        why t1 could not point to ct1.ch_id?
    // $display("ct1.pkt_id = %0d, ct1.ch_id = %0d", t1.pkt_id, t1.ch_id);

    $display("ct2.pkt_id = %0d, ct2.ch_id = %0d", ct2.pkt_id, ct2.ch_id);
  end

endmodule

package的使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package sky_pkg;

  class sun;
    typedef enum {RISE, FALL} state_e;
    state_e state = RISE;
  endclass
  sun apollo = new();

  class cloud;
  endclass
endpackage

package sea_pkg;

  class fish;
  endclass

  class island;
    string name;
    function new(string name = "island");
      this.name = name;
    endfunction
  endclass

  island hainan = new("hainan");
endpackage

module package_usage;
  import sky_pkg::cloud;
  import sea_pkg::*;
  import sea_pkg::hainan;

  // TODO-2 why hainan could not be delcared here?
  // island hainan;

  initial begin
    // TODO-1 why sun type is not recognized? how to make it recognizable?
    // sun s;

    // TODO-2 why hainan could be declared here?
    island hainan;

    // TODO-3 why apollo is not recognized?
    // $display("sun state is %s", apollo.state);

    hainan = new("HAINAN");
    $display("hainan name is %s", hainan.name);
    $display("sea_pkg::hainan name is %s", sea_pkg::hainan.name);
  end
endmodule

随机约束

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
module constrained_random;

bit b_system_random_func = 1;
bit b_class_randomization = 1;

// TODO-1 understand how to use system random function, and its advanced
// method such as to generate unique values
initial begin: system_random_func
  int unsigned rval;
  int unsigned gen_vals[$];
  wait(b_system_random_func == 1); $display("b_system_random_func process block started");
  // randomize 10 times, and each rand value doesnot care previous generated
  // value
  repeat(10) begin
    rval = $urandom_range(0, 9);
    $display("$urandom_range(0, 9) generated rand value is %0d", rval);
  end

  // Do you have other ways to generate unique number each time which should
  // not be duplicated with previously generate ones?
  repeat(10) begin
    $display("gen_vals queue content is %p", gen_vals);
    std::randomize(rval) with {foreach(gen_vals[i]) rval != gen_vals[i]; rval inside {[0:9]};};
    gen_vals.push_back(rval);
    $display("std::randomize with inline constrait generated rand value %0d", rval);
  end
end
  class chnl_trans;
    rand bit[31:0] data[];
    rand int ch_id;
    rand int pkt_id;
    rand int data_nidles;
    rand int pkt_nidles;
    bit rsp;
    constraint cstr{
      data.size inside {[4:8]};
      foreach(data[i]) data[i] == 'hC000_0000 + (this.ch_id<<24) + (this.pkt_id<<8) + i;
      ch_id == 1;
      pkt_id == 1;
      data_nidles inside {[0:2]};
      pkt_nidles inside {[1:10]};
    };
  endclass

// TODO-2 learn basic constraint format, class randomization method, soft
// constraint, and how to avolid constraint conflict?
initial begin: class_randomization
  chnl_trans ct1, ct2;
  wait(b_class_randomization == 1); $display("b_class_randomization process block started");
  ct1 = new();
  // is ct1 already randomized?
  $display("ct1.data.size = %0d, ct1.ch_id = %0d, ct1.pkt_id = %0d", ct1.data.size(), ct1.ch_id, ct1.pkt_id);
  ct2 = new();
  // is ct2 already randomized?
  $display("ct2.data.size = %0d, ct2.ch_id = %0d, ct2.pkt_id = %0d", ct2.data.size(), ct2.ch_id, ct2.pkt_id);
  // why ct2.ch_id and ct2.pkt_id kept the same number even though it has been
  // randomized several times?
  repeat(5) begin
    void'(ct2.randomize());
    $display("ct2.data.size = %0d, ct2.ch_id = %0d, ct2.pkt_id = %0d", ct2.data.size(), ct2.ch_id, ct2.pkt_id);
  end
  // the randomization with inline constraint would meets randomization
  // failure, how to modify?
  // if(!ct1.randomize() with {ch_id == 2;})
  //   $error("ct1 randomization failure!");
  // else
  //   $display("ct1.data.size = %0d, ct1.ch_id = %0d, ct1.pkt_id = %0d", ct1.data.size(), ct1.ch_id, ct1.pkt_id);
end

endmodule

线程的同步

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
module thread_sync;

bit b_event_use = 0;
bit b_mailbox_use = 1;
bit b_mailbox_user_define = 1;

// TODO-1.1 event does not new()
// TODO-1.2 learn event copy (direct assignment between two events)
// TODO-1.3 compare @ operator and triggered() method
initial begin: event_use
  event e1, e2, e3a, e3b;
  wait(b_event_use == 1); $display("b_event_use process block started");
  e3b = e3a; // event copy, e3b and e3a are the same event

  ->e1; // trigger e1 before @e1;
  ->e2; // trigger e2 before e2.triggered();

  fork
    begin @e1;  $display("@%0t, @e1 finished" , $time); end
    begin wait(e2.triggered());  $display("@%0t, wait(e2.triggered()) finished" , $time); end
    begin @e3a; $display("@%0t, @e3a finished", $time); end
    begin @e3b; $display("@%0t, @e3b finished", $time); end
  join_none

  #10ns;
  -> e3a;

  #10ns;
  -> e1; // trigger e1 again
end

class box;
  int id;
  function new(int id);
    this.id = id;
  endfunction
endclass

// TODO-2 learn the parameterized mailbox and general storage method
initial begin: mailbox_use
  mailbox #(int) mb_id;
  mailbox #(box) mb_handle;
  box bx[5];
  wait(b_mailbox_use == 1); $display("b_mailbox_use process block started");
  // mailbox need new(N) for instantiation
  mb_id = new();
  mb_handle = new();
  // mailbox storage
  foreach(bx[i]) begin
    bx[i] = new(i);
    mb_id.put(i);
    mb_handle.put(bx[i]);
  end
  $display("box handles array bx content is %p", bx);
  // mailbox extraction
  $display("extracting ID and HANDLE from the TWO mailboxes");
  repeat(mb_id.num()) begin
    int id;
    box handle;
    mb_id.get(id);
    mb_handle.get(handle);
    $display("ID:%0d, HANDLE:%p", id, handle);
  end
  // check mailbox size
  $display("ID mailbox size is %0d", mb_id.num());
  $display("HANDLE mailbox size is %0d", mb_handle.num());
end

// TODO-3 learn how to maximal utilize the mailbox storage with user defined
// type, we modify the 'mailbox_use' block and give a new block
// 'mailbox_user_define'
typedef struct{
  int id;
  box handle;
} mb_pair_element_t;

initial begin: mailbox_user_define
  mailbox #(mb_pair_element_t) mb_pair;
  box bx[5];
  wait(b_mailbox_user_define == 1); $display("b_mailbox_user_define process block started");
  // mailbox need new(N) for instantiation
  mb_pair = new();
  // mailbox storage
  foreach(bx[i]) begin
    bx[i] = new(i);
    mb_pair.put('{i, bx[i]});
  end
  $display("box handles array bx content is %p", bx);
  // mailbox extraction
  $display("extracting ID and HANDLE from the ONE mailbox");
  repeat(mb_pair.num()) begin
    mb_pair_element_t pair;
    mb_pair.get(pair);
    $display("ID:%0d, HANDLE:%p", pair.id, pair.handle);
  end
  // check mailbox size
  $display("PAIR mailbox size is %0d", mb_pair.num);
end


endmodule

线程的控制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
module thread_control;

bit b_fork_join = 1;
bit b_fork_join_any = 0;
bit b_fork_join_none = 0;

class box;
  int id;
  function new(int id);
    this.id = id;
  endfunction
endclass

box bx[3];

task automatic thread(int id = 1, int t = 10);
  $display("@%0t, thread id:%0d entered", $time, id);
  bx[id] = new(id); // allocate space
  #(t*1ns);
  bx[id] = null; // deallocate space
  $display("@%0t, thread id:%0d exited", $time, id);
endtask

// TODO-1 learn fork-join thread exited when all sub-thread exit.
initial begin: fork_join
  wait(b_fork_join == 1); $display("b_fork_join process block started");
  bx = '{null, null, null};
  $display("@%0t, fork_join_thread entered", $time);
  fork: fork_join_thread
    thread(0, 10);
    thread(1, 20);
    thread(2, 30);
  join
  $display("@%0t, fork_join_thread exited", $time);
  $display("@%0t, box handles array is %p", $time, bx);

  #10;
  b_fork_join = 0;
  b_fork_join_any = 1;
end

// TODO-2.1 learn fork-join_any thread exited when just one sub-thread exites,
// but all other sub-thread are still running.
// TODO-2.2 learn disable BLOCK/fork statement, and check if the running
// sub-threads haven been disabled.
initial begin: fork_join_any
  wait(b_fork_join_any == 1); $display("b_fork_join_any process block started");
  bx = '{null, null, null};
  $display("@%0t, fork_join_any_thread entered", $time);
  fork: fork_join_any_thread
    thread(0, 10);
    thread(1, 20);
    thread(2, 30);
  join_any
  $display("@%0t, fork_join_any_thread exited", $time);
  $display("@%0t, box handles array is %p", $time, bx);
  disable fork_join_any_thread;
  $display("@%0t, disabled fork_join_any_thread", $time);
  #100ns;
  $display("@%0t, box handles array is %p", $time, bx);

  #10ns;
  b_fork_join_any = 0;
  b_fork_join_none = 1;
end

// TODO-3.1 learn fork-join_none thread exited directly without calling any
// sub-thread, and continue executing other statements. Then the
// fork-join_none sub-threads would be still running.
// TODO-3.2 learn the wait fork statement, and check the time after it is
// satisified. The time should be the point when all fork sub-thread finished.
initial begin: fork_join_none
  wait(b_fork_join_none == 1); $display("b_fork_join_none process block started");
  bx = '{null, null, null};
  $display("@%0t, fork_join_none_thread entered", $time);
  fork: fork_join_none_thread
    thread(0, 10);
    thread(1, 20);
    thread(2, 30);
  join_none
  $display("@%0t, fork_join_none_thread exited", $time);
  $display("@%0t, box handles array is %p", $time, bx);
  #15ns;
  $display("@%0t, box handles array is %p", $time, bx);
  wait fork;
  $display("@%0t, fork_join_none_thread's all sub-threads finished", $time);
  $display("@%0t, box handles array is %p", $time, bx);
end


endmodule

虚方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// This example is referred to the lec2/class_inheritance
// The purpose is to learn the convenience of virtual method
module virtual_methods;

  class trans;
    bit[31:0] data[];
    int pkt_id;
    int data_nidles;
    int pkt_nidles;
    bit rsp;

    virtual function trans clone(trans t = null);
      if(t == null) t = new();
      t.data = data;
      t.pkt_id = pkt_id;
      t.data_nidles = data_nidles;
      t.pkt_nidles = pkt_nidles;
      t.rsp = rsp;
      return t;
    endfunction
  endclass

  class chnl_trans extends trans;
    int ch_id; // new member in child class

    virtual function trans clone(trans t = null);
      chnl_trans ct;
      if(t == null)
        ct = new();
      else
        void'($cast(ct, t));
      void'(super.clone(ct));
      ct.ch_id = ch_id; // new member
      return ct;
    endfunction
  endclass

  initial begin
    trans t1, t2;
    chnl_trans ct1, ct2;

    ct1 = new();
    ct1.pkt_id = 200;
    ct1.ch_id = 2;
    // t1 pointed to ct1's trans class data base
    t1 = ct1;
    $display("before cloning ct1 object");
    $display("ct1.pkt_id = %0d, ct1.ch_id = %0d", ct1.pkt_id, ct1.ch_id);

    // TODO-1 compare with lec2/class_inheritance TODO-2
    // why it is legal to call t1.clone() here?
    // TODO-2 via this example, please summarize the virtual method
    // convenience
    t2 = t1.clone();
    void'($cast(ct2, t2));

    // TODO-3 to access ct2.ch_id, could we directly use t2.ch_id?
    // is it possible to add modified virtual before chnl_trans::ch_id, and
    // then access it by 't2.ch_id'? and why?
    $display("after cloning ct1 object");
    $display("ct1.pkt_id = %0d, ct1.ch_id = %0d", ct1.pkt_id, ct1.ch_id);
    $display("ct2.pkt_id = %0d, ct2.ch_id = %0d", ct2.pkt_id, ct2.ch_id);
  end

endmodule

方法(任务与函数)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
module task_and_function;

bit b_function_define = 1;
bit b_task_define = 1;
bit b_inout_vs_ref = 1;

function int double_f0(int a);
  return 2*a;
endfunction

function void double_f1(input int a, output int b);
  b = 2*a;
endfunction

function void double_f2(inout int a);
  a = 2*a;
endfunction

function automatic void double_f3(ref int a);
  a = 2*a;
endfunction

task double_t1(input int a, output int b);
  b = 2*a;
endtask

task double_t2(inout int a);
  a = 2*a;
endtask

task automatic double_t3(ref int a);
  a = 2*a;
endtask

task double_t2_delay(inout int a);
  a = 2*a;
  #10ns;
endtask

task automatic double_t3_delay(ref int a);
  a = 2*a;
  #10ns;
endtask

// TODO-1 lear the function definition possible ways
initial begin: function_define
  int v1, v2;
  wait(b_function_define == 1); $display("b_function_define process block started");
  v1 = 10;
  v2 = double_f0(v1);
  $display("v1 = %0d, double function result is %0d", v1, v2);

  v1 = 10;
  double_f1(v1, v2);
  $display("v1 = %0d, double function result is %0d", v1, v2);

  v1 = 10;
  $display("v1 is %0d before calling double_f2(v1)", v1);
  double_f2(v1);
  $display("v1 is %0d (result) after calling double_f2(v1)", v1);

  v1 = 10;
  $display("v1 is %0d before calling double_f3(v1)", v1);
  double_f3(v1);
  $display("v1 is %0d (result) after calling double_f3(v1)", v1);
end

// TODO-2 learn the task definition possible ways
initial begin: task_define
  int v1, v2;
  wait(b_task_define == 1); $display("b_task_define process block started");
  v1 = 10;
  double_t1(v1, v2);
  $display("v1 = %0d, double task result is %0d", v1, v2);

  v1 = 10;
  $display("v1 is %0d before calling double_t2(v1)", v1);
  double_t2(v1);
  $display("v1 is %0d (result) after calling double_t2(v1)", v1);

  v1 = 10;
  $display("v1 is %0d before calling double_t3(v1)", v1);
  double_t3(v1);
  $display("v1 is %0d (result) after calling double_t3(v1)", v1);
end

// TODO-3 compare the inout and ref argument between function and task use
initial begin: inout_vs_ref
  int v1, v2;
  wait(b_inout_vs_ref == 1); $display("b_inout_vs_ref process block started");

  v1 = 10;
  $display("v1 is %0d before calling double_t2_delay(v1)", v1);
  fork
    double_t2_delay(v1);
    begin #5ns; $display("@%0t v1 = %0d in task call double_t2_delay(v1)", $time, v1); end
  join
  $display("v1 is %0d (result) after calling double_t2_delay(v1)", v1);

  v1 = 10;
  $display("v1 is %0d before calling double_t3_delay(v1)", v1);
  fork
    double_t3_delay(v1);
    begin #5ns; $display("@%0t v1 = %0d in task call double_t3_delay(v1)", $time, v1); end
  join
  $display("v1 is %0d (result) after calling double_t3_delay(v1)", v1);
end

endmodule

SV用于设计

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
module sv_for_design;

bit b_always_compare = 1;
bit b_compare_operator = 1;
bit b_inside_operator = 1;
bit b_case_statement = 1;

// TODO-1 why l2 != l3 at time 0 ?
logic l1 = 0, l2, l3;
always @(l1) l2 <= l1;
always_comb l3 = l1;
initial begin: always_compare
  wait(b_always_compare == 1); $display("always_compare process block started");
  #0;
  $display("@%0t, l2 = %b", $time, l2);
  $display("@%0t, l3 = %b", $time, l3);

  #10;
  l1 <= 1;
  #1;
  $display("@%0t, l2 = %b", $time, l2);
  $display("@%0t, l3 = %b", $time, l3);
end

// TODO-2 learn the compare operators' difference
initial begin: compare_operator
  logic [3:0] v1, v2;

  wait(b_compare_operator == 1); $display("compare_operator process block started");
  v1 = 'b111x;
  v2 = 'b1110;
  if(v1 != v2) // binary logical equality operator
    $display("v1 %b != v2 %b", v1, v2);
  else
    $display("v1 %b == v2 %b", v1, v2);
  $display("the operator result (v1 != v2) is %b", (v1 != v2));


  if(v1 !== v2) // binary case equality operator
    $display("v1 %b !== v2 %b", v1, v2);
  else
    $display("v1 %b === v2 %b", v1, v2);
  $display("the operator result (v1 !== v2) is %b", (v1 !== v2));

end

// TODO-3 learn the inside operator
initial begin: inside_operator
  bit [2:0] v1;
  wait(b_inside_operator == 1); $display("inside_operator process block started");
  v1 = 'b100;
  if(v1 == 'b100 || v1 == 'b010 || v1 == 'b001)
    $display("v1: %0b meets onehot vector requirement!", v1);
  else
    $display("v1: %0b does not meet onehot vector requirement!", v1);

  if(v1 inside {'b100, 'b010, 'b001})
    $display("v1: %0b meets onehot vector requirement!", v1);
  else
    $display("v1: %0b does not meet onehot vector requirement!", v1);

  if($onehot(v1) == 1)
    $display("v1: %0b meets onehot vector requirement!", v1);
  else
    $display("v1: %0b does not meet onehot vector requirement!", v1);
end

// TODO-4 learn {unique, priority} case{ ,x, z} statement
initial begin: case_statement
  parameter width_p = 4;
  bit [width_p-1:0] v_arr[3];
  wait(b_case_statement == 1); $display("case_statement process block started");
  v_arr = '{'b1000, 'b1111, 'b0110};
  foreach(v_arr[i]) begin
    unique case(v_arr[i])
      'b0001, 'b0010, 'b0100, 'b1000: $display("v1: %0b meets onehot vector requirement!", v_arr[i]);
      0: $display("v1: %0b is ZERO", v_arr[i]);
      'b1111: $display("v1: %0b is ALL ONES", v_arr[i]);
      default: $display("v1: %0b has [2~%0d] ones", v_arr[i], width_p-1);
    endcase
  end
end

endmodule

SV进阶练习

设计描述

所有SV练习都是针对硬件设计MCDF进行的, 在这里介绍一下它的结构、功能、寄存器和时序。

该设计我们称之为多通道数据整形器(MCDF, multi-channel data formatter) , 它可以将上行(uplink) 多个通道数据经过内部的FIFO,最终以数据包(data packet) 的形式送出。由于上行数据和下行数据的接口协议不同,我们也将在后面的接口描述和时序部分进一步讲解。此外,多通道数据整形器也有寄存器的读写接口,可以支持更多的控制功能。

MCDF结构
  • 从上图的MCDF结构来看主要可以分为如下几个部分

    1. 上行数据的通道从端(Channel Slave) , 负责接收上行数据, 并且存储到其FIFO中。
    2. 仲裁器(Arbiter) 可以选择从不同的FIFO中读取数据, 进而将数据进一步传送至整形器(formatter) 。
    3. 整形器(Formatter) 将数据按照一定的接口时序送出至下行接收端。
    4. 控制寄存器(Control Registers) 有专用的寄存器读写接口, 负责接收命令并且对MCDF的功能做出修改。
  • 接口描述

系统信号接口

信号名 位宽 方向 说明
clk [0] I 时钟信号
RSTN [0] I 复位信号,低有效

通道从端接口

信号名 位宽 方向 说明
CHx_DATA [31:0] I 通道数据输入(x为0/1/2)
CHx_VALID [0] I 通道数据有效标志,高有效
CHx_READY [0] O 通道数据接收信号,高位表示接收成功

整形器接口

信号名 位宽 方向 说明
FMT_CHID [1:0] O 整形数据包的通道ID号
FMT_LENGTH [4:0] O 整形数据包的长度信号
FMT_REQ [0] O 整形数据包发送请求
FMT_GRANT [0] I 整形数据包被允许发送的标志
FMT_DATA [31:0] O 数据输出端口
FMT_START [0] O 数据包起始标志
FMT_END [0] O 数据包结束标志

控制寄存器接口

信号名 位宽 方向 说明
CMD [1:0] I 寄存器读写命令
CMD_ADDR [7:0] I 寄存器地址
CMD_DATA_IN [31:0] I 寄存器输入端口
CMD_DATA_OUT [31:0] O 寄存器输出端口
  • 接口时序

通道从端接口时序

通道从端接口时序

当valid为高时,表示要写入数据。如果该时钟周期ready为高,则表示已经将数据写入;如果该时钟周期ready为低,则需要等到ready为高的时钟周期才可以将数据写入。

整形器接口时序

整形器接口时序

整形器发送数据是按照数据包的形式发送的,可以选择数据包的长度有4、8、16和32。整形器必须完整发送某一个通道的数据包后, 才可以转而准备发送下一个数据包, 在发送数据包期间, fmt_chid和fmt_length应该保持不变, 直到数据包发送完毕。

在整形器准备发送数据包时, 首先应该将fmt_req置为高, 同时等待接收端的fmt_grant。当fmt_grant变为高时, 应该在下一个周期将fmt_req置为低。fmt_start也必须在接收到fmt_grant高有效的下一个时钟被置为高, 且需要维持一个时钟周期。在fmt_start被置为高有效的同一个周期,数据也开始传送,数据之间不允许有空闲周期,即应该连续发送数据,直到发送完最后一个数据时, fm_tend也应当被置为高并保持一个时钟周期。

相邻的数据包之间应该至少有一个时钟周期的空闲,即fmt_end从高位拉低后,至少需要经过一个时钟周期,fmt_req才可以被再次置为高。

控制寄存器接口时序

整形器接口时序

在控制寄存器接口上, 需要在每一个时钟解析cmd。当cmd为写指令时, 需要把数据cmd_data_in写入到cmd_addr对应的寄存器中; 当cmd为读指令时, 即需要从cmd_addr对应的寄存器中读取数据, 并在下一个周期, 将数据驱动至cmd_data_out接口。

  • 寄存器描述

地址 0x00 通道0控制寄存器32bits读写寄存器

功能
bit(0) 通道使能信号。1为打开, 0位关闭。复位值为1。
bit(2:1) 优先级。0为最高, 3为最低。复位值为3。
bit(5:3) 数据包长度, 解码对应表为, 0对应长度4,1对应长度8,2对应长度16,3对应长度32,其它数值(4-7)均暂时对应长度32。复位值为0。
bit(31:6) 保留位, 无法写入。复位值为0。

地址 0x04 通道1控制寄存器32bits读写寄存器
同通道0控制寄存器描述。

地址0x08通道2控制寄存器32bits读写寄存器
同通道0控制寄存器描述。

地址0x10通道0状态寄存器32bits只读寄存器

功能
bit(7:0) 上行数据从端FIFO的可写余量, 同FIFO的数据余量保持同步变化。复位值为FIFO的深度数
bit(31:8) 保留位,复位值为0

地址0x14通道1状态寄存器32bits只读寄存器
同通道0状态寄存器描述。

地址0x18通道2状态寄存器32bits只读寄存器
同通道0状态寄存器描述。

  • 简化版MCDF

在了解了MCDF的设计之后,我们接下来的几次实验将首先围绕着MCDF中的子模块channel和arbiter的组合MCDT着手, 暂时不考虑formatter的数据打包功能和寄存器的配置作用。因此我们所选择的MCDF的“片段”MCDT即由三个slave channel和一个arbiter构成。从之前介绍的各个模块功能来看, 这个组合的作用类似于一个高级的“MUX”多路选择器, 只不过这个选择器的无需外部的选择配置, 而是会根据slave channel的数据发起请求来做选择授权, 继而将数据送出。

MCDT结构

lab1

  • 从verilog到SV的进场

    1. 将下面是tb1.v的代码,将文件名修改为tb1.sv后,编译仿真,查看仿真行为是否同tb1.v的仿真行为一致?这说明了什么呢?
    2. 将tb1.sv中的信号变量类型由reg或者wire修改为logic类型, 再编译仿真,查看行为是否同修改之前的一致呢?这是为什么?

    3)在步骤2) 的基础上, 如果将rstn的类型由logic修改为bit类型,再编译仿真, 行为是否同步骤2)的一致呢?这是为什么?

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
`timescale 1ns/1ps

module tb1;
reg          clk;
reg          rstn;
reg  [31:0]  ch0_data;
reg          ch0_valid;
wire         ch0_ready;
wire [ 5:0]  ch0_margin;
reg  [31:0]  ch1_data;
reg          ch1_valid;
wire         ch1_ready;
wire [ 5:0]  ch1_margin;
reg  [31:0]  ch2_data;
reg          ch2_valid;
wire         ch2_ready;
wire [ 5:0]  ch2_margin;
wire [31:0]  mcdt_data;
wire         mcdt_val;
wire [ 1:0]  mcdt_id;

mcdt dut(
   .clk_i(clk)
  ,.rstn_i(rstn)
  ,.ch0_data_i(ch0_data)
  ,.ch0_valid_i(ch0_valid)
  ,.ch0_ready_o(ch0_ready)
  ,.ch0_margin_o(ch0_margin)
  ,.ch1_data_i(ch1_data)
  ,.ch1_valid_i(ch1_valid)
  ,.ch1_ready_o(ch1_ready)
  ,.ch1_margin_o(ch1_margin)
  ,.ch2_data_i(ch2_data)
  ,.ch2_valid_i(ch2_valid)
  ,.ch2_ready_o(ch2_ready)
  ,.ch2_margin_o(ch2_margin)
  ,.mcdt_data_o(mcdt_data)
  ,.mcdt_val_o(mcdt_val)
  ,.mcdt_id_o(mcdt_id)
);

// clock generation
initial begin
  clk <= 0;
  forever begin
    #5 clk <= !clk;
  end
end

// reset trigger
initial begin
  #10 rstn <= 0;
  repeat(10) @(posedge clk);
  rstn <= 1;
end

// data test
initial begin
  @(posedge rstn);
  repeat(5) @(posedge clk);
  // channel 0 test
  chnl_write(0, 'h00C0_0000);
  chnl_write(0, 'h00C0_0001);
  chnl_write(0, 'h00C0_0002);
  chnl_write(0, 'h00C0_0003);
  chnl_write(0, 'h00C0_0004);
  chnl_write(0, 'h00C0_0005);
  chnl_write(0, 'h00C0_0006);
  chnl_write(0, 'h00C0_0007);
  chnl_write(0, 'h00C0_0008);
  chnl_write(0, 'h00C0_0009);
  // channel 1 test
  chnl_write(1, 'h00C1_0000);
  chnl_write(1, 'h00C1_0001);
  chnl_write(1, 'h00C1_0002);
  chnl_write(1, 'h00C1_0003);
  chnl_write(1, 'h00C1_0004);
  chnl_write(1, 'h00C1_0005);
  chnl_write(1, 'h00C1_0006);
  chnl_write(1, 'h00C1_0007);
  chnl_write(1, 'h00C1_0008);
  chnl_write(1, 'h00C1_0009);
  // channel 2 test
  chnl_write(2, 'h00C2_0000);
  chnl_write(2, 'h00C2_0001);
  chnl_write(2, 'h00C2_0002);
  chnl_write(2, 'h00C2_0003);
  chnl_write(2, 'h00C2_0004);
  chnl_write(2, 'h00C2_0005);
  chnl_write(2, 'h00C2_0006);
  chnl_write(2, 'h00C2_0007);
  chnl_write(2, 'h00C2_0008);
  chnl_write(2, 'h00C2_0009);
end

// channel write task
task chnl_write(input reg[1:0] id, input reg[31:0] data);
  case(id)
    0: begin
      @(posedge clk);
      ch0_valid <= 1;
      ch0_data <= data;
      @(posedge clk);
      ch0_valid <= 0;
      ch0_data <= 0;
    end
    1: begin
      @(posedge clk);
      ch1_valid <= 1;
      ch1_data <= data;
      @(posedge clk);
      ch1_valid <= 0;
      ch1_data <= 0;
    end
    2: begin
      @(posedge clk);
      ch2_valid <= 1;
      ch2_data <= data;
      @(posedge clk);
      ch2_valid <= 0;
      ch2_data <= 0;
    end
    default: $error("channel id %0d is invalid", id);
  endcase
endtask

endmodule
  • 方法task和函数function
    1. 在tb2.sv文件中, 可以看到不同于tb1.sv文件的是,之前产生时钟和发起复位的两个inital过程块语句都被两个task(即clk_gen和rstn_gen)取代了。
    2. 在两个intal块中分别调用产生时钟和复位的task,再编译仿真查看时钟信号和复位信号,是否正常?
    3. 为什么要将两个task在两个initial块中调用?这是为什么呢?是否可以在一个initial块中调用呢?如果可以,调用它们的顺序是什么?
    4. 是否可以读出目前时钟的周期和频率呢?该如何测量呢?如果我们想改进clk_gen()方法, 使其变为可以设置时钟周期的任务clk_gen(int peroid), 那么该怎么修改目前的任务clk_gen()呢? 修改成功后,请在initial块中调用任务ck_gen(20),看看波形中的时钟周期是否变为20ns呢?
    5. 如果将`timescale 1ns/1ps修改为`timescale 1ps/1ps, 那么仿真中的时钟周期是否发生变化?这是为什么呢?
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
`timescale 1ns/1ps

module tb2_ref;
logic         clk;
logic         rstn;
logic [31:0]  ch0_data;
logic         ch0_valid;
logic         ch0_ready;
logic [ 5:0]  ch0_margin;
logic [31:0]  ch1_data;
logic         ch1_valid;
logic         ch1_ready;
logic [ 5:0]  ch1_margin;
logic [31:0]  ch2_data;
logic         ch2_valid;
logic         ch2_ready;
logic [ 5:0]  ch2_margin;
logic [31:0]  mcdt_data;
logic         mcdt_val;
logic [ 1:0]  mcdt_id;

mcdt dut(
   .clk_i(clk)
  ,.rstn_i(rstn)
  ,.ch0_data_i(ch0_data)
  ,.ch0_valid_i(ch0_valid)
  ,.ch0_ready_o(ch0_ready)
  ,.ch0_margin_o(ch0_margin)
  ,.ch1_data_i(ch1_data)
  ,.ch1_valid_i(ch1_valid)
  ,.ch1_ready_o(ch1_ready)
  ,.ch1_margin_o(ch1_margin)
  ,.ch2_data_i(ch2_data)
  ,.ch2_valid_i(ch2_valid)
  ,.ch2_ready_o(ch2_ready)
  ,.ch2_margin_o(ch2_margin)
  ,.mcdt_data_o(mcdt_data)
  ,.mcdt_val_o(mcdt_val)
  ,.mcdt_id_o(mcdt_id)
);

// clock generation
// TODO:: please create task clk_gen(int peroid)
task clk_gen();
  clk <= 0;
  forever begin
    #5 clk<= !clk;
  end
endtask

initial begin
  // generate clk
  clk_gen();
end

// reset trigger
// create task rstn_gen()
task rstn_gen();
  #10 rstn <= 0;
  repeat(10) @(posedge clk);
  rstn <= 1;
endtask

initial begin
  // trigger rstn
  rstn_gen();
end


// data test
initial begin
  @(posedge rstn);
  repeat(5) @(posedge clk);
  // channel 0 test
  chnl_write(0, 'h00C0_0000);
  chnl_write(0, 'h00C0_0001);
  chnl_write(0, 'h00C0_0002);
  chnl_write(0, 'h00C0_0003);
  chnl_write(0, 'h00C0_0004);
  chnl_write(0, 'h00C0_0005);
  chnl_write(0, 'h00C0_0006);
  chnl_write(0, 'h00C0_0007);
  chnl_write(0, 'h00C0_0008);
  chnl_write(0, 'h00C0_0009);
  // channel 1 test
  chnl_write(1, 'h00C1_0000);
  chnl_write(1, 'h00C1_0001);
  chnl_write(1, 'h00C1_0002);
  chnl_write(1, 'h00C1_0003);
  chnl_write(1, 'h00C1_0004);
  chnl_write(1, 'h00C1_0005);
  chnl_write(1, 'h00C1_0006);
  chnl_write(1, 'h00C1_0007);
  chnl_write(1, 'h00C1_0008);
  chnl_write(1, 'h00C1_0009);
  // channel 2 test
  chnl_write(2, 'h00C2_0000);
  chnl_write(2, 'h00C2_0001);
  chnl_write(2, 'h00C2_0002);
  chnl_write(2, 'h00C2_0003);
  chnl_write(2, 'h00C2_0004);
  chnl_write(2, 'h00C2_0005);
  chnl_write(2, 'h00C2_0006);
  chnl_write(2, 'h00C2_0007);
  chnl_write(2, 'h00C2_0008);
  chnl_write(2, 'h00C2_0009);
end

// channel write task
task chnl_write(input reg[1:0] id, input reg[31:0] data);
  case(id)
    0: begin
      @(posedge clk);
      ch0_valid <= 1;
      ch0_data <= data;
      @(posedge clk);
      ch0_valid <= 0;
      ch0_data <= 0;
    end
    1: begin
      @(posedge clk);
      ch1_valid <= 1;
      ch1_data <= data;
      @(posedge clk);
      ch1_valid <= 0;
      ch1_data <= 0;
    end
    2: begin
      @(posedge clk);
      ch2_valid <= 1;
      ch2_data <= data;
      @(posedge clk);
      ch2_valid <= 0;
      ch2_data <= 0;
    end
    default: $error("channel id %0d is invalid", id);
  endcase
endtask

endmodule
  • 数组的使用 :
    1. 如果要求对每一个slave的数据发出100个数, 那么该怎么实现呢?
    2. 如果现在要先生成100个数,并对它们按照目前的数值规则进行赋值,那么请创建3个动态数组, 分别放置要发送给3个slave的数据。
    3. 接下来利用之前生成的数组数据, 将它们读取并发送给三个channel。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
`timescale 1ns/1ps

module tb3_ref;
logic         clk;
logic         rstn;
logic [31:0]  ch0_data;
logic         ch0_valid;
logic         ch0_ready;
logic [ 5:0]  ch0_margin;
logic [31:0]  ch1_data;
logic         ch1_valid;
logic         ch1_ready;
logic [ 5:0]  ch1_margin;
logic [31:0]  ch2_data;
logic         ch2_valid;
logic         ch2_ready;
logic [ 5:0]  ch2_margin;
logic [31:0]  mcdt_data;
logic         mcdt_val;
logic [ 1:0]  mcdt_id;

mcdt dut(
   .clk_i(clk)
  ,.rstn_i(rstn)
  ,.ch0_data_i(ch0_data)
  ,.ch0_valid_i(ch0_valid)
  ,.ch0_ready_o(ch0_ready)
  ,.ch0_margin_o(ch0_margin)
  ,.ch1_data_i(ch1_data)
  ,.ch1_valid_i(ch1_valid)
  ,.ch1_ready_o(ch1_ready)
  ,.ch1_margin_o(ch1_margin)
  ,.ch2_data_i(ch2_data)
  ,.ch2_valid_i(ch2_valid)
  ,.ch2_ready_o(ch2_ready)
  ,.ch2_margin_o(ch2_margin)
  ,.mcdt_data_o(mcdt_data)
  ,.mcdt_val_o(mcdt_val)
  ,.mcdt_id_o(mcdt_id)
);

// clock generation
initial begin
  clk <= 0;
  forever begin
    #5 clk <= !clk;
  end
end

// reset trigger
initial begin
  #10 rstn <= 0;
  repeat(10) @(posedge clk);
  rstn <= 1;
end

logic [31:0] chnl0_arr[];
logic [31:0] chnl1_arr[];
logic [31:0] chnl2_arr[];
// USER TODO
// generate 100 data for each dynamic array
initial begin
  chnl0_arr = new[100];
  chnl1_arr = new[100];
  chnl2_arr = new[100];
  foreach(chnl0_arr[i]) begin
    chnl0_arr[i] = 'h00C0_00000 + i;
        chnl1_arr[i] = 'h00C1_00000 + i;
        chnl2_arr[i] = 'h00C2_00000 + i;
  end
end

// USER TODO
// use the dynamic array, user would send all of data
// data test
initial begin
  @(posedge rstn);
  repeat(5) @(posedge clk);
  // channel 0 test
  // TODO use chnl0_arr to send all data
  foreach(chnl0_arr[i]) chnl_write(0, chnl0_arr[i]);

  // channel 1 test
  // TODO use chnl1_arr to send all data
  foreach(chnl1_arr[i]) chnl_write(1, chnl1_arr[i]);

  // channel 2 test
  // TODO use chnl2_arr to send all data
  foreach(chnl2_arr[i]) chnl_write(2, chnl2_arr[i]);

end

// channel write task
task chnl_write(input reg[1:0] id, input reg[31:0] data);
  case(id)
    0: begin
      @(posedge clk);
      ch0_valid <= 1;
      ch0_data <= data;
      @(posedge clk);
      ch0_valid <= 0;
      ch0_data <= 0;
    end
    1: begin
      @(posedge clk);
      ch1_valid <= 1;
      ch1_data <= data;
      @(posedge clk);
      ch1_valid <= 0;
      ch1_data <= 0;
    end
    2: begin
      @(posedge clk);
      ch2_valid <= 1;
      ch2_data <= data;
      @(posedge clk);
      ch2_valid <= 0;
      ch2_data <= 0;
    end
    default: $error("channel id %0d is invalid", id);
  endcase
endtask

endmodule
  • 验证结构

为了实现清晰的验证结构,我们希望将DUT和激励发生器(stimulator)之间划分。因此,我们可以将激励方法chnl_write()封装在新的模块chnl_initiator中。

从tb4.sv中可以发现之前的inital语句块channel_write_task已经不见了,在其位置上的变为了三个例化的chnl_initiator实例chnl0_init、chnl1_init和chnl2_init。它们的作用扮演每个channel slave通道对应的stimulator,发送激励,因此我们在其模块chnl_initiator中定义了它的三个方法, 即set_name() 、chnl_write() 和chnl_idle() 。

  • chnl_ixle() 要实现一个时钟周期的空闲,在该周期中,ch_valid应为低,ch_data应为0。
  • chn_write() 要实现一次有效的写数据, 并随后调用chnl_idle(), 实现一个空闲周期,在实现有效写数据时,请考虑如何使用ch_ready信号,结合功能描述的channel slave接口时序来看, 只有当valid为高且ready为高时,数据写入才算成功,如果此时ready为低,那么则应该保持数据和valid信号,直到ready拉高时,数据写入才算成功。
  • set_name()即设置实例的名称, 在inital过程块“data test”中, 在发送各个channel数据前, 请设置各个channel initiator的实例名称, 这样方便在仿真时各个实例的打印信息可以显示它们各自的名称、数据发送时间和数据内容,便于阅读和调试。
  • 最后,进入了本次实验的最后一个步骤了,之所以提出发送更多的数据,并且发送更紧凑高速的数据, 是为了可以观察到,是否你的三个channel_slave各自的chX_ready信号可以拉低呢?如果拉低了,这代表着什么?那么请你试试看,考虑如何发送更多更快的数据,让M CDT的三个chX_ready信号可以拉低吧。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
`timescale 1ns/1ps


module chnl_initiator(
  input               clk,
  input               rstn,
  output logic [31:0] ch_data,
  output logic        ch_valid,
  input               ch_ready,
  input        [ 5:0] ch_margin
);

string name;

function void set_name(string s);
  name = s;
endfunction

task chnl_write(input logic[31:0] data);
  // USER TODO
  // drive valid data
  // ...
  @(posedge clk);
  ch_valid <= 1;
  ch_data <= data;
  @(negedge clk);
  wait(ch_ready === 'b1);
  $display("%t channel initial [%s] sent data %x", $time, name, data);
  chnl_idle();
endtask

task chnl_idle();
  // USER TODO
  // drive idle data
  // ...
  @(posedge clk);
  ch_valid <= 0;
  ch_data <= 0;
endtask

endmodule

module tb4_ref;
logic         clk;
logic         rstn;
logic [31:0]  ch0_data;
logic         ch0_valid;
logic         ch0_ready;
logic [ 5:0]  ch0_margin;
logic [31:0]  ch1_data;
logic         ch1_valid;
logic         ch1_ready;
logic [ 5:0]  ch1_margin;
logic [31:0]  ch2_data;
logic         ch2_valid;
logic         ch2_ready;
logic [ 5:0]  ch2_margin;
logic [31:0]  mcdt_data;
logic         mcdt_val;
logic [ 1:0]  mcdt_id;

mcdt dut(
   .clk_i(clk)
  ,.rstn_i(rstn)
  ,.ch0_data_i(ch0_data)
  ,.ch0_valid_i(ch0_valid)
  ,.ch0_ready_o(ch0_ready)
  ,.ch0_margin_o(ch0_margin)
  ,.ch1_data_i(ch1_data)
  ,.ch1_valid_i(ch1_valid)
  ,.ch1_ready_o(ch1_ready)
  ,.ch1_margin_o(ch1_margin)
  ,.ch2_data_i(ch2_data)
  ,.ch2_valid_i(ch2_valid)
  ,.ch2_ready_o(ch2_ready)
  ,.ch2_margin_o(ch2_margin)
  ,.mcdt_data_o(mcdt_data)
  ,.mcdt_val_o(mcdt_val)
  ,.mcdt_id_o(mcdt_id)
);

// clock generation
initial begin
  clk <= 0;
  forever begin
    #5 clk <= !clk;
  end
end

// reset trigger
initial begin
  #10 rstn <= 0;
  repeat(10) @(posedge clk);
  rstn <= 1;
end

logic [31:0] chnl0_arr[];
logic [31:0] chnl1_arr[];
logic [31:0] chnl2_arr[];
// USER TODO
// generate 100 data for each dynamic array
initial begin
  chnl0_arr = new[100];
  chnl1_arr = new[100];
  chnl2_arr = new[100];
  foreach(chnl0_arr[i]) begin
    chnl0_arr[i] = 'h00C0_00000 + i;
        chnl1_arr[i] = 'h00C1_00000 + i;
        chnl2_arr[i] = 'h00C2_00000 + i;
  end
end

// USER TODO
// use the dynamic array, user would send all of data
// data test
initial begin
  @(posedge rstn);
  repeat(5) @(posedge clk);
  // USER TODO
  // Give unique names to each channel initiator
  // ...
  chnl0_init.set_name("chnl0_init");
  chnl1_init.set_name("chnl0_init");
  chnl2_init.set_name("chnl0_init");

  // channel 0 test
  // TODO use chnl0_arr to send all data
  foreach(chnl0_arr[i]) chnl0_init.chnl_write(chnl0_arr[i]);

  // channel 1 test
  // TODO use chnl1_arr to send all data
  foreach(chnl1_arr[i]) chnl1_init.chnl_write(chnl1_arr[i]);

  // channel 2 test
  // TODO use chnl2_arr to send all data
  foreach(chnl2_arr[i]) chnl2_init.chnl_write(chnl2_arr[i]);
end


chnl_initiator chnl0_init(
  .clk      (clk),
  .rstn     (rstn),
  .ch_data  (ch0_data),
  .ch_valid (ch0_valid),
  .ch_ready (ch0_ready),
  .ch_margin(ch0_margin)
);

chnl_initiator chnl1_init(
  .clk      (clk),
  .rstn     (rstn),
  .ch_data  (ch1_data),
  .ch_valid (ch1_valid),
  .ch_ready (ch1_ready),
  .ch_margin(ch1_margin)
);

chnl_initiator chnl2_init(
  .clk      (clk),
  .rstn     (rstn),
  .ch_data  (ch2_data),
  .ch_valid (ch2_valid),
  .ch_ready (ch2_ready),
  .ch_margin(ch2_margin)
);

endmodule

lab2

练习2部分将主要回顾之前接口、仿真结束、类和包的使用,在这一个练习中,将逐渐从使用硬件盒子(module)验证过渡到使用接口和软件盒子(class) 来验证设计。而这一个练习之所以重要也是因为它是硬件验证方式与软件验证方式之间的过渡,同时作为验证环境的启蒙。在本练习的最后一个小节中也能够初步体会到类的继承和层次包含关系,而这些都将作为日后学习高阶UVM知识的重要基础。

  • 接口的使用

在lab2的tb1.sv的代码部分承接的是上一次实验的最后代码部分,不过最显著的差别在于,我们将验证组件和DUT之间通过接口来连接,所以验证组件chnl_initiator的端口变得非常*干净”, 即chnl_intf intf。在使用接口之前我们需要定义接口chnl_intf和内部的端口,同时我们也声明了一个时钟块(clocking block) , 它的功能是为了消除可能存在的竞争(race) 问题,确保时钟驱动数据之间有一定的延迟, 以便于DUT顺利采样。

因此更新后的代码, 可以发现例化的实例包括了只产生数据的channel generator, 只负责发送数据的channel_initiator以及作为验证组件和DUT之间的接口chnl intf。而例化之后,在inital块中需要通过调用组件的方法完成初始化、名字设置和空闲周期的设置。这里初始化是为了设置ID,名字设置是为了稍后打印时容易区分各个组件,而空闲周期的设置则关系到我们接下来的实验要求:

  1. 首先观察波形,可以发现channel_initiator发送的数据例如valid和data与时钟clk均在同一个变化沿,没有任何延迟。同我们课程中所讲到的,这种0延迟的数据发送不利于波形查看和阅读, 因此请在已有代码的基础上使用intf.ck的方式来做数据举动,并且再观察波形,查看驱动的数据与时钟上升沿的延迟是多少。
  2. 为了更好地控制相邻数据之间的空闲间隔,又引入了一个变量idle_cycles,它表示相邻有效数据之间的间隔。已有代码会使得有效数据之间保持固定的一个空闲周期,需要使用idle_cycles变量,来灵活控制有效数据之间的空闲周期。通过这个方法,在tb的initial块中 通过方法set_idle_cycles()使得三个channel_initiator的空闲周期变为0, 即可以实现有效数据的连续发送。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
`timescale 1ns/1ps

interface chnl_intf(input clk, input rstn);
  logic [31:0] ch_data;
  logic        ch_valid;
  logic        ch_ready;
  logic [ 5:0] ch_margin;
  clocking drv_ck @(posedge clk);
    default input #1ns output #1ns;
    output ch_data, ch_valid;
    input ch_ready, ch_margin;
  endclocking
endinterface

module chnl_initiator(chnl_intf intf);
  string name;
  int idle_cycles = 1;
  function automatic void set_idle_cycles(int n);
    idle_cycles = n;
  endfunction
  function automatic void set_name(string s);
    name = s;
  endfunction
  task automatic chnl_write(input logic[31:0] data);
    @(posedge intf.clk);
    // USER TODO 1.1
    // Please use the clocking drv_ck of chnl_intf to drive data
    intf.drv_ck.ch_valid <= 1;
    intf.drv_ck.ch_data <= data;
    @(negedge intf.clk);
    wait(intf.ch_ready === 'b1);
    $display("%t channel initiator [%s] sent data %x", $time, name, data);
    // USER TODO 1.2
    // Apply variable idle_cycles and decide how many idle cycles to be
    // inserted between two sequential data
    repeat(idle_cycles) chnl_idle();
  endtask
  task automatic chnl_idle();
    @(posedge intf.clk);
    // USER TODO 1.1
    // Please use the clocking drv_ck of chnl_intf to drive data
    intf.drv_ck.ch_valid <= 0;
    intf.drv_ck.ch_data <= 0;
  endtask
endmodule

module chnl_generator;
  int chnl_arr[$];
  int num;
  int id;
  function automatic void initialize(int n);
    id = n;
    num = 0;
  endfunction
  function automatic int get_data();
    int data;
    data = 'h00C0_0000 + (id<<16) + num;
    num++;
    chnl_arr.push_back(data);
    return data;
  endfunction
endmodule

module tb1_ref;
  logic         clk;
  logic         rstn;
  logic [31:0]  mcdt_data;
  logic         mcdt_val;
  logic [ 1:0]  mcdt_id;

  mcdt dut(
     .clk_i       (clk                )
    ,.rstn_i      (rstn               )
    ,.ch0_data_i  (chnl0_if.ch_data   )
    ,.ch0_valid_i (chnl0_if.ch_valid  )
    ,.ch0_ready_o (chnl0_if.ch_ready  )
    ,.ch0_margin_o(chnl0_if.ch_margin )
    ,.ch1_data_i  (chnl1_if.ch_data   )
    ,.ch1_valid_i (chnl1_if.ch_valid  )
    ,.ch1_ready_o (chnl1_if.ch_ready  )
    ,.ch1_margin_o(chnl1_if.ch_margin )
    ,.ch2_data_i  (chnl2_if.ch_data   )
    ,.ch2_valid_i (chnl2_if.ch_valid  )
    ,.ch2_ready_o (chnl2_if.ch_ready  )
    ,.ch2_margin_o(chnl2_if.ch_margin )
    ,.mcdt_data_o (mcdt_data          )
    ,.mcdt_val_o  (mcdt_val           )
    ,.mcdt_id_o   (mcdt_id            )
  );

  // clock generation
  initial begin
    clk <= 0;
    forever begin
      #5 clk <= !clk;
    end
  end

  // reset trigger
  initial begin
    #10 rstn <= 0;
    repeat(10) @(posedge clk);
    rstn <= 1;
  end

  initial begin
    // verification component initializationi
    chnl0_gen.initialize(0);
    chnl1_gen.initialize(1);
    chnl2_gen.initialize(2);
    chnl0_init.set_name("chnl0_init");
    chnl1_init.set_name("chnl1_init");
    chnl2_init.set_name("chnl2_init");
    chnl0_init.set_idle_cycles(0);
    chnl1_init.set_idle_cycles(0);
    chnl2_init.set_idle_cycles(0);
  end

  initial begin
    @(posedge rstn);
    repeat(5) @(posedge clk);
    repeat(100) begin
      chnl0_init.chnl_write(chnl0_gen.get_data());
    end
    chnl0_init.chnl_idle();
  end

  initial begin
    @(posedge rstn);
    repeat(5) @(posedge clk);
    repeat(100) begin
      chnl1_init.chnl_write(chnl1_gen.get_data());
    end
    chnl1_init.chnl_idle();
  end

  initial begin
    @(posedge rstn);
    repeat(5) @(posedge clk);
    repeat(100) begin
      chnl2_init.chnl_write(chnl2_gen.get_data());
    end
    chnl2_init.chnl_idle();
  end

  chnl_intf chnl0_if(.*);
  chnl_intf chnl1_if(.*);
  chnl_intf chnl2_if(.*);

  chnl_initiator chnl0_init(chnl0_if);
  chnl_initiator chnl1_init(chnl1_if);
  chnl_initiator chnl2_init(chnl2_if);

  chnl_generator chnl0_gen();
  chnl_generator chnl1_gen();
  chnl_generator chnl2_gen();

endmodule
  • 仿真的结束

tb2.sv中需要课外学习fork join的基本功能和使用方法,了解它的并行运行特性,以此此来区分不同的测试内容,这是由于每一个测试任务的测试目的和要求都不相同,具体要求可以来实现三个chnl_initiator同时发送数据的要求。同时我们又将不同的test也组装到task中,以在代码中查找tb2.sv需要首先移植tb1.sv的要求内容,接下来再完成新的实验要求。

  • 可以参考task basic_test(), 来完成burst_test(),它的要求是使得每个chnl_initiator的idle_cycles设置为0,同时发送500个数据, 最后结束测试。
  • 参考task basic_test()来完成task fifo_full_test() 。它的要求是无论采取什么数值的idle_cycles,也无论发送多少个数据,只要各个chnl_initiator的不停发送使得对应的channel缓存变为满标志(ready拉低),那么可以在三个channel都拉低过ready时(不必要同时拉低,先后拉低即可),便可以立即结束测试。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
`timescale 1ns/1ps

interface chnl_intf(input clk, input rstn);
  logic [31:0] ch_data;
  logic        ch_valid;
  logic        ch_ready;
  logic [ 5:0] ch_margin;
  clocking drv_ck @(posedge clk);
    default input #1ns output #1ns;
    output ch_data, ch_valid;
    input ch_ready, ch_margin;
  endclocking
endinterface

module chnl_initiator(chnl_intf intf);
  string name;
  int idle_cycles = 1;
  function automatic void set_idle_cycles(int n);
    idle_cycles = n;
  endfunction
  function automatic void set_name(string s);
    name = s;
  endfunction
  task automatic chnl_write(input logic[31:0] data);
    @(posedge intf.clk);
    // USER TODO 1.1
    // Please use the clocking drv_ck of chnl_intf to drive data
    intf.drv_ck.ch_valid <= 1;
    intf.drv_ck.ch_data <= data;
    @(negedge intf.clk);
    wait(intf.ch_ready === 'b1);
    $display("%t channel initiator [%s] sent data %x", $time, name, data);
    // USER TODO 1.2
    // Apply variable idle_cycles and decide how many idle cycles to be
    // inserted between two sequential data
    repeat(idle_cycles) chnl_idle();
  endtask
  task automatic chnl_idle();
    @(posedge intf.clk);
    // USER TODO 1.1
    // Please use the clocking drv_ck of chnl_intf to drive data
    intf.drv_ck.ch_valid <= 0;
    intf.drv_ck.ch_data <= 0;
  endtask
endmodule

module chnl_generator;
  int chnl_arr[$];
  int num;
  int id;
  function automatic void initialize(int n);
    id = n;
    num = 0;
  endfunction
  function automatic int get_data();
    int data;
    data = 'h00C0_0000 + (id<<16) + num;
    num++;
    chnl_arr.push_back(data);
    return data;
  endfunction
endmodule

module tb2_ref;
  logic         clk;
  logic         rstn;
  logic [31:0]  mcdt_data;
  logic         mcdt_val;
  logic [ 1:0]  mcdt_id;

  mcdt dut(
     .clk_i       (clk                )
    ,.rstn_i      (rstn               )
    ,.ch0_data_i  (chnl0_if.ch_data   )
    ,.ch0_valid_i (chnl0_if.ch_valid  )
    ,.ch0_ready_o (chnl0_if.ch_ready  )
    ,.ch0_margin_o(chnl0_if.ch_margin )
    ,.ch1_data_i  (chnl1_if.ch_data   )
    ,.ch1_valid_i (chnl1_if.ch_valid  )
    ,.ch1_ready_o (chnl1_if.ch_ready  )
    ,.ch1_margin_o(chnl1_if.ch_margin )
    ,.ch2_data_i  (chnl2_if.ch_data   )
    ,.ch2_valid_i (chnl2_if.ch_valid  )
    ,.ch2_ready_o (chnl2_if.ch_ready  )
    ,.ch2_margin_o(chnl2_if.ch_margin )
    ,.mcdt_data_o (mcdt_data          )
    ,.mcdt_val_o  (mcdt_val           )
    ,.mcdt_id_o   (mcdt_id            )
  );

  // clock generation
  initial begin
    clk <= 0;
    forever begin
      #5 clk <= !clk;
    end
  end

  // reset trigger
  initial begin
    #10 rstn <= 0;
    repeat(10) @(posedge clk);
    rstn <= 1;
  end

  initial begin
    basic_test();
    burst_test();
    fifo_full_test();
    $display("*****************all of tests have been finished********************");
    $finish();
  end

  // each channel send data with idle_cycles inside [1:3]
  // each channel send out 200 data
  // then to finish the test
  task automatic basic_test();
    // verification component initializationi
    chnl0_gen.initialize(0);
    chnl1_gen.initialize(1);
    chnl2_gen.initialize(2);
    chnl0_init.set_name("chnl0_init");
    chnl1_init.set_name("chnl1_init");
    chnl2_init.set_name("chnl2_init");
    chnl0_init.set_idle_cycles($urandom_range(1, 3));
    chnl1_init.set_idle_cycles($urandom_range(1, 3));
    chnl2_init.set_idle_cycles($urandom_range(1, 3));
    $display("basic_test initialized components");
    wait (rstn === 1'b1);
    repeat(5) @(posedge clk);
    $display("basic_test started testing DUT");
    // Please check the SV book for fork-join basic knowledge
    // and get understood it is for parallel thread running
    fork
      repeat(100) chnl0_init.chnl_write(chnl0_gen.get_data());
      repeat(100) chnl1_init.chnl_write(chnl1_gen.get_data());
      repeat(100) chnl2_init.chnl_write(chnl2_gen.get_data());
    join
        fork
      wait(chnl0_init.intf.ch_margin == 'h20);
      wait(chnl1_init.intf.ch_margin == 'h20);
      wait(chnl2_init.intf.ch_margin == 'h20);
    join
    $display("basic_test finished testing DUT");
  endtask

  // USER TODO 2.1
  // each channel send data with idle_cycles == 0
  // each channel send out 500 data
  // then to finish the test
  task automatic burst_test();
    // verification component initializationi
    chnl0_gen.initialize(0);
    chnl1_gen.initialize(1);
    chnl2_gen.initialize(2);
    chnl0_init.set_name("chnl0_init");
    chnl1_init.set_name("chnl1_init");
    chnl2_init.set_name("chnl2_init");
    chnl0_init.set_idle_cycles(0);
    chnl1_init.set_idle_cycles(0);
    chnl2_init.set_idle_cycles(0);
    $display("basic_test initialized components");
    wait (rstn === 1'b1);
    repeat(5) @(posedge clk);
    $display("basic_test started testing DUT");
    // Please check the SV book for fork-join basic knowledge
    // and get understood it is for parallel thread running
    fork
      begin
            repeat(500) chnl0_init.chnl_write(chnl0_gen.get_data());
                chnl0_init.chnl_idle();
          end
      begin
            repeat(500) chnl1_init.chnl_write(chnl1_gen.get_data());
                chnl1_init.chnl_idle();
          end
          begin
            repeat(500) chnl2_init.chnl_write(chnl2_gen.get_data());
                chnl2_init.chnl_idle();
          end
    join
        fork
      wait(chnl0_init.intf.ch_margin == 'h20);
      wait(chnl1_init.intf.ch_margin == 'h20);
      wait(chnl2_init.intf.ch_margin == 'h20);
    join
    $display("basic_test finished testing DUT");
  endtask

  // USER TODO 2.2
  // The test should be immediately finished when all of channels
  // have been reached fifo full state, but not all reaching
  // fifo full at the same time
  task automatic fifo_full_test();
    // verification component initializationi
    chnl0_gen.initialize(0);
    chnl1_gen.initialize(1);
    chnl2_gen.initialize(2);
    chnl0_init.set_name("chnl0_init");
    chnl1_init.set_name("chnl1_init");
    chnl2_init.set_name("chnl2_init");
    chnl0_init.set_idle_cycles(0);
    chnl1_init.set_idle_cycles(0);
    chnl2_init.set_idle_cycles(0);
    $display("fifo_full_test started testing DUT");
    fork: fork_all_run
          forever chnl0_init.chnl_write(chnl0_gen.get_data());
          forever chnl1_init.chnl_write(chnl1_gen.get_data());
          forever chnl2_init.chnl_write(chnl2_gen.get_data());
    join_none
    $display("fifo_full_test: 3 initiators running now");

    $display("fifo_full_test: waiting 3 channel fifos to be full");
    fork
      wait(chnl0_init.intf.ch_margin == 0);
      wait(chnl1_init.intf.ch_margin == 0);
      wait(chnl2_init.intf.ch_margin == 0);
    join
    $display("fifo_full_test: 3 channel fifos have reached full");

    $display("fifo_full_test: stop 3 initiators running");
    disable fork_all_run;
    $display("fifo_full_test: set and ensure all agents' initiator are idle state");
    fork
      chnl0_init.chnl_idle();
      chnl1_init.chnl_idle();
      chnl2_init.chnl_idle();
    join

    $display("fifo_full_test waiting DUT transfering all of data");
    fork
      wait(chnl0_init.intf.ch_margin == 'h20);
      wait(chnl1_init.intf.ch_margin == 'h20);
      wait(chnl2_init.intf.ch_margin == 'h20);
    join
    $display("fifo_full_test: 3 channel fifos have transferred all data");

    $display("fifo_full_test finished testing DUT");
  endtask

  chnl_intf chnl0_if(.*);
  chnl_intf chnl1_if(.*);
  chnl_intf chnl2_if(.*);

  chnl_initiator chnl0_init(chnl0_if);
  chnl_initiator chnl1_init(chnl1_if);
  chnl_initiator chnl2_init(chnl2_if);

  chnl_generator chnl0_gen();
  chnl_generator chnl1_gen();
  chnl_generator chnl2_gen();

endmodule
  • 类的例化和类的成员

在这一部分, 将之前用来封装验证功能的硬件盒子(module) 中的数据和内容移植到软件盒子(class) 中来,以通过前后代码的相同点和不同点来比较使用类的时候,需要注意什么地方,同时也可以基本掌握类的例化,类的成员变量访问权限以及类的成员方法如何定义和使用。

  1. 在将module chnl_initiator和module chnl_generator分别改造为class chnl_initiator和class chnl_generator后,可以发现我们同时定义了一个用来封装发送数据的类chnl_trans。要求需要在inital块中分别例化3个已经声明过的chnl_initiator和3个chnl_generator。
  2. 由于每一个chnl_initiator都需要使用接口chnl_intf来发送数据,在发送数据之前我们需要确保chnl_initiator中的接口不是悬空的,即需要由外部被传递。所以接下来的实验要求需要通过调用chnl_initiator的方法来完成接口的传递。
  3. 接下来就可以调用已经定义过的三个test任务来展开测试了。
  4. 最后是关于类的例化问题,请欢察chnl_generator在例化chnl_trans t 时,有没有不恰当的地方,如果有请指出来现有的代码会造成什么样的潜在问题呢?
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
`timescale 1ns/1ps

interface chnl_intf(input clk, input rstn);
  logic [31:0] ch_data;
  logic        ch_valid;
  logic        ch_ready;
  logic [ 5:0] ch_margin;
  clocking drv_ck @(posedge clk);
    default input #1ns output #1ns;
    output ch_data, ch_valid;
    input ch_ready, ch_margin;
  endclocking
endinterface

class chnl_trans;
  int data;
  int id;
  int num;
endclass

class chnl_initiator;
  local string name;
  local int idle_cycles;
  virtual chnl_intf intf;

  function new(string name = "chnl_initiator");
    this.name = name;
    this.idle_cycles = 1;
  endfunction

  function void set_idle_cycles(int n);
    this.idle_cycles = n;
  endfunction

  function void set_name(string s);
    this.name = s;
  endfunction

  function void set_interface(virtual chnl_intf intf);
    if(intf == null)
      $error("interface handle is NULL, please check if target interface has been intantiated");
    else
      this.intf = intf;
  endfunction

    task chnl_write(input chnl_trans t);
      @(posedge intf.clk);
      // USER TODO 1.1
      // Please use the clocking drv_ck of chnl_intf to drive data
      intf.drv_ck.ch_valid <= 1;
      intf.drv_ck.ch_data <= t.data;
              @(negedge intf.clk);
      wait(intf.ch_ready === 'b1);
      $display("%t channel initiator [%s] sent data %x", $time, name, t.data);
      // USER TODO 1.2
      // Apply variable idle_cycles and decide how many idle cycles to be
      // inserted between two sequential data
      repeat(this.idle_cycles) chnl_idle();
    endtask

    task chnl_idle();
      @(posedge intf.clk);
      // USER TODO 1.1
      // Please use the clocking drv_ck of chnl_intf to drive data
      intf.drv_ck.ch_valid <= 0;
      intf.drv_ck.ch_data <= 0;
    endtask
endclass

// USER TODO 3.4
// check if the object use is correct?
class chnl_generator;
  chnl_trans trans[$];
  int num;
  int id;
  chnl_trans t;
  function new(int n);
    this.id = n;
    this.num = 0;
  endfunction
  function chnl_trans get_trans();
    t = new();
    t.data = 'h00C0_0000 + (this.id<<16) + this.num;
    t.id = this.id;
    t.num = this.num;
    this.num++;
    this.trans.push_back(t);
    return t;
  endfunction
endclass

module tb3_ref;
  logic         clk;
  logic         rstn;
  logic [31:0]  mcdt_data;
  logic         mcdt_val;
  logic [ 1:0]  mcdt_id;

  mcdt dut(
     .clk_i       (clk                )
    ,.rstn_i      (rstn               )
    ,.ch0_data_i  (chnl0_if.ch_data   )
    ,.ch0_valid_i (chnl0_if.ch_valid  )
    ,.ch0_ready_o (chnl0_if.ch_ready  )
    ,.ch0_margin_o(chnl0_if.ch_margin )
    ,.ch1_data_i  (chnl1_if.ch_data   )
    ,.ch1_valid_i (chnl1_if.ch_valid  )
    ,.ch1_ready_o (chnl1_if.ch_ready  )
    ,.ch1_margin_o(chnl1_if.ch_margin )
    ,.ch2_data_i  (chnl2_if.ch_data   )
    ,.ch2_valid_i (chnl2_if.ch_valid  )
    ,.ch2_ready_o (chnl2_if.ch_ready  )
    ,.ch2_margin_o(chnl2_if.ch_margin )
    ,.mcdt_data_o (mcdt_data          )
    ,.mcdt_val_o  (mcdt_val           )
    ,.mcdt_id_o   (mcdt_id            )
  );

  // clock generation
  initial begin
    clk <= 0;
    forever begin
      #5 clk <= !clk;
    end
  end

  // reset trigger
  initial begin
    #10 rstn <= 0;
    repeat(10) @(posedge clk);
    rstn <= 1;
  end

  chnl_intf chnl0_if(.*);
  chnl_intf chnl1_if(.*);
  chnl_intf chnl2_if(.*);

  chnl_initiator chnl0_init;
  chnl_initiator chnl1_init;
  chnl_initiator chnl2_init;
  chnl_generator chnl0_gen;
  chnl_generator chnl1_gen;
  chnl_generator chnl2_gen;

  initial begin
    // USER TODO 3.1
    // instantiate the components chn0/1/2_init chnl0/1/2_gen
        chnl0_init = new("chnl0_init");
        chnl1_init = new("chnl1_init");
        chnl2_init = new("chnl2_init");
        chnl0_gen = new(0);
        chnl1_gen = new(1);
        chnl2_gen = new(2);

    // USER TODO 3.2
    // assign the interface handle to each chnl_initiator objects
        chnl0_init.set_interface(chnl0_if);
        chnl1_init.set_interface(chnl1_if);
        chnl2_init.set_interface(chnl2_if);

    // USER TODO 3.3
    // START TESTs
    $display("*****************all of tests have been finished********************");
    basic_test();
        burst_test();
        fifo_full_test();
        $finish();
  end

  // each channel send data with idle_cycles inside [1:3]
  // each channel send out 200 data
  // then to finish the test
  task automatic basic_test();
    chnl0_init.set_idle_cycles($urandom_range(1, 3));
    chnl1_init.set_idle_cycles($urandom_range(1, 3));
    chnl2_init.set_idle_cycles($urandom_range(1, 3));
    $display("basic_test initialized components");
    wait (rstn === 1'b1);
    repeat(5) @(posedge clk);
    $display("basic_test started testing DUT");
    // Please check the SV book for fork-join basic knowledge
    // and get understood it is for parallel thread running
    fork
      repeat(100) chnl0_init.chnl_write(chnl0_gen.get_trans());
      repeat(100) chnl1_init.chnl_write(chnl1_gen.get_trans());
      repeat(100) chnl2_init.chnl_write(chnl2_gen.get_trans());
    join
    fork
      wait(chnl0_init.intf.ch_margin == 'h20);
      wait(chnl1_init.intf.ch_margin == 'h20);
      wait(chnl2_init.intf.ch_margin == 'h20);
    join
    $display("basic_test finished testing DUT");
  endtask

  // USER TODO 2.1
  // each channel send data with idle_cycles == 0
  // each channel send out 500 data
  // then to finish the test
  task automatic burst_test();
    chnl0_init.set_idle_cycles(0);
    chnl1_init.set_idle_cycles(0);
    chnl2_init.set_idle_cycles(0);
        $display("basic_test initialized components");
    wait (rstn === 1'b1);
    repeat(5) @(posedge clk);
    $display("basic_test started testing DUT");
    // Please check the SV book for fork-join basic knowledge
    // and get understood it is for parallel thread running
    fork
      begin
            repeat(500) chnl0_init.chnl_write(chnl0_gen.get_trans());
                chnl0_init.chnl_idle();
          end
      begin
            repeat(500) chnl1_init.chnl_write(chnl1_gen.get_trans());
                chnl1_init.chnl_idle();
          end
          begin
            repeat(500) chnl2_init.chnl_write(chnl2_gen.get_trans());
                chnl2_init.chnl_idle();
          end
    join
        fork
      wait(chnl0_init.intf.ch_margin == 'h20);
      wait(chnl1_init.intf.ch_margin == 'h20);
      wait(chnl2_init.intf.ch_margin == 'h20);
    join
    $display("basic_test finished testing DUT");
  endtask

  // USER TODO 2.2
  // The test should be immediately finished when all of channels
  // have been reached fifo full state, but not all reaching
  // fifo full at the same time
  task automatic fifo_full_test();
    chnl0_init.set_idle_cycles(0);
    chnl1_init.set_idle_cycles(0);
    chnl2_init.set_idle_cycles(0);
    $display("fifo_full_test started testing DUT");
    fork: fork_all_run
          forever chnl0_init.chnl_write(chnl0_gen.get_trans());
          forever chnl1_init.chnl_write(chnl1_gen.get_trans());
          forever chnl2_init.chnl_write(chnl2_gen.get_trans());
    join_none
    $display("fifo_full_test: 3 initiators running now");

    $display("fifo_full_test: waiting 3 channel fifos to be full");
    fork
      wait(chnl0_init.intf.ch_margin == 0);
      wait(chnl1_init.intf.ch_margin == 0);
      wait(chnl2_init.intf.ch_margin == 0);
    join
    $display("fifo_full_test: 3 channel fifos have reached full");

    $display("fifo_full_test: stop 3 initiators running");
    disable fork_all_run;
    $display("fifo_full_test: set and ensure all agents' initiator are idle state");
    fork
      chnl0_init.chnl_idle();
      chnl1_init.chnl_idle();
      chnl2_init.chnl_idle();
    join

    $display("fifo_full_test waiting DUT transfering all of data");
    fork
      wait(chnl0_init.intf.ch_margin == 'h20);
      wait(chnl1_init.intf.ch_margin == 'h20);
      wait(chnl2_init.intf.ch_margin == 'h20);
    join
    $display("fifo_full_test: 3 channel fifos have transferred all data");

    $display("fifo_full_test finished testing DUT");
  endtask

endmodule
  • 包的定义和类的继承

到了tb4.sv,又进一步引入了新的类chnl_agent、chnl_root_test、chnl_basic_test、chnl_burst_test和chnl_fifo_full_test,同时将所有的类(都是与chanel相关的验证组件类),封装到专门包裹软件类的容器package chnl_pkg中且完成编译。因此编译后的chnl_pkg会被默认编译到work库中,与其它的module是一同并列放置的。

关于chnl_agent,将它作为一个标准组件单元,它应该包括generator、driver(initiator)和monitor。在tb4.sv中,在tb4.sv中暂时只有chn_generater和chnl_initiator,因此将它们在agent中例化。同时,将之前用task来实现的测试任务也由类来实现。可以发现,父类是chnl_root_test,而我们已经先移植了chnl_basic_test,接下来需要实现另外两个类。

  1. 由于将各个类首先封装在了package chnl_pkg中,因此在module tb4中要声明类的句柄,首先应该从chnl_pkg中引入其中定义的类。
  2. 可以参考之前已经实现的burst_test()和fifo_full_test()任务,以及已经实现的类chnl_basic_test,按照同样的要求来实现两个新的类chn_burst_test和chnl_fifo_full_test。
  3. 例化已经声明过的三个test组件。
  4. 完成从test一层的接口传递任务,使得其内部各个组件都可以得到需要的接口。
  5. 调用各个test的方法,展开测试。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
`timescale 1ns/1ps

interface chnl_intf(input clk, input rstn);
  logic [31:0] ch_data;
  logic        ch_valid;
  logic        ch_ready;
  logic [ 5:0] ch_margin;
  clocking drv_ck @(posedge clk);
    default input #1ns output #1ns;
    output ch_data, ch_valid;
    input ch_ready, ch_margin;
  endclocking
endinterface

package chnl_pkg;
  class chnl_trans;
    int data;
    int id;
    int num;
  endclass: chnl_trans

  class chnl_initiator;
    local string name;
    local int idle_cycles;
    local virtual chnl_intf intf;

    function new(string name = "chnl_initiator");
      this.name = name;
      this.idle_cycles = 1;
    endfunction

    function void set_idle_cycles(int n);
      this.idle_cycles = n;
    endfunction

    function void set_name(string s);
      this.name = s;
    endfunction

    function void set_interface(virtual chnl_intf intf);
      if(intf == null)
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
    endfunction

    task chnl_write(input chnl_trans t);
      @(posedge intf.clk);
      // USER TODO 1.1
      // Please use the clocking drv_ck of chnl_intf to drive data
      intf.drv_ck.ch_valid <= 1;
      intf.drv_ck.ch_data <= t.data;
              @(negedge intf.clk);
      wait(intf.ch_ready === 'b1);
      $display("%t channel initiator [%s] sent data %x", $time, name, t.data);
      // USER TODO 1.2
      // Apply variable idle_cycles and decide how many idle cycles to be
      // inserted between two sequential data
      repeat(this.idle_cycles) chnl_idle();
    endtask

    task chnl_idle();
      @(posedge intf.clk);
      // USER TODO 1.1
      // Please use the clocking drv_ck of chnl_intf to drive data
      intf.drv_ck.ch_valid <= 0;
      intf.drv_ck.ch_data <= 0;
    endtask
  endclass: chnl_initiator

  class chnl_generator;
    chnl_trans trans[$];
    int num;
    int id;
    function new(int n);
      this.id = n;
      this.num = 0;
    endfunction
    function chnl_trans get_trans();
      chnl_trans t = new();
      t.data = 'h00C0_0000 + (this.id<<16) + this.num;
      t.id = this.id;
      t.num = this.num;
      this.num++;
      this.trans.push_back(t);
      return t;
    endfunction
  endclass: chnl_generator

  class chnl_agent;
    chnl_generator gen;
    chnl_initiator init;
    local int ntrans;
    virtual chnl_intf vif;
    function new(string name = "chnl_agent", int id = 0, int ntrans = 1);
      this.gen = new(id);
      this.init = new(name);
      this.ntrans = ntrans;
    endfunction
    function void set_ntrans(int n);
      this.ntrans = n;
    endfunction
    function void set_interface(virtual chnl_intf vif);
      this.vif = vif;
      init.set_interface(vif);
    endfunction
    task run();
      repeat(this.ntrans) this.init.chnl_write(this.gen.get_trans());
      this.init.chnl_idle(); // set idle after all data sent out
    endtask
  endclass: chnl_agent

  class chnl_root_test;
    chnl_agent agent[3];
    protected string name;
    function new(int ntrans = 100, string name = "chnl_root_test");
      foreach(agent[i]) begin
        this.agent[i] = new($sformatf("chnl_agent%0d",i), i, ntrans);
      end
      this.name = name;
      $display("%s instantiate objects", this.name);
    endfunction
    task run();
      $display("%s started testing DUT", this.name);
      fork
        agent[0].run();
        agent[1].run();
        agent[2].run();
      join
      $display("%s waiting DUT transfering all of data", this.name);
      fork
        wait(agent[0].vif.ch_margin == 'h20);
        wait(agent[1].vif.ch_margin == 'h20);
        wait(agent[2].vif.ch_margin == 'h20);
      join
      $display("%s: 3 channel fifos have transferred all data", this.name);
      $display("%s finished testing DUT", this.name);
    endtask
    function void set_interface(virtual chnl_intf ch0_vif, virtual chnl_intf ch1_vif, virtual chnl_intf ch2_vif);
      agent[0].set_interface(ch0_vif);
      agent[1].set_interface(ch1_vif);
      agent[2].set_interface(ch2_vif);
    endfunction
  endclass

  // each channel send data with idle_cycles inside [1:3]
  // each channel send out 200 data
  // then to finish the test
  class chnl_basic_test extends chnl_root_test;
    function new(int ntrans = 200, string name = "chnl_basic_test");
      super.new(ntrans, name);
      foreach(agent[i]) begin
        this.agent[i].init.set_idle_cycles($urandom_range(1, 3));
      end
      $display("%s configured objects", this.name);
    endfunction
  endclass: chnl_basic_test

  // USER TODO 4.2
  // Refer to chnl_basic_test, and extend another 2 tests
  // chnl_burst_test, chnl_fifo_full_test
  // each channel send data with idle_cycles == 0
  // each channel send out 500 data
  // then to finish the test
  class chnl_burst_test extends chnl_root_test;
    //USER TODO
    function new(int ntrans = 500, string name = "chnl_burst_test");
      super.new(ntrans, name);
      foreach(agent[i]) begin
        this.agent[i].init.set_idle_cycles(0);
      end
      $display("%s configured objects", this.name);
    endfunction
  endclass: chnl_burst_test

  // USER TODO 4.2
  // The test should be immediately finished when all of channels
  // have been reached fifo full state, but not all reaching
  // fifo full at the same time
  class chnl_fifo_full_test extends chnl_root_test;
    // USER TODO
    function new(int ntrans = 1_000_000, string name = "chnl_fifo_full_test");
      super.new(ntrans, name);
      foreach(agent[i]) begin
        this.agent[i].init.set_idle_cycles(0);
      end
      $display("%s configured objects", this.name);
    endfunction
    task run();
      $display("%s started testing DUT", this.name);
      fork: fork_all_run
        agent[0].run();
        agent[1].run();
        agent[2].run();
      join_none
      $display("%s: 3 agents running now", this.name);

      $display("%s: waiting 3 channel fifos to be full", this.name);
      fork
        wait(agent[0].vif.ch_margin == 0);
        wait(agent[1].vif.ch_margin == 0);
        wait(agent[2].vif.ch_margin == 0);
      join
      $display("%s: 3 channel fifos have reached full", this.name);

      $display("%s: stop 3 agents running", this.name);
      disable fork_all_run;
      $display("%s: set and ensure all agents' initiator are idle state", this.name);
      fork
        agent[0].init.chnl_idle();
        agent[1].init.chnl_idle();
        agent[2].init.chnl_idle();
      join

      $display("%s waiting DUT transfering all of data", this.name);
      fork
        wait(agent[0].vif.ch_margin == 'h20);
        wait(agent[1].vif.ch_margin == 'h20);
        wait(agent[2].vif.ch_margin == 'h20);
      join
      $display("%s: 3 channel fifos have transferred all data", this.name);

      $display("%s finished testing DUT", this.name);
    endtask
  endclass: chnl_fifo_full_test

endpackage: chnl_pkg


module tb4_ref;
  logic         clk;
  logic         rstn;
  logic [31:0]  mcdt_data;
  logic         mcdt_val;
  logic [ 1:0]  mcdt_id;

  mcdt dut(
     .clk_i       (clk                )
    ,.rstn_i      (rstn               )
    ,.ch0_data_i  (chnl0_if.ch_data   )
    ,.ch0_valid_i (chnl0_if.ch_valid  )
    ,.ch0_ready_o (chnl0_if.ch_ready  )
    ,.ch0_margin_o(chnl0_if.ch_margin )
    ,.ch1_data_i  (chnl1_if.ch_data   )
    ,.ch1_valid_i (chnl1_if.ch_valid  )
    ,.ch1_ready_o (chnl1_if.ch_ready  )
    ,.ch1_margin_o(chnl1_if.ch_margin )
    ,.ch2_data_i  (chnl2_if.ch_data   )
    ,.ch2_valid_i (chnl2_if.ch_valid  )
    ,.ch2_ready_o (chnl2_if.ch_ready  )
    ,.ch2_margin_o(chnl2_if.ch_margin )
    ,.mcdt_data_o (mcdt_data          )
    ,.mcdt_val_o  (mcdt_val           )
    ,.mcdt_id_o   (mcdt_id            )
  );

  // clock generation
  initial begin
    clk <= 0;
    forever begin
      #5 clk <= !clk;
    end
  end

  // reset trigger
  initial begin
    #10 rstn <= 0;
    repeat(10) @(posedge clk);
    rstn <= 1;
  end

  // USER TODO 4.1
  // import defined class from chnl_pkg
  import chnl_pkg::*;

  chnl_intf chnl0_if(.*);
  chnl_intf chnl1_if(.*);
  chnl_intf chnl2_if(.*);

  chnl_basic_test basic_test;
  chnl_burst_test burst_test;
  chnl_fifo_full_test fifo_full_test;

  initial begin
    basic_test = new();
    burst_test = new();
    fifo_full_test = new();

    // USER TODO 4.4
    // assign the interface handle to each chnl_initiator objects
    basic_test.set_interface(chnl0_if, chnl1_if, chnl2_if);
    burst_test.set_interface(chnl0_if, chnl1_if, chnl2_if);
    fifo_full_test.set_interface(chnl0_if, chnl1_if, chnl2_if);

    // USER TODO 4.5
    // START TESTs
    basic_test.run();
    burst_test.run();
    fifo_full_test.run();
    $display("*****************all of tests have been finished********************");
    $finish();
  end

endmodule
  • 验证结构

请画出来你理解的tb4.sv的验证环境结构。环境结构中需要包含以下要素:

  • DUT
  • 接口的名称和数量
  • 不同验证组件的名称、数量以及结构关系

这不单单是一个实验要求,这对于接下来后续练习中不断强调的验证环境构成、模块环境到系统环境的集成都是有很大帮助的,所以务必尝试画出你认为的tb4.sv的验证结构来。尽管目前每个人对环境的认识都有差别,但考虑到绘制验证环境将是日后从事验证工作的基本功,所以无论你的第一幅验证框图是什么样的,至少请你从这个实验开始迈出这一步。

lab3

练习3的部分将主要就随机约束和环境结构做实践。在这个练习中,将升级练习2部分中对generator和initiator之间的数据生成和数据传输的处理, 同时也将完善何时结束测试,将其主动权交于generator而不再是test组件。在组件结构实践部分中,将在原有的的initiator,generator、agent和test组件的基础上再认识monitor和checker,并且使其构成一个有机的整体,最终可以通过在线比较数据的方式完成对MCDT的测试。

  • 随机约束

为了更早习惯各个验证文件独立放置的原则,已经先将chnl_pkg1.sv文件和tb1.sv文件独立开来,所以tb1需要编译两个文件即chnl_pkg1.sv和tb.sv。在这个练习中会进一步了解随机约束在类中定义方式、如何随机化对象、随机种子的使用方法、对象的产生等等。接下来,请按照要求开始练习吧。

  1. lab3继承了大部分lab2的代码,包括chnl_basic_test类,而对于这个类所需要生成的数据包提出了新的约束要求。需要注意的是,与lab2不同的是,这次数据类chnl_trans的定义发生了很大的变化,它不再只局限于包含一个数据,而是多个数据,同时跟数据之间时间间隔的控制变量也在该类中声明为随机成员变量, 那么请按照代码中具体的约束来定义chnl_basic_test类,注意该代码的修改需要在chnl_trans类中实现,因为目前的代码结构只有chnl_trans类的更新是较为合适的办法。
  2. 需要将原本在chnl_root_test类中用来结束仿真的$finish(变迁到generator中,那么请将它放置到合适的地方,然后由generator来结束仿真吧)。
  3. 请尝试着多次重新启动仿真,可以使用“restart”命令来重启,再对比连续两次生成的随机数据,看看它们之间是否相同呢?然后再在仿真器命令行处使用命令"vsim -novopt -solvefaildebug -sv_seed 0 work.tb1"来加载仿真。这里我们多传递了两个必须的仿真参数, -solvefaildebug是为了调试随机变量的,而-sv_seed NUM则是为了传递随机的种子。那么使用这个命令再看, 是否与之前没有使用-sv_seed 0的命令产生了相同的数据呢?最后,“vsim -novopt -solvefaildebug -sv_seed random work.tb1"命令再来加载仿真,比较前后两次的数据是否相同?那么,对-sv_seed random的仿真选项的认识是什么?
  4. 在仿真的最后,可以发现最后打印出来的chnl_obj对象的obj_id值是1200,那么这代表什么含义?为什么会有1200个chanl_obj对象产生呢?整个仿真过程中,在同一时刻,最多的时候一同有几个chnl_trans对象在仿真器内存中存在呢?这么做对内存的利用是否合理?是否还有更好的办法使得在同一时间chnl_trans对象的数量比代码中用到的更少呢?
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
`timescale 1ns/1ps

interface chnl_intf(input clk, input rstn);
  logic [31:0] ch_data;
  logic        ch_valid;
  logic        ch_ready;
  logic [ 5:0] ch_margin;
  clocking drv_ck @(posedge clk);
    default input #1ns output #1ns;
    output ch_data, ch_valid;
    input ch_ready, ch_margin;
  endclocking
endinterface

module tb1;
  logic         clk;
  logic         rstn;
  logic [31:0]  mcdt_data;
  logic         mcdt_val;
  logic [ 1:0]  mcdt_id;

  mcdt dut(
     .clk_i       (clk                )
    ,.rstn_i      (rstn               )
    ,.ch0_data_i  (chnl0_if.ch_data   )
    ,.ch0_valid_i (chnl0_if.ch_valid  )
    ,.ch0_ready_o (chnl0_if.ch_ready  )
    ,.ch0_margin_o(chnl0_if.ch_margin )
    ,.ch1_data_i  (chnl1_if.ch_data   )
    ,.ch1_valid_i (chnl1_if.ch_valid  )
    ,.ch1_ready_o (chnl1_if.ch_ready  )
    ,.ch1_margin_o(chnl1_if.ch_margin )
    ,.ch2_data_i  (chnl2_if.ch_data   )
    ,.ch2_valid_i (chnl2_if.ch_valid  )
    ,.ch2_ready_o (chnl2_if.ch_ready  )
    ,.ch2_margin_o(chnl2_if.ch_margin )
    ,.mcdt_data_o (mcdt_data          )
    ,.mcdt_val_o  (mcdt_val           )
    ,.mcdt_id_o   (mcdt_id            )
  );

  // clock generation
  initial begin
    clk <= 0;
    forever begin
      #5 clk <= !clk;
    end
  end

  // reset trigger
  initial begin
    #10 rstn <= 0;
    repeat(10) @(posedge clk);
    rstn <= 1;
  end

  import chnl_pkg1::*;

  chnl_intf chnl0_if(.*);
  chnl_intf chnl1_if(.*);
  chnl_intf chnl2_if(.*);

  chnl_basic_test basic_test;
  chnl_burst_test burst_test;
  chnl_fifo_full_test fifo_full_test;

  initial begin
    basic_test = new();
    basic_test.set_interface(chnl0_if, chnl1_if, chnl2_if);
    basic_test.run();
  end

endmodule
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
package chnl_pkg1;
  class chnl_trans;
    rand bit[31:0] data[];
    rand int ch_id;
    rand int pkt_id;
    rand int data_nidles;
    rand int pkt_nidles;
    bit rsp;
    local static int obj_id = 0;
    // USER TODO 1.1.
    // Specify constraint to match the chnl_basic_test request
    constraint cstr{
      data.size inside {[4:8]};
      foreach(data[i]) data[i] == 'hC000_0000 + (this.ch_id<<24) + (this.pkt_id<<8) + i;
      soft ch_id == 0;
      soft pkt_id == 0;
      data_nidles inside {[0:2]};
      pkt_nidles inside {[1:10]};
    };

    function new();
      this.obj_id++;
    endfunction

    function chnl_trans clone();
      chnl_trans c = new();
      c.data = this.data;
      c.ch_id = this.ch_id;
      c.pkt_id = this.pkt_id;
      c.data_nidles = this.data_nidles;
      c.pkt_nidles = this.pkt_nidles;
      c.rsp = this.rsp;
      // USER TODO 1.2
      // Could we put c.obj_id = this.obj_id here? and why?
      return c;
    endfunction

    function string sprint();
      string s;
      s = {s, $sformatf("=======================================\n")};
      s = {s, $sformatf("chnl_trans object content is as below: \n")};
      s = {s, $sformatf("obj_id = %0d: \n", this.obj_id)};
      foreach(data[i]) s = {s, $sformatf("data[%0d] = %8x \n", i, this.data[i])};
      s = {s, $sformatf("ch_id = %0d: \n", this.ch_id)};
      s = {s, $sformatf("pkt_id = %0d: \n", this.pkt_id)};
      s = {s, $sformatf("data_nidles = %0d: \n", this.data_nidles)};
      s = {s, $sformatf("pkt_nidles = %0d: \n", this.pkt_nidles)};
      s = {s, $sformatf("rsp = %0d: \n", this.rsp)};
      s = {s, $sformatf("=======================================\n")};
      return s;
    endfunction
  endclass: chnl_trans

  class chnl_initiator;
    local string name;
    local virtual chnl_intf intf;
    mailbox #(chnl_trans) req_mb;
    mailbox #(chnl_trans) rsp_mb;

    function new(string name = "chnl_initiator");
      this.name = name;
    endfunction

    function void set_name(string s);
      this.name = s;
    endfunction

    function void set_interface(virtual chnl_intf intf);
      if(intf == null)
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
    endfunction

    task run();
      this.drive();
    endtask

    task drive();
      chnl_trans req, rsp;
      @(posedge intf.rstn);
      forever begin
        this.req_mb.get(req);
        this.chnl_write(req);
        rsp = req.clone();
        rsp.rsp = 1;
        this.rsp_mb.put(rsp);
      end
    endtask

    task chnl_write(input chnl_trans t);
      foreach(t.data[i]) begin
        @(posedge intf.clk);
        intf.drv_ck.ch_valid <= 1;
        intf.drv_ck.ch_data <= t.data[i];
        wait(intf.ch_ready === 'b1);
        $display("%0t channel initiator [%s] sent data %x", $time, name, t.data[i]);
        repeat(t.data_nidles) chnl_idle();
      end
      repeat(t.pkt_nidles) chnl_idle();
    endtask

    task chnl_idle();
      @(posedge intf.clk);
      intf.drv_ck.ch_valid <= 0;
      intf.drv_ck.ch_data <= 0;
    endtask
  endclass: chnl_initiator

  class chnl_generator;
    int pkt_id;
    int ch_id;
    int ntrans;
    int data_nidles;
    mailbox #(chnl_trans) req_mb;
    mailbox #(chnl_trans) rsp_mb;

    function new(int ch_id, int ntrans);
      this.ch_id = ch_id;
      this.pkt_id = 0;
      this.ntrans = ntrans;
      this.req_mb = new();
      this.rsp_mb = new();
    endfunction

    task run();
      repeat(ntrans) send_trans();
    endtask

    // generate transaction and put into local mailbox
    task send_trans();
      chnl_trans req, rsp;
      req = new();
      assert(req.randomize with {ch_id == local::ch_id; pkt_id == local::pkt_id;})
        else $fatal("[RNDFAIL] channel packet randomization failure!");
      this.pkt_id++;
      $display(req.sprint());
      this.req_mb.put(req);
      this.rsp_mb.get(rsp);
      $display(rsp.sprint());
      assert(rsp.rsp)
        else $error("[RSPERR] %0t error response received!", $time);
    endtask
  endclass: chnl_generator

  class chnl_agent;
    chnl_generator gen;
    chnl_initiator init;
    local virtual chnl_intf vif;
    function new(string name = "chnl_agent", int id = 0, int ntrans = 1);
      this.gen = new(id, ntrans);
      this.init = new(name);
    endfunction
    function void set_interface(virtual chnl_intf vif);
      this.vif = vif;
      init.set_interface(vif);
    endfunction
    task run();
      this.init.req_mb = this.gen.req_mb;
      this.init.rsp_mb = this.gen.rsp_mb;
      fork
        gen.run();
        init.run();
      join_any
    endtask
  endclass: chnl_agent

  class chnl_root_test;
    chnl_agent agent[3];
    protected string name;
    function new(int ntrans = 100, string name = "chnl_root_test");
      foreach(agent[i]) begin
        this.agent[i] = new($sformatf("chnl_agent%0d",i), i, ntrans);
      end
      this.name = name;
      $display("%s instantiate objects", this.name);
    endfunction
    task run();
      $display($sformatf("*****************%s started********************", this.name));
      fork
        agent[0].run();
        agent[1].run();
        agent[2].run();
      join
      $display($sformatf("*****************%s finished********************", this.name));
      // USER TODO 1.3
      // Please move the $finish statement from the test run task to generator
      // You woudl put it anywhere you like inside generator to stop test when
      // all transactions have been transfered
      $finish();
      // USER TODO 1.4
      // Apply 'vsim -novopt -solvefaildebug -sv_seed 0 work.tb1' to run the
      // simulation, and check if the generated data is the same as previously
      // Then use 'vsim -novopt -solvefaildebug -sv_seed random work.tb1' to
      // run the test 2 times, and check if the data generated of 2 times are
      // is the same or not?

      // USER TODO 1.5
      // In the last chnl_trans object content display, why the object_id is
      // 1200? How is it counted and finally as the value 1200?
    endtask
    function void set_interface(virtual chnl_intf ch0_vif, virtual chnl_intf ch1_vif, virtual chnl_intf ch2_vif);
      agent[0].set_interface(ch0_vif);
      agent[1].set_interface(ch1_vif);
      agent[2].set_interface(ch2_vif);
    endfunction
  endclass

  // USER TODO 1.1
  // each channel send data with idle_cycles inside [0:2]
  // and idle peroids between sequential packets should be inside [3:5]
  // each channel send out 200 data then to finish the test
  // The idle_cycle constraint should be specified inside chnl_trans
  class chnl_basic_test extends chnl_root_test;
    function new(int ntrans = 200, string name = "chnl_basic_test");
      super.new(ntrans, name);
    endfunction
  endclass: chnl_basic_test

  class chnl_burst_test extends chnl_root_test;
    //USER TODO
  endclass: chnl_burst_test

  class chnl_fifo_full_test extends chnl_root_test;
    // USER TODO
  endclass: chnl_fifo_full_test

endpackage
  • 更加灵活的测试控制

如果要实现不同的test类,例如chnl_basic_test、chnl burst_test和chnl_fifo_full_test,那么对于不同的test需要对chnl_generator的随机变量做出不同的控制,继而进一步控制其内部随机的chnl_tans对象。也就是说,随机化也是可以分层次的,例如在test层可以随机化generator层,而依靠generator被随机化的成员变量,再来利用它们进一步随机化generator中的chnl_tans对象,由此来达到顶层到底层的随机化灵活控制。那么从这个角度出发,就需要将generator从agent单元中搬迁出来,并且搁置在test层中来方便test层的随机控制,因此在chnl_pkg2.sv和tb2.sv中主要去认识如何更好的组织验证结构,从而实现更加方便的测试控制。

  1. 由于将generator搬迁到test层次中,所以需要将gen和agent中组件的mailbox连接起来,方便gen与agent中init的数据通信。
  2. 在领略了如何在test中的do_config对gen[0]进行随机化控制后,需要对gen[1]也按照代码中的具体要求进行随机控制。
  3. 请按照代码中的具体要求对gen[2]进行随机控制。
  4. 按照代码中的具体要求,在chnl_burst_test::do_config()任务中对三个generator进行随机控制。
  5. 按照代码中的具体要求,在chnl_fifo_full_test::do_config()任务中对三个generator进行随机控制。
  6. 在tb2.sv中,对于测试的选择将由仿真时的参数传递来完成。这意味着,以后的递归测试,即创建脚本命令,由仿真器读入,分别传递不同的随机种子和测试名称即可完成对应的随机测试,而这种方式即是回归测试的雏形。所以请按照之前的仿真命令,在命令窗口中添加额外的命令"+TESTNAME=testname”,这里的+TESTNAME=表示的仿真命令项,在由内部解析之后,testname会被捕捉并且识别,例如可以传递命令"+TESTNAME=chnl_burst_test"来在仿真时运行测试chnl_burst_test。请充分理解此要求,懂得如何捕捉命令,如何解析命令,最后如何选择正确的测试来运行。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
`timescale 1ns/1ps

interface chnl_intf(input clk, input rstn);
  logic [31:0] ch_data;
  logic        ch_valid;
  logic        ch_ready;
  logic [ 5:0] ch_margin;
  clocking drv_ck @(posedge clk);
    default input #1ns output #1ns;
    output ch_data, ch_valid;
    input ch_ready, ch_margin;
  endclocking
endinterface

module tb2;
  logic         clk;
  logic         rstn;
  logic [31:0]  mcdt_data;
  logic         mcdt_val;
  logic [ 1:0]  mcdt_id;

  mcdt dut(
     .clk_i       (clk                )
    ,.rstn_i      (rstn               )
    ,.ch0_data_i  (chnl0_if.ch_data   )
    ,.ch0_valid_i (chnl0_if.ch_valid  )
    ,.ch0_ready_o (chnl0_if.ch_ready  )
    ,.ch0_margin_o(chnl0_if.ch_margin )
    ,.ch1_data_i  (chnl1_if.ch_data   )
    ,.ch1_valid_i (chnl1_if.ch_valid  )
    ,.ch1_ready_o (chnl1_if.ch_ready  )
    ,.ch1_margin_o(chnl1_if.ch_margin )
    ,.ch2_data_i  (chnl2_if.ch_data   )
    ,.ch2_valid_i (chnl2_if.ch_valid  )
    ,.ch2_ready_o (chnl2_if.ch_ready  )
    ,.ch2_margin_o(chnl2_if.ch_margin )
    ,.mcdt_data_o (mcdt_data          )
    ,.mcdt_val_o  (mcdt_val           )
    ,.mcdt_id_o   (mcdt_id            )
  );

  // clock generation
  initial begin
    clk <= 0;
    forever begin
      #5 clk <= !clk;
    end
  end

  // reset trigger
  initial begin
    #10 rstn <= 0;
    repeat(10) @(posedge clk);
    rstn <= 1;
  end

  import chnl_pkg2::*;

  chnl_intf chnl0_if(.*);
  chnl_intf chnl1_if(.*);
  chnl_intf chnl2_if(.*);

  chnl_basic_test basic_test;
  chnl_burst_test burst_test;
  chnl_fifo_full_test fifo_full_test;
  chnl_root_test tests[string];
  string name;

  // USER TODO 2.6
  // User runtime option '+TESTNAME=[testname]' to specify which test to run
  // It also implies each time should only run only one test
  initial begin
    basic_test = new();
    burst_test = new();
    fifo_full_test = new();
    tests["chnl_basic_test"] = basic_test;
    tests["chnl_burst_test"] = burst_test;
    tests["chnl_fifo_full_test"] = fifo_full_test;
    if($value$plusargs("TESTNAME=%s", name)) begin
      if(tests.exists(name)) begin
        tests[name].set_interface(chnl0_if, chnl1_if, chnl2_if);
        tests[name].run();
      end
      else begin
        $fatal($sformatf("[ERRTEST], test name %s is invalid, please specify a valid name!", name));
      end
    end
    else begin
      $display("NO runtime optiont TEST=[testname] is configured, and run default test chnl_basic_test");
      tests["chnl_basic_test"].set_interface(chnl0_if, chnl1_if, chnl2_if);
      tests["chnl_basic_test"].run();
    end
  end
endmodule
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
package chnl_pkg2;
  class chnl_trans;
    rand bit[31:0] data[];
    rand int ch_id;
    rand int pkt_id;
    rand int data_nidles;
    rand int pkt_nidles;
    bit rsp;
    local static int obj_id = 0;
    constraint cstr{
      data.size inside {[4:8]};
      foreach(data[i]) data[i] == 'hC000_0000 + (this.ch_id<<24) + (this.pkt_id<<8) + i;
      soft ch_id == 0;
      soft pkt_id == 0;
      data_nidles inside {[0:2]};
      pkt_nidles inside {[1:10]};
    };

    function new();
      this.obj_id++;
    endfunction

    function chnl_trans clone();
      chnl_trans c = new();
      c.data = this.data;
      c.ch_id = this.ch_id;
      c.pkt_id = this.pkt_id;
      c.data_nidles = this.data_nidles;
      c.pkt_nidles = this.pkt_nidles;
      c.rsp = this.rsp;
      return c;
    endfunction

    function string sprint();
      string s;
      s = {s, $sformatf("=======================================\n")};
      s = {s, $sformatf("chnl_trans object content is as below: \n")};
      s = {s, $sformatf("obj_id = %0d: \n", this.obj_id)};
      foreach(data[i]) s = {s, $sformatf("data[%0d] = %8x \n", i, this.data[i])};
      s = {s, $sformatf("ch_id = %0d: \n", this.ch_id)};
      s = {s, $sformatf("pkt_id = %0d: \n", this.pkt_id)};
      s = {s, $sformatf("data_nidles = %0d: \n", this.data_nidles)};
      s = {s, $sformatf("pkt_nidles = %0d: \n", this.pkt_nidles)};
      s = {s, $sformatf("rsp = %0d: \n", this.rsp)};
      s = {s, $sformatf("=======================================\n")};
      return s;
    endfunction
  endclass: chnl_trans

  class chnl_initiator;
    local string name;
    local virtual chnl_intf intf;
    mailbox #(chnl_trans) req_mb;
    mailbox #(chnl_trans) rsp_mb;

    function new(string name = "chnl_initiator");
      this.name = name;
    endfunction

    function void set_name(string s);
      this.name = s;
    endfunction

    function void set_interface(virtual chnl_intf intf);
      if(intf == null)
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
    endfunction

    task run();
      this.drive();
    endtask

    task drive();
      chnl_trans req, rsp;
      @(posedge intf.rstn);
      forever begin
        this.req_mb.get(req);
        this.chnl_write(req);
        rsp = req.clone();
        rsp.rsp = 1;
        this.rsp_mb.put(rsp);
      end
    endtask

    task chnl_write(input chnl_trans t);
      foreach(t.data[i]) begin
        @(posedge intf.clk);
        intf.drv_ck.ch_valid <= 1;
        intf.drv_ck.ch_data <= t.data[i];
        wait(intf.ch_ready === 'b1);
        $display("%0t channel initiator [%s] sent data %x", $time, name, t.data[i]);
        repeat(t.data_nidles) chnl_idle();
      end
      repeat(t.pkt_nidles) chnl_idle();
    endtask

    task chnl_idle();
      @(posedge intf.clk);
      intf.drv_ck.ch_valid <= 0;
      intf.drv_ck.ch_data <= 0;
    endtask
  endclass: chnl_initiator

  class chnl_generator;
    rand int pkt_id = -1;
    rand int ch_id = -1;
    rand int data_nidles = -1;
    rand int pkt_nidles = -1;
    rand int data_size = -1;
    rand int ntrans = 10;

    mailbox #(chnl_trans) req_mb;
    mailbox #(chnl_trans) rsp_mb;

    constraint cstr{
      soft ch_id == -1;
      soft pkt_id == -1;
      soft data_size == -1;
      soft data_nidles == -1;
      soft pkt_nidles == -1;
      soft ntrans == 10;
    }

    function new();
      this.req_mb = new();
      this.rsp_mb = new();
    endfunction

    task run();
      repeat(ntrans) send_trans();
    endtask

    // generate transaction and put into local mailbox
    task send_trans();
      chnl_trans req, rsp;
      req = new();
      assert(req.randomize with {local::ch_id >= 0 -> ch_id == local::ch_id;
                                 local::pkt_id >= 0 -> pkt_id == local::pkt_id;
                                 local::data_nidles >= 0 -> data_nidles == local::data_nidles;
                                 local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
                                 local::data_size >0 -> data.size() == local::data_size;
                               })
        else $fatal("[RNDFAIL] channel packet randomization failure!");
      this.pkt_id++;
      $display(req.sprint());
      this.req_mb.put(req);
      this.rsp_mb.get(rsp);
      $display(rsp.sprint());
      assert(rsp.rsp)
        else $error("[RSPERR] %0t error response received!", $time);
    endtask

    function string sprint();
      string s;
      s = {s, $sformatf("=======================================\n")};
      s = {s, $sformatf("chnl_generator object content is as below: \n")};
      s = {s, $sformatf("ntrans = %0d: \n", this.ntrans)};
      s = {s, $sformatf("ch_id = %0d: \n", this.ch_id)};
      s = {s, $sformatf("pkt_id = %0d: \n", this.pkt_id)};
      s = {s, $sformatf("data_nidles = %0d: \n", this.data_nidles)};
      s = {s, $sformatf("pkt_nidles = %0d: \n", this.pkt_nidles)};
      s = {s, $sformatf("data_size = %0d: \n", this.data_size)};
      s = {s, $sformatf("=======================================\n")};
      return s;
    endfunction

    function void post_randomize();
      string s;
      s = {"AFTER RANDOMIZATION \n", this.sprint()};
      $display(s);
    endfunction
  endclass: chnl_generator

  class chnl_agent;
    chnl_initiator init;
    local virtual chnl_intf vif;
    function new(string name = "chnl_agent");
      this.init = new(name);
    endfunction
    function void set_interface(virtual chnl_intf vif);
      this.vif = vif;
      init.set_interface(vif);
    endfunction
    task run();
      fork
        init.run();
      join
    endtask
  endclass: chnl_agent

  class chnl_root_test;
    chnl_generator gen[3];
    chnl_agent agent[3];
    protected string name;

    function new(string name = "chnl_root_test");
      foreach(agent[i]) begin
        this.agent[i] = new($sformatf("chnl_agent%0d",i));
        this.gen[i] = new();
        // USER TODO 2.1
        // Connect the mailboxes handles of gen[i] and agent[i].init
      end
      this.name = name;
      $display("%s instantiate objects", this.name);
    endfunction

    virtual task run();
      $display($sformatf("*****************%s started********************", this.name));
      this.do_config();
      fork
        agent[0].run();
        agent[1].run();
        agent[2].run();
      join_none
      fork
        gen[0].run();
        gen[1].run();
        gen[2].run();
      join
      $display($sformatf("*****************%s finished********************", this.name));
      // USER TODO 1.3
      // Please move the $finish statement from the test run task to generator
      // You woudl put it anywhere you like inside generator to stop test when
      // all transactions have been transfered
      $finish();
    endtask

    virtual function void set_interface(virtual chnl_intf ch0_vif, virtual chnl_intf ch1_vif, virtual chnl_intf ch2_vif);
      agent[0].set_interface(ch0_vif);
      agent[1].set_interface(ch1_vif);
      agent[2].set_interface(ch2_vif);
    endfunction


    virtual function void do_config();
      assert(gen[0].randomize() with {ntrans==100; data_nidles==0; pkt_nidles==1; data_size==8;})
        else $fatal("[RNDFAIL] gen[0] randomization failure!");

      // USER TODO 2.2
      // To randomize gen[1] with
      // ntrans==50, data_nidles inside [1:2], pkt_nidles inside [3:5],
      // data_size == 6

      // USER TODO 2.3
      // ntrans==80, data_nidles inside [0:1], pkt_nidles inside [1:2],
      // data_size == 32
    endfunction
  endclass

  class chnl_basic_test extends chnl_root_test;
    function new(string name = "chnl_basic_test");
      super.new(name);
    endfunction
  endclass: chnl_basic_test

  // USER TODO 2.4
  // each channel send data packet number inside [80:100]
  // data_nidles == 0, pkt_nidles == 1, data_size inside {8, 16, 32}
  class chnl_burst_test extends chnl_root_test;
    function new(string name = "chnl_burst_test");
      super.new(name);
    endfunction
    //USER TODO
  endclass: chnl_burst_test

  // USER TODO 2.5
  // keep channel sending out data packet with number, and please
  // let all of slave channels raising fifo_full (ready=0) at the same time
  // and then to stop the test
  class chnl_fifo_full_test extends chnl_root_test;
    function new(string name = "chnl_fifo_full_test");
      super.new(name);
    endfunction
    // USER TODO
  endclass: chnl_fifo_full_test

endpackage
  • 测试平台的结构

最后一个实验部分即指导认识验证环境的其它组件,monitor和checker。并且通过合理的方式来构成最终用来测试MCDT的验证环境,在这个环境中需要再回顾generator、intitator、monitor和checker各自的作用。在顶层环境中,将checker置于test层中,而不是agent中,需要思考这么做的好处在什么地方。同时需要在认识generator和initiator有数据通信的同时,可以掌握monitor与checker之间的数据通信,还有checker如何针对MCDT利用内部的数据缓存进行数据比较。

  • 在chnl_monitor类和mcdt_monitor类各自的mon_trans()方法中需要采集正确的数据,将它们写入mailbox缓存,同时将捕捉的数据也打印出来,便于我们的调试。
  • 在chnl_agent中,参考如何例化的initiator对象,也对chnl_monitor对象开始例化、传递虚接口和使其运行。
  • 在chnl_checker的任务do_compare()中,需要从checker自己的数据缓存mailbox中分别取得一个输出端的采集数据和一个输入端的采集数据,继而将它们的内容进行比较,需要注重的是,输出端的缓存只有一个,而输入端的缓存有三个,需要考虑好从哪个输入端获取数据与输出端缓存的数据进行比对。
  • 在顶层环境chnl_root_test类中,需要对mcdt_monitor和chnl_checker进行例化、传递虚接口,并且将chnl_monitor、mcdt_monitor的邮箱句柄分别指向chnl_checker中的邮箱实例。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
`timescale 1ns/1ps

interface chnl_intf(input clk, input rstn);
  logic [31:0] ch_data;
  logic        ch_valid;
  logic        ch_ready;
  logic [ 5:0] ch_margin;
  clocking drv_ck @(posedge clk);
    default input #1ns output #1ns;
    output ch_data, ch_valid;
    input ch_ready, ch_margin;
  endclocking
  clocking mon_ck @(posedge clk);
    default input #1ns output #1ns;
    input ch_data, ch_valid, ch_ready, ch_margin;
  endclocking
endinterface

interface mcdt_intf(input clk, input rstn);
  logic [31:0]  mcdt_data;
  logic         mcdt_val;
  logic [ 1:0]  mcdt_id;
  clocking mon_ck @(posedge clk);
    default input #1ns output #1ns;
    input mcdt_data, mcdt_val, mcdt_id;
  endclocking
endinterface

module tb3_ref;
  logic         clk;
  logic         rstn;

  mcdt dut(
     .clk_i       (clk                )
    ,.rstn_i      (rstn               )
    ,.ch0_data_i  (chnl0_if.ch_data   )
    ,.ch0_valid_i (chnl0_if.ch_valid  )
    ,.ch0_ready_o (chnl0_if.ch_ready  )
    ,.ch0_margin_o(chnl0_if.ch_margin )
    ,.ch1_data_i  (chnl1_if.ch_data   )
    ,.ch1_valid_i (chnl1_if.ch_valid  )
    ,.ch1_ready_o (chnl1_if.ch_ready  )
    ,.ch1_margin_o(chnl1_if.ch_margin )
    ,.ch2_data_i  (chnl2_if.ch_data   )
    ,.ch2_valid_i (chnl2_if.ch_valid  )
    ,.ch2_ready_o (chnl2_if.ch_ready  )
    ,.ch2_margin_o(chnl2_if.ch_margin )
    ,.mcdt_data_o (mcdt_if.mcdt_data  )
    ,.mcdt_val_o  (mcdt_if.mcdt_val   )
    ,.mcdt_id_o   (mcdt_if.mcdt_id    )
  );

  // clock generation
  initial begin
    clk <= 0;
    forever begin
      #5 clk <= !clk;
    end
  end

  // reset trigger
  initial begin
    #10 rstn <= 0;
    repeat(10) @(posedge clk);
    rstn <= 1;
  end

  import chnl_pkg3::*;

  chnl_intf chnl0_if(.*);
  chnl_intf chnl1_if(.*);
  chnl_intf chnl2_if(.*);
  mcdt_intf mcdt_if(.*);

  chnl_basic_test basic_test;
  chnl_burst_test burst_test;
  chnl_fifo_full_test fifo_full_test;
  chnl_root_test tests[string];
  string name;

  initial begin
    basic_test = new();
    burst_test = new();
    fifo_full_test = new();
    tests["chnl_basic_test"] = basic_test;
    tests["chnl_burst_test"] = burst_test;
    tests["chnl_fifo_full_test"] = fifo_full_test;
    if($value$plusargs("TESTNAME=%s", name)) begin
      if(tests.exists(name)) begin
        tests[name].set_interface(chnl0_if, chnl1_if, chnl2_if, mcdt_if);
        tests[name].run();
      end
      else begin
        $fatal($sformatf("[ERRTEST], test name %s is invalid, please specify a valid name!", name));
      end
    end
    else begin
      $display("NO runtime optiont +TESTNAME=xxx is configured, and run default test chnl_basic_test");
      tests["chnl_basic_test"].set_interface(chnl0_if, chnl1_if, chnl2_if, mcdt_if);
      tests["chnl_basic_test"].run();
    end
  end
endmodule
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
package chnl_pkg3;

  // static variables shared by resources
  semaphore run_stop_flags = new();

  class chnl_trans;
    rand bit[31:0] data[];
    rand int ch_id;
    rand int pkt_id;
    rand int data_nidles;
    rand int pkt_nidles;
    bit rsp;
    local static int obj_id = 0;
    constraint cstr{
      soft data.size inside {[4:8]};
      foreach(data[i]) data[i] == 'hC000_0000 + (this.ch_id<<24) + (this.pkt_id<<8) + i;
      soft ch_id == 0;
      soft pkt_id == 0;
      data_nidles inside {[0:2]};
      pkt_nidles inside {[1:10]};
    };

    function new();
      this.obj_id++;
    endfunction

    function chnl_trans clone();
      chnl_trans c = new();
      c.data = this.data;
      c.ch_id = this.ch_id;
      c.pkt_id = this.pkt_id;
      c.data_nidles = this.data_nidles;
      c.pkt_nidles = this.pkt_nidles;
      c.rsp = this.rsp;
      return c;
    endfunction

    function string sprint();
      string s;
      s = {s, $sformatf("=======================================\n")};
      s = {s, $sformatf("chnl_trans object content is as below: \n")};
      s = {s, $sformatf("obj_id = %0d: \n", this.obj_id)};
      foreach(data[i]) s = {s, $sformatf("data[%0d] = %8x \n", i, this.data[i])};
      s = {s, $sformatf("ch_id = %0d: \n", this.ch_id)};
      s = {s, $sformatf("pkt_id = %0d: \n", this.pkt_id)};
      s = {s, $sformatf("data_nidles = %0d: \n", this.data_nidles)};
      s = {s, $sformatf("pkt_nidles = %0d: \n", this.pkt_nidles)};
      s = {s, $sformatf("rsp = %0d: \n", this.rsp)};
      s = {s, $sformatf("=======================================\n")};
      return s;
    endfunction
  endclass: chnl_trans

  class chnl_initiator;
    local string name;
    local virtual chnl_intf intf;
    mailbox #(chnl_trans) req_mb;
    mailbox #(chnl_trans) rsp_mb;

    function new(string name = "chnl_initiator");
      this.name = name;
    endfunction

    function void set_interface(virtual chnl_intf intf);
      if(intf == null)
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
    endfunction

    task run();
      this.drive();
    endtask

    task drive();
      chnl_trans req, rsp;
      @(posedge intf.rstn);
      forever begin
        this.req_mb.get(req);
        this.chnl_write(req);
        rsp = req.clone();
        rsp.rsp = 1;
        this.rsp_mb.put(rsp);
      end
    endtask

    task chnl_write(input chnl_trans t);
      foreach(t.data[i]) begin
        @(posedge intf.clk);
        intf.drv_ck.ch_valid <= 1;
        intf.drv_ck.ch_data <= t.data[i];
        @(negedge intf.clk);
        wait(intf.ch_ready === 'b1);
        $display("%0t channel initiator [%s] sent data %x", $time, name, t.data[i]);
        repeat(t.data_nidles) chnl_idle();
      end
      repeat(t.pkt_nidles) chnl_idle();
    endtask

    task chnl_idle();
      @(posedge intf.clk);
      intf.drv_ck.ch_valid <= 0;
      intf.drv_ck.ch_data <= 0;
    endtask
  endclass: chnl_initiator

  class chnl_generator;
    rand int pkt_id = -1;
    rand int ch_id = -1;
    rand int data_nidles = -1;
    rand int pkt_nidles = -1;
    rand int data_size = -1;
    rand int ntrans = 10;

    mailbox #(chnl_trans) req_mb;
    mailbox #(chnl_trans) rsp_mb;

    constraint cstr{
      soft ch_id == -1;
      soft pkt_id == -1;
      soft data_size == -1;
      soft data_nidles == -1;
      soft pkt_nidles == -1;
      soft ntrans == 10;
    }

    function new();
      this.req_mb = new();
      this.rsp_mb = new();
    endfunction

    task run();
      repeat(ntrans) send_trans();
      run_stop_flags.put();
    endtask

    // generate transaction and put into local mailbox
    task send_trans();
      chnl_trans req, rsp;
      req = new();
      assert(req.randomize with {local::ch_id >= 0 -> ch_id == local::ch_id;
                                 local::pkt_id >= 0 -> pkt_id == local::pkt_id;
                                 local::data_nidles >= 0 -> data_nidles == local::data_nidles;
                                 local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
                                 local::data_size >0 -> data.size() == local::data_size;
                               })
        else $fatal("[RNDFAIL] channel packet randomization failure!");
      this.pkt_id++;
      $display(req.sprint());
      this.req_mb.put(req);
      this.rsp_mb.get(rsp);
      $display(rsp.sprint());
      assert(rsp.rsp)
        else $error("[RSPERR] %0t error response received!", $time);
    endtask

    function string sprint();
      string s;
      s = {s, $sformatf("=======================================\n")};
      s = {s, $sformatf("chnl_generator object content is as below: \n")};
      s = {s, $sformatf("ntrans = %0d: \n", this.ntrans)};
      s = {s, $sformatf("ch_id = %0d: \n", this.ch_id)};
      s = {s, $sformatf("pkt_id = %0d: \n", this.pkt_id)};
      s = {s, $sformatf("data_nidles = %0d: \n", this.data_nidles)};
      s = {s, $sformatf("pkt_nidles = %0d: \n", this.pkt_nidles)};
      s = {s, $sformatf("data_size = %0d: \n", this.data_size)};
      s = {s, $sformatf("=======================================\n")};
      return s;
    endfunction

    function void post_randomize();
      string s;
      s = {"AFTER RANDOMIZATION \n", this.sprint()};
      $display(s);
    endfunction
  endclass: chnl_generator

  typedef struct packed {
    bit[31:0] data;
    bit[1:0] id;
  } mon_data_t;

  class chnl_monitor;
    local string name;
    local virtual chnl_intf intf;
    mailbox #(mon_data_t) mon_mb;
    function new(string name="chnl_monitor");
      this.name = name;
    endfunction
    function void set_interface(virtual chnl_intf intf);
      if(intf == null)
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
    endfunction
    task run();
      this.mon_trans();
    endtask

    task mon_trans();
      mon_data_t m;
      forever begin
        @(posedge intf.clk iff (intf.mon_ck.ch_valid==='b1 && intf.mon_ck.ch_ready==='b1));
        // USER TODO 3.1
        // Put the data into the mon_mb and use $display() to print the stored
        // data value with monitor name
        m.data = intf.mon_ck.ch_data;
        mon_mb.put(m);
        $display("%0t %s monitored channle data %8x", $time, this.name, m.data);
      end
    endtask
  endclass

  class mcdt_monitor;
    local string name;
    local virtual mcdt_intf intf;
    mailbox #(mon_data_t) mon_mb;
    function new(string name="mcdt_monitor");
      this.name = name;
    endfunction
    task run();
      this.mon_trans();
    endtask

    function void set_interface(virtual mcdt_intf intf);
      if(intf == null)
        $error("interface handle is NULL, please check if target interface has been intantiated");
      else
        this.intf = intf;
    endfunction

    task mon_trans();
      mon_data_t m;
      forever begin
        @(posedge intf.clk iff intf.mon_ck.mcdt_val==='b1);
        // USER TODO 3.1
        // Put the data into the mon_mb and use $display() to print the stored
        // data value with monitor name
        m.data = intf.mon_ck.mcdt_data;
        m.id = intf.mon_ck.mcdt_id;
        mon_mb.put(m);
        $display("%0t %s monitored mcdt data %8x and id %0d", $time, this.name, m.data, m.id);
      end
    endtask
  endclass

  class chnl_agent;
    local string name;
    chnl_initiator init;
    chnl_monitor mon;
    // USER TODO 3.2
    // Refer to how we create, set virtual interface and run the initiator
    // object, use do the similar action to the monitor object
    virtual chnl_intf vif;
    function new(string name = "chnl_agent");
      this.name = name;
      this.init = new({name, ".init"});
      this.mon = new({name, ".mon"});
    endfunction

    function void set_interface(virtual chnl_intf vif);
      this.vif = vif;
      init.set_interface(vif);
      mon.set_interface(vif);
    endfunction
    task run();
      fork
        init.run();
        mon.run();
      join
    endtask
  endclass: chnl_agent

  class chnl_checker;
    local string name;
    local int error_count;
    local int cmp_count;
    mailbox #(mon_data_t) in_mbs[3];
    mailbox #(mon_data_t) out_mb;

    function new(string name="chnl_checker");
      this.name = name;
      foreach(this.in_mbs[i]) this.in_mbs[i] = new();
      this.out_mb = new();
      this.error_count = 0;
      this.cmp_count = 0;
    endfunction

    task run();
      this.do_compare();
    endtask


    task do_compare();
      mon_data_t im, om;
      forever begin
        // USER TODO 3.3
        // compare data once there is data in in_mb0/in_mb1/in_mb2 and out_mb
        // first, get om from out_mb, and im from one of in_mbs
        out_mb.get(om);
        case(om.id)
          0: in_mbs[0].get(im);
          1: in_mbs[1].get(im);
          2: in_mbs[2].get(im);
          default: $fatal("id %0d is not available", om.id);
        endcase

        if(om.data != im.data) begin
          this.error_count++;
          $error("[CMPFAIL] Compared failed! mcdt out data %8x ch_id %0d is not equal with channel in data %8x", om.data, om.id, im.data);
        end
        else begin
          $display("[CMPSUCD] Compared succeeded! mcdt out data %8x ch_id %0d is equal with channel in data %8x", om.data, om.id, im.data);
        end
        this.cmp_count++;
      end
    endtask
  endclass

  // USER TODO 3.4
  // Create, set interface and run the object mcdt_mon and checker
  class chnl_root_test;
    chnl_generator gen[3];
    chnl_agent agents[3];
    mcdt_monitor mcdt_mon;
    chnl_checker chker;
    protected string name;
    event gen_stop_e;

    function new(string name = "chnl_root_test");
      this.name = name;
      this.chker = new();
      foreach(agents[i]) begin
        this.agents[i] = new($sformatf("chnl_agent%0d",i));
        this.gen[i] = new();
        // USER TODO 2.1
        // Connect the mailboxes handles of gen[i] and agents[i].init
        this.agents[i].init.req_mb = this.gen[i].req_mb;
        this.agents[i].init.rsp_mb = this.gen[i].rsp_mb;
        this.agents[i].mon.mon_mb = this.chker.in_mbs[i];
      end
      this.mcdt_mon = new();
      this.mcdt_mon.mon_mb = this.chker.out_mb;
      $display("%s instantiated and connected objects", this.name);
    endfunction

    virtual task gen_stop_callback();
      // empty
    endtask

    virtual task run_stop_callback();
      $display("run_stop_callback enterred");
      // by default, run would be finished once generators raised 'finish'
      // flags
      $display("%s: wait for all generators have generated and tranferred transcations", this.name);
      run_stop_flags.get(3);
      $display($sformatf("*****************%s finished********************", this.name));
      $finish();
    endtask

    virtual task run();
      $display($sformatf("*****************%s started********************", this.name));
      this.do_config();
      fork
        agents[0].run();
        agents[1].run();
        agents[2].run();
        mcdt_mon.run();
        chker.run();
      join_none

      // run first the callback thread to conditionally disable gen_threads
      fork
        this.gen_stop_callback();
        @(this.gen_stop_e) disable gen_threads;
      join_none

      fork : gen_threads
        gen[0].run();
        gen[1].run();
        gen[2].run();
      join

      run_stop_callback(); // wait until run stop control task finished

      // USER TODO 1.3
      // Please move the $finish statement from the test run task to generator
      // You would put it anywhere you like inside generator to stop test when
      // all transactions have been transfered
    endtask

    virtual function void set_interface(virtual chnl_intf ch0_vif
                                        ,virtual chnl_intf ch1_vif
                                        ,virtual chnl_intf ch2_vif
                                        ,virtual mcdt_intf mcdt_vif
                                      );
      agents[0].set_interface(ch0_vif);
      agents[1].set_interface(ch1_vif);
      agents[2].set_interface(ch2_vif);
      mcdt_mon.set_interface(mcdt_vif);
    endfunction

    virtual function void do_config();
    endfunction

  endclass

  class chnl_basic_test extends chnl_root_test;
    function new(string name = "chnl_basic_test");
      super.new(name);
    endfunction
    virtual function void do_config();
      super.do_config();
      assert(gen[0].randomize() with {ntrans==100; data_nidles==0; pkt_nidles==1; data_size==8;})
        else $fatal("[RNDFAIL] gen[0] randomization failure!");

      // USER TODO 2.2
      // To randomize gen[1] with
      // ntrans==50, data_nidles inside [1:2], pkt_nidles inside [3:5],
      // data_size == 6
      assert(gen[1].randomize() with {ntrans==50; data_nidles inside {[1:2]}; pkt_nidles inside {[3:5]}; data_size==6;})
        else $fatal("[RNDFAIL] gen[1] randomization failure!");

      // USER TODO 2.3
      // ntrans==80, data_nidles inside [0:1], pkt_nidles inside [1:2],
      // data_size == 32
      assert(gen[2].randomize() with {ntrans==80; data_nidles inside {[0:1]}; pkt_nidles inside {[1:2]}; data_size==32;})
        else $fatal("[RNDFAIL] gen[2] randomization failure!");
    endfunction
  endclass: chnl_basic_test

  // USER TODO 2.4
  // each channel send data packet number inside [80:100]
  // data_nidles == 0, pkt_nidles == 1, data_size inside {8, 16, 32}
  class chnl_burst_test extends chnl_root_test;
    function new(string name = "chnl_burst_test");
      super.new(name);
    endfunction
    virtual function void do_config();
      super.do_config();
      assert(gen[0].randomize() with {ntrans inside {[80:100]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
        else $fatal("[RNDFAIL] gen[0] randomization failure!");
      assert(gen[1].randomize() with {ntrans inside {[80:100]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
        else $fatal("[RNDFAIL] gen[1] randomization failure!");
      assert(gen[2].randomize() with {ntrans inside {[80:100]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
        else $fatal("[RNDFAIL] gen[2] randomization failure!");
    endfunction
  endclass: chnl_burst_test

  // USER TODO 2.5
  // keep channel sending out data packet with number, and please
  // let at least two slave channels raising fifo_full (ready=0) at the same time
  // and then to stop the test
  class chnl_fifo_full_test extends chnl_root_test;
    function new(string name = "chnl_fifo_full_test");
      super.new(name);
    endfunction
    virtual function void do_config();
      super.do_config();
      assert(gen[0].randomize() with {ntrans inside {[1000:2000]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
        else $fatal("[RNDFAIL] gen[0] randomization failure!");
      assert(gen[1].randomize() with {ntrans inside {[1000:2000]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
        else $fatal("[RNDFAIL] gen[1] randomization failure!");
      assert(gen[2].randomize() with {ntrans inside {[1000:2000]}; data_nidles==0; pkt_nidles==1; data_size inside {8, 16, 32};})
        else $fatal("[RNDFAIL] gen[2] randomization failure!");
    endfunction

    // get all of 3 channles slave ready signals as a 3-bits vector
    local function bit[3] get_chnl_ready_flags();
      return {agents[2].vif.mon_ck.ch_ready
             ,agents[1].vif.mon_ck.ch_ready
             ,agents[0].vif.mon_ck.ch_ready
             };
    endfunction

    virtual task gen_stop_callback();
      bit[3] chnl_ready_flags;
      $display("gen_stop_callback enterred");
      @(posedge agents[0].vif.rstn);
      forever begin
        @(posedge agents[0].vif.clk);
        chnl_ready_flags = this.get_chnl_ready_flags();
        if($countones(chnl_ready_flags) <= 1) break;
      end

      $display("%s: stop 3 generators running", this.name);
      -> this.gen_stop_e;
    endtask

    virtual task run_stop_callback();
      $display("run_stop_callback enterred");

      // since generators have been forced to stop, and run_stop_flag would
      // not be raised by each generator, so no need to wait for the
      // run_stop_flags any more

      $display("%s: waiting DUT transfering all of data", this.name);
      fork
        wait(agents[0].vif.ch_margin == 'h20);
        wait(agents[1].vif.ch_margin == 'h20);
        wait(agents[2].vif.ch_margin == 'h20);
      join
      $display("%s: 3 channel fifos have transferred all data", this.name);

      $display($sformatf("*****************%s finished********************", this.name));
      $finish();
    endtask
  endclass: chnl_fifo_full_test

endpackage

lab4

在接下来进入lab4之前,你头脑中需要再复习这些概念: 第一,验证环境按照隔离的观念,应该分为硬件DUT,软件验证环境,和处于信号媒介的接口interface;第二,对于软件验证环境,它需要经历立阶段(build)、连接阶段(connect)、产生激励阶段(generate)和发送激励阶段(transfer),只有当所有的激励发送完毕并且比较完全之后,才可以结束该测试

与lab3相比,lab4的环境是完善的,这其中的原因主要有两方面,一方面是因为从lab4开始在验证更大的子系统,即MCDF。与MCDT相比,MCDF主要添加了寄存器控制和状态显示功能,同时也添加了一个重要的数据整形功能,因此,你会发现lab4的验证文件多了。另外一方面,代码之所以增多是为了让整个验证环境的各个组件之间相互独立,功能清晰,可以发现不同的package之间的功能是独立的,同一个package中的各个验证组件的功能也是独立的。那么不同组件之间的同步和通信依靠什么呢?没错,那就是已经学习到的event和mailbox。

如何开始lab4呢?大胆地编译所有的文件,然后给出仿真命令就可以了:

vsim -novopt -classdebug -solvefaildebug -sv_seed 0 +TESTNAME=mcdf_data_consistence_basic_test -I mcdf_data_consistence_basic_test.log work.tb

来看看上面主要的仿真命令项:

  • -classdebug,这是为了提供更多的SV类调试功能
  • -solvefaildebug,这是为了在SV随机化失败之后有更多的信息提示出来,
  • -sv_seed 0,暂时给固定的随机种子值0
  • +TESTNAME=mcdf_data_consistence_basic_test,这是指定仿真选择的测试
  • -I mcdf_data_consistence_basic_test.log,这是让仿真的记录保存在特定的测试文件名称中

那么在开始测试并且最终结束之后,在你的项目目录下会有两个文件产生:mcdf_data_consistence_basic_test_sim.log会保存所有的仿真信息,,而mcdf_data_consistence_basic_test_checker.log则只会保存做数据比较和最终比较报告信息。需要注意的是,在测试的过程中,如果测试发生错误会立即停止下来,可以根据当前时刻的错误报告定位出数据比较错误的时间点,再去核对波形,分析是硬件问题还是环境问题。

那么验证环境这么完善,是不是不需要做lab4了呢?NONONO…,lab4的重点是要在本次练习中领会,怎么样才是一个完整的验证计划和实施。什么是验证计划的核心要素点?那就是围绕着设计的功能点罗列出需要展开测试的功能点,以及如何展开测试,并且指明测试的验收标准是什么。我们lab4中主要以测试是否通过为原则,而在lab5中,将进一步掌握代码覆盖率和功能覆盖率的验收标准。

  • 从验证框图可以更好地理解验证环境的组件和组件之间的通信连接情况。无论对于接下来着手构建环境,还是将它作为验证代码的一个形象说明,它都比代码更直接地形容整个验证环境。那么接下来,请画出验证结构吧。
验证结构
checker
  • 接下来,会指定你需要在mcdf_pkg.sv中参考以后测试类mcdf_data_consistence_basic_test而去创建其它的测试类名,它们对应的测试功能点,以及测试标准是什么,在你按照要求,实现了所有的测试之后,记得将每个测试都至少跑一遍,如果你跑的过程中发现mcdf_checker报错,分析是不是设计问题,确定是设计bug可以提交bug,待设计修复后继续测试。理论上,如果对同一个测试,每次使用不同的随机种子即”-sv_seed RANDNUM”,那么你得次数越多,就越有机会发现新的bug,而至于需要跑多少遍,或者每个测试中需要发送多少次数据包(发送得越多越好,那么究竟要发送多少次才可以停下来呢?标准是什么?) 这个问题在lab5会解答。
测试功能点 测试内容 测试通过标准 测试类名
寄存器读写测试 所有控制寄存器的读写测试 所有状态寄存器的读写测试 读写值是否正确 mcdf_reg_write_read_test
寄存器稳定性测试 非法地址读写,对控制寄存器的保留域进行读写,对状态寄存器进行写操作 通过写入和读出,确定寄存器的值是预期值,而不是紊乱值,同时非法寄存器操作也不能影响MCDF的整体功能 mcdf_reg_illegal_access_test
数据通道开关测试 对每一个数据通道对应的控制寄存器域en配置为0,在关闭状态下测试数据写入是否通过 在数据通道关闭情况下,数据无法写入,同时ready信号应该保持为低,表示不接收数据,但又能使得数据不被丢失,因此数据只会停留在数据通道端口 mcdf_channel_disable_test
优先级测试 将不同数据通道配置为相同或者不同的优先级,在数据通道使能的情况下进行测试 如果优先级相同,那么arbiter应该采取轮询机制从各个通道接收数据,如果优先级不同,那么arbiter应该先接收高优先级通道的数据,同时,最终所有的数据都应该从MCDF发送出来 mcdf_arbiter_priority_test
发包长度测试 将不同数据通道随机配置为各自的长度,在数据通道使能的情况下进行测试 从formatter发送出来的数据包长度应该同对应通道寄存器的配置值保持一—对应,同时数据也应该保持完整 mcdf_formatter_length test
下行从端低带宽测试 将MCDF下行数据接收端设置为小存储量,低带宽的类型,由此使得在由formatter发送出数据之后,下行从端有更多的机会延迟grant信号的置位,用来模拟真实场景 在req拉高之后,grant应该在至少两个时钟周期以后拉高,以此来模拟下行从端数据余量不足的情况。当这种激励时序发生10次之后,可以停止测试。 mcdf formatter_grant_test

测试代码根据后文链接,自行下载。

lab5

本次练习将认识如何定义覆盖率,如何从验证计划到测试用例的实现,最后再到覆盖率的量化。从本次练习,可以掌握验证量化的两种基本数据,即代码覆盖率和功能覆盖率。从这两种覆盖率,就可以掌握何时结束验证,确认验证的完备性。

lab5的代码是基于lab4的代码,主要修改的文件即mcdf_pkg.sv。在mcdf_pkg.sv中,已经为添加了一个关于MCDF的覆盖率模型mcdf_coverage,并且将它例化在顶层环境中。你可以阅读代码,了解mcdf_coverage的例化、虚接口的传递、覆盖率的定义和采样。同时,提供了一个新的test,即mcdf_full_random_test。这个test尽可能的将一些测试相关参数在仿真时进行了随机化。在接下来的仿真中,依然可以复用你之前lab4按照要求创建的几个test,将它们搬迁到lab5中。不过,lab5的要求是,需要最终达到“尽可能高的代码覆盖率和功能覆盖率”。所以结合本次练习最终的验收目标,可以复用你之前的测试用例。当然,在学习了覆盖率相关的课程之后,就懂得了,当最终覆盖率无法提升时,需要修改的约束,或者创建新的test。

  • 通过等级:代码覆盖率大于90%,功能覆盖率大于90%。
  • 优秀等级:代码覆盖率大于95%,功能覆盖率等于100%。

测试代码根据后文链接,自行下载。


文章原创,可能存在部分错误,欢迎指正,联系邮箱 cao_arvin@163.com。