Skip to content

编写可复用的验证组件

Verilua 提倡编写可复用的验证组件,以便在多个场景(HVL、HSE、WAL)下复用,例如常见的 Monitor 组件,或者 Scoreboard 组件。

使用 Bundle 编写验证组件

正常情况下,Verilua 中可以通过一系列的数据结构 来和硬件信号进行交互,但是在这些数据结构通常需要指定一些和当前设计信号相关的信息从而才能构建出来,例如 hierarchy path、signal name 等。

针对这两个信息,不同的 DUT 下可能会有所不同,有可能是 hierarchy path 不同,例如在 DUT_A 中,hierarchy path 是 tb_top.path.to.mod,在 DUT_B 中是 tb_top.another.path.to.mod,也有可能是 signal name 不同,例如在 DUT_A 中,signal name 是 validvalue,在 DUT_B 中是 vldvalue_2。假设我们的一个验证组件 Monitor 需要接收一个 Bundle 来作为信号输入来监测前面提到的这两个信号,作用是当 validvld 有效的时候,打印出当前的 valuevalue_2 的值:

Monitor.lua
local class = require "pl.class"
local texpect = require "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

上述代码的相关说明如下:

  • local class = require "pl.class" 这里使用到了 penlight 库的 class,具体可以参考 penlight 官方文档

  • local texpect = require "TypeExpect" 这里使用到了 Verilua 内置的 TypeExpect 模块,具体可以参考 TypeExpect 模块,主要用来检查参数的类型。

  • texpect.expect_bdl(signals, "signals") 的作用和下面是一样的:

    assert(type(signals) == "table")
    assert(signals.__type == "Bundle")
    

    Verilua 的数据结构都有 __type 字段,这个字段用来表示这个数据结构的类型

有了上述的模块,我们在 DUT_A 中可以这样创建并使用:

test_dut_a.lua
local Monitor = require "Monitor"

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

local mon = Monitor(signals_bdl)

fork {
    function ()
        -- ...

        dut.clock:posedge(100, function (c)
            mon:sample(c)
        end)

        -- ...

        sim.finish()
    end
}

上述代码在 DUT_A 中可以完美使用,但是如果我们在 DUT_B 中使用,则需要改动一下 Monitor.lua 的内容来适配 DUT_B 中的不一样的信号信息:

  • Monitor.lua 中的第 13 行,将 valid 改为 vld
  • Monitor.lua 中的第 14 行,将 value 改为 value_2

这样我们就得到了下面的代码:

Monitor_1.lua
local class = require "pl.class"
local texpect = require "TypeExpect"

local Monitor = class()

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

    self.signals = signals
end

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

可以看到和 Monitor.lua 一样,只是在 Monitor.lua 中的第 13 行和第 14 行中的 validvalue 改为了 vldvalue_2

我们同样可以在 DUT_B 中使用:

test_dut_b.lua
local Monitor = require "Monitor_1"

local signals_bdl = ([[
    | vld
    | value_2
]]):bdl {hier = "tb_top.another.path.to.mod", prefix = "", is_decoupled = false}

local mon = Monitor(signals_bdl)

fork {
    function ()
        -- ...

        dut.clock:posedge(100, function (c)
            mon:sample(c)
        end)

        -- ...

        sim.finish()
    end
}

这样的做法就会导致原本可以复用的 Monitor 组件因为信号信息不同而不得不重新编写。核心的问题在于两个 DUT 中的信号命名不一样,我们的 Monitor 组件实现的时候使用的是某个 DUT 中的信号,如果在另一个 DUT 中使用,那么就需要改动一下 Monitor.lua 代码,将信号名进行对应的修改。

使用 AliasBundle 编写验证组件

我们可以使用 AliasBundle 来解决这个问题,Monitor 组件可以接收一个 AliasBundle 作为信号输入(代替原有的 Bundle) ,在 AliasBundle 可以对信号创建别名,这个别名在 DUT_A 中和 DUT_B 中都可以设置成一样的,这样我们的 Monitor 组件不需要做任何修改,就可以在不同的 DUT 中使用了。

下面是一个例子:

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

local Monitor = class()

function Monitor:_init(signals)
    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

上述代码中,使用了 texpect.expect_abdl(signals, "signals", { "valid", "value" }) 来确保用户输入的 signals 是一个 AliasBundle,其中包含了 validvalue 两个信号。

在 DUT_A 中可以这样使用:

test_dut_a.lua
local Monitor = require "Monitor"

local signals_bdl = ([[
    | valid => valid
    | value => value
]]):abdl {hier = "tb_top.path.to.mod", prefix = ""}
-- or
-- local signals_bdl = ([[
--     | valid
--     | value
-- ]]):abdl {hier = "tb_top.path.to.mod", prefix = ""}

local mon = Monitor(signals_bdl)

-- ...

在 DUT_B 中可以这样使用:

test_dut_b.lua
local Monitor = require "Monitor"

local signals_bdl = ([[
    | vld => valid
    | value_2 => value
]]):abdl {hier = "tb_top.another.path.to.mod", prefix = ""}

local mon = Monitor(signals_bdl)

-- ...

这样我们在 DUT_A 和 DUT_B 中都可以复用同一个 Monitor 组件了,不需要因为具体的信号名称不同而修改 Monitor 的代码。

总结

通过上面的例子,我们可以使用 AliasBundle 结合 TypeExpect 可以编写出一个可复用的验证组件,并且在不同的 DUT 中使用。TypeExpectexpect_abdl 方法还能检查信号的位宽是否满足要求,具体可以参考此处的代码。