同步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的构成

  1. 双端口RAM:读写操作独立,可同时进行;
  2. 写指针:总是指向下一个写入数据的存储地址,如果已经写入了4个数据,则指向地址5;
  3. 读指针:总是指向下一个要被读取数据的存储地址,如果已经读取了8个数据,则指向地址9;
  4. 满标志:写入的数据加上未被读出的数据个数等于FIFO的深度,输出满信号,阻止上游模块继续写入数据;
  5. 空标志:FIFO中所有数据被读出后,输出空信号,阻止下游模块继续读出数据。

1.3 FIFO的精华:空满状态的判断

本文使用计数器的方法实现空满状态判断。

定义一个计数器,计数器的量程等于FIFO的深度,即假设FIFO深度为8,那么计数器的计数范围是0~8。

计数器自增自减有如下5种情况:

  1. 上下游模块同时读写FIFO,且FIFO非空非满:计数器不变;
  2. 上游模块写FIFO,且非满:计数器自增;
  3. 下游模块读FIFO,且非空:计数器自减;
  4. 上游模块写FIFO,但满:计数器不变;
  5. 下游模块读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个数据,
image

读出8个数据,
image

同时读写,
image

三、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基础

  1. sfifo_driver继承uvm_driver的内容,最主要的是main_phase。
  2. phase分为function phase和task phase。function phase不消耗时间,每个时刻只有一个function phase执行;task phase消耗时间,所有task phase并行执行。
  3. 最常用的三个phase是build_phase、connect_phase和main_phase,只有main_phase是task phase。
  4. task phase的核心是reset、configure、main、shutdown四个phase。
  5. 每个class主要要执行的代码都写在main_phase里,应该是类似于主函数。
  6. 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); 
  1. #()中是要传递的数据的数据类型,这里是itf的数据类型virtual sfifo_if(见3.3);
  2. set的第一个参数是uvm_component实例的指针。如果用this,那么第二个参数可以为空;如果为null,会被自动替换为uvm_root::get(),即uvm_top;
  3. 前两个参数组合起来形成路径,将数据传到这个路径下的phase中,具体看哪个phase可以接收这个数据;
  4. 由于run_test中实例化sfifo_env,所以uvm_test_top.drv就是sfifo_driver,会将数据传给sfifo_driver;
  5. 找到路径后,还需要保证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的区别:

  1. uvm_object_utils用于数据单元,而uvm_component_utils用于组件;
  2. 使用uvm_object_utils就没有层次结构的概念,因此new函数只有一个参数string name;
  3. 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);
  1. type_name要替换成子节点的类型;
  2. type_id是子节点类型的标识符,直接写type_id即可;
  3. name是子节点的名字;
  4. this代表子节点挂在该节点下。
agt = sfifo_agent::type_id::create("agt", this);
agt.is_active = UVM_ACTIVE;

agt中包含driver和monitor两种组件,这个agt可能有两种应用场景:

  1. 用在输入端,driver和monitor都要使用;
  2. 用在输出端,只有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算是通道,有一个仲裁机制:

  1. sequence想发送一个transaction,driver想接收一个transaction,sequencer就批准双方接收;
  2. sequence想发送一个transaction,但driver不想接收,sequencer就让sequence等;
  3. 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做一些处理:

  1. new一次transaction的实例;
  2. 对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

posted @ 2025-10-10 19:34  Holybanban  阅读(15)  评论(0)    收藏  举报