如何为自动售货机设计编写UVM Driver与Sequence Item(面试题)
自动售货机DV面试实现优化建议
一、Sequence Item问题与优化
原实现的问题点
- 枚举类型错误:定义的
currency枚举包含3,但规格明确货币面额为1、2、5、10,属于规格不符。 - randc使用不当:
randc currency value中randc是周期性不重复随机,仅用于同面额约束场景,用普通rand即可,避免不必要的随机限制。 - 约束过于僵化:
constraint c1 {amount.sum() >=60 ;}强制总金额≥60,但测试场景需要覆盖总金额不足60的情况(如59美元),应改为可动态开关的约束,或默认不限制,由sequence按需添加。 - 缺少UVM必要宏与构造函数:未添加
uvm_object_utils注册宏及构造函数,无法正常在UVM环境中实例化与使用。 - 输出信号定义冗余:
change和soda_out是DUT的输出信号,无需在sequence item中随机化,应作为预期值的存储变量,可添加计算函数自动生成预期结果,方便后续比对。
优化后的Sequence Item示例
class vending_pkt extends uvm_sequence_item; typedef enum {DOLLAR_1, DOLLAR_2, DOLLAR_5, DOLLAR_10} currency_t; rand currency_t amount[]; rand currency_t same_currency; // 用于同面额约束,替换原randc int expected_change; bit expected_soda_out; // UVM注册宏 `uvm_object_utils(vending_pkt) // 构造函数 function new(string name = "vending_pkt"); super.new(name); endfunction // 默认不限制总金额,由sequence根据场景添加约束 constraint amount_size {amount.size() inside {[1:100]};} // 限制数组长度合理范围 // 同面额约束:可通过sequence的constraint_mode控制开关 constraint same_currency_c { foreach(amount[i]) amount[i] == same_currency; } // 包含所有面额约束:可通过constraint_mode控制开关 constraint all_currency_c { amount.sum() with (int'(item == DOLLAR_1)) > 0; amount.sum() with (int'(item == DOLLAR_2)) > 0; amount.sum() with (int'(item == DOLLAR_5)) > 0; amount.sum() with (int'(item == DOLLAR_10)) > 0; } // 自动计算预期输出 function void post_randomize(); int total = 0; foreach(amount[i]) begin case(amount[i]) DOLLAR_1: total += 1; DOLLAR_2: total += 2; DOLLAR_5: total += 5; DOLLAR_10: total += 10; endcase end if(total >= 60) begin expected_soda_out = 1; expected_change = total - 60; end else begin expected_soda_out = 0; expected_change = 0; // 若设计要求退钱则改为total,需按规格调整 end endfunction endclass
二、Driver问题与优化
原实现的问题点
- 时序不符合规格:
- 规格要求“每次投入货币后,valid信号在下一周期拉高”,原代码中
valid拉高后未拉低,会持续保持高电平,不符合“拉高一个周期”的要求。 done信号仅赋值为1,未处理拉低逻辑,且未考虑与最后一次valid信号的时序关系。
- 规格要求“每次投入货币后,valid信号在下一周期拉高”,原代码中
- run_phase结构错误:仅执行一次
get_next_item就结束,无法持续处理sequence发送的多个交易。 - 时钟域不一致:drive任务中混用
vif.cb和@(posedge clk),应统一使用virtual interface的clocking block,保证时序对齐。
优化后的Driver伪代码
virtual task run_phase(uvm_phase phase); vending_pkt req; forever begin // 持续获取交易 seq_item_port.get_next_item(req); drive(req); seq_item_port.item_done(); end endtask virtual task drive(vending_pkt req); int i = 0; // 初始化信号 vif.cb.amount <= '0; vif.cb.valid <= 1'b0; vif.cb.done <= 1'b0; repeat(req.amount.size()) begin // 1. 投放当前面额货币 case(req.amount[i]) DOLLAR_1: vif.cb.amount <= 1; DOLLAR_2: vif.cb.amount <= 2; DOLLAR_5: vif.cb.amount <= 5; DOLLAR_10: vif.cb.amount <= 10; endcase @(posedge vif.cb); // 2. 下一周期拉高valid一个周期 vif.cb.valid <= 1'b1; @(posedge vif.cb); vif.cb.valid <= 1'b0; // valid拉低 i++; end // 3. 所有货币投入完成后,下一周期拉高done一个周期 @(posedge vif.cb); vif.cb.done <= 1'b1; @(posedge vif.cb); vif.cb.done <= 1'b0; // done拉低 endtask
三、补充测试场景
在你列出的基础场景外,需补充以下关键测试场景:
- 找零组合验证:总金额为73(找零13,即10+2+1)、87(找零27,即10+10+5+2)等,验证DUT找零的面额组合是否正确。
- 小面额凑单场景:用最多1美元(60个1)、最多2美元(30个2),或零散小面额凑60(如58个1+1个2),验证金额累加逻辑的正确性。
- 异常信号时序场景:
- valid连续多周期拉高(未切换amount)
- valid与done同时拉高
- done信号在货币未投完时提前拉高
- 非法面额输入场景:输入规格外的面额(如3、20),验证DUT的错误处理逻辑(如忽略、报错)。
- 连续交易场景:连续完成多次购买(如第一次投60拿汽水,第二次投70拿汽水+找零10),验证DUT的状态重置是否正常。
- 大额超额场景:投入100美元,验证找零40美元的计算与输出是否正确。
- 金额不足的退钱场景:若设计支持投钱未达60时退钱,需测试投59美元后拉done,验证DUT是否退回全部金额。
内容的提问来源于stack exchange,提问作者Grace90
相关产品推荐
相关产品推荐

