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调一天