@[T
dirver的实现
关于UVM验证的项目与资料有点少,由于刚入门,想找一个简单的项目来做。决定从零开始搭建一个异步fifo验证环境,参考了很多大佬的资料(发现对着大佬的代码写下来环境跑不起来),用绿皮书+UVM实战两本书决定自己一步一步做一个吧。会按照自己做的顺序来更新组件,水平有限,有问题的地方还请指出,感谢。
1.异步fifo的设计:
异步FIFO的设计代码有很多,在这里不做赘述,直接将DUT+TB的代码贴出:数据16位,fifo存储深度4位:
DRAM模块:
-
`timescale
1ns
/
1ns
-
module DPRAM #
-
(
-
parameter WIDTH
=
16,
-
parameter DEPTH
=
16,
-
parameter ADDR
=
4
-
)
-
(
-
input wrclk,
-
input rdclk,
-
input rd_rst_n,
-
input wr_en,
-
input rd_en,
-
input [WIDTH
-1:
0]wr_data,
-
input [ADDR
-1:
0]wr_addr,
-
input [ADDR
-1:
0]rd_addr,
-
output reg [WIDTH
-1:
0]rd_data
-
);
-
-
-
reg[WIDTH
-1:
0]DPRAM[DEPTH
-1:
0];
-
-
always@(posedge wrclk)
begin
-
if(wr_en)
-
DPRAM[wr_addr]
<= wr_data;
-
end
-
-
always@(posedge rdclk
or negedge rd_rst_n)
begin
-
if(
!rd_rst_n)
-
rd_data
<=
'b0;
-
else if(rd_en)
-
rd_data <= DPRAM[rd_addr];
-
end
-
endmodule
-
-
FIFO模块:
-
`timescale 1ns/1ns
-
`include "DPRAM.v"
-
module ASFIFO #
-
(
-
parameter WIDTH = 16, //数据总线宽度
-
parameter PTR = 4 //fifo存储深度
-
)
-
(
-
input wrclk,
-
input rdclk,
-
input wr
_rst_n,
-
input rd
_rst_n,
-
input wr
_en,
-
input rd_en,
-
input [WIDTH-1:0]wr
_data,
-
output [WIDTH-1:0]rd_data,
-
output reg wr
_full,
-
output reg rd_empty
-
);
-
-
//
****
****
***写时钟信号定义***
****
****
****//
-
reg[PTR:0] wr
_bin;
-
reg[PTR:0] wr_gray;
-
reg[PTR:0] rd
_gray_ff1;
-
reg[PTR:0] rd
_gray_ff2;
-
reg[PTR:0] rd
_bin_wr;
-
-
//
****
****
***读时钟信号定义***
****
****
****//
-
reg[PTR:0] rd
_bin;
-
reg[PTR:0] rd_gray;
-
reg[PTR:0] wr
_gray_ff1;
-
reg[PTR:0] wr
_gray_ff2;
-
reg[PTR:0] wr
_bin_rd;
-
-
integer i,j;
-
-
//
****
****
**DPRAM控制信号**
****
****
**//
-
wire dpram_wr_en;
-
wire [PTR-1:0] dpram_wr_addr;
-
wire [WIDTH-1:0] dpram_wr_data;
-
wire dpram_rd_en;
-
wire [PTR-1:0] dpram_rd_addr;
-
wire [WIDTH-1:0] dpram_rd_data;
-
-
//**
****
****
****
****
***写时钟域***
****
****
****
****//
-
//二进制写地址递增
-
always@(posedge wrclk or posedge wr
_rst_n)begin
-
if(!wr
_rst_n)begin
-
wr_bin <= 'b0;
-
end
-
else if(wr_en == 1'b1 && wr_full == 1'b0)begin
-
wr_bin <= wr_bin + 1'b1;
-
end
-
else begin
-
wr_bin <= wr_bin;
-
end
-
end
-
//*********二进制转换格雷码***********//
-
//********写地址**********************//
-
-
always@(posedge wrclk or posedge wr
_rst_n)begin
-
if(!wr
_rst_n)begin
-
wr_gray <= 'b0;
-
end
-
else begin
-
wr_gray <= {wr_bin[PTR],wr_bin[PTR:1]^wr_bin[PTR-1:0]};
-
end
-
end
-
-
//
****
****
***r2w***
****
****
****
****
*//
-
//*************使用多个中间值打两拍gray>ff1>ff2最终两拍后取ff2
****
****
****
***//
-
always@(posedge
-
or posedge wr_rst_n)begin
-
if(!wr_rst_n)begin
-
rd_gray_ff1 <= 'b0;
-
rd_gray_ff2 <= 'b0;
-
end
-
else begin
-
rd_gray_ff1 <= rd_gray;
-
rd_gray_ff2 <= rd_gray_ff1;
-
end
-
end
-
//***
****
***rd_addr_bin 2 wr_addr_bin***
****
****
****
*//
-
always@(*)begin
-
rd
_bin_wr[PTR] = rd
_gray_ff2[PTR];
-
for(i=PTR-1;i>=0;i=i-1)
-
rd_bin_wr[i] = rd_bin_wr[i+1]^rd_gray_ff2[i];
-
end
-
-
//
****
****
**写满**
****
****
****
***//
-
-
always@(*)begin
-
if(wr_bin[PTR]!=rd_bin_wr[PTR]&&(wr_bin[PTR-1:0]==rd_bin_wr[PTR-1:0]))
-
wr_full = 1'b1;
-
else
-
wr_full = 1'b0;
-
end
-
-
//**
****
****
**读时钟域**
****
****
****
****
**//
-
always@(posedge rdclk or posedge rd_rst_n)begin
-
if(!rd_rst_n)begin
-
rd_bin <= 'b0;
-
end
-
else if(rd_en == 1'b1 && rd
_empty == 1'b0)begin
-
rd_bin <= rd
_bin + 1'b1;
-
end
-
else begin
-
rd_bin <= rd
_bin;
-
end
-
end
-
//***********读地址**************//
-
-
always@(posedge rdclk or posedge rd_rst
_n)begin
-
if(!rd_rst
_n)begin
-
rd_gray <= 'b0;
-
end
-
else begin
-
rd_gray <= {rd_bin[PTR],rd_bin[PTR:1]^rd_bin[PTR-1:0]};
-
end
-
end
-
-
always@(posedge rdclk or posedge rd_rst_n)begin
-
if(!rd_rst_n)begin
-
wr_gray_ff1 <= 'b0;
-
wr_gray_ff2 <= 'b0;
-
end
-
else begin
-
wr_gray_ff1 <= wr_gray;
-
wr_gray
_ff2 <= wr_gray
_ff1;
-
end
-
end
-
-
always@(*)begin
-
wr_bin
_rd[PTR] = wr_gray
_ff2[PTR];
-
for(j=PTR-1;j>=0;j=j-1)
-
wr_bin
_rd[j] = wr_bin
_rd[j+1]^wr_gray
_ff2[j];
-
end
-
-
always@(*)begin
-
if(rd_bin == wr
_bin_rd)
-
rd_empty = 1'b1;
-
else
-
rd_empty = 1'b0;
-
end
-
-
DPRAM
-
#(.WIDTH(16),.DEPTH(16),.ADDR(4))
-
U_DPRAM
-
(
-
.wrclk(wrclk),
-
.rdclk(rdclk),
-
.rd_rst
_n(rd_rst
_n),
-
.wr_en(dpram
_wr_en),
-
.rd_en(dpram_rd_en),
-
.wr_data(dpram
_wr_data),
-
.rd_data(dpram_rd_data),
-
.wr_addr(dpram
_wr_addr),
-
.rd_addr(dpram_rd_addr)
-
);
-
-
assign dpram_wr
_en = (wr_en == 1'b1 && wr
_full == 1'b0)? 1'b1:1'b0;
-
assign dpram_rd
_en = (rd_en == 1'b1 && rd
_empty == 1'b0)? 1'b1:1'b0;
-
-
assign dpram_wr
_data = wr_data;
-
assign rd_data = dpram_rd_data;
-
-
assign dpram_wr
_addr = wr_bin[PTR-1:0];
-
assign dpram_rd_addr = rd_bin[PTR-1:0];
-
endmodule
TB:
-
`timescale
1ns/
1ns
-
-
`include
"FIFO.v"
-
-
module ASFIFO_tb;
-
parameter WIDTH =
16;
-
parameter PTR =
4 ;
-
-
reg wrclk;
-
reg wr_rst_n;
-
reg[WIDTH-
1:
0] wr_data;
-
reg wr_en;
-
wire wr_full;
-
-
reg rdclk;
-
reg rd_rst_n;
-
wire[WIDTH-
1:
0] rd_data;
-
reg rd_en;
-
wire rd_empty;
-
-
reg init_done;
-
reg[
3:
0] cnt;
-
initial begin
-
wr_rst_n =
1;
-
rd_rst_n =
1;
-
wrclk =
0;
-
rdclk =
0;
-
wr_en =
0;
-
rd_en =
0;
-
wr_data =
'b0;
-
init_done=
0;
-
-
#
30 wr_rst_n =
0;
-
rd_rst_n =
0;
-
#
30 wr_rst_n =
1;
-
rd_rst_n =
1;
-
-
#
30 init_done =
1;
-
end
-
-
always
-
#
2 wrclk = ~wrclk;
-
-
always
-
#
4 rdclk = ~rdclk;
-
-
always@(*)begin
-
if(init_done)begin
-
if(wr_full) wr_en =
0;
-
else wr_en =
1;
-
end
-
end
-
-
always@(*)begin
-
if(init_done)begin
-
if(rd_empty) rd_en =
0;
-
else rd_en =
1;
-
end
-
end
-
-
always@(posedge wrclk)begin
-
if(init_done)begin
-
if(wr_full ==
1
'b0)begin
-
wr_data <= wr_data +
1;
-
end
-
else begin
-
wr_data <= wr_data;
-
end
-
end
-
else
-
wr_data <=
'b0;
-
end
-
-
ASFIFO
-
#(.WIDTH(
16),.PTR(
4))
-
ASFIFO
-
(
-
.wrclk(wrclk),
-
.rdclk(rdclk),
-
.rd_rst_n(rd_rst_n),
-
.wr_rst_n(wr_rst_n),
-
.wr_en(wr_en),
-
.rd_en(rd_en),
-
.wr_data(wr_data),
-
.rd_data(rd_data),
-
.wr_full(wr_full),
-
.rd_empty(rd_empty)
-
);
-
endmodule
2.搭建UVM验证环境
(1)top.tb的搭建:
这里的写法其实类似于tb,主要是我们将发送数据更改到了driver中,多个initial块并行执行,执行到$finish后跳出,这里我们将dirver例化,然后调用main_phase,执行发送随机data的task
-
`timescale
1ns
/
1ns
-
`include "uvm_macros.svh"
-
-
import uvm_pkg::
*;
-
`include "fifo_driver.sv"
-
-
module top_tb;
-
parameter WIDTH
=
16;
-
parameter PTR
=
4 ;
-
reg wrclk;
-
reg wr_rst_n;
-
reg[WIDTH
-1:
0] wr_data;
-
reg wr_en;
-
wire wr_full;
-
-
reg rdclk;
-
reg rd_rst_n;
-
wire[WIDTH
-1:
0] rd_data;
-
reg rd_en;
-
wire rd_empty;
-
-
reg init_done;
-
-
ASFIFO
-
#(.WIDTH(
16),.PTR(
4))
-
ASFIFO
-
(
-
.wrclk(wrclk),
-
.rdclk(rdclk),
-
.rd_rst_n(rd_rst_n),
-
.wr_rst_n(wr_rst_n),
-
.wr_en(wr_en),
-
.rd_en(rd_en),
-
.wr_data(wr_data),
-
.rd_data(rd_data),
-
.wr_full(wr_full),
-
.rd_empty(rd_empty)
-
);
-
-
initial
begin
-
wrclk
=
0;
-
forever
begin
-
#
2 wrclk
=
~wrclk;
-
end
-
end
-
-
initial
begin
-
rdclk
=
0;
-
forever
begin
-
#
4 rdclk
=
~rdclk;
-
end
-
end
-
-
initial
begin
-
wr_rst_n
=
1;
-
rd_rst_n
=
1;
-
wr_en
=
0;
-
rd_en
=
0;
-
wr_data
=
'b0;
-
init_done= 0;
-
-
#30 wr_rst_n = 0;
-
rd_rst_n = 0;
-
#30 wr_rst_n = 1;
-
rd_rst_n = 1;
-
-
#30 init_done = 1;
-
end
-
always@(*)begin
-
if(init_done)begin
-
if(wr_full) wr_en = 0;
-
else wr_en = 1;
-
end
-
end
-
-
always@(*)begin
-
if(init_done)begin
-
if(rd_empty) rd_en = 0;
-
else rd_en = 1;
-
end
-
end
-
initial begin
-
fifo_driver drv;
-
drv = new("drv",null);
-
drv.main_phase(null);
-
$finish();
-
end
-
endmodule
-
(2)fifo_driver的搭建
首先定义一个driver,比较简单,只需要一个new函数与main_phase(run_phase 中的一个phase)
这里考虑到要让线程同步进行,选用fork join可以解决这个问题,之前调试了半天,有一个bug就是init_done拉高前,wr_en拉高已经开始有wrdata了,但是rddata读取数据是从init_done拉高后一个cycle开始读的,用了fork join后完美解决。用forever 会导致这个sim无法finish 一直卡在第一个线程中。使用for循环来控制我们要发送多少个随机数据,然后把每一个发送的数据与发送时间打印出来,如果fifo已经满了 显示full,等全部发送完后,打印finished。逻辑比较简单,其实跟tb的区别也就是可以发送随机的数据。
-
`ifndef
MY_DRIVER__SV
-
`define
MY_DRIVER__SV
-
-
import uvm_pkg::*;
-
`include
"uvm_macros.svh"
-
-
class fifo_driver extends uvm_driver;
-
function
new(string name =
"fifo_driver",uvm_component parent =
null);
-
super.
new(name,parent);
-
endfunction
-
extern virtual task main_phase(uvm_phase phase);
-
endclass
-
-
task fifo_driver::main_phase(uvm_phase phase);
-
fork
-
-
//forever begin
-
@(top_tb.init_done ==
0)begin
-
top_tb.wr_data <= 'b0;
-
end
-
-
-
@(top_tb.init_done ==
1) begin
-
//@(posedge top_tb.wrclk);
-
for(int i =
0;i <
32;i++) begin
-
@(posedge top_tb.wrclk);
-
if(top_tb.wr_full ==
0)begin
-
top_tb.wr_data <= $urandom_range(
0,
255);
-
`uvm_info(
"fifo_driver",$sformatf(
"%0d is driverd at %0t",top_tb.wr_data,$time),
UVM_LOW)
-
end
-
else begin
-
top_tb.wr_data <= top_tb.wr_data;
-
`uvm_info(
"fifo_driver",
"fifo is full",
UVM_LOW)
-
end
-
end
-
end
-
-
join
-
`uvm_info(
"fifo_driver",
"drive is finished",
UVM_LOW)
-
endtask
-
`endif
最后给各位附上波形图:
可见在90ns init_done拉高的下一个wrclk 开始写入随机数据
然后就先写这么多,后面会根据自己做的进度来慢慢更新的。实在是太菜了,调个driver调一天
OC](这里写自定义目录标题)欢迎使用Markdown编辑器
你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
新的改变
我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:
- 全新的界面设计 ,将会带来全新的写作体验;
- 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
- 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
- 全新的 KaTeX数学公式 语法;
- 增加了支持甘特图的mermaid语法1 功能;
- 增加了 多屏幕编辑 Markdown文章功能;
- 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
- 增加了 检查列表 功能。
功能快捷键
撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G
合理的创建标题,有助于目录的生成
直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC
语法后生成一个完美的目录。
如何改变文本的样式
强调文本 强调文本
加粗文本 加粗文本
标记文本
删除文本
引用文本
H2O is是液体。
210 运算结果是 1024.
插入链接与图片
链接: link.
图片:
带尺寸的图片:
居中的图片:
居中并且带尺寸的图片:
当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。
如何插入一段漂亮的代码片
去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片
.
// An highlighted block
var foo = 'bar';
生成一个适合你的列表
- 项目
- 项目
- 项目
- 项目
- 项目1
- 项目2
- 项目3
- 计划任务
- 完成任务
创建一个表格
一个简单的表格是这么创建的:
项目 | Value |
---|---|
电脑 | $1600 |
手机 | $12 |
导管 | $1 |
设定内容居中、居左、居右
使用:---------:
居中
使用:----------
居左
使用----------:
居右
第一列 | 第二列 | 第三列 |
---|---|---|
第一列文本居中 | 第二列文本居右 | 第三列文本居左 |
SmartyPants
SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:
TYPE | ASCII | HTML |
---|---|---|
Single backticks | 'Isn't this fun?' | ‘Isn’t this fun?’ |
Quotes | "Isn't this fun?" | “Isn’t this fun?” |
Dashes | -- is en-dash, --- is em-dash | – is en-dash, — is em-dash |
创建一个自定义列表
-
Markdown
- Text-to- HTML conversion tool Authors
- John
- Luke
如何创建一个注脚
一个具有注脚的文本。2
注释也是必不可少的
Markdown将文本转换为 HTML。
KaTeX数学公式
您可以使用渲染LaTeX数学表达式 KaTeX:
Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n−1)!∀n∈N 是通过欧拉积分
Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=∫0∞tz−1e−tdt.
你可以找到更多关于的信息 LaTeX 数学表达式here.
新的甘特图功能,丰富你的文章
- 关于 甘特图 语法,参考 这儿,
UML 图表
可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:
这将产生一个流程图。:
- 关于 Mermaid 语法,参考 这儿,
FLowchart流程图
我们依旧会支持flowchart的流程图:
- 关于 Flowchart流程图 语法,参考 这儿.
导出与导入
导出
如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。
导入
如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。
注脚的解释 ↩︎