Skip to main content

信号句柄(CallableHDL)

创建 CallableHDL

使用 dut 创建(推荐)

使用 dut 来创建 chdl 需要完整地使用 dut 表示出一个信号的 hierarchy path(这里的 dut 表示的是 Testbench 的模块名称,默认是 tb_top),例如:

local signal = dut.path.to.signal:chdl()

通常对于一些 DUT 的顶层信号,可以使用 dut.xxx 表示,例如:

local signal = dut.clock:chdl()
local signal2 = dut.reset:chdl()

对于 DUT 的内部信号,可以使用 dut.u_<top_module_name>.<internal_signal_name> 表示,具体原因可以查看此处的说明,代码例子如下:

local signal = dut.u_Design.value:chdl()
基于 dut 灵活创建 chdl

Lua 中,可以使用 string 来灵活访问某个 table 的子变量,例如:

local t = {a0 = 1, a1 = 2, b0 = 3, b1 = 4}
local a = t.a0
local b = t.b0

local aa = t["a0"]
local bb = t["b0"]

assert(a == aa)
assert(b == bb)

for i = 0, 1 do
if i == 0 then
assert(t["a" .. i] == t.a0)
else
assert(t["a" .. i] == t.a1)
end
end

可以看到利用字符串拼接等方法访问一些有规律的子变量,这提供了更灵活的方式来访问子变量。

dut 本质上也是一个 table,因此也可以使用 string 来访问 dut 的子变量,例如:

local signals = {}
for i = 1, 5 do
table.insert(signals, dut.path.to["some_module_" .. i].value:chdl())
end

上面这个例子中我们使用 for 循环批量获得了 5 个 chdl 对象,这些对象分别对应 dut 中 some_module_1 到 some_module_5 这五个有规律的模块的 value 字段,并将其放入了一个 table 中。同样地,对于信号名也可以这么操作:

local signals = {}
for i = 1, 5 do
table.insert(signals, dut.path.to["some_value_" .. i]:chdl())
end

使用 class 创建

本质上 CallableHDL 是一个 class,因此可以使用类似 class 的方式创建,例如:

local CallableHDL = require "verilua.handles.LuaCallableHDL"

local chdl = CallableHDL("tb_top.clock", "clock")

-- equivalent to

local chdl = dut.clock:chdl()

CallableHDL 接收两个参数,第一个是信号的完整 hierarchy path,第二个则是 chdl 的名称。

使用 string literal 创建(推荐)

Lua 允许重载 string 的 metatable,Verilua 基于这个机制实现了一种简化的方式来创建 chdl,例如:

local chdl = ("tb_top.clock"):chdl()

这种方式在 Verilua 中被称为 String Literal Constructor Pattern(SLCP),使用 SLCP 的好处在于可以不用像 class 构建那样提前使用 requireLuaCallableHDL 加载到代码中,而是在使用时根据后缀的方法名(:chdl())来直接创建 chdl。除了 chdl 之外,其他的数据结构同样也支持使用 SLCP 的方式来创建,详见 字符串字面量构造模式(SLCP)

CallableHDL 接口

CallableHDL 支持多种信号操作接口,且相关接口函数的功能会与具体信号的位宽有关,因此在使用时需要注意一些细节。

Verilua 中根据信号的位宽不同,隐式地将 chdl 分为了三种类型:

  1. Single:位宽 <= 32 bit 的信号
  2. Double:位宽 > 32 bit 且 <= 64 bit 的信号
  3. Multi:位宽 > 64 bit 的信号
在后续的 API 介绍的内容中,如果没有特别写出针对这三种情况的说明,那么默认情况下相关 API 的行为都是一致的
之所以会有区分是为了尽可能针对位宽实施更多性能上的优化
beat 的概念

硬件信号的位宽是没有限制的,但是在 Lua 中普通的 number 类型能够保存 32 bit 的数值,因此 Verilua 以 32 bit 为单位来表示一部分的信号的值,这个单位称为 beat。例如:1 ~ 32 bit 的信号可以用 1 个 beat 来表示,33 ~ 64 bit 的信号可以用 2 个 beat 来表示,以此类推。beat 的概念在后续的 API 介绍中会经常遇到。

可以使用 <chdl>.beat_num 来获得 beat 数。

信号读取

  1. <chdl>:get()

    返回一个 Lua 的 number 类型的数值,表示当前的信号值。

  2. <chdl>:get64()

    返回一个 Lua 的 number 类型的数值,表示当前的信号值。

  3. <chdl>:get_bitvec()

    返回一个 BitVec,关于 BitVec 可以查看 位向量(BitVec) 的文档。

  4. <chdl>:get_str(fmt)

    获得当前信号的数值,并以 String 的类型返回,接受一个 fmt 参数,用于指定返回的字符串的格式,可以是 HexStrBinStrDecStr

    local signal = dut.value:chdl()

    local value = signal:get()
    assert(value == 0x123)

    local value_hex_str = signal:get_str(HexStr)
    assert(value_hex_str == "123")

    local value_bin_str = signal:get_str(BinStr)
    assert(value_bin_str == "100100011")

    local value_dec_str = signal:get_str(DecStr)
    assert(value_dec_str == "291")
    这里的 HexStrBinStrDecStr 是 Verilua 预定义的全局变量,可以直接使用
  5. <chdl>:get_hex_str()

    获得当前信号的数值,并以 Hex String 的类型返回。

    local signal = dut.value:chdl()

    local value = signal:get()
    assert(value == 0x123)

    local value_hex_str = signal:get_hex_str()
    assert(value_hex_str == "123")
  6. <chdl>:get_dec_str()

    类似 get_hex_str,但是返回的是 Decimal String 类型的字符串。

  7. <chdl>:get_bin_str()

    类似 get_hex_str,但是返回的是 Binary String 类型的字符串。

信号赋值

  1. <chdl>:set(value)

    value 赋值给当前信号。

    local signal = dut.value:chdl()
    local value = 0x123
    signal:set(value)
  2. <chdl>:set_unsafe(value)

    set 类似,但是不会检查 value 的正确性,省去了 assert 的开销。适合在热路径(例如每拍都需要驱动的信号)中使用;功能性测试建议使用 set

  3. <chdl>:set_cached(value)

    set 一样都用于赋值信号,但是在赋值的时候会将当前的信号值加入到缓存中,如果下次赋值的时候发现当前的信号值没有变化,那么就不会赋值,这样可以减少一些不必要的信号赋值。

  4. <chdl>:set_str(str)

    一个通用的字符串赋值方式,可以接收一个 Lua string 类型的字符串,具体的字符串类型由 str 的前两位来区分,例如:

    local signal = dut.value:chdl()

    -- set hex string
    signal:set_str("0x123")

    -- set binary string
    signal:set_str("0b01011")

    -- set decimal string
    signal:set_str("123")

    0x 表示为 Hex String,0b 表示为 Binary String,其他的字符串则表示为 Decimal String。

  5. <chdl>:set_hex_str(str)

    使用 Hex String 赋值,str 是一个 Lua string 类型的字符串。

    local signal = dut.value:chdl()

    signal:set_hex_str("123")

    clock:posedge()

    signal:expect(0x123)
  6. <chdl>:set_bin_str(value)

    使用 Binary String 赋值,value 是一个 Lua string 类型的字符串。

    local signal = dut.value:chdl()

    signal:set_bin_str("1010")

    clock:posedge()

    signal:expect(0xA)
  7. <chdl>:set_dec_str(value)

    使用 Decimal String 赋值,value 是一个 Lua string 类型的字符串。

    local signal = dut.value:chdl()

    signal:set_dec_str("12")

    clock:posedge()

    signal:expect(12)
  8. <chdl>:set_shuffled()

    对当前信号进行随机赋值,这在验证中很常用。

  9. <chdl>:set_bitfield(s, e, v)

    设置信号的值 v,并且只在 se 之间的位宽上赋值。v 可以是 Lua number 类型的数值,也可以是一个 uint64_t 类型的 cdata

    local signal = dut.value:chdl()

    local value = 0x123
    signal:set_bitfield(0, 7, value)
  10. <chdl>:set_bitfield_hex_str(s, e, hex_str)

    设置信号的值,并且只在 se 之间的位宽上赋值。hex_str 是一个 Hex String 类型的字符串。

    local signal = dut.value:chdl()

    local value = "123"
    signal:set_bitfield_hex_str(0, 7, value)
  11. <chdl>:set_force(value)

    强制赋值(与 set_release 配合使用),与 SystemVerilog 中的 force 关键字相同。除了 force 这个属性上的区别之外,其他和 set 一样。

    local signal = dut.value:chdl()
    local value = 0x123
    signal:set_force(value)

    -- ...

    signal:set_release()
  12. <chdl>:set_release()

    释放赋值(与 set_force 配合使用),与 SystemVerilog 中的 release 关键字相同。对于使用了 set_force 的信号,需要使用 set_release 来释放赋值,否则会导致信号的值不会更新。

  13. <chdl>:set_imm(value)

    立即赋值版本的 set,除了立即赋值的属性之外,其他和 set 一样。

    立即赋值和普通赋值的区别

    set 是延迟赋值:调用时只把值放入 pending 队列,不会立刻更新 HDL;Verilua 会在 VPI 的 cbReadWriteSynch 阶段统一写入,因此后续进入 cbReadOnlySynch(例如 await_rd())时可以读到新值。set() 的生效点不由时钟边沿决定。

    set_imm 是立即赋值:调用时直接写入 HDL,随后读取同一个信号通常能立刻看到新值。

    阶段示意:

    Lua code VPI ReadWrite VPI ReadOnly
    ──────── ───────────── ────────────
    signal:set(0x42) -> flush pending set -> get() == 0x42
    get() == 旧值

    signal:set_imm(0x42)
    get() == 0x42

    代码示例:

    local signal = dut.value:chdl() -- assume that the initial value is 0x00

    signal:set(0x123)
    assert(signal:get() == 0x00) -- set() 不会立即生效

    await_rd()
    assert(signal:get() == 0x123) -- ReadOnly 阶段可见

    signal:set_imm(0x100)
    assert(signal:get() == 0x100) -- set_imm() 立即生效

    同一阶段内多次调用 set(),只有最后一次写入的值会在 pending 队列 flush 后可见:

    signal:set(0x11)
    signal:set(0x22)
    signal:set(0x33) -- flush 后可见的是 0x33
    常见陷阱

    陷阱:set 后立即 get 拿到旧值

    signal:set(0xFF)
    local v = signal:get() -- v == 旧值,不是 0xFF!
    -- 如果需要写入后立即读回,请使用 set_imm
  14. set_imm_* 立即赋值变体

    每个赋值方法均有对应的 set_imm_* 立即赋值变体,行为与普通版本完全相同,但立即生效(参见立即赋值和普通赋值的区别):

    set_imm_unsafeset_imm_cachedset_imm_strset_imm_hex_strset_imm_bin_strset_imm_dec_strset_imm_shuffledset_imm_bitfieldset_imm_bitfield_hex_strset_imm_forceset_imm_release

  15. <chdl>:set_freeze()

    冻结当前的信号值,在后续的仿真中,该信号的值将保持不变。需要调用 <chdl>:set_release() 来取消冻结。

debug 相关

  1. <chdl>:dump()

    用于将信号的值(主要是以 Hex String 的形式)输出到控制台,可以用于查看信号的值,打印的内容如下所示:

    Terminal
    [tb_top.value] => 0x01
  2. <chdl>:dump_str()

    会将原本 <chdl>:dump() 的输出的内容作为一个返回值进行返回,因此 <chdl>:dump() 也等价于 print(<chdl>:dump_str())

  3. <chdl>:get_width()

    获得信号的位宽,也可以直接访问 <chdl>.width 来获得。

  4. <chdl>:__len()

    chdl 重载了 Lua 的 metatable 的 __len 方法,可以直接使用 #<chdl> 来获得信号的位宽。下面的三种方式来获得信号位宽是等价的:

    local signal = dut.value:chdl()
    assert(#signal == 32)
    assert(signal:get_width() == 32)
    assert(signal.width == 32)

验证相关

  1. <chdl>:expect(value)

    用于断言信号的值,在验证中很常用,如果信号的值与期望值相等则什么也不会发生,如果不相等则会打印报错信息并停止仿真。错误信息格式如下所示:

    Terminal
    [tb_top.value] expect => 10, but got => 0

    value 的值是一个 Lua 的 number 类型的值。

  2. <chdl>:expect_not(value)

    <chdl>:expect(value) 类似,但是如果信号的值与期望值相等则会打印报错信息并停止仿真。

  3. <chdl>:expect_hex_str(hex_value_str)

    比较信号的值是否为指定的 Hex String 值,如果不相等则会打印报错信息并停止仿真。

    local signal = dut.value:chdl()

    signal:expect_hex_str("123")
  4. <chdl>:expect_bin_str(bin_value_str)

    比较信号的值是否为指定的 Binary String 值,如果不相等则会打印报错信息并停止仿真。

    local signal = dut.value:chdl()

    signal:expect_bin_str("1010")
  5. <chdl>:expect_dec_str(dec_value_str)

    比较信号的值是否为指定的 Decimal String 值,如果不相等则会打印报错信息并停止仿真。

    local signal = dut.value:chdl()

    signal:expect_dec_str("12")
  6. <chdl>:expect_not_hex_str / expect_not_bin_str / expect_not_dec_str

    分别与 expect_hex_strexpect_bin_strexpect_dec_str 作用相反。

  7. <chdl>:is(value)

    判断信号的值是否等于某个值,如果等于则返回 true,否则返回 false

    value 的值是一个 Lua 的 number 类型的值。

  8. <chdl>:is_not(value)

    <chdl>:is(value) 的作用相反。

  9. <chdl>:is_hex_str(hex_value_str) / is_bin_str(bin_value_str) / is_dec_str(dec_value_str)

    判断信号的值是否等于某个 Hex / Binary / Decimal String 值,等于则返回 true,否则返回 false

    local signal = dut.value:chdl()

    local correct = signal:is_hex_str("123")
    local correct = signal:is_bin_str("1010")
    local correct = signal:is_dec_str("12")

信号回调管理

信号回调管理函数只能作用在信号位宽为 1 的信号上
  1. <chdl>:posedge(times, func)

    用于等待信号的上升沿到来,timesfunc 是可选的两个参数,times 表示等待的次数,func 表示回调函数。

    local clock = dut.clock:chdl()

    clock:posedge() -- wait for one posedge
    clock:posedge(10) -- wait for 10 posedges

    clock:posedge(10, function (c)
    -- `func` will be called every time posedge arrives, and the argument `c` is the count of the posedge
    print("posedge count => ", c)
    end)
  2. <chdl>:negedge(times, func)

    <chdl>:posedge(times, func) 类似,但是在等待信号的下降沿到来。

  3. <chdl>:posedge_until(max_limit, func)

    在每一轮检查中,Verilua 会先调用一次 func;若返回 true 则停止并返回 true,若返回 false 才会继续等待下一个时钟上升沿并进入下一轮。若达到 max_limit 轮后 func 仍未返回 true,则返回 false

    注意:第一次调用 func 发生在进入等待之前,因此如果 func(1) 立即返回 true<chdl>:posedge_until(...) 会直接返回 true,此时可能还没有发生任何 posedge

    local clock = dut.clock:chdl()

    local ok = clock:posedge_until(100, function ()
    return dut.u_Design.ready:chdl():get() == 1
    end)
    assert(ok, "ready never asserted within 100 cycles")
  4. <chdl>:negedge_until(max_limit, func)

    <chdl>:posedge_until(max_limit, func) 类似,但是在等待信号的下降沿到来。调用顺序也相同:会先立即调用一次 func,只有返回 false 时才会继续等待下一个 negedge

具备自动格式识别的赋值接口

上述的 API 介绍中可以看到针对信号赋值有许多的函数,有时候用户可能需要一种更灵活的方式来赋值信号,能够根据此时输入的值的格式来调用合适的赋值接口函数,Verilua 通过重载 Lua metatable 的 __newindex 方法来实现了这一点。

调用的格式为:<chdl>.value = <value>,其中 <value> 是一个任意能够表示数值的值,注意到这里等号左边的 .value,这是为了能够触发 __newindex 元方法,因此在调用时需要加上。

目前 <value> 支持的格式包括:

  • Lua number 类型的数值;

    <chdl>.value = 123
    <chdl>.value = 0x123
  • Lua string 类型的字符串(对于 Hex 和 Binary 需要带上前缀);

    <chdl>.value = "123"
    <chdl>.value = "0x123"
    <chdl>.value = "0b01011"
  • Lua table(或者叫 list),里面的元素为 number 类型;

    <chdl>.value = {0x123, 0x456, 0x789}
  • LuaJIT 的 cdata,包括:uint64_tuint32_t[]

    <chdl>.value = 0x123ULL -- uint64_t cdata

    local vec = ffi.new("uint32_t[?]", 4) -- uint32_t[] cdata
    vec[1] = 0x123 -- Notice: the index is 1-based
    vec[2] = 0x456
    vec[3] = 0x789
    <chdl>.value = vec
    对于 uint32_t[] 的赋值,需要用户手动使用 ffi 创建,因此建议还是采用 Lua table 的方式
  • Lua boolean 类型的值;

    <chdl>.value = true
    <chdl>.value = false
可以在一些性能不敏感的场景下使用这种自动识别格式的赋值方式,例如在一些模块的 UT 测试中,这样可以简化业务代码

具备自动格式识别的比较接口

在实际的验证中,往往需要对信号的值进行比较,来判断信号的值是否符合预期,但是信号的数值表示方式在 chdl 中有多种类型,例如 Lua 的 number、string、table、cdata 等,这些类型的比较方式也是不同的,用户可能需要一种能够根据输入的值的格式来进行数值比较的接口,因此 Verilua 通过重载 Lua metatable 的 __eq 方法来实现了这一点。

调用的格式为:<chdl> == <value_wrapper>(<value>),其中 <value> 是一个任意能够表示数值的值,为了能够触发 __eq 元方法,需要给 <value> 包上一个 <value_wrapper>

<value_wrapper> 有三种可以选择,都是全局可用的一个全局变量,分别是:

  • v:最普通的 <value_wrapper>,只是用来触发 __eq 元方法,不对 <value> 做额外的处理;

  • vv:verbose 版本的 v,在比较信号的值时,如果比较失败,那么会在命令行中打印出 log 信息,便于调试,报错信息如下所示:

    Terminal
    [tb_top.value] expect => 0132, but got => 0032
  • vs:verbose + stop 版本的 v,在比较信号的值时,如果比较失败,那么会在命令行中打印出 log 信息,同时还会触发 assert 断言报错停止仿真,因此 vs 也等价于 assert(<chdl> == vv(<value>))

目前 <value> 支持的格式包括(以 v 这个 <value_wrapper> 为例):

  • Lua number 类型的数值;

    local correct = <chdl> == v(123)
    local correct = <chdl> == v(0x123)
  • Lua string 类型的字符串(对于 Hex 和 Binary 需要带上前缀);

    local correct = <chdl> == v("0x123")
    local correct = <chdl> == v("0b01011")
  • Lua table(或者叫 list),里面的元素为 number 类型;

    local correct = <chdl> == v({0x123, 0x456, 0x789})
  • LuaJIT 的 cdata,包括:uint64_tuint32_t[]

    local correct = <chdl> == v(0x123ULL) -- uint64_t cdata

    local vec = ffi.new("uint32_t[?]", 4) -- uint32_t[] cdata
    vec[1] = 0x456 -- Notice: the index is 1-based
    vec[2] = 0x789
    vec[3] = 0x000
    local correct = <chdl> == v(vec)
    对于 uint32_t[] 的赋值,需要用户手动使用 ffi 创建,因此建议还是采用 Lua table 的方式
  • Lua boolean 类型的值;

    local correct = <chdl> == v(true)
    local correct = <chdl> == v(false)
  • BitVec 类型的值;

    local correct = <chdl> == v(BitVec(123))
    local correct = <chdl> == v(BitVec("123"))
可以在一些性能不敏感的场景下使用这种自动识别格式的比较方式,例如在一些模块的 UT 测试中,这样可以简化业务代码

CallableHDL 接口(Array)

TODO: 针对 Array 类型的信号,Verilua 的 chdl 由独立的 API 进行赋值。

相关文档