在之前的内容中我们已经完成了monitor的搭建,在这一章节中我们就要开始搭建scoreboard了,理解scoreboard是如何从monitor中拿到数据并进行比较的。
scoreboard和ref_model的区别:
- reference_model对于复杂的设计而言,就是需要单独做一个模型来模拟设计的部分,例如MCDF中的model就是模拟了设计中仲裁等功能;
- scoreboard对于一些简单的设计而言,可以从memory中存放数据的一些功能就放到scoreboard中,拿到数据以后对数据进行归类,排序,整理和比较,如果排序和整理的过程比较复杂,那就会使用reference_model进行替换:
- scoreboard需要同时具备扮演memory模型的能力:
- 知道序列的位宽;
- 首地址和结束的地址;
- 这些信息可以放在ahb_config中。
5.1 ahb_config及test对信息的配置
- 在Verilog中的地址是14位的,我们在TB中连接参数时的地址默认是十六位的,所以这里的首地址和结束的地址采用16位即可;
- config中的首地址和结束的地址我们在test中对他们进行配置,这两个参数应当和设计中的位宽一致,如果放进来的是不在这个地址范围内的数据,那么memory是吃不进去的。也就是说scoreboard应该和design中的参数一致,scoreboard起到模拟design的作用。
``systemverilog
ifndef AHB_RAM_CONFIG_SV `define AHB_RAM_CONFIG_SV
class ahb_ram_config extends uvm_object;
int seq_check_count; int seq_check_error;
int scb_check_count; int scb_check_error;
bit scb_enable = 1; bit cov_enable = 1;
bit [31:0] addr_start; bit [31:0] addr_end;
ahb_agent_configuration ahb_cfg; virtual ahb_ram_if vif; ahb_ram_rgm rgm;
`uvm_object_utils(ahb_ram_config)
// USER to specify the config items
function new (string name = “ahb_ram_config”); super.new(name); ahb_cfg = ahb_agent_configuration::type_id::create(“ahb_cfg”); endfunction : new
endclass
`endif // AHB_RAM_CONFIG_SV
```systemverilog
`ifndef AHB_RAM_BASE_TEST_SV
`define AHB_RAM_BASE_TEST_SV
virtual class ahb_ram_base_test extends uvm_test;
ahb_ram_config cfg;
ahb_ram_env env;
ahb_ram_rgm rgm;
function new(string name = "ahb_ram_base_test", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
rgm = ahb_ram_rgm::type_id::create("rgm");
rgm.build();
uvm_config_db#(ahb_ram_rgm)::set(this, "env", "rgm", rgm);
cfg = ahb_ram_config::type_id::create("cfg");
cfg.rgm = rgm;
// do parameter configuration
cfg.addr_start = 32'h0;
cfg.addr_end = 32'h0000_ffff;
if(!uvm_config_db#(virtual ahb_ram_if)::get(this,"","vif", cfg.vif)) begin
`uvm_fatal("GETCFG","cannot get virtual interface from config DB")
end
uvm_config_db#(ahb_ram_config)::set(this, "env", "cfg", cfg);
env = ahb_ram_env::type_id::create("env", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
phase.phase_done.set_drain_time(this, 1us);
phase.raise_objection(this);
phase.drop_objection(this);
endtask
endclass
`endif //AHB_RAM_BASE_TEST
5.2 scoreboard的编辑
5.2.1 读写数据采样及比较的实现
- scoreboard是继承于subscriber的,所以我们在scoreboard中的write函数进行补充。
- scoreboard和monitor都是env层次下的,所以他们的连接也发生在env的connect_phase中。
- 接下来我们就需要考虑来自monitor的数据应该放到scoreboard中的什么地方?
- 在scoreboard中声明一个32bit的关联数组用于存放数据,通过32位的无符号数来进行索引。 :::info
- 写入数据,首先需要判断进来的addr在不在我们之前定义的范围里面(is_trans_valid);
- 接下来需要考虑如何存储data和addr:
- 首先根据关键词burst_type,使用case语句来存放数据;
- 根据burst_type判断完成以后,接下来需要根据关键词hsize来进行判断,存放addr和data。对于SINGLE操作而言,它是只有一个数据的,所以我们将data数组中的第一个数据进行存放即可。
- 既然根据burst_type进行判断以后并使用hsize函数来存放addr和data,接下来我们就需要实现hsize对于数据的存放,通过这个函数将数据存放到mem中。
- 在smoke_test中我们也知道对于transaction而言,既有写操作也有读操作,因此我们需要将这两部分分开进行表达,在进行完成读操作以后就可以对写入的数据和读出来的数据进行比较:
- 读回来的数据也需要根据hsize来读取数据,这样读取到的数据是准确的,在读到数据以后带给出一个返回值即可;
- 如果在scoreboard中我们想要进行一个数据的比较,根据single传输数据的方式而言,我们可以对每一个数据进行比较而不需要采用burst的方式一起比较,那么check_data_with_burst这个函数我们可以声明成带有返回值的类型。
:::
在使用函数的值的时候它是由返回值的,但是只return的话是不会打印信息的,所以我们需要使用if_else语句对返回值再进行一次判断,同时将打印的信息作为执行的语句。
`ifndef AHB_RAM_SCOREBOARD_SV
`define AHB_RAM_SCOREBOARD_SV
class ahb_ram_scoreboard extends ahb_ram_subscriber;
bit [31:0] mem [int unsigned];
// Events of scoreboard
`uvm_component_utils(ahb_ram_scoreboard)
function new (string name = "ahb_ram_scoreboard", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
endtask
virtual function void write(ahb_transaction tr);
if(is_trans_valid(tr)) begin
case(tr.xact_type)
WRITE : store_data_with_burst(tr);
READ : check_data_with_burst(tr);
endcase
end
store_data_with_burst(tr);
endfunction
function is_trans_valid(ahb_transaction tr);
if(tr.addr >= cfg.addr_start && tr.addr <= cfg.addr_end);
return 1;
endfunction
function void store_data_with_burst(ahb_transaction tr);
case(tr.burst_type)
SINGLE : begin
store_data_with_hsize(tr, 0);
end
INCR : begin `uvm_error("TYPEERR", "burst type not supported yet") end
WRAP4 : begin `uvm_error("TYPEERR", "burst type not supported yet") end
WRAP8 : begin `uvm_error("TYPEERR", "burst type not supported yet") end
WRAP16 : begin `uvm_error("TYPEERR", "burst type not supported yet") end
INCR4 : begin `uvm_error("TYPEERR", "burst type not supported yet") end
INCR8 : begin `uvm_error("TYPEERR", "burst type not supported yet") end
INCR16 : begin `uvm_error("TYPEERR", "burst type not supported yet") end
default : begin `uvm_error("TYPEERR", "burst type not defined") end
endcase
endfunction
function void store_data_with_hsize(ahb_transaction tr, int beat);
case(tr.burst_size)
BURST_SIZE_8BIT : mem[tr.addr] = tr.data[beat] & 'h0000_00FF;
BURST_SIZE_16BIT : mem[tr.addr] = tr.data[beat] & 'h0000_FFFF;
BURST_SIZE_32BIT : mem[tr.addr] = tr.data[beat] & 'hFFFF_FFFF;
BURST_SIZE_64BIT : begin `uvm_error("TYPEERR", "burst_size is not supported yet") end
default : begin `uvm_error("TYPEERR", "burst_size is not supported yet") end
endcase
endfunction
function bit check_data_with_burst(ahb_transaction tr);
case(tr.burst_type)
SINGLE : begin
check_data_with_burst = check_data_with_hsize(tr, 0);
end
INCR : begin `uvm_error("TYPEERR", "burst type not supported yet") end
WRAP4 : begin `uvm_error("TYPEERR", "burst type not supported yet") end
WRAP8 : begin `uvm_error("TYPEERR", "burst type not supported yet") end
WRAP16 : begin `uvm_error("TYPEERR", "burst type not supported yet") end
INCR4 : begin `uvm_error("TYPEERR", "burst type not supported yet") end
INCR8 : begin `uvm_error("TYPEERR", "burst type not supported yet") end
INCR16 : begin `uvm_error("TYPEERR", "burst type not supported yet") end
default : begin `uvm_error("TYPEERR", "burst type not defined") end
endcase
if(check_data_with_burst) begin
`uvm_info("DATACHECK", $sformatf("ahbram[0%x] hburst[0%x] is as expected", tr.addr, tr.burst_type), UVM_HIGH)
end
else begin
`uvm_error("DATACHECK", $sformatf("ahbram[0%x] hburst[0%x] is not as expected", tr.addr, tr.burst_type))
end
endfunction
function bit check_data_with_hsize(ahb_transaction tr, int beat);
bit [63:0] data;
case(tr.burst_size)
BURST_SIZE_8BIT : begin data = tr.data[beat] & 'h0000_00FF; if(mem[tr.addr] == data & 'h0000_00FF) return 1; end
BURST_SIZE_16BIT : begin data = tr.data[beat] & 'h0000_FFFF; if(mem[tr.addr] == data & 'h0000_FFFF) return 1; end
BURST_SIZE_32BIT : begin data = tr.data[beat] & 'hFFFF_FFFF; if(mem[tr.addr] == data & 'hFFFF_FFFF) return 1; end
BURST_SIZE_64BIT : begin `uvm_error("TYPEERR", "burst_size is not supported yet") end
default : begin `uvm_error("TYPEERR", "burst_size is not supported yet") end
endcase
if(check_data_with_hsize) begin
`uvm_info("DATACHECK", $sformatf("ahb_ram[%0x] data expected 'h%0x = actual 'h%0x", tr.addr, mem[tr.addr], data), UVM_HIGH)
end
else begin
`uvm_error("DATACHECK", $sformatf("ahb_ram[%0x] data expected 'h%0x != actual 'h%0x", tr.addr, mem[tr.addr], data))
end
endfunction
task do_listen_events();
endtask
virtual task do_data_check();
endtask
endclass
`endif // AHB_RAM_SCOREBOARD_SV
【思考】为什么在这里要使用按位与呢?
- 对于一个8bit数据而言,其有效的数据就是最低的8位,所以我们让他和最两位为FF的值进行按位与,与出来的结果就是高bit是无效位,低bit是我们关心的数据;
- 同样的,对于一个16bit的数据而言,我们就让他跟’FFFF按位与。
5.2.2 比较次数的实现
同之前的项目一样,在实现了scoreboard以后我们是要对比较的次数进行补充,分别对比较的次数和error的次数都进行添加
function bit check_data_with_hsize(ahb_transaction tr, int beat);
bit [63:0] data;
case(tr.burst_size)
BURST_SIZE_8BIT : begin data = tr.data[beat] & 'h0000_00FF; if(mem[tr.addr] == data & 'h0000_00FF) return 1; end
BURST_SIZE_16BIT : begin data = tr.data[beat] & 'h0000_FFFF; if(mem[tr.addr] == data & 'h0000_FFFF) return 1; end
BURST_SIZE_32BIT : begin data = tr.data[beat] & 'hFFFF_FFFF; if(mem[tr.addr] == data & 'hFFFF_FFFF) return 1; end
BURST_SIZE_64BIT : begin `uvm_error("TYPEERR", "burst_size is not supported yet") end
default : begin `uvm_error("TYPEERR", "burst_size is not supported yet") end
endcase
cfg.scb_check_count++;
if(check_data_with_hsize) begin
`uvm_info("DATACHECK", $sformatf("ahb_ram[%0x] data expected 'h%0x = actual 'h%0x", tr.addr, mem[tr.addr], data), UVM_HIGH)
end
else begin
cfg.scb_check_error++;
`uvm_error("DATACHECK", $sformatf("ahb_ram[%0x] data expected 'h%0x != actual 'h%0x", tr.addr, mem[tr.addr], data))
end
endfunction
写入数据以后就会进行一次读操作,因此我们只需要在读数据的时候将计数的个数+1即可
【调试及结果】
在命令窗口中查看结果,一共比较了10次,均成功且没有错误。
但是在之前的命令行中提示了get_register by offset: block rgm is not locked的bug,我们这里采用DVT进行调试。
另外,调试结果中并没有将scoreboard中的信息打印出来
解决的方案就是在Makefile中设置信息的优先级。
#############################
# User variables
#############################
TB = ahb_ram_tb
SEED = 1
GUI ?= 0
COV ?= 0
DOTCL ?= 1
VERB ?= UVM_HIGH
OUT ?= out
TESTNAME ?= ahb_ram_smoke_test
DFILES = ../../verilog/ahb_blockram_32.v
VFILES += ../vip_lib/ahb_pkg/ahb_pkg.sv \
../vip_lib/ahb_pkg/ahb_if.sv \
../env/ahb_ram_pkg.sv \
../tb/ahb_ram_if.sv \
../tb/ahb_ram_tb.sv
#############################
# Environment variables
#############################
VCOMP_INC = +incdir+../../verilog \
+incdir+../vip_lib/ahb_pkg/{.,sequence_lib} \
+incdir+../{cfg,cov,reg,env,seq_lib,seq_lib/elem_seqs,test}
VCOMP = vlogan -full64 -ntb_opts uvm-1.2 -sverilog -timescale=1ps/1ps -nc -l $(OUT)/log/comp.log $(VCOMP_INC)
ELAB = vcs -full64 -ntb_opts uvm-1.2 -debug_acc+all -l $(OUT)/log/elab.log -sim_res=1ps
RUN = $(OUT)/obj/$(TB).simv -l run.log -sml +ntb_random_seed=$(SEED) +UVM_TESTNAME=$(TESTNAME) +UVM_VERBOSITY=$(VERB) -cm_dir $(CM_DIR) -cm_name $(CM_NAME)
COV_OPTS = -full64 -dir $(CM_DIR)
CM_DIR ?= $(OUT)/cov.vdb
CM_NAME ?= $(TESTNAME)_$(SEED)
SIMRUNFILE = ahb_ram_sim_run.do
ifeq ($(GUI),1)
RUN += -gui
endif
ifeq ($(DOTCL),1)
RUN += -ucli -do $(SIMRUNFILE)
endif
ifeq ($(COV),1)
ELAB += -cm line+cond+fsm+tgl+branch+assert -cm_dir $(CM_DIR)
RUN += -cm line+cond+fsm+tgl+branch+assert -covg_cont_on_error
endif
prepare:
mkdir -p $(OUT)/work
mkdir -p $(OUT)/log
mkdir -p $(OUT)/sim
mkdir -p $(OUT)/obj
mkdir -p .shadow
comp: prepare
$(VCOMP)
$(VCOMP) $(DFILES) $(VFILES)
elab: comp
$(ELAB) -top $(TB) -o $(OUT)/obj/$(TB).simv
run:
$(RUN)
mergecov:
urg -format both $(COV_OPTS)
dvecov:
dve $(COV_OPTS)
verdicov:
verdi -cov -covdir $(CM_DIR)
htmlcov:
firefox urgReport/dashboard.html
clean:
rm -rf $(OUT) 64 AN.DB DVEfiles csrc *.simv *.simv.daidir *.simv.vdb ucli.key
rm -rf *.log* *.vpd *.h urgReport
5.3 验证结构层次
到此为止,验证层次结构如上图所示