同步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自留地 - 博客園>
http://www.rzrgm.cn/DoreenLiu/p/17348480.html
浙公網安備 33010602011771號