SPI & UART 完整UVM验证环境与测试用例
以下验证环境,包含:配置类+顶层Env+基础Test+5个实用测试用例,与之前提供的事务类、Driver、Monitor、Agent无缝对接。
一、配置类(Config)
用于统一配置SPI和UART的参数,支持在test中动态修改。
c
class spi_uart_config extends uvm_object;
`uvm_object_utils(spi_uart_config)
// SPI 配置参数
bit spi_active = 1'b1; // 是否启用SPI agent
spi_transaction::spi_mode_e spi_default_mode = spi_transaction::MODE_0;
int unsigned spi_default_clk_div = 10; // 100MHz系统时钟 → 10MHz SPI时钟
int unsigned spi_max_clk_div = 255;
int unsigned spi_min_clk_div = 2;
// UART 配置参数
bit uart_active = 1'b1; // 是否启用UART agent
uart_transaction::parity_e uart_default_parity = uart_transaction::PARITY_NONE;
int unsigned uart_default_baud_div = 868; // 100MHz系统时钟 → 115200波特率
int unsigned uart_default_stop_bits = 1;
function new(string name = "spi_uart_config");
super.new(name);
endfunction
endclass
二、顶层环境类(Env)
集成SPI和UART agent,以及可选的scoreboard和reference model。
c
class spi_uart_env extends uvm_env;
`uvm_component_utils(spi_uart_env)
// 配置对象
spi_uart_config cfg;
// Agent
spi_agent spi_agt;
uart_agent uart_agt;
// 分析端口
uvm_analysis_port #(spi_transaction) spi_ap;
uvm_analysis_port #(uart_transaction) uart_ap;
function new(string name = "spi_uart_env", uvm_component parent = null);
super.new(name, parent);
spi_ap = new("spi_ap", this);
uart_ap = new("uart_ap", this);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// 获取配置对象
if (!uvm_config_db#(spi_uart_config)::get(this, "", "cfg", cfg)) begin
`uvm_fatal("NO_CFG", "无法获取spi_uart_config配置对象")
end
// 创建SPI agent
if (cfg.spi_active) begin
spi_agt = spi_agent::type_id::create("spi_agt", this);
uvm_config_db#(uvm_active_passive_enum)::set(this, "spi_agt", "is_active", UVM_ACTIVE);
end
// 创建UART agent
if (cfg.uart_active) begin
uart_agt = uart_agent::type_id::create("uart_agt", this);
uvm_config_db#(uvm_active_passive_enum)::set(this, "uart_agt", "is_active", UVM_ACTIVE);
end
`uvm_info("ENV", "SPI-UART验证环境构建完成", UVM_MEDIUM)
endfunction
virtual function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// 连接分析端口
if (cfg.spi_active) begin
spi_agt.mon.ap.connect(spi_ap);
end
if (cfg.uart_active) begin
uart_agt.mon.ap.connect(uart_ap);
end
// 配置monitor参数
if (cfg.spi_active) begin
spi_agt.mon.set_config(cfg.spi_default_mode, cfg.spi_default_clk_div);
end
if (cfg.uart_active) begin
uart_agt.mon.set_config(cfg.uart_default_baud_div,
cfg.uart_default_parity,
cfg.uart_default_stop_bits);
end
endfunction
virtual function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
uvm_top.print_topology(); // 打印UVM拓扑结构
endfunction
endclass
三、基础测试类(Base Test)
所有测试用例的基类,负责创建环境和配置。
c
class base_test extends uvm_test;
`uvm_component_utils(base_test)
spi_uart_env env;
spi_uart_config cfg;
function new(string name = "base_test", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// 创建并设置配置对象
cfg = spi_uart_config::type_id::create("cfg");
uvm_config_db#(spi_uart_config)::set(this, "env", "cfg", cfg);
// 创建环境
env = spi_uart_env::type_id::create("env", this);
// 设置仿真超时时间(10ms)
uvm_top.set_timeout(10ms);
endfunction
virtual function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
// 打印配置信息
`uvm_info("TEST", "测试配置信息:", UVM_MEDIUM)
`uvm_info("TEST", $sformatf("SPI默认模式:%s", cfg.spi_default_mode.name()), UVM_MEDIUM)
`uvm_info("TEST", $sformatf("UART默认波特率分频:%0d", cfg.uart_default_baud_div), UVM_MEDIUM)
endfunction
virtual task run_phase(uvm_phase phase);
super.run_phase(phase);
phase.raise_objection(this);
// 等待复位完成
#100ns;
// 运行测试用例(子类重写)
run_test_case();
// 等待所有事务完成
#1ms;
phase.drop_objection(this);
endtask
// 测试用例入口(子类重写)
virtual task run_test_case();
`uvm_info("TEST", "运行基础测试用例", UVM_MEDIUM)
endtask
endclass
四、实用测试用例集合
1. 冒烟测试(Smoke Test)
验证最基本的功能是否正常工作,是所有测试的第一步。
c
class smoke_test extends base_test;
`uvm_component_utils(smoke_test)
function new(string name = "smoke_test", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual task run_test_case();
spi_transaction spi_tr;
uart_transaction uart_tr;
`uvm_info("SMOKE_TEST", "开始SPI冒烟测试", UVM_MEDIUM)
// SPI 简单读写测试
for (int i=0; i<5; i++) begin
spi_tr = spi_transaction::type_id::create("spi_tr");
// 固定参数,确保可重复性
spi_tr.mode = cfg.spi_default_mode;
spi_tr.clk_div = cfg.spi_default_clk_div;
spi_tr.mosi_data = new[1];
spi_tr.mosi_data[0] = 8'hA5 + i;
spi_tr.data_delay = new[1];
spi_tr.data_delay[0] = 0;
spi_tr.cs_low_delay = 1;
spi_tr.cs_high_delay = 1;
spi_tr.start(env.spi_agt.seqr);
#1us;
end
`uvm_info("SMOKE_TEST", "开始UART冒烟测试", UVM_MEDIUM)
// UART 简单发送测试
for (int i=0; i<5; i++) begin
uart_tr = uart_transaction::type_id::create("uart_tr");
uart_tr.dir = uart_transaction::TX;
uart_tr.data = 8'h5A + i;
uart_tr.parity_type = cfg.uart_default_parity;
uart_tr.stop_bits = cfg.uart_default_stop_bits;
uart_tr.baud_rate_div = cfg.uart_default_baud_div;
uart_tr.start(env.uart_agt.seqr);
#100us; // 等待UART传输完成
end
`uvm_info("SMOKE_TEST", "冒烟测试完成", UVM_MEDIUM)
endtask
endclass
2. SPI 全模式测试
验证SPI所有四种工作模式是否正常。
c
class spi_all_mode_test extends base_test;
`uvm_component_utils(spi_all_mode_test)
function new(string name = "spi_all_mode_test", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual task run_test_case();
spi_transaction spi_tr;
spi_transaction::spi_mode_e modes[] = {
spi_transaction::MODE_0,
spi_transaction::MODE_1,
spi_transaction::MODE_2,
spi_transaction::MODE_3
};
foreach (modes[i]) begin
`uvm_info("SPI_MODE_TEST", $sformatf("测试SPI模式:%s", modes[i].name()), UVM_MEDIUM)
// 配置monitor为当前模式
env.spi_agt.mon.set_config(modes[i], cfg.spi_default_clk_div);
// 发送多个事务
for (int j=0; j<10; j++) begin
spi_tr = spi_transaction::type_id::create("spi_tr");
spi_tr.randomize() with {
mode == modes[i];
clk_div == cfg.spi_default_clk_div;
mosi_data.size() inside {[1:8]};
};
spi_tr.start(env.spi_agt.seqr);
#1us;
end
end
`uvm_info("SPI_MODE_TEST", "SPI全模式测试完成", UVM_MEDIUM)
endtask
endclass
3. UART 全配置测试
验证UART所有波特率、校验方式和停止位的组合。
c
class uart_full_config_test extends base_test;
`uvm_component_utils(uart_full_config_test)
function new(string name = "uart_full_config_test", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual task run_test_case();
uart_transaction uart_tr;
uart_transaction::parity_e parities[] = {
uart_transaction::PARITY_NONE,
uart_transaction::PARITY_EVEN,
uart_transaction::PARITY_ODD
};
int stop_bits[] = {1, 2, 3};
int baud_divs[] = {868, 434, 217, 108}; // 115200, 230400, 460800, 921600
foreach (baud_divs[i]) begin
foreach (parities[j]) begin
foreach (stop_bits[k]) begin
`uvm_info("UART_CONFIG_TEST",
$sformatf("测试UART配置:波特率分频=%0d, 校验=%s, 停止位=%0d",
baud_divs[i], parities[j].name(), stop_bits[k]), UVM_MEDIUM)
// 配置monitor
env.uart_agt.mon.set_config(baud_divs[i], parities[j], stop_bits[k]);
// 发送多个字节
for (int l=0; l<5; l++) begin
uart_tr = uart_transaction::type_id::create("uart_tr");
uart_tr.randomize() with {
dir == uart_transaction::TX;
parity_type == parities[j];
stop_bits == stop_bits[k];
baud_rate_div == baud_divs[i];
};
uart_tr.start(env.uart_agt.seqr);
#200us;
end
end
end
end
`uvm_info("UART_CONFIG_TEST", "UART全配置测试完成", UVM_MEDIUM)
endtask
endclass
4. 随机测试(Random Test)
生成大量随机事务,快速覆盖各种场景。
c
class random_test extends base_test;
`uvm_component_utils(random_test)
// 测试参数
int num_spi_trans = 100;
int num_uart_trans = 100;
function new(string name = "random_test", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual task run_test_case();
`uvm_info("RANDOM_TEST", "开始随机测试", UVM_MEDIUM)
// 并行运行SPI和UART随机测试
fork
run_spi_random();
run_uart_random();
join
`uvm_info("RANDOM_TEST", "随机测试完成", UVM_MEDIUM)
endtask
virtual task run_spi_random();
spi_transaction spi_tr;
for (int i=0; i<num_spi_trans; i++) begin
spi_tr = spi_transaction::type_id::create("spi_tr");
// 完全随机,覆盖所有约束
if (!spi_tr.randomize()) begin
`uvm_error("RANDOM_TEST", "SPI事务随机化失败")
end
spi_tr.start(env.spi_agt.seqr);
#1us;
end
endtask
virtual task run_uart_random();
uart_transaction uart_tr;
for (int i=0; i<num_uart_trans; i++) begin
uart_tr = uart_transaction::type_id::create("uart_tr");
// 完全随机,覆盖所有约束
if (!uart_tr.randomize()) begin
`uvm_error("RANDOM_TEST", "UART事务随机化失败")
end
uart_tr.start(env.uart_agt.seqr);
#100us;
end
endtask
endclass
5. 错误注入测试(Error Injection Test)
验证DUT对各种错误情况的处理能力。
c
class error_injection_test extends base_test;
`uvm_component_utils(error_injection_test)
function new(string name = "error_injection_test", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual task run_test_case();
spi_transaction spi_tr;
uart_transaction uart_tr;
`uvm_info("ERROR_TEST", "开始SPI错误注入测试", UVM_MEDIUM)
// SPI 错误注入:不同时钟分频和模式的组合
for (int i=0; i<20; i++) begin
spi_tr = spi_transaction::type_id::create("spi_tr");
spi_tr.randomize() with {
clk_div inside {[2:20]}; // 测试不同时钟频率
mosi_data.size() inside {[1:16]};
};
spi_tr.start(env.spi_agt.seqr);
#1us;
end
`uvm_info("ERROR_TEST", "开始UART错误注入测试", UVM_MEDIUM)
// UART 错误注入:校验错误和帧错误
for (int i=0; i<10; i++) begin
uart_tr = uart_transaction::type_id::create("uart_tr");
uart_tr.randomize() with {
dir == uart_transaction::TX;
parity_type != uart_transaction::PARITY_NONE;
parity_error dist {0:=50, 1:=50}; // 50%概率注入校验错误
frame_error dist {0:=70, 1:=30}; // 30%概率注入帧错误
};
uart_tr.start(env.uart_agt.seqr);
#200us;
end
`uvm_info("ERROR_TEST", "错误注入测试完成", UVM_MEDIUM)
endtask
endclass
五、完整文件结构
将所有文件按照以下结构组织,便于管理和编译:
spi_uart_uvm/
├── src/
│ ├── spi/
│ │ ├── spi_transaction.sv
│ │ ├── spi_interface.sv
│ │ ├── spi_driver.sv
│ │ ├── spi_monitor.sv
│ │ ├── spi_sequencer.sv
│ │ ├── spi_agent.sv
│ │ └── spi_coverage.sv
│ ├── uart/
│ │ ├── uart_transaction.sv
│ │ ├── uart_interface.sv
│ │ ├── uart_driver.sv
│ │ ├── uart_monitor.sv
│ │ ├── uart_sequencer.sv
│ │ ├── uart_agent.sv
│ │ └── uart_coverage.sv
│ ├── env/
│ │ ├── spi_uart_config.sv
│ │ └── spi_uart_env.sv
│ └── test/
│ ├── base_test.sv
│ ├── smoke_test.sv
│ ├── spi_all_mode_test.sv
│ ├── uart_full_config_test.sv
│ ├── random_test.sv
│ └── error_injection_test.sv
└── tb/
└── top_tb.sv
六、编译与运行命令
VCS 编译命令
bash
vcs -sverilog -ntb_opts uvm-1.2 \
+incdir+./src/spi+./src/uart+./src/env+./src/test \
./src/spi/*.sv \
./src/uart/*.sv \
./src/env/*.sv \
./src/test/*.sv \
./tb/top_tb.sv \
-o simv
运行指定测试用例
bash
# 运行冒烟测试
./simv +UVM_TESTNAME=smoke_test
# 运行随机测试
./simv +UVM_TESTNAME=random_test
# 运行错误注入测试
./simv +UVM_TESTNAME=error_injection_test
# 生成覆盖率报告
./simv +UVM_TESTNAME=random_test -cm line+cond+fsm+branch+tgl
七、最佳实践与扩展建议
1. 验证流程建议
- 先跑冒烟测试:确保基本功能正常
- 再跑全模式/全配置测试:覆盖所有接口配置
- 然后跑随机测试:快速提升覆盖率
- 最后跑错误注入测试:验证鲁棒性
- 根据覆盖率报告:编写定向测试用例覆盖剩余点
2. 扩展建议
- 添加Scoreboard:用于比较DUT的输出和预期结果
- 添加Reference Model:实现一个简单的SPI/UART参考模型
- 添加Virtual Sequencer:用于协调SPI和UART的事务发送
- 添加更多测试用例:如长包测试、背靠背传输测试、中断测试等
3. 常见问题解决
- 覆盖率收敛慢:调整约束的权重分布,增加关键场景的概率
- 随机化失败 :检查约束是否有冲突,使用
soft约束降低优先级 - 时序问题:调整接口的时钟块延时,确保采样和驱动的时机正确