UART IP验证实战:从环境搭建到序列生成

张开发
2026/4/5 17:02:23 15 分钟阅读

分享文章

UART IP验证实战:从环境搭建到序列生成
1. UART IP验证环境搭建搞芯片验证的朋友都知道UART这种基础外设IP的验证看似简单实际搭建环境时处处是坑。去年我在一个28nm项目上就遇到过时钟域不同步导致数据丢失的问题折腾了整整两周才定位到根本原因。下面我就把踩过的坑和验证经验分享给大家。1.1 顶层信号连接先说说最基础的时钟和复位信号连接。很多新手会直接照搬参考代码但实际项目中时钟频率和复位策略都需要根据具体需求调整。比如这个例子define CLK_GEN(CLK_NAME, FREQ) \ logic CLK_NAME; \ int CLK_NAME_freq FREQ; \ initial begin \ CLK_NAME 0; \ forever begin \ #(500.0/CLK_NAME_freq) CLK_NAME ~CLK_NAME; \ end \ end define RST_GEN(CLK,RST) \ always (posedge CLK) begin \ RST sys_resetn; \ end // 实例化两个不同频率的时钟 CLK_GEN(uart_sclk_nor, 50) // 50MHz主时钟 CLK_GEN(uart_sclk_24m, 24) // 24MHz备用时钟 // 选择时钟源 assign uart_sclk clk_24m ? uart_sclk_24m : uart_sclk_nor;这里有几个关键点需要注意时钟生成宏要处理好初始相位避免多个时钟同时跳变复位信号建议使用异步复位同步释放策略如果有多个时钟域务必做好跨时钟域处理接口信号连接也有讲究特别是流控信号CTS/RTS的连接方式会影响测试场景设计。建议在验证初期就规划好所有信号连接方案。1.2 UVM环境配置UVM环境搭建是验证工作的核心。我习惯先定义好配置对象这样后续测试用例可以灵活调整参数class uart_shared_cfg extends uvm_object; rand int baud_divisor; rand bit en_9bit; rand bit fifo_en; // 其他配置参数... endclass环境构建时要注意几个关键点配置对象的层次结构要清晰重要参数要通过uvm_config_db传递组件间的连接关系要在connect_phase明确建议在env中统一管理所有配置对象这样维护起来更方便。比如virtual function void build_phase(uvm_phase phase); // 获取或创建配置对象 if(!uvm_config_db#(uart_shared_cfg)::get(this,,uart_cfg,uart_cfg)) begin uart_cfg uart_shared_cfg::type_id::create(uart_cfg); end // 创建agent时传入配置 uart_agent svt_uart_agent::type_id::create(uart_agent, this); uvm_config_db#(svt_uart_configuration)::set(this, uart_agent,cfg,uart_cfg); endfunction2. 验证组件设计与实现2.1 序列(Sequence)设计序列是验证场景的灵魂。设计uart序列时我通常会考虑以下几个维度基础功能序列覆盖基本收发功能异常场景序列测试错误处理能力性能测试序列验证不同波特率下的稳定性这里分享一个实用的序列模板class uart_base_seq extends uvm_sequence; task body(); // 1. 随机化配置 cfg.randomize() with { baud_divisor inside {[1:100]}; data_width EIGHT_BIT; }; // 2. 寄存器配置 set_baudrate(cfg.baud_divisor); set_data_format(cfg.data_width, cfg.stop_bit); // 3. 数据收发 uvm_do_with(req, { payload.size() 10; foreach(payload[i]) payload[i] i; }) endtask endclass实际项目中我还会添加序列的pre_body和post_body方法用于统一处理前后置条件。2.2 记分板(Scoreboard)实现记分板是验证正确性的最后一道防线。对于UART验证我通常会实现以下检查数据一致性检查发送和接收的数据是否一致时序检查波特率是否符合配置协议检查起始位、停止位、校验位是否正确class uart_scoreboard extends uvm_scoreboard; uvm_analysis_imp#(uart_item, uart_scoreboard) item_imp; function void write(uart_item item); // 检查数据一致性 if(item.direction TX) begin tx_queue.push_back(item); end else begin rx_queue.push_back(item); check_data_match(); end // 检查时序 check_baudrate(item); endfunction endclass3. 测试用例开发3.1 基础功能测试基础测试用例要覆盖所有标准功能点。我通常会按照以下顺序开发回环测试验证最基本的数据通路波特率测试覆盖常用波特率数据格式测试不同数据位宽、停止位、校验位组合class uart_loopback_test extends uvm_test; task run_phase(uvm_phase phase); // 配置为回环模式 cfg.loopback 1; // 运行基础序列 uart_base_seq seq uart_base_seq::type_id::create(seq); seq.start(null); endtask endclass3.2 异常场景测试异常测试往往能发现设计中最隐蔽的问题。我重点会测试错误数据格式错误的起始位、停止位波特率失配收发双方波特率不一致流控异常CTS/RTS信号异常变化class uart_error_test extends uvm_test; task run_phase(uvm_phase phase); // 强制注入错误 force_top_error(); // 运行错误检测序列 uart_error_seq seq uart_error_seq::type_id::create(seq); seq.start(null); // 检查设计是否正确处理错误 check_error_handling(); endtask endclass4. 调试技巧与最佳实践4.1 常见问题排查在实际项目中我遇到过各种奇怪的问题。这里分享几个典型案例数据丢失问题通常是时钟域不同步导致建议检查时钟频率是否匹配复位信号是否同步释放FIFO指针是否跨时钟域同步波特率偏差问题可能是分频系数计算错误建议用逻辑分析仪抓取实际波形检查寄存器配置值验证分频系数计算公式流控失效问题重点检查CTS/RTS信号连接是否正确流控使能位是否配置设计是否正确处理流控信号4.2 验证效率优化为了提高验证效率我总结了几个实用技巧自动化检查在记分板中添加自动检查点减少人工检查覆盖率驱动定义清晰的覆盖率模型指导随机测试回归测试建立自动化回归测试框架快速验证修改// 覆盖率收集示例 class uart_cov extends uvm_subscriber; covergroup baudrate_cg; baud_divisor: coverpoint cfg.baud_divisor { bins low {[1:10]}; bins mid {[11:100]}; bins high {[101:200]}; } endgroup endclass最后提醒大家验证环境要尽早搭建最好与设计开发同步进行。我在实际项目中发现越早开始验证后期节省的时间越多。

更多文章