Skip to main content

编写可复用的验证组件

在硬件验证中,我们经常需要编写可复用的验证组件(如 Monitor、Scoreboard),以便在不同的测试环境和设计中复用。Verilua 提供了强大的数据结构(BundleAliasBundle)以及类型检查工具(TypeExpect),帮助我们构建真正可复用的组件。

问题:组件与设计紧耦合

假设我们有一个 Monitor 组件,用于监控两个信号:validvalue。它的实现可能是这样的:

Monitor.lua
local class = require "pl.class"
local texpect = require "verilua.TypeExpect"

local Monitor = class()

function Monitor:_init(signals)
texpect.expect_bdl(signals, "signals")
self.signals = signals
end

function Monitor:sample(cycles)
if self.signals.valid:is(1) then
print("[Monitor] get value =>", self.signals.value:get_hex_str(), "at", cycles)
end
end

return Monitor

这个组件期望传入一个包含 validvalue 两个信号的 Bundle。现在有两个不同的设计:

  • DUT_A:信号名为 validvalue,层次路径为 tb_top.path.to.mod
  • DUT_B:信号名为 vldvalue_2,层次路径为 tb_top.another.path.to.mod

如果直接将此组件用于 DUT_B,就必须修改组件内部代码(将 valid 改为 vldvalue 改为 value_2),这样组件就失去了复用性。

解决方案一:使用 Bundle(仍不理想)

使用 Bundle 可以将信号打包,但信号名称仍然固定。在 DUT_A 中可以这样使用:

test_dut_a.lua
local signals_bdl = ([[
| valid
| value
]]):bdl {
hier = "tb_top.path.to.mod",
prefix = "",
is_decoupled = false
}

local mon = Monitor(signals_bdl)

但在 DUT_B 中,为了复用,我们必须创建一个名称完全匹配的 Bundle,这要求信号名恰好是 validvalue。如果 DUT_B 中信号名不同,就必须在测试代码中重新定义,或者修改 Monitor 的代码。Bundle 本身并不能解决信号名称差异的问题。

解决方案二:使用 AliasBundle(推荐)

AliasBundle 允许为信号创建别名,从而将实际信号名与组件内部使用的名称解耦。组件内部只关心别名,而外部测试代码负责将实际信号映射到这些别名上。

定义组件时声明所需信号的别名

Monitor.lua
local class = require "pl.class"
local texpect = require "verilua.TypeExpect"

local Monitor = class()

function Monitor:_init(signals)
-- 检查 signals 是一个 AliasBundle,并且包含必需的别名 valid 和 value
texpect.expect_abdl(signals, "signals", { "valid", "value" })
self.signals = signals
end

function Monitor:sample(cycles)
if self.signals.valid:is(1) then
print("[Monitor] get value =>", self.signals.value:get_hex_str(), "at", cycles)
end
end

return Monitor

关键点:

  • 使用 texpect.expect_abdl(signals, "signals", { "valid", "value" }) 确保传入的是一个 AliasBundle,并且其中必须包含别名为 validvalue 的信号。
  • 组件内部通过别名 validvalue 访问信号,与实际信号名无关。

在不同设计中映射实际信号到别名

DUT_A(信号名与别名恰好一致,可以直接映射):

test_dut_a.lua
local signals_bdl = ([[
| valid => valid
| value => value
]]):abdl {
hier = "tb_top.path.to.mod",
prefix = ""
}
-- 如果信号名和别名相同,也可以简写为:
-- local signals_bdl = ([[ | valid | value ]]):abdl { hier = "..." }

local mon = Monitor(signals_bdl)

DUT_B(信号名不同,通过 => 显式映射):

test_dut_b.lua
local signals_bdl = ([[
| vld => valid -- 实际信号 vld 映射到别名 valid
| value_2 => value -- 实际信号 value_2 映射到别名 value
]]):abdl {
hier = "tb_top.another.path.to.mod",
prefix = ""
}

local mon = Monitor(signals_bdl)

这样,Monitor 的代码无需任何修改,就能在 DUT_B 中复用。

特殊情况:某些信号不存在怎么办?

有时不同设计的信号配置不同,例如:

  • DUT_A:有 validvalue 两个信号
  • DUT_B:只有 valid 信号,没有 value 信号

我们的 Monitor 期望同时接收 validvalue,直接复用会失败。此时可以使用 虚拟信号(fake_chdl) 来解决。

虚拟信号是一个实现了 CallableHDL 接口的 Lua 对象,可以模拟真实信号的行为。通过将虚拟信号注入到 AliasBundle 中,我们可以满足组件对信号的依赖。

示例:在 DUT_B 中注入虚拟 value 信号

test_dut_b.lua
local Monitor = require "Monitor"

-- 创建 AliasBundle,包含 valid 信号,value 信号先留空(用 [value] 标记为可选)
local signals_bdl = ([[
| vld => valid
| [value]
]]):abdl {
hier = "tb_top.another.path.to.mod",
prefix = ""
}

-- 创建一个虚拟的 value 信号,总是返回 0
local fake_value = ("tb_top.u_dut.fake_value"):fake_chdl {
get = function(self)
return 0 -- 返回固定值
end,
set = function(self, value)
-- 忽略设置操作
end,
get_width = function(self)
return 32 -- 模拟 32 位信号
end,
is = function(self, value)
return value == 0
end
}

-- 将虚拟信号注入到 AliasBundle 的 value 别名中
signals_bdl.value = fake_value

local mon = Monitor(signals_bdl)

更复杂的虚拟信号示例

如果需要更真实的行为,可以模拟随机值或记录写入值:

local fake_value = ("tb_top.u_dut.fake_value"):fake_chdl {
get = function(self)
return math.random(0, 255) -- 返回 0-255 之间的随机值
end,
set = function(self, value)
self._last_value = value -- 记录最后一次写入的值
end,
get_width = function(self)
return 8
end,
is = function(self, value)
return value == self:get()
end
}

注意事项

  1. 虚拟信号的行为应合理:根据验证场景,虚拟信号应该返回有意义的默认值,避免影响验证逻辑。
  2. 必须实现 get_width 方法TypeExpect 在检查位宽时可能会调用此方法,必须提供。
  3. 虚拟信号只是临时方案:长期来看,应考虑修改 DUT 或调整组件以支持可选信号。
  4. 在 AliasBundle 中标记可选信号:使用方括号 [signal_name] 标记信号为可选,这样即使实际设计中不存在,也不会报错。

总结

编写可复用的验证组件,核心在于解耦

  • 使用 AliasBundle 将信号名称与组件内部逻辑解耦。
  • 使用 TypeExpect 在组件入口处进行类型和信号存在性检查,提前暴露错误。
  • 当某些设计缺少信号时,使用 fake_chdl 注入虚拟信号,保持组件兼容性。

遵循这些原则,您编写的验证组件可以在多个设计、多种场景(HVL、HSE、WAL)中无缝复用,极大提高验证效率。