信号句柄(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 灵活创建 chdlLua 中,可以使用 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 构建那样提前使用 require 将 LuaCallableHDL 加载到代码中,而是在使用时根据后缀的方法名(:chdl())来直接创建 chdl。除了 chdl 之外,其他的数据结构同样也支持使用 SLCP 的方式来创建,详见 字符串字面量构造模式(SLCP)。
CallableHDL 接口
CallableHDL 支持多种信号操作接口,且相关接口函数的功能会与具体信号的位宽有关,因此在使用时需要注意一些细节。
Verilua 中根据信号的位宽不同,隐式地将 chdl 分为了三种类型:
- Single:位宽 <= 32 bit 的信号
- Double:位宽 > 32 bit 且 <= 64 bit 的信号
- Multi:位宽 > 64 bit 的信号
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 数。
信号读取
-
<chdl>:get()- Single
- Double
- Multi
返回一个 Lua 的 number 类型的数值,表示当前的信号值。
<chdl>:get(force_multi_beat)-
如果
force_multi_beat为true,那么返回的是一个类型为uint32_t[]的 LuaJITcdata,这个cdata的大小为 2,可以使用[1]访问第一个元素,[2]访问第二个元素。由于cdata不是 Lua 的table,因此实际上[0]也是可以访问的,但是这里 Verilua 会将[0]赋值为 beat 的大小,也就是这里的 2。这里的cdata的 index 从 1 开始,这与 Lua 的table的 index 是一样的 -
如果
force_multi_beat为false,那么返回的是uint64_t类型的数值,由于 Double 类型的chdl的位宽为 32 ~ 64 bit,因此可以完整表达当前的数值。
返回的是一个类型为
uint32_t[]的 LuaJITcdata,这个cdata的大小为当前信号的 beat 数,可以使用[1]访问第一个元素,[2]访问第二个元素,以此类推,[0]也是可以访问的,但是这里 Verilua 会将[0]赋值为 beat 的大小,例如对于 128 bit 的信号,[0]会被赋值为 4。 -
<chdl>:get64()- Single
- Double / Multi
返回一个 Lua 的 number 类型的数值,表示当前的信号值。
返回
uint64_t类型的数值。由于 Double 类型的chdl的位宽为 32 ~ 64 bit,因此可以完整表达当前的数值,但是对于 Multi 类型的chdl,此时 beat 大于 2,因此返回的uint64_t类型的数值不能完整表示当前的信号值,只会返回低 64 bit 的值。 -
<chdl>:get_bitvec()返回一个
BitVec,关于BitVec可以查看 位向量(BitVec) 的文档。 -
<chdl>:get_str(fmt)获得当前信号的数值,并以 String 的类型返回,接受一个
fmt参数,用于指定返回的字符串的格式,可以是HexStr、BinStr、DecStr。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")这里的HexStr、BinStr、DecStr是 Verilua 预定义的全局变量,可以直接使用 -
<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") -
<chdl>:get_dec_str()类似
get_hex_str,但是返回的是 Decimal String 类型的字符串。 -
<chdl>:get_bin_str()类似
get_hex_str,但是返回的是 Binary String 类型的字符串。
信号赋值
-
<chdl>:set(value)- Single
- Double / Multi
将
value赋值给当前信号。local signal = dut.value:chdl()local value = 0x123signal:set(value)<chdl>:set(value)根据
value的类型自动选择赋值路径:-
如果
value是一个 table,则按多拍赋值。table 的大小需要和信号的 beat 数相同,否则会报错。table 的数值以<LSB> ~ <MSB>的顺序排列。local signal = dut.value:chdl()local value = {0x123, 0x456}signal:set(value) -
如果
value是一个 number 或uint64_t类型的cdata,则按标量赋值。number 赋值信号的低 32 bit,uint64_t赋值信号的低 64 bit,高于 64 bit 的位置将会被赋值为 0。local signal = dut.value:chdl()local value = 0x123signal:set(value)local value64 = 0x1234567890ABCDEFULLsignal:set(value64)LuaJIT 中对一串数字添加上ULL的后缀就可以表示一个uint64_t类型的cdata如果你刚从标准 Lua 切换过来,还不熟悉
ULL和cdata,可以先看LuaJIT 与标准 Lua 的常见差异。
-
<chdl>:set_unsafe(value)和
set类似,但是不会检查value的正确性,省去了assert的开销。适合在热路径(例如每拍都需要驱动的信号)中使用;功能性测试建议使用set。 -
<chdl>:set_cached(value)- Single
- Double / Multi
和
set一样都用于赋值信号,但是在赋值的时候会将当前的信号值加入到缓存中,如果下次赋值的时候发现当前的信号值没有变化,那么就不会赋值,这样可以减少一些不必要的信号赋值。暂不支持 Cached 赋值方式。
-
<chdl>:set_str(str)一个通用的字符串赋值方式,可以接收一个 Lua string 类型的字符串,具体的字符串类型由
str的前两位来区分,例如:local signal = dut.value:chdl()-- set hex stringsignal:set_str("0x123")-- set binary stringsignal:set_str("0b01011")-- set decimal stringsignal:set_str("123")0x表示为 Hex String,0b表示为 Binary String,其他的字符串则表示为 Decimal String。 -
<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) -
<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) -
<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) -
<chdl>:set_shuffled()对当前信号进行随机赋值,这在验证中很常用。
-
<chdl>:set_bitfield(s, e, v)设置信号的值
v,并且只在s到e之间的位宽上赋值。v可以是 Lua number 类型的数值,也可以是一个uint64_t类型的cdata。local signal = dut.value:chdl()local value = 0x123signal:set_bitfield(0, 7, value) -
<chdl>:set_bitfield_hex_str(s, e, hex_str)设置信号的值,并且只在
s到e之间的位宽上赋值。hex_str是一个 Hex String 类型的字符串。local signal = dut.value:chdl()local value = "123"signal:set_bitfield_hex_str(0, 7, value) -
<chdl>:set_force(value)强制赋值(与
set_release配合使用),与 SystemVerilog 中的force关键字相同。除了force这个属性上的区别之外,其他和set一样。local signal = dut.value:chdl()local value = 0x123signal:set_force(value)-- ...signal:set_release() -
<chdl>:set_release()释放赋值(与
set_force配合使用),与 SystemVerilog 中的release关键字相同。对于使用了set_force的信号,需要使用set_release来释放赋值,否则会导致信号的值不会更新。 -
<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() == 0x42get() == 旧值signal:set_imm(0x42)get() == 0x42代码示例:
local signal = dut.value:chdl() -- assume that the initial value is 0x00signal: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 -
set_imm_*立即赋值变体每个赋值方法均有对应的
set_imm_*立即赋值变体,行为与普通版本完全相同,但立即生效(参见立即赋值和普通赋值的区别):set_imm_unsafe、set_imm_cached、set_imm_str、set_imm_hex_str、set_imm_bin_str、set_imm_dec_str、set_imm_shuffled、set_imm_bitfield、set_imm_bitfield_hex_str、set_imm_force、set_imm_release -
<chdl>:set_freeze()冻结当前的信号值,在后续的仿真中,该信号的值将保持不变。需要调用
<chdl>:set_release()来取消冻结。
debug 相关
-
<chdl>:dump()用于将信号的值(主要是以 Hex String 的形式)输出到控制台,可以用于查看信号的值,打印的内容如下所示:
Terminal[tb_top.value] => 0x01 -
<chdl>:dump_str()会将原本
<chdl>:dump()的输出的内容作为一个返回值进行返回,因此<chdl>:dump()也等价于print(<chdl>:dump_str())。 -
<chdl>:get_width()获得信号的位宽,也可以直接访问
<chdl>.width来获得。 -
<chdl>:__len()chdl重载了 Lua 的 metatable 的__len方法,可以直接使用#<chdl>来获得信号的位宽。下面的三种方式来获得信号位宽是等价的:local signal = dut.value:chdl()assert(#signal == 32)assert(signal:get_width() == 32)assert(signal.width == 32)
验证相关
-
<chdl>:expect(value)用于断言信号的值,在验证中很常用,如果信号的值与期望值相等则什么也不会发生,如果不相等则会打印报错信息并停止仿真。错误信息格式如下所示:
Terminal[tb_top.value] expect => 10, but got => 0- Single
- Double
- Multi
value的值是一个 Lua 的 number 类型的值。value的值可以是一个 Lua 的 number 类型的值,也可以是一个uint64_t类型的cdata。例如:local signal = dut.value:chdl()signal:expect(10) -- Lua numbersignal:expect(0x123ULL) -- uint64_t cdata(the `ULL` suffix is for 64 bit in LuaJIT)value的值是一个 Lua 的 table,其大小为当前信号的 beat 数。例如:local signal = dut.value:chdl()assert(#signal == 128)signal:expect({0x123, 0x456, 0x111, 0x222}) -- Lua table -
<chdl>:expect_not(value)和
<chdl>:expect(value)类似,但是如果信号的值与期望值相等则会打印报错信息并停止仿真。 -
<chdl>:expect_hex_str(hex_value_str)比较信号的值是否为指定的 Hex String 值,如果不相等则会打印报错信息并停止仿真。
local signal = dut.value:chdl()signal:expect_hex_str("123") -
<chdl>:expect_bin_str(bin_value_str)比较信号的值是否为指定的 Binary String 值,如果不相等则会打印报错信息并停止仿真。
local signal = dut.value:chdl()signal:expect_bin_str("1010") -
<chdl>:expect_dec_str(dec_value_str)比较信号的值是否为指定的 Decimal String 值,如果不相等则会打印报错信息并停止仿真。
local signal = dut.value:chdl()signal:expect_dec_str("12") -
<chdl>:expect_not_hex_str/expect_not_bin_str/expect_not_dec_str分别与
expect_hex_str、expect_bin_str、expect_dec_str作用相反。 -
<chdl>:is(value)判断信号的值是否等于某个值,如果等于则返回
true,否则返回false。- Single
- Double
- Multi
value的值是一个 Lua 的 number 类型的值。value的值可以是一个 Lua 的 number 类型的值,也可以是一个uint64_t类型的cdata。例如:local signal = dut.value:chdl()local correct = signal:is(10) -- Lua numberlocal correct = signal:is(0x123ULL) -- uint64_t cdata(the `ULL` suffix is for 64 bit in LuaJIT)value的值是一个 Lua 的 table,其大小为当前信号的 beat 数。例如:local signal = dut.value:chdl()assert(#signal == 128)local correct = signal:is({0x123, 0x456, 0x111, 0x222}) -- Lua table -
<chdl>:is_not(value)和
<chdl>:is(value)的作用相反。 -
<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")
信号回调管理
-
<chdl>:posedge(times, func)用于等待信号的上升沿到来,
times和func是可选的两个参数,times表示等待的次数,func表示回调函数。local clock = dut.clock:chdl()clock:posedge() -- wait for one posedgeclock:posedge(10) -- wait for 10 posedgesclock:posedge(10, function (c)-- `func` will be called every time posedge arrives, and the argument `c` is the count of the posedgeprint("posedge count => ", c)end) -
<chdl>:negedge(times, func)和
<chdl>:posedge(times, func)类似,但是在等待信号的下降沿到来。 -
<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() == 1end)assert(ok, "ready never asserted within 100 cycles") -
<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_t和uint32_t[];<chdl>.value = 0x123ULL -- uint64_t cdatalocal vec = ffi.new("uint32_t[?]", 4) -- uint32_t[] cdatavec[1] = 0x123 -- Notice: the index is 1-basedvec[2] = 0x456vec[3] = 0x789<chdl>.value = vec对于uint32_t[]的赋值,需要用户手动使用 ffi 创建,因此建议还是采用 Lua table 的方式 -
Lua boolean 类型的值;
<chdl>.value = true<chdl>.value = false
具备自动格式识别的比较接口
在实际的验证中,往往需要对信号的值进行比较,来判断信号的值是否符合预期,但是信号的数值表示方式在 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_t和uint32_t[];local correct = <chdl> == v(0x123ULL) -- uint64_t cdatalocal vec = ffi.new("uint32_t[?]", 4) -- uint32_t[] cdatavec[1] = 0x456 -- Notice: the index is 1-basedvec[2] = 0x789vec[3] = 0x000local 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"))
CallableHDL 接口(Array)
TODO: 针对 Array 类型的信号,Verilua 的 chdl 由独立的 API 进行赋值。
相关文档
- 数据结构概览:查看
CallableHDL、Bundle、ProxyTableHandle等对象之间的关系。 - 位向量(BitVec):查看
get_bitvec()返回值和大位宽数据操作方式。 - String Literal Constructor Pattern (SLCP):了解通过字符串字面量创建
chdl的统一模式。 - 多任务系统:查看
posedge、negedge和时间等待在任务调度中的用法。