目录
2.2.2 override/overwrite/overload的区别
3.1.2 UVM -- factory机制与平台组件构建:
3.1.3 UVM -- TLM通信机制与平台的connect :
3.2.3 virtual sequence与virtual sequencer
一、总线
1.1 amba总线学习
-
AMBA学习 - 知乎19lessons附代码
二、sv以及问题汇总学习
2.1 sv基础知识
-
SV复习(三) 连接设计和测试平台_sv clock output必须用非阻塞赋值_whddddddddddd的博客-CSDN博客
- SV复习(四)oop_sv中packet_whddddddddddd的博客-CSDN博客
- SV复习(五)随机化_sv中rand_whddddddddddd的博客-CSDN博客
- SV复习(六)线程间通信_sv的旗语_whddddddddddd的博客-CSDN博客
- SV复习(七) 覆盖率_sv查看覆盖率命令_whddddddddddd的博客-CSDN博客
2.2 sv小问题汇总
2.2.1 动态变量 静态变量 局部变量 全局变量
SV笔记:static 和 automatic 概念及相关规定_sv中function automatic_小汪的IC自习室的博客-CSDN博客
2.2.2 override/overwrite/overload的区别
【SV_overload/overwrite/override】_日拱半卒的博客-CSDN博客
(1) overload——重载
- 简单来说overload是重载 允许一个class有两个不同参的函数,sv不支持
(2) overwrite——重写
- overwrite发生在子类和父类之间,即不同的类域之间。overwrite发生在同名函数中,同不同参无所谓。
- 发生overwrite的函数必须是非虚函数,因为一旦父类中函数被声明为虚函数,调用同名函数时会发生多态(会根据对象类型来决定调用父类中的还是子类中的同名函数),当这些条件满足的时候,我们就说这个函数被overwrite了;
- 在调用被overwrite的函数时,会根据句柄的类型(而不是对象的类型)决定调用父类中的还是子类中的同名函数(与override会有明显的区别);
(3) override——覆盖
- override发生在子类和基类之间的同名同参函数身上,并且该同名函数在基类中要求被声明为虚函数。
- 和overwrite的区别就是多了函数参数相同和基类中必须为虚函数这两个要求,当这些条件都满足时,就可以认为这个函数在类间被override了
- 当发生override的函数被调用的时候,会根据句柄指向的对象类型来动态地决定要调用基类中还是子类中的同名函数(与多态性结合);
class A;
function void print_1(string str);
$display({str,": not virtual function 1 in class A"});
endfunction
virtual function void vri_print_1(string str);
$display({str,": virtual function 1 in class A"});
endfunction
function void print_2(string str);
$display({str,": not virtual function 2 in class A"});
endfunction
virtual function void vir_print_2(string str);
$display({str,": virtual function 2 in class A"});
endfunction
end
class B extends A;
function void print_1(string str);
$display({str,": not virtual function 1 in class B"});
endfunction
virtual function void vri_print_1(string str);
$display({str,": virtual function 1 in class B"});
endfunction
function void print_2(int number);
$display("%d: not virtual function 2 in class B", number);
endfunction
// Error override必须是同名同参的虚函数
// virtual function void vir_print_2(int number);
// $display("%d: virtual function 2 in class A", number);
// endfunction
endclass
module tb;
A a_inst_1;
A a_inst_2;
B b_inst_1;
initial begin
a_inst_1 = new();
b_inst_1 = new();
a_inst_2 = b_inst_1; // 多态性
$display("The object address of a_inst_1: %h", a_inst_1);
$display("The object address of b_inst_1: %h", b_inst_1);
$display("The object address of a_inst_2: %h", a_inst_2);
$diaply("*************************************");
a_inst_1.print_1("a_inst_1");
b_inst_1.print_1("b_inst_1");
a_inst_2.print_1("a_inst_2");
$diaply("*************************************");
a_inst_1.vri_print_1("a_inst_1");
b_inst_1.vri_print_1("b_inst_1");
a_inst_2.vri_print_1("a_inst_2");
$diaply("*************************************");
a_inst_1.print_2("a_inst_1");
b_inst_1.print_2(666);
a_inst_2.print_2("a_inst_2");
end
endmodule
//*************************run_result**************************
// The object address of a_inst_1: f0568842
// The object address of a_inst_1: f0568870
// The object address of a_inst_1: f0568870
//***************************************************
// a_inst_1: not virtual function 1 in class A
// b_inst_1: not virtual function 1 in class B
// a_inst_2: not virtual function 1 in class A
//***************************************************
// a_inst_1: virtual function 1 in class A
// b_inst_1: virtual function 1 in class B
// a_inst_2: virtual function 1 in class B
//***************************************************
// a_inst_1: not virtual function 2 in class A
// 666: not virtual function 2 in class B
// a_inst_2: not virtual function 2 in class A
2.2.3 sv覆盖率
SV--覆盖率_covergroup_创芯人-- Fly的博客-CSDN博客
典型覆盖率收集代码:
class Transactor;
Transaction tr;
mailbox mbx_in;
covergroup CovPort;//定义一个covergroup
coverpoint tr.port; //coverpoint 声明变量
endgroup
function new(mailbox mbx_in);
CovPort =new(); //例化 推荐CovPort cg1=new();
this.mbx_in = mbx_in;
endfunction
task main;
forever begin
tr = mbx_in.get;
ifc.cb.port <= tr.port;
ifc.cb.data <= tr.data;
CovPort.sample(); //采样
end
endtask
endclass
2.2.4 类型转换
sv类型转换_whddddddddddd的博客-CSDN博客
2.2.5 句柄复制,浅拷贝深拷贝详解以及clone函数
sv中句柄复制,浅拷贝深拷贝详解以及clone函数_whddddddddddd的博客-CSDN博客
2.2.6 跳出循环
systemVerilog过程语句:for循环语句控制/跳转 continue break return_verilog for break_狮子座硅农(Leo ICer)的博客-CSDN博客
三、uvm学习
3.1 uvm基础知识学习
3.1.1 uvm基础知识:
https://www.cnblogs.com/thisway2014/p/16461987.html
uvm结构树根节点:
UVM -- phase机制与UVM验证平台的运行:https://www.cnblogs.com/thisway2014/p/16463676.html
phase:
phase执行顺序:
测试用例的启动以及执行流程:
phase跳转:
task main_phase;
@(posedge clk);
phase.jump(uvm_reset_phase::get());
endtask
objection机制:
其他task phase内耗时语句想要执行,必须要raise objection以及drop objection 而run phase则不需要,有组件中的reset phase或者main phase有raise objection以及drop objection就可以运行耗时语句,反过来则不行,即run phase可以借别的小phase的 raise objection以及drop objection用,而其他小phase不能借run phase的用
objection控制策略:
1、在scoreboard中设置,main phase的无限循环改成手收集确定数量的包便跳出objection,例如(p153):
task scb::main_phase(uvm_phase phase);
phase.raise_objection(this);
fork
while(1) begin
exp_port.get(get_expect);
expect_queue.push_back(get_expect);
end
for(int i= 0;i<pkt_num;i++) begin
act.port.get(get_actual);
...
end
join_any
phase.drop_objection(this);
endtask
2、在sequence中提起sequencer的objection,sequence完成后撤销例如:
task sqr::main_phase(uvm_phase phase);
sequence seq;
phase.raise_objection(this);
seq=sequence::type_id::create("seq");
seq.start(i_agt.sqr);
phase.drop_objection(this);
endtask
3.1.2 UVM -- factory机制与平台组件构建:
https://www.cnblogs.com/thisway2014/p/16487814.html
3.1.3 UVM -- TLM通信机制与平台的connect :
UVM中的TLM通信_uvm的tlm通信_数字IC小白_lhp的博客-CSDN博客
UVM基础-TLM通信机制(一)_不吃葱的酸菜鱼的博客-CSDN博客
https://www.cnblogs.com/thisway2014/p/16488147.html
3.1.4 UVM -- suquence机制:
https://www.cnblogs.com/thisway2014/p/16503021.html
uvm中sequence机制详解_whddddddddddd的博客-CSDN博客
3.2 uvm小问题汇总
3.2.1 sequence机制的运行
uvm中sequence机制详解_whddddddddddd的博客-CSDN博客
3.2.2 sequence机制仲裁
在sequence被启动是会自动执行它的body、pre_body和post_bosy三个任务,我们常用的是body任务。body任务中可以通过uvm_do_pri及uvm_do_pri_with改变所产生的transaction的优先级。uvm_do_pri与uvm_do_pri_with的第二个参数是优先级, 这个数值必须是一个大于等于-1的整数。 数字越大, 优先级越高。
如下面的这段代码指定了sequence0的优先级为100,sequence1的优先级为200,sequence1的优先级高于sequence0的优先级,同事设定了sequencer采用优先级仲裁机制,这样则会将sequence1的transcation都发送完成后再发送sequence0的transaction。对sequence设置优先级的本质即设置其内产生的transaction的优先级。
`ifndef MY_CASE0__SV
`define MY_CASE0__SV
class sequence0 extends uvm_sequence #(my_transaction);
my_transaction m_trans;
function new(string name= "sequence0");
super.new(name);
endfunction
virtual task body();
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat (5) begin
`uvm_do_pri(m_trans, 100)
`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask
`uvm_object_utils(sequence0)
endclass
class sequence1 extends uvm_sequence #(my_transaction);
my_transaction m_trans;
function new(string name= "sequence1");
super.new(name);
endfunction
virtual task body();
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat (5) begin
`uvm_do_pri_with(m_trans, 200, {m_trans.pload.size < 500;})
`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask
`uvm_object_utils(sequence1)
endclass
class my_case0 extends base_test;
function new(string name = "my_case0", uvm_component parent = null);
super.new(name,parent);
endfunction
`uvm_component_utils(my_case0)
extern virtual task main_phase(uvm_phase phase);
endclass
task my_case0::main_phase(uvm_phase phase);
sequence0 seq0;
sequence1 seq1;
seq0 = new("seq0");
seq0.starting_phase = phase;
seq1 = new("seq1");
seq1.starting_phase = phase;
env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO);//设置sqr仲裁算法
fork
seq0.start(env.i_agt.sqr);
seq1.start(env.i_agt.sqr);
join
endtask
`endif
此时该sqr先发seq1全部发完再发seq0,因为seq1优先级大于seq0
start函数同样可以设置优先级,start(sqr,parent seq,pri),参数依次为sqr 父seq,以及优先级,这个设置其实和uvm_do设置中一样,都是在设置transaction。
lock与grap
lock和grap其实对sequence的仲裁是相似的,都是在lock/grap期间独占sequencer进行transaction传输,区别在于grab操作比lock操作优先级更高。 lock请求是被插入sequencer仲裁队列的最后面, 等到它时, 它前面的仲裁请求都已经结束了。 grab请求则被放入sequencer仲裁队列的最前面, 它几乎是一发出就拥有了sequencer的所有权。如果是两个lock或者grap都试图独占sequencer,则先占用的发送完成再执行后占用的。这样前面说grap有比lock更高的优先级,如果发生在使用lock一个sequence独占sequencer发送transaction没有unlock时来了一个grap请求要怎么处理呢,这个时候grap会等lock的sequence执行完毕再执行自己的请求。
`ifndef MY_CASE0__SV
`define MY_CASE0__SV
class sequence0 extends uvm_sequence #(my_transaction);
my_transaction m_trans;
function new(string name= "sequence0");
super.new(name);
endfunction
virtual task body();
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat (2) begin
`uvm_do(m_trans)
`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
end
lock();
`uvm_info("sequence0", "locked the sequencer ", UVM_MEDIUM)
repeat (5) begin
`uvm_do(m_trans)
`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
end
`uvm_info("sequence0", "unlocked the sequencer ", UVM_MEDIUM)
unlock();
repeat (2) begin
`uvm_do(m_trans)
`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask
`uvm_object_utils(sequence0)
endclass
class sequence1 extends uvm_sequence #(my_transaction);
my_transaction m_trans;
function new(string name= "sequence1");
super.new(name);
endfunction
virtual task body();
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat (3) begin
`uvm_do_with(m_trans, {m_trans.pload.size < 500;})
`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
end
lock();/、开始lock
`uvm_info("sequence1", "locked the sequencer ", UVM_MEDIUM)
repeat (4) begin
`uvm_do_with(m_trans, {m_trans.pload.size < 500;})
`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
end
`uvm_info("sequence1", "unlocked the sequencer ", UVM_MEDIUM)
unlock();//结束lock
repeat (3) begin
`uvm_do_with(m_trans, {m_trans.pload.size < 500;})
`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask
`uvm_object_utils(sequence1)
endclass
class my_case0 extends base_test;
function new(string name = "my_case0", uvm_component parent = null);
super.new(name,parent);
endfunction
`uvm_component_utils(my_case0)
extern virtual task main_phase(uvm_phase phase);
endclass
task my_case0::main_phase(uvm_phase phase);
sequence0 seq0;
sequence1 seq1;
seq0 = new("seq0");
seq0.starting_phase = phase;
seq1 = new("seq1");
seq1.starting_phase = phase;
fork
seq0.start(env.i_agt.sqr);
seq1.start(env.i_agt.sqr);
join
endtask
`endif
此时seq0,seq1先交提产生,lock被调用后只产生lock后的transact
is_relevant和wait_for_relevant进行重载
可以对is_relevant和wait_for_relevant进行重载来决定仲裁。sequencer在仲裁时, 会查看sequence的is_relevant函数的返回结果。 如果为1, 说明此sequence有效, 否则无效。
class sequence0 extends uvm_sequence #(my_transaction);
my_transaction m_trans;
int num;
bit has_delayed;
function new(string name= "sequence0");
super.new(name);
num = 0;
has_delayed = 0;
endfunction
virtual function bit is_relevant();
if((num >= 3)&&(!has_delayed)) return 0;
else return 1;
endfunction
virtual task wait_for_relevant();
#10000;
has_delayed = 1;
endtask
virtual task body();
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat (10) begin
num++;
`uvm_do(m_trans)
`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask
`uvm_object_utils(sequence0)
endclass
3.2.3 virtual sequence与virtual sequencer
如果验证平台中存在两个agent,每个agent下的driver接收的sequence还需要保证一定的同步关系,这个时候就需要virtual sequence和virtual sequencer出马了。如果不适用virtual sequence还要保证drv0_seq和drv1_seq有同步关系,这个时候就需要引入全局变量来做同步了,但是如果是中间存在多次的同步,如sequence A要先执行, 之后是B, B执行后才能是C, C执行后才能是D, D执行后才能是E。 这依然可以使用上面的全局方法解决, 只是这会显得相当笨拙。这种情况下使用virtual sequence将非常方便。
其中virtual sequence其中做了例化sequence,启动调度seq执行的作用,起到的作用为更好地控制seq发包的顺序以及操作,还可以发送不同的包,不过此时sqr与drv的参数需要改为uvm_sequence_item且drv操作时得到的seq_item_port.get_next_item(req)中的req需要cast,即$cast(m_tr,req)
sequence怎么使用config_db
sequence中使用config机制时可以这样使用:
uvm_config_db#(int)::set(this,"env.i_agt.aqr.*","count",count);
uvm_config_db#(int)::get(this(sequence无结构树因此不可用this)null,get_full_name(),"count",count);
response
sequence可以等待driver返回response值决定接下来发送的trans,可以用get_response(rsp)来等待driver返回,而driver也需要调用
seq_item_port.get_next_item(req);
rsp=new("rsp");
rsp.set_id_info("rsq");
seq_item_port.put_response(rsq);
seq_item_port.item_done;
4、5行的代码也可以写成seq_item_port.item_done(rsp);也是一样的,当多个response时只能为第一种写法。
response详解:https://www.cnblogs.com/Alfred-HOO/articles/16519230.html
3.2.4 sequence library
UVM实战 卷I学习笔记9——UVM中的sequence(7)_sequence library_菜鸡想要飞的博客-CSDN博客
随机选择sequence
所谓sequence library,就是一系列sequence的集合。sequence_library类的原型为:
class uvm_sequence_library #(type REQ=uvm_sequence_item,RSP=REQ)
extends uvm_sequence #(REQ,RSP);
sequence library派生自uvm_sequence,从本质上是sequence,根据特定算法随机选择注册在其中的一些sequence,并在body中执行这些sequence。
一个sequence library的定义如下:
class simple_seq_library extends uvm_sequence_library#(my_transaction);
function new(string name= "simple_seq_library");
super.new(name);
init_sequence_library();
endfunction
`uvm_object_utils(simple_seq_library)
`uvm_sequence_library_utils(simple_seq_library);
endclass
在定义sequence library时要特别注意:
- 一是从uvm_sequence派生时要指明此sequence library产生的transaction类型;
- 二是在其new函数中要调用init_sequence_library,否则其内部的候选sequence队列就是空的;
- 三是要调用uvm_sequence_library_utils注册。
sequence library在定义之后如果没有任何sequence注册到其中,是没有任何意义的。一个sequence在定义时使用宏`uvm_add_to_seq_lib来将其加入某个sequence library中:
class seq0 extends uvm_sequence#(my_transaction);
…
`uvm_object_utils(seq0)
`uvm_add_to_seq_lib(seq0, simple_seq_library)
virtual task body();
repeat(10) begin
`uvm_do(req)
`uvm_info("seq0", "this is seq0", UVM_MEDIUM)
end
endtask
endclass
uvm_add_to_seq_lib有两个参数:此sequence的名字和要加入的sequence library名字。一个sequence可以加入多个不同的sequence library中:
class seq0 extends uvm_sequence#(my_transaction);
`uvm_object_utils(seq0)
`uvm_add_to_seq_lib(seq0, simple_seq_library)
`uvm_add_to_seq_lib(seq0, hard_seq_library)
virtual task body();
repeat(10) begin
`uvm_do(req)
`uvm_info("seq0", "this is seq0", UVM_MEDIUM)
end
endtask
endclass
同样的,可以有多个sequence加入同一sequence library中:
class seq1 extends uvm_sequence#(my_transaction);
…
`uvm_object_utils(seq1)
`uvm_add_to_seq_lib(seq1, simple_seq_library)
virtual task body();
repeat(10) begin
`uvm_do(req)
`uvm_info("seq1", "this is seq1", UVM_MEDIUM)
end
endtask
endclass
当sequence与sequence library定义好后,可以将sequence library作为sequencer的default sequence:
function void my_case0::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(uvm_object_wrapper)::set(this, "env.i_agt.sqr.main_phase",
"default_sequence", simple_seq_library::type_id::get());
endfunction
执行代码会发现UVM随机从加入simple_seq_library的sequence中选择几个并顺序启动它们。
控制选择算法
上节中sequence library随机从其sequence队列中选择几个执行。这是由其变量selection_mode决定的,这个变量的定义为:uvm_sequence_lib_mode selection_mode;
uvm_sequence_lib_mode是一个枚举类型,共有四个值:
typedef enum
{
UVM_SEQ_LIB_RAND,
UVM_SEQ_LIB_RANDC,
UVM_SEQ_LIB_ITEM,
UVM_SEQ_LIB_USER
} uvm_sequence_lib_mode;
- UVM_SEQ_LIB_RAND就是完全的随机,上节例子就是这种算法;
- UVM_SEQ_LIB_RANDC就是将加入的sequence随机排一个顺序,然后按照此顺序执行。这可以保证每个sequence执行一遍,在所有sequence被执行完一遍之前,不会有sequence被执行第二次,其配置方式如下:
function void my_case0::build_phase(uvm_phase phase);
…
uvm_config_db#(uvm_sequence_lib_mode)::set(this, "env.i_agt.sqr.main_phase",
"default_sequence.selection_mode", UVM_SEQ_LIB_RANDC);
endfunction
- UVM_SEQ_LIB_ITEM是sequence library并不执行其sequence队列中的sequence,而是自己产生transaction。sequence library在此种情况下就是普通的sequence,只是其产生的transaction除了定义时施加的约束外,没有任何额外的约束。
- UVM_SEQ_LIB_USER是用户自定义选择的算法。需用户重载select_sequence参数:
使用sequence_library_cfg
使用3个config_db设置迭代次数和选择算法稍显麻烦。UVM提供uvm_sequence_library_cfg对sequence library进行配置。它一共有三个成员变量:
通过配置如上三个成员变量,并将其传递给sequence library就可对sequence library进行配置:
function void my_case0::build_phase(uvm_phase phase);
uvm_sequence_library_cfg cfg;
super.build_phase(phase);
cfg = new("cfg", UVM_SEQ_LIB_RANDC, 5, 20); //notice
uvm_config_db#(uvm_object_wrapper)::set(this, "env.i_agt.sqr.main_phase",
"default_sequence", simple_seq_library::type_id::get());
uvm_config_db#(uvm_sequence_library_cfg)::set(this, "env.i_agt.sqr.main_phase",
"default_sequence.config", cfg);
endfunction
除使用专门的cfg外,还有一种简单的配置方法是启动sequence时,在对sequence library进行实例化后,对其中的变量进行赋值:
function void my_case0::build_phase(uvm_phase phase);
simple_seq_library seq_lib;
super.build_phase(phase);
seq_lib = new("seq_lib");
seq_lib.selection_mode = UVM_SEQ_LIB_RANDC;
seq_lib.min_random_count = 10;
seq_lib.max_random_count = 15;
uvm_config_db#(uvm_sequence_base)::set(this, "env.i_agt.sqr.main_phase",
"default_sequence", seq_lib);
endfunction
四、基础工具
4.1 vim
4.1.1 基本操作
4.1.2 问题汇总
4.2 tmux
4.2.1 基本操作
4.2.2 问题汇总
4.3 git版本管理
4.3.1 基本操作
Learn Git Branching git学习小游戏
4.3.2 问题汇总
4.4 verdi
verdi打开覆盖率相关:
五、脚本语言以及系统函数
5.1 makefile
Makefile介绍和基本规则(一)_牛谱乐的博客-CSDN博客
Makefile中变量的使用(二)_makefile使用变量_牛谱乐的博客-CSDN博客
Makefile函数(六)_addsuffix使用方法_牛谱乐的博客-CSDN博客
Makefile的使用中的一些容易遗忘的小知识点_牛谱乐的博客-CSDN博客
教你写Makefile(很全,含有工作经验的)_makefile do if_alpha_2017的博客-CSDN博客
5.2 问题汇总
Makefile中$$使用 - 蓝色矩阵 - OSCHINA - 中文开源技术交流社区
$@ 表示目标文件
$^ 表示所有的依赖文件
$< 表示第一个依赖文件
$? 表示比目标还要新的依赖文件列表
【工具使用】VCS & Verdi使用Makefile联合仿真 - 知乎
Makefile中常用vcs仿真命令及含义_incdir_SD.ZHAI的博客-CSDN博客
Shell中if判断_shell if 数值判断_努力者Mr李的博客-CSDN博客
常见Linux shell脚本中的“-e -d -f -eq -ne -gt -ge”操作符的含义_linux -ge___Benco的博客-CSDN博客
shell脚本三剑客之sed_我叫yyy的博客-CSDN博客
shell脚本三剑客之grep_shell脚本中使用grep_我叫yyy的博客-CSDN博客
shell脚本三剑客之awk_我叫yyy的博客-CSDN博客
file.f语法:
数字电路仿真编译文件指定方式_vlogan命令_笨牛慢耕的博客-CSDN博客
verdi使用:
5.3 系统函数
verilog的系统任务----$fopen、$fclose和$fdisplay, $fwrite,$fstrobe,$fmonitor,$fopen() $feof() $fscanf() $fgetc()
Verilog的系统任务----$fopen、$fclose和$fdisplay, $fwrite,$fstrobe,$fmonitor_verilog fopen-CSDN博客
$fopen() $feof() $fscanf() $fgetc()的用法-CSDN博客
六、断言
6.1 断言