测试用例简介:
uart ip来自于openc910项目的uart源代码,本测试用例通过配置uart寄存器来实现uart的组件的发送和接收以及数据的检查,使用dsl语言抽象描述apb组件和vip_uart组件,并通过PSS的action infer feature来推断生成各种合法的配置场景。
测试用例资源:
1. uart ip
2. uart vip
3. apb vip
验证组件抽象:
1. testbench中总共有 apb vip,uart ip 和 uart vip组件,driver transactions的组件分别是apb vip和uart vip,因此需要使用dsl语言对这两个组件做描述。
2. PSS IP验证需要约束生成代码是uvm domain的代码,因此将executor_s属性配置为UVM_TYPE类型来让PSS工具生成支持uvm平台的代码,并配置thread id。uart vip组件和apb组件的atomic action将继承core_action_a,将thread和component binding。
3. 定义apb vip的HSI接口,HSI接口将以import target的接口形式导入到model中调用,我们需要定义一个apb的写接口xpss_scalar_write32和读接口xpss_scalar_read32。
4. 定义uart vip的HSI接口,HSI接口将以import target的接口形式导入到model中调用,我们需要定义vip uart 配置接口vip_uart_config,vip uart读字符接口vip_uart_receive和vip uart写字符接口vip_uart_transmit。
/* executor pkg */
package insts_pkg {
import executor_pkg::*;
struct inst_trait_s : executor_trait_s {
rand int in [0:1] thread_id;
};
abstract action insts_action_a {
rand executor_cleaim_s<inst_trait_s> inst;
};
};
package apb_sv_binding_pkg {
import target C function void xpss_scalar_read32(bit[64] addr, output bit[32] data);
import target C function void xpss_scalar_write32(bit[64] addr, input bit[32] data);
};
/* apb uart */
component uart_c {
import insts_pkg::*;
abstract action uart_base_a : insts_action_a {
constraint default inst.trait.thread_id == 0;
};
action uart_config_a : uart_base_a {
output uart_config_s cfg_stream; // configure data stream
};
action uart_tx_a : uart_base_a {
output uart_tx_stream to_chs; // transmit data byte stream
rand int in [1..127] size; // constraint data bytes
};
action uart_rx_a : uart_base_a {
input uart_rx_stream from_chs; // receive data byte stream
};
}; // apb_uart_c
package vip_uart_sv_binding_pkg {
import target C function void vip_uart_config(int char_len, int nb_stop, int parity_en int parity_mode);
import target C function void vip_uart_transmit(bit[8] payload);
import target C function void vip_uart_receive(bit[8] expected_data, bit[8] actual_data);
}
/* vip uart */
component vip_uart_c {
import insts_pkg::*;
abstract action uart_base_a : insts_action_a {
constraint default inst.trait.thread_id == 1;
};
action uart_config_a : uart_base_a {
input uart_config_s cfg_stream;
};
action uart_tx_a : uart_base_a {
output uart_rx_stream to_chs; // transmit data byte stream
rand int in [1..127] size; // constraint data bytes
};
action uart_rx_a : uart_base_a {
input uart_tx_stream from_chs; // receive data byte stream
};
}; // vip_uart_c
PSS组件间数据交互是通过Flow objects来实现的,DSL提供了一些Flow objects数据类型,例如Buffer objects,Stream objects,State objects和Resource objects,这些objects具体有什么作用就不做说明了。我们将使用到stream objects,因为uart的发送和接收是并行执行的,具有瞬态交换属性性质。
package uart_pkg {
stream uart_tx_stream {
list<bit[8]> data;
};
stream uart_rx_stream {
list<bit[8]> data;
};
stream uart_config_s {
rand bit[2] char_leng;
rand bit[2] parity_mode;
rand bit[2] in [0..3] nb_stop;
};
enum baud_rate_e {
BAUD_RATE_9600 = 9600,
BAUD_RATE_115200 = 115200,
};
};
apb vip和uart vip接口HSI,HSI接口实现了一个承上启下的作用,承上对应的是modeling的调用,建模时可以不关心HSI接口的具体实现,只需要知道HSI接口的具体功能和传入参数就可以, 启下对应的是HSI接口可以使用如下uvm/C/systemC等语法来进行具体的功能实现,工具不提供任何具体实现。下面分别实现了vip_uart_sv_binding_pkg.pss和apb_sv_binding_pkg.pss,为uart vip提供的HSI接口和apb vip提供的HSI接口。
/* vip uart HSI function */
export "DPI-C" task uvm_set_config_int;
task automatic uvm_set_config_int(input string path, string field, int value);
uvm_config_int::set(null, path, field, value);
endtask;
export "DPI-C" vip_uart_config;
task vip_uart_config(input int char_len, input int nb_stop, input int parity_en, input int parity_mode);
uart_env uart;
static string path = {"*.demo_tb.", inst_name};
uart.uart_config(char_len, nb_stop, parity_en, parity_mode);
endtask; // vip_uart_config
export "DPI-C" task vip_uart_transmit;
task vip_uart_transmit(input int unsinged payload);
static string tx_agent = {"*.", inst_name, ".tx"};
uart_tx_agent tx;
uart_pkg::uart_transmit_seq uart_tx;
uart_tx = uart_pkg::uart_transmit_seq::type_id::create("uart_transmit", tx);
uart_tx.num_of_tx = 1;
uart_tx.payload = payload;
uart_tx.start(tx.sequencer);
endtask; // vip_uart_transmit
export "DPI-C" task vip_uart_receive;
task vip_uart_receive(input int unsigned expected_data, input int unsigned actual_data);
demo_tb tb;
tb = find_demo_tb(".demo_tb");
tb.scoreboard.data_check(expected_data, actual_data);
endtask; // vip_uart_receive
/* apb vip HSI function */
typedef longint unsigned addr_t;
typedef int unsigned data_t;
task automic apb_write(input apb_master_agent master, input addr_t addr, input data_t data, input int width);
case (width)
1: begin
apb_pkg::write_byte_seq byte_seq;
byte_seq = apb_pkg::write_byte_seq::type_id::create("write_byte_seq", maser);
byte_seq.addr = addr[39:0];
byte_seq.data = data[7:0];
byte_seq.start(master.sequencer);
end
2: begin
apb_pkg::write_hword_seq hword_seq;
hword_seq = apb_pkg::write_hword_seq::type_id::create("write_hword_seq", maser);
hword_seq.addr = addr[39:0];
hword_seq.data = data[15:0];
hword_seq.start(master.sequencer);
end
4: begin
apb_pkg::write_word_seq word_seq;
word_seq = apb_pkg::write_word_seq::type_id::create("write_word_seq", maser);
word_seq.addr = addr[39:0];
word_seq.data = data[15:0];
word_seq.start(master.sequencer);
end
endtask;
task automic apb_read(input apb_master_agent master, input addr_t addr, output data_t data, input int width);
case (width)
1: begin
apb_pkg::read_byte_seq byte_seq;
byte_seq = apb_pkg::read_byte_seq::type_id::create("read_byte_seq", maser);
byte_seq.addr = addr[39:0];
byte_seq.start(master.sequencer);
data = byte_seq.rsp.data;
end
2: begin
apb_pkg::read_hword_seq hword_seq;
hword_seq = apb_pkg::read_hword_seq::type_id::create("read_hword_seq", maser);
hword_seq.addr = addr[39:0];
hword_seq.start(master.sequencer);
data = hword_seq.rsp.data;
end
4: begin
apb_pkg::read_word_seq word_seq;
word_seq = apb_pkg::read_word_seq::type_id::create("read_word_seq", maser);
word_seq.addr = addr[39:0];
word_seq.start(master.sequencer);
data = word_seq.rsp.data;
end
endtask;
export "DPI-C" task xpss_scalar_write8;
task xpss_scalar_write8(input longint unsigned addr, input byte unsigned data);
static string apb_master = {"*.", inst_name, ".master"};
apb_master_agent master;
master = find_apb_master_agent(apb_master);
apb_write(master, addr, 1, data);
endtask;
export "DPI-C" task xpss_scalar_write32;
task xpss_scalar_write32(input longint unsigned addr, input int unsigned data);
static string apb_master = {"*.", inst_name, ".master"};
apb_master_agent master;
master = find_apb_master_agent(apb_master);
apb_write(master, addr, 4, data);
endtask;
export "DPI-C" task xpss_scalar_read8;
task xpss_scalar_read8(input longint unsigned addr, output byte unsigned data);
static string apb_master = {"*.", inst_name, ".master"};
apb_master_agent master;
master = find_apb_master_agent(apb_master);
apb_read(master, addr, 1, data);
endtask;
export "DPI-C" task xpss_scalar_read32;
task xpss_scalar_read32(input longint unsigned addr, output int unsigned data);
static string apb_master = {"*.", inst_name, ".master"};
apb_master_agent master;
master = find_apb_master_agent(apb_master);
apb_read(master, addr, 4, data);
endtask;
uart,vip uart激励实现部分automic action body建模,这里我们需要配置传输控制寄存器控制 UART 的传输和接收,主要控制传输的数据位长度和是否奇偶校验等,并实现发送和接收相关的action body的功能。
extend compont uart_c {
bit[32] uart_lcr;
int baud_div;
extend action config_uart_a {
exec post_solve {
baud_div = / BADU_RATE_9600;
//
if (cfg_stream.char_length) {
uart_lcr |= ;
}
if (cfg_stream.parity_mode) {
uart_lcr |= ;
}
if (cfg_stream.char_nb_stop) {
uart_lcr |= ;
}
};
exec body {
...
// write baudrate register
xpss_scalar_write32(UART_DDL, (baud_div) &0xff);
// write contral register
xpss_scalar_write32(UART_LCR, uart_lcr);
};
};
extend action tx_uart_data_a {
exec post_solve {
repeat (size) {
to_chs.data.push_back(urandom() & 0xff);
}
};
exec body {
repeat (i: size) {
// waiting till transmit
while ([condition, TODO]) xpss_yield();
xpss_scalar_write32(UART_THR, to_chs.data[i]);
}
};
extend action rx_uart_data_a {
exec body {
bit[8] actual_data;
repeat(i : from_chs.data.size()) {
// waiting till data incoming fifo
while ([condition, TODO]) xpss_yield();
xpss_scalar_read32(UART_RBR, actual_data);
// compare excepted data and actual data TODO
}
};
};
};
};
extend compont vip_uart_c {
extend action config_uart_a {
int parity_mode;
int char_length;
int nb_stop;
exec post_solve {
char_length = cfg_stream.char_length;
parity_mode = cfg_stream.parity_mode;
nb_stop = cfg_stream.char_nb_stop;
};
exec run_start {
uvm_sv_binging_pkg::uvm_set_config_int(uart_path, "baud_rate", 0xaf8);
uvm_sv_binging_pkg::uvm_set_config_int(uart_path, "prity_mode", parity_mode);
uvm_sv_binging_pkg::uvm_set_config_int(uart_path, "char_length", char_length);
uvm_sv_binging_pkg::uvm_set_config_int(uart_path, "nb_stop", nb_stop);
};
exec body {
vip_config_uart(char_length, parity_mode, nb_stop);
};
};
extend action tx_uart_data_a {
exec post_solve {
repeat (size) {
to_chs.data.push_back(urandom() & 0xff);
}
};
exec body {
repeat (i: size) {
vip_uart_transmit(to_chs.data[i]);
}
};
};
extend action rx_uart_data_a {
exec body {
bit[8] actual_data;
repeat(i : from_chs.data.size()) {
actual_data = vip_uart_receive();
// compare excepted data and actual data, TODO
};
};
};
};
到现在我们定义了component组件,并实现了atomic actions,接下来我们再来看看场景scenario是如何实现的。首先在pss_top组件定义object pool资源池,例化组件,并且将threads与executor进行绑定,将thread0分配给apb0线程执行,将thread1分配给uart0线程执行。
component pss_top {
import uart_pkg::*;
import insts_pkg::*;
import addr_reg_pkg::*;
// bind stream objects polls
pool uart_config_s uart_cfg_p;
bind uart_cfg_p *;
// bind stream objects polls
pool uart_tx_stream uart_tx_p;
bind uart_tx_p *;
// bind stream objects polls
pool uart_rx_stream uart_rx_p;
bind uart_rx_p *;
// instant component
insts_c insts;
uart_c uart;
vip_uart_c vip_uart;
transparent_addr_space_c<> sys_mem;
exec init_down {
// bind threads to exectors
insts.inst[0].tag = "apb0";
insts.inst[0].agent_context_type = UVM_TYPE;
insts.inst[1].tag = "uart0";
insts.inst[1].agent_context_type = UVM_TYPE;
// set uart component base address
transparent_addr_region_s<> uart_mmio_region;
uart_mmio_region.addr = 0x10015000;
uart.base_addr = sys_mem.add_nonallocatable_region(uart_mio_region);
};
};
让后我们再定义uart场景action, 这些action是复合类型action,即不存在body生成具体的实现。
第一个场景:uart_multi_transmit_a是uart实现多次transmit操作,constraint transmit操作次数在1~10次,发送的字节在1~20字节。
第二个场景:uart_tx_rx_a是uart执行一次transmit操作和一次receive操作。
extend component pss_top {
action uart_multi_transmit_a {
rand int in [1..10] count;
rand int in [1..20] size;
activity {
repeat (i : count) {
do uart_c::tx_uart_data_a with { size == this.size; };
}
};
};
action uart_tx_rx_a {
rand int in [1..20] size;
activity {
do uart_c::tx_uart_data_a with { size == this.size; };
do uart_c::rx_uart_data_a with { size == this.size; };
};
};
action scenario_test_a {
do uart_tx_rx_a;
};
};
好了,到此使用DSL对Uart IP建模部分就结束了。model已经写好了,那么我们就可以使用PSS工具来解析模型,生成可执行的代码了。这里我们选择使用的是galaxpss这PSS工具。下面是工具使用的filelist:
-dsl insts_pkg.pss
-dsl insts_c.pss
-dsl insts_exec.pss
-dsl uvm_sv_binding_pkg.pss
-dsl uart_pkg.pss
-dsl uart_c.pss
-dsl uart_exec.pss
-dsl vip_uart_c.pss
-dsl vip_uart_exec.pss
-dsl pss_top.pss
-r pss_top::scenario_test_a
-std;
-addr_reg
-pformat h
-debug 2
-msg_level FULL
-uvm
-l
执行:galaxpss -f filelist
下图是do uart_c::tx_uart_data_a 这个action执行生成的图,这只有一个atomic action啊,怎么会生成出这么多的其它atomic action呢?这就是PSS的一个奇特feature,其拥有强大的action infer推断能力,即使使用者没有将场景补充完全,像这里的仅仅是设想了一个uart transmit的动作,PSS工具自动推断出执行这个uart transmit动作所依赖的其它动作行为,并合法的生成相对应的动作,比如init_uart_a做uart的初始化动作,然后是uart的config动作,同时将vip uart的config动作也推断出来了,根据数据流控的input/output推断出执行流控,能够使其生成的场景更加丰富,即使我们验证工程师没有考虑到的验证点,PSS工具也会根据对数据,数据流和场景的约束而补充各种合法的场景。
生成文件目录:
各个文件的作用:
1. test_sv_case.c是执行激励的文件;
2. test_sv_case.sv是SV实现的HSI接口文件;
3. lib_entry_top.sv文件是PSS的入口module,主要是启动test_sv_case.c文件中的线程;
4. lib_startup_pkg.sv文件提供线程与testbench启动平台的同步机制。
下面介绍如何将上面生成的文件集成到testbench。
1. 在module tb_top中例化module lib_entry_top;
2. 在pss_sequence_test类的run_phase,启动C main同步
module tb_top;
......
// instance pss entry module
lib_entry_top entry_top();
// instance uart interface and apb interface
uart_if uart_if_0(...);
apb_if apb_if_0(...);
uart i_uart(
.sys_clk(sys_clk),
.rst_b(reset_n),
.apb_uart_paddr(apb_if_0.paddr[31:0]),
.apb_uart_pwdata(apb_if_0.pwdata[31:0]),
.uart_apb_prdata(apb_if_0.prdata[31:0]),
.apb_uart_pwrite(apb_if_0.prwd),
.apb_uart_penable(apb_if_0.penable),
.apb_uart_psel(apb_if_0.psel[0]),
.uart_vic_int(),
.s_in(uart_if_0.txd),
.s_out(uart_if_0.rxd)
);
initial begin
......
run_test();
end
......
class pss_sequence_test extends pss_base_test;
`uvm_component_utils(pss_sequence_test);
function new(string name = "pss_sequence_test", uvm_component parent);
super.new(name, parent);
endfunction: new
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction: build_phase
task run_phase(uvm_phase phase);
phase.raise_objection(this);
// startup the PSS C test main
lib_startup_pkg::xpss_sync_entry_main();
phase.drop_objection(this);
endtask: run_phase
endclass: pss_sequence_test;
这个case我们是分别使用xrun, vcs和galaxsim仿真器来跑,结果如下:
xrun运行结果:
vcs运行结果:
galaxsim运行结果: