同步FIFO
一、原理介绍
FIFO(First in, First out),顾名思义是先入先出存储器,数据的写入顺序和读出顺序一致。
一条数据流中有两个模块A和B,B接收A处理好的数据。假如A处理10个数据的时间,B只能处理5个数据,那么就会丢失5个数据,FIFO的作用就是存储A处理好的数据,B每处理完一个数据,就从FIFO中取下一个处理。
1.1 同步FIFO原理
同步FIFO和异步FIFO相比,核心区别是读写使用同一个时钟。
核心功能是:作为一个数据缓冲区,允许数据以写入的顺序被读出,同时通过空满状态标志来防止数据读空或溢出。
1.2 同步FIFO的构成
- 双端口RAM:读写操作独立,可同时进行;
- 写指针:总是指向下一个写入数据的存储地址,如果已经写入了4个数据,则指向地址5;
- 读指针:总是指向下一个要被读取数据的存储地址,如果已经读取了8个数据,则指向地址9;
- 满标志:写入的数据加上未被读出的数据个数等于FIFO的深度,输出满信号,阻止上游模块继续写入数据;
- 空标志:FIFO中所有数据被读出后,输出空信号,阻止下游模块继续读出数据。
1.3 FIFO的精华:空满状态的判断
本文使用计数器的方法实现空满状态判断。
定义一个计数器,计数器的量程等于FIFO的深度,即假设FIFO深度为8,那么计数器的计数范围是0~8。
计数器自增自减有如下5种情况:
- 上下游模块同时读写FIFO,且FIFO非空非满:计数器不变;
- 上游模块写FIFO,且非满:计数器自增;
- 下游模块读FIFO,且非空:计数器自减;
- 上游模块写FIFO,但满:计数器不变;
- 下游模块读FIFO,但空:计数器不变。
空满状态判断:
- 当计数器等于FIFO深度,输出满信号;
- 当计数器等于0,输出空信号。
二、同步FIFO代码
2.1 代码
`timescale 1ns/1ns
module sfifo1 #(
parameter DATA_WIDTH = 8,
parameter FIFO_DEPTH = 8
)(
input wire clk_i ,
input wire rstn_i ,
input wire wr_en_i ,
input wire [DATA_WIDTH-1:0] wr_data_i ,
output wire fifo_full_o ,
input wire rd_en_i ,
output wire [DATA_WIDTH-1:0] rd_data_o ,
output wire fifo_empty_o
);
reg wr_able_d1;
reg rd_able_d1;
reg [DATA_WIDTH-1:0] wr_data_d1;
reg [$clog2(FIFO_DEPTH):0] fifo_cnt_d1;
wire wr_able = wr_en_i && !fifo_full_o;
wire rd_able = rd_en_i && !fifo_empty_o;
wire [$clog2(FIFO_DEPTH):0] fifo_cnt = (wr_able && rd_able) ? fifo_cnt_d1 :
(wr_able) ? fifo_cnt_d1 + 1'b1 :
(rd_able) ? fifo_cnt_d1 - 1'b1 : fifo_cnt_d1;
always @(posedge clk_i or negedge rstn_i) if(!rstn_i) wr_able_d1 <= 1'b0; else wr_able_d1 <= wr_able;
always @(posedge clk_i or negedge rstn_i) if(!rstn_i) rd_able_d1 <= 1'b0; else rd_able_d1 <= rd_able;
always @(posedge clk_i or negedge rstn_i) if(!rstn_i) wr_data_d1 <= 'd0 ; else wr_data_d1 <= wr_data_i;
always @(posedge clk_i or negedge rstn_i) if(!rstn_i) fifo_cnt_d1 <= 'd0 ; else fifo_cnt_d1 <= fifo_cnt;
//--------------------------------------------------------------------------------------------------
reg [DATA_WIDTH-1:0] mem [0:FIFO_DEPTH-1];
reg [DATA_WIDTH-1:0] rd_data;
reg [$clog2(FIFO_DEPTH)-1:0] wr_ptr_d2;
reg [$clog2(FIFO_DEPTH)-1:0] rd_ptr_d2;
wire [$clog2(FIFO_DEPTH)-1:0] wr_ptr = (wr_able_d1) ? wr_ptr_d2 + 1'b1 : wr_ptr_d2;
wire [$clog2(FIFO_DEPTH)-1:0] rd_ptr = (rd_able_d1) ? rd_ptr_d2 + 1'b1 : rd_ptr_d2;
always @(posedge clk_i) if(wr_able_d1) mem[wr_ptr_d2] <= wr_data_d1;
always @(posedge clk_i or negedge rstn_i) if(!rstn_i) rd_data <= 'd0; else if(rd_able_d1) rd_data <= mem[rd_ptr_d2];
always @(posedge clk_i or negedge rstn_i) if(!rstn_i) wr_ptr_d2 <= 'd0; else wr_ptr_d2 <= wr_ptr;
always @(posedge clk_i or negedge rstn_i) if(!rstn_i) rd_ptr_d2 <= 'd0; else rd_ptr_d2 <= rd_ptr;
//--------------------------------------------------------------------------------------------------
assign fifo_full_o = (fifo_cnt_d1 == FIFO_DEPTH);
assign fifo_empty_o = (fifo_cnt_d1 == 0);
assign rd_data_o = rd_data;
endmodule
2.2 仿真
`timescale 1ns/1ns
module sfifo_tb();
reg clk_i;
reg rstn_i;
reg wr_en_i;
reg [7:0] wr_data_i;
wire fifo_full_o;
reg rd_en_i;
wire [7:0] rd_data_o;
wire fifo_empty_o;
sfifo #(
.DATA_WIDTH(8),
.FIFO_DEPTH(8)
)my_sfifo(
.clk_i (clk_i),
.rstn_i (rstn_i),
.wr_en_i (wr_en_i),
.wr_data_i (wr_data_i),
.fifo_full_o (fifo_full_o),
.rd_en_i (rd_en_i),
.rd_data_o (rd_data_o),
.fifo_empty_o (fifo_empty_o)
);
always #10 clk_i = ~clk_i;
initial begin
clk_i = 0;
rstn_i = 1;
wr_en_i = 0;
wr_data_i = 0;
rd_en_i = 0;
#20 rstn_i = 0;
#20 rstn_i = 1;
@(negedge clk_i);
pop();
push(1);
pop();
@(posedge clk_i) $finish;
end
task push(input [7:0] data);
if(fifo_full_o) begin
wr_en_i = 1'b1;
wr_data_i = data;
$display("FULL! Can't push now!");
@(negedge clk_i) wr_en_i = 1'b0;
end else begin
wr_en_i = 1'b1;
wr_data_i = data;
$display("Push: %0d", data);
@(negedge clk_i) wr_en_i = 1'b0;
end
endtask
task pop();
if(fifo_empty_o) begin
$display("Empty! Can't pop now!");
end else begin
rd_en_i = 1'b1;
@(negedge clk_i)
rd_en_i = 1'b0;
$display("Pop: %0d", rd_data_o);
end
endtask
endmodule
2.6 部分仿真结果
写入8个数据,

读出8个数据,

同时读写,

三、UVM验证
3.1 UVM结构
uvm_test_top
(my_casen)
|
env
(sfifo_env)
|
----------------------------------------
| | |
agt mdl scb
(sfifo_agent) (sfifo_model) (sfifo_scoreboard)
|
----------------------------------
| | |
drv i/o_mon sqr
(sfifo_driver) (sfifo_monitor)(sfifo_sequencer)
3.2 Top
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;
`include "sfifo_if.sv"
`include "sfifo_transaction.sv"
`include "sfifo_sequencer.sv"
`include "sfifo_driver.sv"
`include "sfifo_out_monitor.sv"
`include "sfifo_in_monitor.sv"
`include "sfifo_agent.sv"
`include "sfifo_model.sv"
`include "sfifo_scoreboard.sv"
`include "sfifo_env.sv"
`include "base_test.sv"
`include "my_case0.sv"
module top;
reg clk_i;
reg rstn_i;
sfifo_if itf(clk_i, rstn_i);
sfifo #(
.DATA_WIDTH(8),
.FIFO_DEPTH(1024)
)my_sfifo(
.clk_i (clk_i),
.rstn_i (rstn_i),
.wr_en_i (itf.wr_en_i),
.wr_data_i (itf.wr_data_i),
.fifo_full_o (itf.fifo_full_o),
.rd_en_i (itf.rd_en_i),
.rd_data_o (itf.rd_data_o),
.fifo_empty_o (itf.fifo_empty_o)
);
initial begin
$fsdbDumpfile("test.fsdb");
$fsdbDumpvars(0, top);
end
initial begin
clk_i = 0;
rstn_i = 1;
#20 rstn_i = 0;
#20 rstn_i = 1;
end
always #10 clk_i = ~clk_i;
initial begin
uvm_config_db#(virtual sfifo_if)::set(null, "uvm_test_top.env.agt.drv", "itf", itf); //pass itf to "uvm_config_db*get()" which has the same third parameter;
uvm_config_db#(virtual sfifo_if)::set(null, "uvm_test_top.env.agt.o_mon", "itf", itf);
uvm_config_db#(virtual sfifo_if)::set(null, "uvm_test_top.env.agt.i_mon", "itf", itf);
end
initial begin
run_test("my_case0");
end
endmodule
3.2.1 factory机制
initial begin
run_test("sfifo_env");
end
使用factory机制后,run_test根据字符串自动创建一个sfifo_env的实例,并调用该类里面的所有函数、任务等。每个实例都有一个名字,而经过run_test创建的实例都叫uvm_test_top,可以通过这个实例名env追溯其下游的其它实例(比如uvm_test_top.my_monitor)。
3.3 Driver
Driver负责输出激励。
`ifndef SFIFO_DRIVER__SV
`define SFIFO_DRIVER__SV
class sfifo_driver extends uvm_driver #(sfifo_transaction);
`uvm_component_utils(sfifo_driver); //factory mechanism
virtual sfifo_if itf; //can't declare interface in class, so here use virtual interface
function new(string name = "sfifo_driver", uvm_component parent = null);
super.new(name, parent);
endfunction
extern virtual function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
extern virtual task push(input [7:0] data);
extern virtual task pop();
endclass
function void sfifo_driver::build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual sfifo_if)::get(this, "", "itf", itf))
`uvm_fatal("sfifo_driver", "you should pass itf");
endfunction
task sfifo_driver::main_phase(uvm_phase phase);
super.main_phase(phase);
itf.wr_en_i = 1'b0;
itf.wr_data_i = 8'd0;
itf.rd_en_i = 1'b0;
wait(itf.rstn_i == 0);
wait(itf.rstn_i == 1);
@(negedge itf.clk_i);
while(1) begin
seq_item_port.get_next_item(req);
if(req.wr_en_i) begin
push(req.rand_pixels);
end
if(req.rd_en_i) begin
pop();
end
seq_item_port.item_done();
end
endtask
task sfifo_driver::push(input [7:0] data);
if(itf.fifo_full_o) begin
itf.wr_en_i = 1'b1;
itf.wr_data_i = data;
//`uvm_info("sfifo_driver", "FULL! Can't push now!", UVM_LOW);
@(negedge itf.clk_i) itf.wr_en_i = 1'b0;
end else begin
itf.wr_en_i = 1'b1;
itf.wr_data_i = data;
`uvm_info("sfifo_driver", $sformatf("Push: %0d", data), UVM_LOW); //system function "$sformatf()" is used to pass a string with parameters
@(negedge itf.clk_i) itf.wr_en_i = 1'b0;
end
endtask
task sfifo_driver::pop();
if(itf.fifo_empty_o) begin
itf.rd_en_i = 1'b1;
@(negedge itf.clk_i);
itf.rd_en_i = 1'b0;
//`uvm_info("sfifo_driver", "Empty! Can't pop now", UVM_LOW);
end else begin
itf.rd_en_i = 1'b1;
@(negedge itf.clk_i)
itf.rd_en_i = 1'b0;
`uvm_info("sfifo_driver", $sformatf("Pop: %0d", itf.rd_data_o), UVM_LOW);
end
endtask
`endif
3.3.1 phase基础
- sfifo_driver继承uvm_driver的内容,最主要的是main_phase。
- phase分为function phase和task phase。function phase不消耗时间,每个时刻只有一个function phase执行;task phase消耗时间,所有task phase并行执行。
- 最常用的三个phase是build_phase、connect_phase和main_phase,只有main_phase是task phase。
- task phase的核心是reset、configure、main、shutdown四个phase。
- 每个class主要要执行的代码都写在main_phase里,应该是类似于主函数。
- build_phase是function phase,用于通过config_db的set和get操作来数据以及实例化成员变量等。
3.3.2 factory机制
`uvm_component_utils(sfifo_driver); //factory mechanism
将sfifo_driver注册到uvm内部的表中,这里的主要目的是Top中可以自动声明类(类由run_test中的字符串决定),并执行类中的task和function。
需要注意的是,使用run_test声明类后,Top中不能直接引用类中的变量和函数等(即top.my_driver.xxx),因为run_test声明了一个脱离top的层次结构。因此uvm引入了uvm_config_db机制,可以将想传递的数据通过set发送出去,然后类中通过get接收数据。
3.3.3 objection机制
task sfifo_driver::main_phase(uvm_phase phase);
phase.raise_objection(this); //if no use raise_objection, uvm think
//there is no work in the main_phase, so it will be killed.
/*
* program
*/
phase.drop_objection(this); //raise_objection and drop_objection must appear in pairs
endtask
最终objection机制不在driver中启停。
使用factory机制后,uvm就会通过objection机制控制phase的运行。如果phase中没有raise_objection,那么uvm就会认为这个phase没有任何工作需要做,执行完第一次消耗时间之前的代码就会kill掉这个phase。
因此要让phase正常运行,需要在第一个消耗时间的代码之前phase.raise_objection(this),然后在最后phase.drop_objection(this)。
raise和drop成对出现。
在加入sequence机制后,driver就只负责接收transaction,驱动dut,不负责别的工作,所以就不需要这部分了。
3.3.4 uvm_config_db机制
3.2.2中提到使用run_test后,Top中不能直接引用类中的变量和函数,需要使用uvm_config_db机制的set和get接收数据。
//top
initial begin
run_test("sfifo_driver");
end
initial begin
uvm_config_db#(virtual sfifo_if)::set(null, "uvm_test_top.drv", "itf", itf); //pass itf to "uvm_config_db*get()" which has the same third parameter;
end
//sfifo_driver
function void sfifo_driver::build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual sfifo_if)::get(this, "", "itf", itf))
`uvm_fatal("sfifo_driver", "you should pass itf");
endfunction
3.3.4.1 set
uvm_config_db#(virtual sfifo_if)::set(null, "uvm_test_top.drv", "itf", itf);
- #()中是要传递的数据的数据类型,这里是itf的数据类型virtual sfifo_if(见3.3);
- set的第一个参数是uvm_component实例的指针。如果用this,那么第二个参数可以为空;如果为null,会被自动替换为uvm_root::get(),即uvm_top;
- 前两个参数组合起来形成路径,将数据传到这个路径下的phase中,具体看哪个phase可以接收这个数据;
- 由于run_test中实例化sfifo_env,所以uvm_test_top.drv就是sfifo_driver,会将数据传给sfifo_driver;
- 找到路径后,还需要保证set和get的第三个参数一样,才能完成数据传输。
3.3.4.2 get
uvm_config_db#(virtual sfifo_if)::get(this, "", "itf", itf)
get的参数含义跟set大致一样。
第四个参数是将set的第四个参数赋值给get的第四个参数。
3.3.5 接收transaction
seq_item_port.get_next_item(req);
seq_item_port.item_done();
通过第一行代码将transaction赋给req;
通过第三行代码表示完成使用这个transaction驱动dut。
3.4 Interface
`ifndef SFIFO_IF__SV
`define SFIFO_IF__SV
interface sfifo_if(input clk_i, input rstn_i);
logic wr_en_i;
logic [7:0] wr_data_i;
logic fifo_full_o;
logic rd_en_i;
logic [7:0] rd_data_o;
logic fifo_empty_o;
endinterface
`endif
3.5 Transaction
`ifndef SFIFO_TRANSACTION__SV
`define SFIFO_TRANSACTION__SV
class sfifo_transaction extends uvm_sequence_item;
rand bit [7:0] rand_pixels;
bit rstn_i;
rand bit wr_en_i;
bit [7:0] wr_data_i;
bit fifo_full_o;
rand bit rd_en_i;
bit [7:0] rd_data_o;
bit fifo_empty_o;
`uvm_object_utils_begin(sfifo_transaction)
`uvm_field_int(rand_pixels, UVM_ALL_ON);
`uvm_field_int(rstn_i, UVM_ALL_ON);
`uvm_field_int(wr_en_i, UVM_ALL_ON);
`uvm_field_int(wr_data_i, UVM_ALL_ON);
`uvm_field_int(fifo_full_o, UVM_ALL_ON);
`uvm_field_int(rd_en_i, UVM_ALL_ON);
`uvm_field_int(rd_data_o, UVM_ALL_ON);
`uvm_field_int(fifo_empty_o, UVM_ALL_ON);
`uvm_object_utils_end
constraint pixels_count{
pixels.size == 640;
}
function new(string name = "sfifo_transaction");
super.new(name);
endfunction
endclass
`endif
uvm_object_utils和uvm_component_utils的区别:
- uvm_object_utils用于数据单元,而uvm_component_utils用于组件;
- 使用uvm_object_utils就没有层次结构的概念,因此new函数只有一个参数string name;
- uvm_object_utils有生命周期,生命周期结束后会被销毁;uvm_component_utils从仿真开始到仿真结束一直存在。
3.6 Env
env是uvm验证平台所有组件(driver、monitor、agent、scoreboard等)的顶层容器。
`ifndef SFIFO_ENV__SV
`define SFIFO_ENV__SV
class sfifo_env extends uvm_env;
`uvm_component_utils(sfifo_env);
uvm_tlm_analysis_fifo #(sfifo_transaction) agt_mdl_fifo;
uvm_tlm_analysis_fifo #(sfifo_transaction) agt_scb_fifo;
uvm_tlm_analysis_fifo #(sfifo_transaction) mdl_scb_fifo;
sfifo_agent agt;
sfifo_model mdl;
sfifo_scoreboard scb;
function new(string name = "sfifo_env", uvm_component parent = null);
super.new(name, parent);
endfunction
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
endclass
function void sfifo_env::build_phase(uvm_phase phase);
super.build_phase(phase);
agt = sfifo_agent::type_id::create("agt", this);
mdl = sfifo_model#(.DATA_WIDTH(8), .FIFO_DEPTH(1024))::type_id::create("mdl", this);
scb = sfifo_scoreboard::type_id::create("scb", this);
agt.is_active = UVM_ACTIVE;
agt_mdl_fifo = new("agt_mdl_fifo", this);
agt_scb_fifo = new("agt_scb_fifo", this);
mdl_scb_fifo = new("mdl_scb_fifo", this);
uvm_config_db#(uvm_object_wrapper)::set(this, "agt.sqr.main_phase", "default_sequence", sfifo_sequence::type_id::get());
endfunction
function void sfifo_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
agt.ap.connect(agt_mdl_fifo.analysis_export);
mdl.port.connect(agt_mdl_fifo.blocking_get_export);
agt.o_ap.connect(agt_scb_fifo.analysis_export);
scb.from_monitor.connect(agt_scb_fifo.blocking_get_export);
mdl.ap.connect(mdl_scb_fifo.analysis_export);
scb.from_model.connect(mdl_scb_fifo.blocking_get_export);
endfunction
`endif
build_phase的执行顺序是自顶向下。因此在每个组件的build_phase中定义自己的子节点,那么父组件的build_phase一定先于子组件的build_phase执行,UVM树就从根到枝叶按序建立,保证UVM树的建立是完整的。
建立子节点的方法是在build_phase中使用
type_name:: type_id::create("name",this);
- type_name要替换成子节点的类型;
- type_id是子节点类型的标识符,直接写type_id即可;
- name是子节点的名字;
- this代表子节点挂在该节点下。
agt = sfifo_agent::type_id::create("agt", this);
agt.is_active = UVM_ACTIVE;
agt中包含driver和monitor两种组件,这个agt可能有两种应用场景:
- 用在输入端,driver和monitor都要使用;
- 用在输出端,只有monitor使用;
所以为了控制是否使用driver,加入了变量is_active,根据is_active的值,在agt的build_phase中决定是否创建driver节点。
引入了uvm_tlm_analysis_fifo类型的fifo,用于连接agent和model。
uvm_config_db#(uvm_object_wrapper)::set(this, "agt.sqr.main_phase", "default_sequence", sfifo_sequence::type_id::get());
这行代码的功能就是通过default sequence的方式启动sequence。
set的前两个参数指明了sequence把transaction传给哪个sequencer的main_phase(这个main_phase不需要在sequencer中写出来),其他的两个参数以及#()中的参数都是固定的。
3.7 Monitor
`ifndef SFIFO_IN_MONITOR__SV
`define SFIFO_IN_MONITOR__SV
class sfifo_in_monitor extends uvm_monitor;
`uvm_component_utils(sfifo_in_monitor);
uvm_analysis_port #(sfifo_transaction) ap;
virtual sfifo_if itf;
extern function new (string name = "sfifo_in_monitor", uvm_component parent = null);
extern virtual function void build_phase(uvm_phase phase);
extern virtual task main_phase (uvm_phase phase);
endclass
function sfifo_in_monitor::new(string name = "sfifo_in_monitor", uvm_component parent = null);
super.new(name, parent);
endfunction
function void sfifo_in_monitor::build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual sfifo_if)::get(this, "", "itf", itf))
`uvm_fatal("sfifo_in_monitor", "error");
ap = new("ap", this);
endfunction
task sfifo_in_monitor::main_phase(uvm_phase phase);
sfifo_transaction tr;
super.main_phase(phase);
while(1) begin
@(posedge itf.clk_i);
tr = new("tr");
tr.rstn_i = itf.rstn_i;
tr.wr_en_i = itf.wr_en_i;
tr.wr_data_i = itf.wr_data_i;
tr.rd_en_i = itf.rd_en_i;
ap.write(tr);
end
endtask
`endif
`ifndef SFIFO_OUT_MONITOR__SV
`define SFIFO_OUT_MONITOR__SV
class sfifo_out_monitor extends uvm_monitor;
`uvm_component_utils(sfifo_out_monitor);
uvm_analysis_port#(sfifo_transaction) ap;
virtual sfifo_if itf;
function new(string name = "sfifo_out_monitor", uvm_component parent = null);
super.new(name, parent);
endfunction
extern virtual function void build_phase(uvm_phase phase);
extern virtual task main_phase (uvm_phase phase);
endclass
function void sfifo_out_monitor::build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual sfifo_if)::get(this, "", "itf", itf))
`uvm_fatal("sfifo_out_monitor", "monitor must pass itf");
ap = new("ap", this);
endfunction
task sfifo_out_monitor::main_phase(uvm_phase phase);
sfifo_transaction tr;
super.main_phase(phase);
while(1) begin
@(posedge itf.clk_i);
tr = new("tr");
tr.fifo_full_o = itf.fifo_full_o;
tr.rd_data_o = itf.rd_data_o;
tr.fifo_empty_o = itf.fifo_empty_o;
ap.write(tr);
end
endtask
`endif
在in_monitor中,每个时钟的上升沿广播出去一个sfifo_transaction类型的tr,广播到uvm_tlm_analysis_fifo的输入端。
3.7.1 是否raise_objection?
monitor中不应该使用raise_objection。
raise_objection的本质是控制仿真phase何时结束的。raise_objection告诉uvm还有组件在运行;drop_objection告诉uvm我运行完了。当所有组件都drop_objection,uvm就结束仿真。
一般来说,monitor需要时刻接收数据,永不停歇,所以永远执行不到drop_objection,因此可能导致仿真永远结束不了。
并且monitor是一个被动的组件,只接收DUT的输入输出端数据,所以不需要monitor决定仿真什么时候结束。可以把monitor想象成一只嗜睡的宠物,driver raise时,相当于人把它给一拳打醒;driver执行到pop时,它睁开眼睛接收信息;driver drop后,它再次陷入沉睡。
3.8 Agent
agent是把功能类似或者处理同一种协议的组件封装起来。
`ifndef SFIFO_AGENT__SV
`define SFIFO_AGENT__SV
class sfifo_agent extends uvm_agent;
`uvm_component_utils(sfifo_agent);
uvm_analysis_port#(sfifo_transaction) ap;
uvm_analysis_port#(sfifo_transaction) o_ap;
sfifo_driver drv;
sfifo_out_monitor o_mon;
sfifo_in_monitor i_mon;
sfifo_sequencer sqr;
function new(string name = "sfifo_agent", uvm_component parent = null);
super.new(name, parent);
endfunction
extern virtual function void build_phase (uvm_phase phase);
extern virtual function void connect_phase(uvm_phase phase);
endclass
function void sfifo_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
if(is_active == UVM_ACTIVE) begin
drv = sfifo_driver::type_id::create("drv", this);
sqr = sfifo_sequencer::type_id::create("sqr", this);
end
i_mon = sfifo_in_monitor::type_id::create("i_mon", this);
o_mon = sfifo_out_monitor::type_id::create("o_mon", this);
endfunction
function void sfifo_agent::connect_phase(uvm_phase phase);
super.connect_phase(phase);
ap = i_mon.ap;
o_ap = o_mon.ap;
if(is_active == UVM_ACTIVE) drv.seq_item_port.connect(sqr.seq_item_export);
endfunction
`endif
当is_active等于UVM_ACTIVE时,才建立drv节点。由于我这里只有一个agt,并且driver驱动输入端,所以is_active设为UVM_ACTIVE;i_mon监测输入端的输入信号,通过ap广播出去。
driver接收transaction端是drv.seq_item_port;sequencer发送transaction端是sqr.seq_item_export。
3.9 Model
在《UVM实战》这本书中,model部分完成的功能是复制monitor组件中的transaction到model中,并在在后续与DUT的结果比较(暂时没看到后面,所以这里是我猜的),所以其实这里的Model是没有意义的。但是由于用到了一些TLM的东西,所以我先把自己对于这部分的理解记录下来,再尝试写一个能用的model。
3.9.1 对于《UVM实战》的model的理解
首先要明确一点,这个model的transaction来自于monitor。
monitor中使用
uvm_analysis_port #(my_transaction) ap;
定义了一个非阻塞的广播port,当tr计算完毕就用ap.write(tr)将tr发送出去。值得注意的是,在agent中
uvm_analysis_port #(my_transaction) ap;
function void my_agent::connect_phase(uvm_phase phase);
super.connect_phase(phase);
ap = mon.ap;
endfunction
意味着agent的ap和monitor的ap直接相连。
model中使用
uvm_blocking_get_port #(my_transaction) port;
port.get(tr);
将monitor发送出来的tr通过阻塞式接收port得到。
那么tr的发送端和接收端都有了,代表着万事大吉了吗?并不是。
由于发送端是非阻塞的,接收端是阻塞的,所以当发送一个tr时,接收端可能因为当前的工作没做完而脱不开身,需要一个缓冲区存储暂时无法被接收的tr,因此在env中引入了
uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
这样一个fifo。使用该fifo还有一个好处是,接收端完成了工作,想取下一个tr,但是还没有新的tr被发送出来时,model可以挂起或者去做别的事情。
fifo应该有输入端和输出端,很容易想到,fifo的输入端连接agent的ap,输出端连接model的port,
function void my_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
i_agt.ap.connect(agt_mdl_fifo.analysis_export); //输入端
mdl.port.connect(agt_mdl_fifo.blocking_get_export); //输出端
endfunction
3.9.2 我的model
`ifndef SFIFO_MODEL__SV
`define SFIFO_MODEL__SV
class sfifo_model#(int DATA_WIDTH = 8, int FIFO_DEPTH = 1024) extends uvm_component;
`uvm_component_utils(sfifo_model);
uvm_blocking_get_port #(sfifo_transaction) port;
uvm_analysis_port #(sfifo_transaction) ap;
extern function new(string name = "sfifo_model", uvm_component parent = null);
extern function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
endclass
function sfifo_model::new(string name = "sfifo_model", uvm_component parent = null);
super.new(name, parent);
endfunction
function void sfifo_model::build_phase(uvm_phase phase);
super.build_phase(phase);
port = new("port", this);
ap = new("ap", this);
endfunction
task sfifo_model::main_phase(uvm_phase phase);
sfifo_transaction tr;
sfifo_transaction mdl_tr;
bit [DATA_WIDTH-1:0] mem [0:FIFO_DEPTH-1];
bit fifo_full;
bit fifo_empty;
bit [$clog2(FIFO_DEPTH)-1:0] wr_ptr;
bit [$clog2(FIFO_DEPTH)-1:0] rd_ptr;
bit [$clog2(FIFO_DEPTH):0] fifo_cnt;
bit [DATA_WIDTH-1:0] rd_data;
super.main_phase(phase);
while(1) begin
mdl_tr = new("mdl_tr");
port.get(tr);
if(!tr.rstn_i) begin
wr_ptr = 'd0;
rd_ptr = 'd0;
rd_data = 'd0;
fifo_cnt = 'd0;
fifo_full = 1'b0;
fifo_empty = 1'b1;
end else begin
if(!fifo_full && tr.wr_en_i) mem[wr_ptr] = tr.wr_data_i;
if(!fifo_empty && tr.rd_en_i) rd_data = mem[rd_ptr];
if(!fifo_full && tr.wr_en_i) wr_ptr++;
if(!fifo_empty && tr.rd_en_i) rd_ptr++;
if ((!fifo_full && tr.wr_en_i) && (!fifo_empty && tr.rd_en_i)) fifo_cnt = fifo_cnt;
else if( !fifo_full && tr.wr_en_i) fifo_cnt++;
else if( !fifo_empty && tr.rd_en_i) fifo_cnt--;
else fifo_cnt = fifo_cnt;
if(fifo_cnt == 0) fifo_empty = 1;
else fifo_empty = 0;
if(fifo_cnt == FIFO_DEPTH) fifo_full = 1;
else fifo_full = 0;
end
mdl_tr.rstn_i = tr.rstn_i;
mdl_tr.wr_en_i = tr.wr_en_i;
mdl_tr.wr_data_i = tr.wr_data_i;
mdl_tr.fifo_full_o = fifo_full;
mdl_tr.rd_en_i = tr.rd_en_i;
mdl_tr.rd_data_o = rd_data;
mdl_tr.fifo_empty_o = fifo_empty;
ap.write(mdl_tr);
end
endtask
`endif
uvm_blocking_get_port类型的port通过get把fifo里的transaction赋给tr。
3.10 Scoreboard
`ifndef SFIFO_SCOREBOARD__SV
`define SFIFO_SCOREBOARD__SV
class sfifo_scoreboard extends uvm_scoreboard;
`uvm_component_utils(sfifo_scoreboard);
uvm_blocking_get_port #(sfifo_transaction) from_model;
uvm_blocking_get_port #(sfifo_transaction) from_monitor;
extern function new(string name = "sfifo_scoreboard", uvm_component parent = null);
extern virtual function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
endclass
function sfifo_scoreboard::new(string name = "sfifo_scoreboard", uvm_component parent = null);
super.new(name, parent);
endfunction
function void sfifo_scoreboard::build_phase(uvm_phase phase);
super.build_phase(phase);
from_monitor = new("from_monitor", this);
from_model = new("from_model", this);
endfunction
task sfifo_scoreboard::main_phase(uvm_phase phase);
sfifo_transaction mdl_tr;
sfifo_transaction mnt_tr;
bit [7:0] mdl_q [$];
bit [7:0] mnt_q [$];
int i = 1;
bit [7:0] mdl_tmp;
bit [7:0] mnt_tmp;
super.main_phase(phase);
while(1) begin
mdl_tr = new("mdl_tr");
mnt_tr = new("mnt_tr");
from_model.get(mdl_tr);
from_monitor.get(mnt_tr);
mdl_q.push_back(mdl_tr.rd_data_o);
mnt_q.push_back(mnt_tr.rd_data_o);
if(i) begin
mnt_tmp = mnt_q.pop_front();
i--;
end
if(mnt_q.size() > 0) begin
mnt_tmp = mnt_q.pop_front();
mdl_tmp = mdl_q.pop_front();
if(mnt_tmp == mdl_tmp) begin
`uvm_info("sfifo_scoreboard", "Success!", UVM_LOW);
end else begin
`uvm_info("sfifo_scoreboard", "Fail!", UVM_LOW);
end
end
end
endtask
`endif
3.11 Sequencer
`ifndef SFIFO_SEQUENCER__SV
`define SFIFO_SEQUENCER__SV
class sfifo_sequencer extends uvm_sequencer #(sfifo_transaction);
`uvm_component_utils(sfifo_sequencer);
extern function new(string name = "sfifo_sequencer", uvm_component parent = null);
endclass
function sfifo_sequencer::new(string name = "sfifo_sequencer", uvm_component parent = null);
super.new(name, parent);
endfunction
`endif
sequencer是一把枪,sequence是一个弹夹,transaction是子弹。
sequencer属于sequence和driver的中间者,因此sequencer算是通道,有一个仲裁机制:
- sequence想发送一个transaction,driver想接收一个transaction,sequencer就批准双方接收;
- sequence想发送一个transaction,但driver不想接收,sequencer就让sequence等;
- sequence不想发送transaction,但driver想接收,sequencer就让driver等。
sequencer的通道不需要写什么代码,只需要在agent中将sequencer的输出端和driver的输入端连起来,见3.8;在env中将sequence和sequencer连起来,见3.6。
3.12 Case and Sequence
`ifndef MY_CASE0__SV
`define MY_CASE0__SV
class case0_sequence extends uvm_sequence #(sfifo_transaction);
`uvm_object_utils(case0_sequence);
sfifo_transaction tr;
`uvm_declare_p_sequencer(sfifo_sequencer);
extern function new(string name = "case0_sequence");
extern virtual task body();
endclass
function case0_sequence::new(string name = "case0_sequence");
super.new(name);
set_automatic_phase_objection(1);
endfunction
task case0_sequence::body();
repeat(10) begin
`uvm_do_with(tr, {tr.wr_en_i == 1;tr.rd_en_i == 0;});
end
repeat(10) begin
`uvm_do_with(tr, {tr.wr_en_i == 0;tr.rd_en_i == 1;});
end
#100;
endtask
class my_case0 extends base_test;
`uvm_component_utils(my_case0);
extern function new(string name = "my_case0", uvm_component parent = null);
extern virtual function void build_phase(uvm_phase phase);
endclass
function my_case0::new(string name = "my_case0", uvm_component parent = null);
super.new(name, parent);
endfunction
function void my_case0::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(uvm_object_wrapper)::set(this, "env.agt.sqr.main_phase", "default_sequence", case0_sequence::type_id::get());
endfunction
`endif
正如3.11中提到sequence是弹夹,所以当子弹transaction打光时,sequence的寿命就结束了,所以sequence应当是`uvm_object_utils。
sequence的主要作用就是把transaction发送给sequencer,在body中完成。并且在sequence中需要对transaction做一些处理:
- new一次transaction的实例;
- 对transaction里的随机变量做随机化。
这两件事都在宏`uvm_do(tr)中执行。
注意:这个body中要重复发10次不同的tr,难道这10个tr是连续不断的发送吗?并不是。每发送完一个tr,uvm_do会停下来,等到driver执行了item_done()后,再再次发送tr或者结束。
在使用了default_sequence后就去掉了原本env的main_phase中的raise/drop_objection,而这东西是必要的,现在把它们移到sequence中。《UVM实战》中提到starting_phase的使用功能变化,去问AI后推荐我用自动phase objection机制,即new中的
set_automatic_phase_objection(1);
作用就是自动在sequence开始时raise,结束时drop。
为了使自动phase objection机制生效,必须声明p_sequencer,
`uvm_declare_p_sequencer(sfifo_sequencer);
3.13 Base_test
`ifndef BASE_TEST__SV
`define BASE_TEST__SV
class base_test extends uvm_test;
`uvm_component_utils(base_test);
sfifo_env env;
extern function new(string name = "base_test", uvm_component parent = null);
extern virtual function void build_phase(uvm_phase phase);
extern virtual function void report_phase(uvm_phase phase);
endclass
function base_test::new(string name = "base_test", uvm_component parent = null);
super.new(name, parent);
endfunction
function void base_test::build_phase(uvm_phase phase);
super.build_phase(phase);
env = sfifo_env::type_id::create("env", this);
endfunction
function void base_test::report_phase(uvm_phase phase);
uvm_report_server server;
int err_num;
super.report_phase(phase);
server = get_report_server();
err_num = server.get_severity_count(UVM_ERROR);
if(!err_num) begin
`uvm_info("base_test", "PASSED", UVM_LOW);
end else begin
`uvm_info("base_test", "FAILED", UVM_LOW);
end
endfunction
`endif
其实我觉得base_test的功能完全可以放在case里面,但是AI说如果是大项目,这样的可重用性太低了。base_test和case可以放在一起理解,case扩展于base_test,他们的主要目的是作为uvm环境的顶层,把sequence和sequencer连起来,当然还要生出env子节点。
base_test负责完成所有case的公共操作,比如生出env子节点,以及报告uvm是否运行成功。
case负责完成自己的独有工作,比如指定自己的sequence与哪一个sequencer连接起来;并且在case文件中顺便把case_sequence定义了,每个case都有不同的sequence,负责告诉driver执行不同的功能,最重要的是要在sequence中执行自动phase objection机制。
四、逻辑综合
set search_path [list /opt/synopsys/syn_2022.03/T-2022.03-SP2/libraries/syn]
set target_library {lsi_10k.db}
set link_library {* lsi_10k.db}
set symbol_library {lsi_10k.sdb}
read_verilog ~/Desktop/user/learning/SFIFO/sfifo1.v
current_design sfifo1; #Specify the subsequent logic synthesis commands targeting the sfifo
link; #Connect all the instances in the design
uniquify; #Create unique copies for the same module that is instantiated multiple times in the design
set_max_area 0
create_clock -period 20 [get_ports clk_i]
set_dont_touch_network [get_clocks clk_i]
set_input_delay -max 6 -clock clk_i [remove_from_collection [all_inputs] [get_ports clk_i]]
set_output_delay -max 6 -clock clk_i [all_outputs]
set_load [load_of lsi_10k/AN2/A] [all_outputs]
set_driving_cell -lib_cell AN2 [all_inputs]
set_operating_conditions -max WCCOM -min BCCOM
set_wire_load_model -name "20x20"
set_max_fanout 4 [current_design]
compile_ultra
report_timing -max_paths 10 > ~/Desktop/user/learning/SFIFO/reports/timing.rpt
report_area > ~/Desktop/user/learning/SFIFO/reports/area.rpt
report_power > ~/Desktop/user/learning/SFIFO/reports/power.rpt
report_constraint -all_violators > ~/Desktop/user/learning/SFIFO/reports/constraint.rpt
report_cell > ~/Desktop/user/learning/SFIFO/reports/cell.rpt
exit
最差路径:
****************************************
Report : timing
-path full
-delay max
-max_paths 10
Design : sfifo1
Version: T-2022.03-SP2
Date : Thu Oct 30 16:40:11 2025
****************************************
Operating Conditions: WCCOM Library: lsi_10k
Wire Load Model Mode: top
Startpoint: fifo_cnt_d1_reg[1]
(rising edge-triggered flip-flop clocked by clk_i)
Endpoint: fifo_cnt_d1_reg[2]
(rising edge-triggered flip-flop clocked by clk_i)
Path Group: clk_i
Path Type: max
Des/Clust/Port Wire Load Model Library
------------------------------------------------
sfifo1 20x20 lsi_10k
Point Incr Path
-----------------------------------------------------------
clock clk_i (rise edge) 0.00 0.00
clock network delay (ideal) 0.00 0.00
fifo_cnt_d1_reg[1]/CP (FD2) 0.00 0.00 r
fifo_cnt_d1_reg[1]/Q (FD2) 4.26 4.26 r
U345/Z (NR2P) 0.51 4.76 f
U346/Z (ND2) 2.32 7.08 r
U350/Z (NR2P) 0.57 7.65 f
U351/Z (IVP) 1.02 8.67 r
U352/Z (ND2P) 0.89 9.56 f
U354/Z (NR2P) 2.10 11.65 r
U283/Y (IVDA) 0.95 12.60 f
U366/Z (NR2) 2.67 15.27 r
U367/Z (AO2) 1.16 16.43 f
U267/Z (AO2) 2.43 18.87 r
fifo_cnt_d1_reg[2]/D (FD2) 0.00 18.87 r
data arrival time 18.87
clock clk_i (rise edge) 20.00 20.00
clock network delay (ideal) 0.00 20.00
fifo_cnt_d1_reg[2]/CP (FD2) 0.00 20.00 r
library setup time -0.85 19.15
data required time 19.15
-----------------------------------------------------------
data required time 19.15
data arrival time -18.87
-----------------------------------------------------------
slack (MET) 0.28
声明
本文的第一节和第二节参考自<掰开揉碎讲 FIFO(同步FIFO和异步FIFO) - Doreen的FPGA自留地 - 博客园>
https://www.cnblogs.com/DoreenLiu/p/17348480.html
浙公网安备 33010602011771号