UVM自学笔记:项目之四(重难点)

您所在的位置:网站首页 drive的怎么读 UVM自学笔记:项目之四(重难点)

UVM自学笔记:项目之四(重难点)

2024-05-30 07:18| 来源: 网络整理| 查看: 265

 

目录

1. driver写功能编写,为了验证driver的功能我们同时需要完善sequence、sequence_item、env、testcase以及模拟DUT行为的Slave_module

1.1 interface的编写:

1.2 driver写功能编写:

1.2.1 初始main_phase代码:

1.2.2 满足AHB二级流水要求的main_phase代码:

1.2.3. do_drive任务代码:

1.2.4.send_addr_control()/send_wdata()/judge_trans_finished()任务代码

1.2.5.对照波形进行判断

1.3. 模拟slave行为具有发送hready信号功能的module的代码

1.4. seq_item的编写,代码如下:

1.5. seq的编写,代码如下:

1.6. 在testcase中启动seq,给seq配置控制参数具体值

1.7. 在tb_top中将tb与slave_module连接起来,并且产生时钟和复位信号

1.8. 写makefile跑仿真验证写信号时序正确性

1.8.1我们打开VCS看看波形

2.完善driver的读写功能时序

2.1driver的代码部分:

2.1.1 driver中的main_phase阶段代码如下:

 2.1.2 get_sim_trans的任务如下,也可以将该task写成方法

 2.1.3 do_drive任务代码如下:

 2.1.4 do_drive中的send_addr_control任务代码:

 2.1.5 do_drive中的send_wdata任务代码:

 2.1.6 do_drive中的judge_trans_finished任务代码:

2.2 接下来我们完善模拟DUT行为的Slave的module的编写

2.2.1 Slave中产生hready信号:

2.2.2 Slave中判断一笔trans是否完成:

 2.2.3 Slave接受总线上的数据:

 2.2.4 Slave发送一笔读数据: 

 2.2.5 其他组件配置:

3. 用makefile编译,VCS仿真查看结果:

1. driver写功能编写,为了验证driver的功能我们同时需要完善sequence、sequence_item、env、testcase以及模拟DUT行为的Slave_module 1.1 interface的编写:

interface如下,在driver,monitor和slave_module的clocking_block中声明好各自信号的方向。 

1.2 driver写功能编写: 1.2.1 初始main_phase代码:

 Driver 通过79行代码get到seq传来的req,随后执行do_drive函数将该笔trans传递到总线上,然后调用82行代码完成与seq的握手,之后通过83行代码,来到下一个时钟上升沿,这样做会有以下几个缺点:

(1)首先,#10ns是一个hard code,一旦我们更改了时钟频率,就要到driver中重新修改该值,非常不便利。

(2)其次,seq中每产生一笔数据就会通过79行的握手被driver给get到,然后执行push到总线上的操作。但是ahb是二级流水,产生的req内既有地址和控制信号,又有数据信号,数据信号总是滞后于地址信号一个时钟周期被发送到总线上,直接将所有信号push到总线上显然是不满足二级流水的时序要求,并且当hready为低时可能会丢掉hready为低时候的信号。

(3)最后,假设我们将产生10笔trans,当执行其中一笔trans时,此时hready为低,do_drive函数不能将当前的trans push到总线上,随后调用82行的代码,让seq产生下一笔trans,那么就会丢掉hready为低时候的trans,并且让总的trans数目不满足10笔。

解决问题的方法如下:

(1)问题一:我们将#10ns修改为@vif.drvCLK

(1)问题二:我们应该设法在driver中加入队列,队列里面按照顺序存放着由seq产生的每一笔trans,然后driver从该队列中按照先入先出的顺序取出数据,并将数据发送到总线上,这样既能保证二级流水,又不会丢失每一笔trans。

(2)问题三:我们让driver从seqr中get到trans的操作与driver将该笔数据按照时序发送到总线上的操作分开。在main_phase中开启两个并发线程,其中一个线程负责不断地通过seq与drv的握手从seq中获取trans并放入drv的队列中等待处理,而另一个线程负责将drv队列中的trans转换成pin级的信号按照AHB二级流水的时序要求发送到总线上。

1.2.2 满足AHB二级流水要求的main_phase代码:

 (1)首先,我们在main_phase中启动了两个forever的并发线程,其中一个负责通过95行方法:get_sim_trans,从seq中get到其产生的数据。并且将该数据放入到队列中等待处理。队列包含存放地址的队列sim_addr_q[$],以及存放控制信号或者写数据信号的其他队列。我们通过第92行的判断条件,保证了当队列中的待处理trans数目少于2个时候,就让seq产生数据,否则就无需让seq产生数据,这样可以避免在hready为低的时候,队列中未处理的数据数目越来越多,而占用大量内存空间。又可以保证在所有trans结束之前,sim_addr_q中都有至少一笔trans交给drive的do_drive函数去处理而不会出现丢数据或者数据断层的情况。

(2)其次,我们通过第二个线程第105行的任务do_drive,将trans中的每一笔信号按照时序要求发送到总线上。

1.2.3. do_drive任务代码:

首先我们来对照ahb的时序波形:

 我们需要将该波形抽象成具体的代码,为了满足二级流水特性,地址和控制信号的相位总是提前于数据相位,并且driver需要知道什么时候可以发送下一笔trans。为了简化设计,我们先只考虑写数据的情况,代码如下:

 采用三个并发线程:其中send_addr_control()负责发送地址和控制信号,send_wdata()负责发送写数据信号,而judge_trans_finished()负责判断当前的trans是否有完成。需要进行如下控制:

1)其中数据相位总是滞后于地址相位,所以我在send_addr_control中增加一个信号,当该线程执行完毕后,把信号拉高,而send_wdata则等待该信号为高时才执行,而拉高的信号只有到下个时钟上升沿才会被感知到,因此完成了时序上的数据相位滞后于地址相位一个时钟周期,在send_wdata执行完以后,再将该信号拉低。

2)三个线程的执行顺序通过握手信号实现。让judge_ready线程最先执行,然后让send_wdata()线程执行,最后让send_addr_control执行

3)这样写还有一个优点:就是三个线程前可以通过#延时来控制具体每个信号前的延时时间。

1.2.4.send_addr_control()/send_wdata()/judge_trans_finished()任务代码

1.地址和控制信号的发送任务:send_addr_control

这里我们采用了一个队列addr_q用于存放从sim_addr_q中get到的地址信号,并且让该队列中最大只能有两笔未完成的trans,当判断完成一笔trans时,则将该trans从addr_q中pop出来,对于地址信号,只要addr_q队列中未完成的信号少于两个,我们就可以一直发送信号到总线上,否则不能发送新trans而应该对未完成的旧trans再发送一次。当发送完地址信号后,将haddr_send_ready信号拉高,这样数据信号才被授权发送。

2.写数据信号的发送任务:send_wdata()

得到了addr发送完毕的授权:haddr_send_ready>0以后,我们将数据信号从sim_wdata_q队列中get到,然后push到总线上,并push进入wdata_q队列中。该队列仅允许有一笔未完成的trans,当该trans完成时,则将该trans从wdata_q中pop出来,当该trans未完成时,我们应该将未完成的trans继续push到总线上。完成一笔读数据信号发送后,将haddr_send_ready拉低。

3.判断一笔trans是否完成的任务(只考虑写操作):judge_trans_finished()

 该任务负责判断总线上该时钟沿是否有一笔trans完成,当hready为高且wdata_q中有一笔写数据时表示有一笔trans将会在这个时钟沿完成,将完成的trans从对应的队列中pop出去。

1.2.5.对照波形进行判断

 1.在t0时刻,judge线程判断没有trans完成,执行发送旧trans操作(x);data_send线程判断不发数据,执行发送旧trans操作(x);addr_send线程判断发数据A到总线上,并将数据push进入addr_q中,将addr_send_ready信号拉高;

2.在t1时刻,judge线程判断没有trans完成,执行空操作;data_send线程判断addr_send_ready信号为高,并且addr_q中没有待完成的trans,执行发送A写数据,并将addr_send_ready拉低,将Apush进入wdata_q中;addr_send线程执行发送B地址数据,将addr_send_ready拉高,addr_q中有两个trans:A和B;

3.在t2时刻,judge线程判断有trans完成,执行将addr_q和wdata_q的第一笔trans:A pop出来,完成该trans;datas_send线程判断执行,发送数据B;addr_send线程执行发动数据C

4.在t3时刻,judge线程发现hready为低,执行发送旧数据操作(C);data_send线程发现wdata_q中有一笔未完成的wdata信号,执行发送旧数据操作(B);addr_send线程发现addr_q中有2笔未完成的trans,执行空操作;

5.在t4时刻,judge线程判断有trans完成,完成该trans(B);data_send线程判断可以发数据,发送数据C;addr_send线程判断sim_addr_q为空,不执行空操作;

6.在t5时刻,judge线程判断有trans完成,完成该trans(C);由于addr_send_ready为低,data_send线程判断不能发数据;并且addr_send线程不发送地址。

1.3. 模拟slave行为具有发送hready信号功能的module的代码

当driver中有关写功能都验证完毕后,我们试着写一个slave的module,能够模拟slave产生hready_resp信号,并以此来验证driver写的是否有问题。

Module的代码如下:

1.4. seq_item的编写,代码如下:

 

 在seq_item中,我们将各个信号包在其中,并且产生一个静态变量count,在new函数中,每新产生一笔trans,count的值就会加1,用来对每一笔trans打印一个trans_id标签,之后我们让每个信号类型是rand型的,利用contraint函数为其随机化设置范围,我们在检验driver功能的时候,让地址信号为trans_id*10,写数据信号为trans_id*100。之后我们利用automatic_field给每个信号开启copy clone print等功能。

1.5. seq的编写,代码如下:

 在body函数中采用手动的方式启动这个seq,通过start_item和finish_item实现与driver的握手。并且在两者之间对seq_item进行randomize。Seq中有一个信号trans_num,是用来控制一共产生多少笔trans的,在tb中通过config db机制配置给seqr,在seq中通过config机制和m_sequencer句柄get到该控制参数。

1.6. 在testcase中启动seq,给seq配置控制参数具体值

 在main_phase阶段通过start函数在固定seqr上启动该seq,注意raise_objection和drop_objection

#1000ns是为了让最后的几笔trans能够完成。

 给seq配置trans_num的值为30,共产生30笔trans

1.7. 在tb_top中将tb与slave_module连接起来,并且产生时钟和复位信号

1.8. 写makefile跑仿真验证写信号时序正确性

Makefile如下,我们跑几个不同的seed

 成功跑通了30笔trans:

 打印的log文件如下:

1.8.1我们打开VCS看看波形

 一共30笔trans,trans完成的时间和log文件完全对应的上

我们将seed换成2,trans数目换成50再试试看:

 

 结果一目了然,完全符合预期。

至此,我们完成了driver的写操作的时序设计。接下来,我们将完善driver的读操作的设计。

2.完善driver的读写功能时序

这部分我们重点把driver中读写功能以及模拟DUT行为的slave module的读写功能全部完善,保证读写时不会出现任何的时序上的错误与丢数据情况,让driver的复用性提高

2.1driver的代码部分: 2.1.1 driver中的main_phase阶段代码如下:

在main_phase中启动了两个并发线程,第一个forever进程负责不断地get从sequence产生的数据,为了不让sequence中产生的数据在仿真的0ns就全部传入driver的内部Queue中,我们通过sim_addr_q.size()进行判断,一旦driver中该Queue中没有足够的数据可发,我们便让driver通过seq_item_port.get_next_item语句从seuqnce中拿到数据,如果drv中的queue中的trans足够用,那么我们在该时钟沿处则不get数据。

第二个线程driver则将get到的trans通过do_drive这个任务按照ahb的二级流水的时序要求发送到总线上,同时,我们将hselx信号也引入,因为hselx信号来源于decoder,我们按照组合逻辑的方式驱动该信号即可,不用在每个时钟的上升沿处。

 2.1.2 get_sim_trans的任务如下,也可以将该task写成方法

从seq中get到的seq_item将其拆解成信号级,并将每个信号放入一个固定的sim_*_q的queue内。

 2.1.3 do_drive任务代码如下:

在每一个时钟的上升沿,三个线程的执行顺序如下:judge线程 -> send_wdata线程 -> send_addr_control线程。

send_addr_control线程负责发送地址和控制信号;

Send_wdata线程负责发送写数据信号;

Judge线程负责判断该时刻是否有trans完成,并决定后续的trans是否应该发送;

 2.1.4 do_drive中的send_addr_control任务代码:

Ahb二级流水最多允许只有两笔未完成的trans,因此让addr_q.size



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3