UART验证项目之测试激励
一、寄存器的读写权限以及复位权限检查
uvm中提供了两个seq用于完成这个测试,可以直接调用,他的原理是比较testbench中的寄存器模型的默认值和dut中写的默认值是否一致
class reg_vseq extends base_vseq;
`uvm_object_utils(reg_vseq)
//uvm中自带的sequence
uvm_reg_hw_reset_seq rst_seq;//寄存器复位检查
uvm_reg_bit_bash_seq bitbash_seq;//寄存器读写权限检查
function new(string name="reg_vseq");
super.new(name);
endfunction
virtual task body();
#10us;
//例化后把我们的寄存器模型变量赋值给seq中自带的model变量即可
rst_seq =new("rst_seq");
rst_seq.model = p_sequencer.p_rm;
rst_seq.start(null);
bitbash_seq = new("bitbash_seq");
bitbash_seq.model = p_sequencer.p_rm;
bitbash_seq.start(null);
endtask
endclass
class tc_uart_reg extends tc_base;
`uvm_component_utils(tc_uart_reg)
function new(string name="tc_uart_reg",uvm_component parent);
super.new(name,parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
//这里采用default_sequence的方式启动sequence,和下面run_phase中的start二选一,实际上对于一些简单的激励,可以用default_sequence来发送,但应该尽量摒弃这种方式
uvm_config_db#(uvm_object_wrapper)::set(this,"v_sqr.main_phase","default_sequence",reg_vseq::type_id::get());
//跳过以下四个寄存器的检查(可选)
uvm_resource_db#(bit)::set({"REG::",rm.get_full_name(), ".UARTDR"}, "NO_REG_TESTS", 1, this);
uvm_resource_db#(bit)::set({"REG::",rm.get_full_name(), ".UARTRSR"}, "NO_REG_TESTS", 1, this);
uvm_resource_db#(bit)::set({"REG::",rm.get_full_name(), ".UARTCR"}, "NO_REG_TESTS", 1, this);
uvm_resource_db#(bit)::set({"REG::",rm.get_full_name(), ".UARTRIS"}, "NO_REG_TESTS", 1, this);
endfunction
//除了default_sequence的方式外,还可以在run_phase中用start的方式启动sequence,这种方式更加推荐,因为在run_phase可以更灵活的控制激励的发送
virtual task run_phase(uvm_phase phase);
reg_vseq vseq;
vseq = reg_vseq::type_id::create("vseq");
//注意这句话不要漏写,否则sequence中就无法raise objection
vseq.starting_phase = phase;
vseq.start(vsqr);
endtask
endclass:tc_uart_reg
base_vseq中只是实现了objection的控制和例化一些公用变量和方法
class base_vseq extends uvm_sequence;
uvm_event e0;//这个事件是触发coverage采样时使用的
//这是reg model中内置的两个变量,用于访问寄存器的状态和存放读取的数据
uvm_status_e status;
uvm_reg_data_t value;
`uvm_object_utils(base_vseq)
`uvm_declare_p_sequencer(virtual_sequencer)
function new(string name = "base_vseq");
super.new(name);
e0 = uvm_event_pool::get_global("sample_cfg");
endfunction
virtual task pre_body();
if(get_parent_sequence() == null && starting_phase != null)
starting_phase.raise_objection(this);
endtask
virtual task post_body();
if(get_parent_sequence() == null && starting_phase != null)
starting_phase.drop_objection(this);
endtask
//定义1个函数,用来计算波特率
function calculate_baud(
input uart_config cfg,
output bit[15:0] int_part,//integer part of baud
output bit[5:0] frac_part//frac part of baud
);
real uart_div = 1.0 * cfg.ratio / 16;
int_part = (uart_div) - 0.5;
frac_part = (uart_div - int_part) * (2 ** 6) + 0.5;
endfunction
//这个函数用于例化uart cfg,随机化后打印
function cfg_randomize(output uart_config cfg);
uart_config cfg;
cfg = p_sequencer.cfg.uart_cfg;
assert(cfg.randomize());
cfg.print();//print parameter value of cfg
endfunction
//通过apb接口发送数据后可以调用该方法,这里通过读取UARTFR寄存器中的busy信号来判断
//transmit fifo为空时,该为置0,表示fifo中没数据了,这样可以确保发送的数据都完成了再进行下一步
//但是有一个点,fifo中空了可能txd端口上还在发最后一个数据
task wait_tx_done();
//polling tx busy done
while(1) begin
p_sequencer.p_rm.UARTFR.read(status, value, UVM_FRONTDOOR);
if(value[3] == 1'b0)//This is transmit busy signal, set 0 means data send compelete
break;
end
enstask
endclass:base_vseq
执行成功后会打印相应的信息,如果匹配不正确则会打印error,没有error就说明都通过了
二、uart模块的写数据功能(tx)
- 这个模块通过testbench的apb_master向dut中写入数据,然后从txd端口发出
class tx_vseq extends base_vseq;
`uvm_object_utils(tx_vseq)
function new(string name = "tx_vseq");
super.new(name);
endfunction
virtual task body();
bit[15:0] int_part;//integer part of baud
bit[5:0] frac_part;//frac part of baud
//config reg
uart_config cfg;
//这些函数都定义在base_vseq中
cfg_randomize(cfg);
//collect coverage
e0.trigger(cfg);
//caculate buad
calculate_baud(cfg, int_part, frac_part);
//配置寄存器
//先配置波特率寄存器,配置值用函数中计算好的值
p_sequencer.p_rm.UARTIBRD.write(status, int_part, UVM_FRONTDOOR);
p_sequencer.p_rm.UARTFBRD.write(status, frac_part, UVM_BACKDOOR);//建议用前门访问
//然后配置控制寄存器值,建议用cfg.的方式直接把cfg中的值写进去这样能确保dut和testbench拿到相同的值
p_sequencer.p_rm.UARTLCR_H.write(status,
{cfg.parity_mode[1], cfg.char_length, 1'b1, cfg.nbstop, cfg.parity_mode[0], cfg.parity_en, 1'b0}, UVM_FRONTDOOR);
//然后是配置中断寄存器,fifo域控制寄存器
p_sequencer.p_rm.UARTIFLS.write(status, 16'h0, UVM_FRONTDOOR);
p_sequencer.p_rm.UARTIMSC.write(status, 16'hff, UVM_FRONTDOOR);
//最后是这个使能寄存器
p_sequencer.p_rm.UARTCR.write(status, 16'h381, UVM_FRONTDOOR);
//配置好寄存器之后通过UARTDR寄存器向dut的fifo中写入数据,这个寄存器会把写入的数据推到transmit fifo中,这里发了10个数据
repeat(10) begin
p_sequencer.p_rm.UARTDR.write(status, $urandom_range(1,100), UVM_FRONTDOOR);
end
//polling tx busy done
wait_tx_done();
endtask
endclass
- 运行case可以看到如下结果
首先看到配置寄存器中的值都被正确写入,apb write也写入了10个数据
txd端口上也发出l数据
仿真报告也显示十个数据的比对都成功了,无error
三、uart模块的读数据功能(rx方向)
- 代码的其他部分和tx功能中是相近的,这里只放一下不同的模块。实现方式是先使用发送10个uart transcation,通过dut的rxd端口接受,然后从UARTDR寄存器中读出来
begin
uart_frame tr;
repeta(10) `uvm_do_on_with(tr, p_sequencer.uart_sqr, {transmit_delay < 5})
end
repeat(10) begin
p_sequencer.p_rm.UARTDR.read(status, value, UVM_FRONTDOOR);
`uvm_info("UARTDR.READ", $sformatf("Read value is %d", value), UVM_LOW);
end
- 运行结果
先写入寄存器配置
dut的rxd端口接受到了数据
最后也成功读出了这10个数据
uvm报告显示数据比较成功,没有error
四、全双工功能
fork
begin
uart_frame tr;
repeat(rx_num)//uart_tx send 10 data
`uvm_do_on_with(tr, p_sequencer.uart_sqr, {transmit_delay < 5;})
end
begin
repeat(10) p_sequencer.p_rm.UARTDR.write(status, $urandom(1,100), UVM_FRONTDOOR);
end
join_none
fork//wati tx done
begin
#10us;//polling tx busy done
wait_tx_done();
end
//wait rx done
while(1) begin
bit[15:0] data;
p_sequencer.p_rm.UARTFR.read(status, data, UVM_FRONTDOOR);
//RXFE == 0
while(data[4] == 0) begin
p_sequencer.p_rm.UARTDR.read(status, value, UVM_FRONTDOOR);
`uvm_info("UARTDR.READ", $sformatf("READED DATA='d%0d", value), UVM_LOW)
rx_cnt++;
p_sequencer.p_rm.UARTFR.read(status, data, UVM_FRONTDOOR);
end
if(rx_cnt ==rx_num)
break;
#200us;
end
join