:::danger
【总结】
这一部分中我们将对以下两部分的内容进行分析:
- 结合AHB协议理解addr是否能按照不对齐进行测试;
- 理解Design给出的数据,在无效位给出的并不是0,而是x;
基于以上两点,monitor在总线上采集数据是无所谓的,master来选择数据的有效位然后进行比较即可,所以这部分选择有效位的逻辑我们放到了scoreboard中进行实现。
memory中采集有效数据的时候需要注意数据的mem的地址一定要正确,否则数据会采集不到。
通过addr,data和bsize来寻找真正的有效位的data,最后采集到有效的数据以后才能进行比较。
:::
7.1 scoreboard分析读数据操作
之前我们进行了diff_hsize的测试,分别在seq和scoreboard中进行了比较,分析scoreboard的逻辑发现我们并不需要在读操作的时候对数据进行一个按位与的运算
假设:这一部分应道由Design来完成,也就是说Design它已经默认判断了一个数据的有效位和无效位,我们将按位与运算去掉以后进行仿真调试:
`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.addr)) begin
case(tr.xact_type)
WRITE : store_data_with_burst(tr);
READ : check_data_with_burst(tr);
endcase
end
endfunction
function is_trans_valid(bit [31:0] addr);
if(addr >= cfg.addr_start && 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] & 32'h0000_00FF;
BURST_SIZE_16BIT : mem[tr.addr] = tr.data[beat] & 32'h0000_FFFF;
BURST_SIZE_32BIT : mem[tr.addr] = tr.data[beat] & 32'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("DATACHK", $sformatf("ahbram[0%x] hburst[%s] is as expected", tr.addr, tr.burst_type), UVM_HIGH)
end
else begin
`uvm_error("DATACHK", $sformatf("ahbram[0%x] hburst[%s] 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]; if(mem[tr.addr] == data) check_data_with_hsize = 1; end
BURST_SIZE_16BIT : begin data = tr.data[beat]; if(mem[tr.addr] == data) check_data_with_hsize = 1; end
BURST_SIZE_32BIT : begin data = tr.data[beat]; if(mem[tr.addr] == data) check_data_with_hsize = 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("DATACHK", $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("DATACHK", $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
【调试及结果】
结果可以发现按照hsize不同读出来的数据rdata在高位都是不定态的,这样的结果放在scoreboard中是难以比较的,通常来说,我们都会希望在高位被读出来的数据会被处理成0,所以在scoreboard中我们使用的是双等号。
也就是说在DUT中,被reset以后读出来的值是x状态的。但是monitor中采集到的数据是bit的,然后将这个数据交给了scoreboard,如果我们继续使用==进行比较的话,结果就会变得不可靠。
:::info
重新查阅手册:
rdata总线是由slave端来进行驱动的,总线上的数据长度我们规定的是32bit,如果给定的激励是低于32bit的,那么slave端是需要在有效bit位上提供数据的(通常都是将整个数据块传输过来,所以会包含高位的不定态),因此只能由master端来选择有效的数据最后进行比较。
:::
对于有效bit位的数据而言我们会读取,其他位置就会当做x进行处理。
:::info 经过上述分析以后我们知道在scoboard中使用双等号来比较这样的一个rd_data是存在危险的,如果读出来的数据高位有其他的数据,那么此时比较的结果就并非我们所想看到的,所以我们对hsize中读出来的数据还是要进行按位与来防止错误出现。 :::
如果我们的地址不一定是word对齐的,我们就需要根据地址来进行sel,所以要更新scoreboard来对case进行比较,进一步查看addr和hsize之间的combine和random问题。
【思考2】 既然我们讨论了从mem一侧读取的数据复位值是x,那么为什么scoreboard中一定要有转换成0,再使用==进行比较呢? 原因就是我们的transaction中的data和addr都是bit类型的,所以scoreboard在比较数据的时候也应当是bit类型的,这样就可以使用==进行比较。
7.2 地址不对齐数据测试
接下来我们进行地址不对齐的数据测试ahb_ram_word_unaligned_test,这个测试中我们不再在seq中比较数据,将比较的任务全部交给scoreboard。
`ifndef AHB_RAM_HADDR_WORD_UNALIGNED_VIRT_SEQ_SV
`define AHB_RAM_HADDR_WORD_UNALIGNED_VIRT_SEQ_SV
class ahb_ram_haddr_word_unaligned_virt_seq extends ahb_ram_base_virt_seq;
`uvm_object_utils(ahb_ram_haddr_word_unaligned_virt_seq)
function new(string name = "ahb_ram_haddr_word_unaligned_virt_seq");
super.new(name);
endfunction
virtual task body();
bit [31:0] addr, data;
burst_size_enum bsize;
super.body();
`uvm_info("body","Entered...",UVM_LOW)
for(int i=0; i<100; i++) begin
std::randomize(bsize) with {bsize inside {BURST_SIZE_8BIT, BURST_SIZE_16BIT, BURST_SIZE_32BIT};};
std::randomize(addr) with {addr inside {['h1000:'h1FFF]};
bsize == BURST_SIZE_16BIT -> addr[0] == 0;
bsize == BURST_SIZE_32BIT -> addr[1:0] == 0;
};
std::randomize(wr_val) with {wr_val == ((i << 24) + (i << 16) + (i << 8) + i);};
data = wr_val;
`uvm_do_with(single_write, {addr == local::addr; data == local::data; bsize == local::bsize;})
`uvm_do_with(single_read, {addr == local::addr; bsize == local::bsize;})
end
`uvm_info("body","EXited...", UVM_LOW)
endtask
endclass
`endif //AHB_RAM_HADDR_WORD_UNALIGNED_VIRT_SEQ_SV
如何确定约束条件及随机化的顺序呢?
- 既然是不按照地址对齐的数据进行随机化的操作,那么我们首先是要给定激励的位宽;
- 原有的addr[1:0]也不再需要为0,我们需要根据不同的位宽大小来确定索引地址的信息:
- 对于一个32位的数据而言,按照1byte进行索引,实际寻址的步长就是4个byte,也就是对编号为4的倍数进行寻址;
- 对于一个16位的数据而言,按照1byte进行索引,实际索引的步长就是2个byte,也就是对编号为2的倍数进行寻址,分别是0,2,4,8。那么就要求最低位为0;
- 对于一个8位的数据而言,按照1byte进行索引,实际索引的补偿就是1个byte,也就是对1的倍数进行寻址,那么就是它本身。
另外,对于32bit的数据而言,为了保证其每一位都有数据,我们将给到的激励也进行改变。
【调试及结果】
根据结果可以发现,在地址不对齐以后进行测试,scoreboard一共比较了100次,其中54次是错误的。
- 我们找到命令栏中的第一个error的地方单击它,在波形上可以看到:在126000时刻,scoreboard报错;
- 在它的前两拍的时候进行了写数据,地址是0202_0202,写入的数据和地址是对其的,写地址是195d,四字节对其,c是对第一个byte进行操作,所以d是对第二个byte进行写操作,也就是说写data数据的有效位是data[16:8],反映到读出来的数据就是0_0000_xxxx_02xx,这样就会导致monitor在采样的时候产生错误,使得scoreboard比较错误。
7.3 monintor & scoreboard逻辑更正
7.3.1 monitor逻辑更正
之前也已经提到过了,monitor从vif上拿到数据然后通过判断以后直接存放到transaction中,这种过程是错误的,因为没有经过有效位的检查。
因此我们声明一个xact_get_valid_data函数,根据burst的大小分别对数据进行移位,最后只检查最低位的数据即可。``systemverilog
ifndef AHB_MONITOR_SV `define AHB_MONITOR_SV
class ahb_monitor extends uvm_monitor;
ahb_agent_configuration cfg; virtual ahb_if vif;
uvm_analysis_port #(ahb_transaction) item_observed_port;
`uvm_component_utils(ahb_monitor)
function new (string name = “ahb_monitor”, uvm_component parent = null); super.new(name,parent); item_observed_port = new(“item_observed_port”, this); endfunction
function void build_phase(uvm_phase phase); super.build_phase(phase); endfunction
function void connect_phase(uvm_phase phase); super.connect_phase(phase); endfunction
task run_phase(uvm_phase phase); super.run_phase(phase); fork monitor_transaction(); join_none endtask
task monitor_transaction(); ahb_transaction t; forever begin collect_transaction(t); item_observed_port.write(t); end endtask
task collect_transaction(output ahb_transaction t); // collect transaction from interfacena t = ahb_transaction::type_id::create(“ahb_transaction”); @(vif.cb_mon iff vif.cb_mon.htrans == NSEQ); t.xact_type = xact_type_enum’(vif.cb_mon.hwrite); t.trans_type = trans_type_enum’(vif.cb_mon.htrans); t.burst_size = burst_size_enum’(vif.cb_mon.hsize); t.burst_type = burst_type_enum’(vif.cb_mon.hburst); t.addr = vif.cb_mon.haddr; forever begin monitor_valid_data(t); if(vif.cb_mon.htrans == IDLE) begin break; end end t.response_type = t.all_beat_response[t.current_data_beat_num]; endtask
task monitor_valid_data(ahb_transaction t); @(vif.cb_mon iff vif.cb_mon.hready); t.increase_data(); t.current_data_beat_num = t.data.size() - 1; // Get draft data from bus t.data[t.current_data_beat_num] = t.xact_type == WRITE ? vif.cb_mon.hwdata : vif.cb_mon.hrdata; // NOTE:: aligned not to extract the valid data after shifted // Exact valid data from current beat // xact_get_valid_data(t); t.all_beat_response[t.current_data_beat_num] = response_type_enum’(vif.cb_mon.hresp); t.trans_type = trans_type_enum’(vif.cb_mon.htrans); endtask
function void xact_get_valid_data(ahb_transaction t); case(t.burst_size) BURST_SIZE_8BIT : t.data[t.current_data_beat_num] = t.data[t.current_data_beat_num] >> (8 t.addr[1:0]); BURST_SIZE_16BIT : t.data[t.current_data_beat_num] = t.data[t.current_data_beat_num] >> (16 t.addr[1]); BURST_SIZE_32BIT : t.data[t.current_data_beat_num] = t.data[t.current_data_beat_num]; endcase endfunction
task monitor_transaction_proc(ahb_transaction t); endtask
endclass
`endif // AHB_MONITOR_SV
<a name="BdRMy"></a>
## 7.3.2 scoreboard逻辑更正
根据我们对ahb协议的理解:
- 如果我们的memory设定合理的话,mem中的地址也应当是按照字对齐来进行索引才算合理,因为这样才能最后将mem中的有效数据和monitor中采集到的数据进行比较。因此我们在存放data到mem的时候,每一次存放数据都应当让mem中的地址是对齐,因此必须对地址进行一个拼写,同时,也必须保证我们往mem中存放的数据就必须是一组数据的有效位;
- 也就是说我们的地址应当按照字索引的方式,如果没有按照这种方式,例如在100的时候作为一个index,101的时候作为一个index,这种搭建的模型对于地址的索引是错误的。
> 如何将地址按照字对齐的方式进行排列?
> 我们直接将最低两位的地址令为0,这样它就是默认按照0,4,8,c的方式进行排列的,内存就是对齐的。
---
<a name="n9WzC"></a>
### (1)实现scoreboard中有效数据向mem中的传输
针对第一点中的两个问题,我们对scoreboard进行以下修改:
- 声明一个新的函数xact_current_beat_mdata来将transaction中的有效数据存放到mem中。
完成了对mem数据存放问题的修改以后,接下来只需要进行数据的check即可,对数据进行相应的位移再进行比对是没有问题的。
```systemverilog
`ifndef AHB_RAM_SCOREBOARD_SV
`define AHB_RAM_SCOREBOARD_SV
class ahb_ram_scoreboard extends ahb_ram_subscriber;
...
function void store_data_with_hsize(ahb_transaction tr, int beat);
case(tr.burst_size)
BURST_SIZE_8BIT : mem[{tr.addr[31:3],2'b00}] = xact_current_beat_mdata(tr, beat);
BURST_SIZE_16BIT : mem[{tr.addr[31:3],2'b00}] = xact_current_beat_mdata(tr, beat);
BURST_SIZE_32BIT : mem[{tr.addr[31:3],2'b00}] = xact_current_beat_mdata(tr, beat);
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("DATACHK", $sformatf("ahbram[0%x] hburst[%s] is as expected", tr.addr, tr.burst_type), UVM_HIGH)
end
else begin
`uvm_error("DATACHK", $sformatf("ahbram[0%x] hburst[%s] is not as expected", tr.addr, tr.burst_type))
end
endfunction
...
function bit [31:0] xact_current_beat_mdata(ahb_transaction t, int beat);
bit [31:0] mdata = mem[{tr.addr[31:2],2'b00}];
bit [31:0] tdata = t.data[beat];
case(t.burst_size)
BURST_SIZE_8BIT : mdata[tr.addr[1:0]*8+7 : -8] = tdata >> (8 * t.addr[1:0]);
BURST_SIZE_16BIT : mdata[tr.addr[1]*16+15 : -16] = tdata >> (16 * t.addr[1]);
BURST_SIZE_32BIT : mdata = tdata;
endcase
return mdata;
endfunction
task do_listen_events();
endtask
virtual task do_data_check();
endtask
endclass
`endif // AHB_RAM_SCOREBOARD_SV
我们在scoreboard的mem中的地址也应当按照字节索引的方式来进行操作: addr为0的话,就是对mem[7:0]位的数据进行操作; addr为1的话,就是对mem[15:8]位的数据进行操作;这样就使得mem中的每一个地址也是对应了8个bit位的数据,最后对数据进行了拼接。
(2)有效数据的移动及monitor & scoreboard逻辑简化
既然scoreboard在获取有效数据到mem的过程中需要移位,monitor在采样到数据以后也需要移位来获取有效值。我们可以将这一部分的函数集成到一个公共可被调用的文件中。
`ifndef AHB_DEFINES_SVH
`define AHB_DEFINES_SVH
`define AHB_MAX_DATA_WIDTH 64
`define AHB_MAX_ADDR_WIDTH 32
typedef enum burst_size_enum;
function bit [31:0] extract_valid_data([`AHB_MAX_DATA_WIDTH - 1:0] data,
[`AHB_MAX_ADDR_WIDTH - 1:0] addr,
burst_size_enum bsize
);
case(bsize)
BURST_SIZE_8BIT : return data >> (8 * addr[1:0]) & 8'hFF
BURST_SIZE_16BIT : return data >> (16 * addr[1]) & 16'hFFFF
BURST_SIZE_32BIT : return data & 32"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
`endif
我们在上面的这个defines文件中集成了extract_valid_data函数,之后就可以方便monitor和scoreboard随时进行调用; 这一部分一定要按位与,原因是data可能是64位的,我们对高位的数据必须用按位与的方式来使得x值可以变成0从而进行进一步的比较。
:::info 将获取数据的有效值并移动以后的逻辑独立出来以后,我们就可以在monitor中对它进一步进行调用: :::
`ifndef AHB_MONITOR_SV
`define AHB_MONITOR_SV
class ahb_monitor extends uvm_monitor;
ahb_agent_configuration cfg;
virtual ahb_if vif;
uvm_analysis_port #(ahb_transaction) item_observed_port;
`uvm_component_utils(ahb_monitor)
function new (string name = "ahb_monitor", uvm_component parent = null);
super.new(name,parent);
item_observed_port = new("item_observed_port", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
fork
monitor_transaction();
join_none
endtask
task monitor_transaction();
ahb_transaction t;
forever begin
collect_transaction(t);
item_observed_port.write(t);
end
endtask
task collect_transaction(output ahb_transaction t);
// collect transaction from interfacena
t = ahb_transaction::type_id::create("ahb_transaction");
@(vif.cb_mon iff vif.cb_mon.htrans == NSEQ);
t.xact_type = xact_type_enum'(vif.cb_mon.hwrite);
t.trans_type = trans_type_enum'(vif.cb_mon.htrans);
t.burst_size = burst_size_enum'(vif.cb_mon.hsize);
t.burst_type = burst_type_enum'(vif.cb_mon.hburst);
t.addr = vif.cb_mon.haddr;
forever begin
monitor_valid_data(t);
if(vif.cb_mon.htrans == IDLE) begin
break;
end
end
t.response_type = t.all_beat_response[t.current_data_beat_num];
endtask
task monitor_valid_data(ahb_transaction t);
@(vif.cb_mon iff vif.cb_mon.hready);
t.increase_data();
t.current_data_beat_num = t.data.size() - 1;
// Get draft data from bus
t.data[t.current_data_beat_num] = t.xact_type == WRITE ? vif.cb_mon.hwdata : vif.cb_mon.hrdata;
// NOTE:: aligned not to extract the valid data after shifted
// Exact valid data from current beat
// extract_valid_data(t.data[t.current_data_beat_num], t.addr, t.burst_size);
t.all_beat_response[t.current_data_beat_num] = response_type_enum'(vif.cb_mon.hresp);
t.trans_type = trans_type_enum'(vif.cb_mon.htrans);
endtask
task monitor_transaction_proc(ahb_transaction t);
endtask
endclass
`endif // AHB_MONITOR_SV
:::info 同样的,我们也可以在scoreboard中在做数据比对的时候,将monitor一侧拿来的数据,使用该函数来对有效数据进行移动 :::
【调试及结果】
对scoreboard和monitor的逻辑重新进行整理以后,我们再次进行编译仿真,首先从简单的测试开始,由于haddr默认了hsize 32bit
scoreboard的比较中只有一个通过。检查发现我们mem中的值都是0,说明在mem中给定有效值的函数有问题。
设置断点以后发癫mdata的值有问题,一直保持为0,说明scoreboard没有将有效的数值输入到mem中。
经过一系列追溯发现问题出在了存储data数据的时候,mem的地址拼接错误了,使得在mem[2]位置的data始终为0。
更改以后运行地址非对齐的测试,结果正常通过。
datacheck比较了200次均是成功的。