一个简单的 WAL 示例
WAL 是 Waveform Analysis Language(波形分析语言)的缩写。
WAL 是一种利用编程语言自动化波形分析的方法。Verilua 采用 Lua 语言实现了 WAL,支持 VCD、FST、FSDB 等多种波形格式。WAL 场景下可以复用 HVL、HSE 中使用的验证组件,极大地提高了波形分析效率。
本节将通过一个简单的例子来展示 Verilua 在 WAL 场景下的基本使用方式。
本节所使用的示例代码可以在 这里 找到。
1. 生成波形
生成波形主要是使用到了 Verilua 的 HVL 场景,可以现查看这里的内容对 HVL 场景的使用有一个基本的了解。
1.1. 准备 RTL 文件
下面是本节使用到的 RTL 代码,是一个简单的 Counter 模块。
module Counter (
input wire clock,
input wire reset,
output wire [7:0] count
);
reg [7:0] count_reg;
initial begin
count_reg = 0;
end
always @(posedge clock) begin
if (reset) begin
count_reg <= 0;
end else if (count_reg < 10) begin
count_reg <= count_reg + 1;
end else begin
count_reg <= 0;
end
end
assign count = count_reg;
endmodule
1.2. 创建相关 Lua 脚本文件
local Monitor = require "Monitor"
fork {
function ()
-- dump wave file
sim.dump_wave("./wave/test.vcd") -- wave file is stored in ./build/<simulator>/Counter/wave/test.vcd
-- reset the dut
dut.reset:set(1)
dut.clock:posedge(10)
dut.reset:set(0)
-- create a monitor for monitoring the dut.count signal
-- this Monitor will be reused when we simulate the generated wave file using @wave_vpi backend
local monitor = Monitor("MonitorForGenWave", dut.count:chdl())
monitor:start()
-- run the simulation for 100 clock cycles
dut.clock:posedge(20)
-- finish the simulation
sim.finish()
end
}
local class = require "pl.class"
local f = string.format
local Monitor = class()
function Monitor:_init(name, signal_chdl)
self.name = name
self.signal = signal_chdl
end
function Monitor:start()
fork {
function ()
local clock = dut.clock:chdl()
while true do
print(f("[Monitor] [%s] %s", self.name, self.signal:dump_str()))
clock:posedge()
end
end
}
end
return Monitor
Monitor.lua 模块将会在接下来仿真波形的时候被复用1.3. 创建 xmake.lua 文件
更具体的 xmake.lua 的介绍可以参考这里的内容。
target("gen_wave", function()
add_rules("verilua")
-- 本节中的例子会使用 Verilator 来作为 HVL 的仿真后端
add_toolchains("@verilator")
add_files("./Counter.v")
add_files("./Monitor.lua")
set_values("cfg.lua_main", "./main.lua")
set_values("cfg.top", "Counter")
-- Verilator 需要添加一些额外的参数来生成波形
set_values("verilator.flags", "--trace", "--no-trace-top")
end)
1.4. 编译
在创建好 xmake.lua 文件之后,我们就可以开始编译了,只需要执行下面的命令即可进行编译:
xmake build -P . gen_wave
1.5. 运行仿真生成波形
编译完成后,可以执行下面的命令运行仿真:
xmake run -P . gen_wave
仿真生成的波形将会保存在 ./build/verilator/Counter/wave/test.vcd 中,可以用 GTKWave 等波形文件查看器打开查看。
Monitor 模块会在命令行中打印出下面的信息:
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x00
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x01
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x02
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x03
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x04
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x05
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x06
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x07
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x08
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x09
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x0a
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x00
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x01
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x02
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x03
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x04
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x05
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x06
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x07
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x08
[Monitor] [MonitorForGenWave] [tb_top.count] => 0x09
2. 仿真波形
2.1. 准备波形文件
WAL 的输入文件不是 Verilog/SystemVerilog,而是具体的波形文件(VCD、FST、FSDB 等格式),这里我们使用前面创建的波形文件。
2.2. 创建相关 Lua 脚本文件
需要有一个 Lua 脚本来作为 WAL 波形分析场景的入口。
local Monitor = require "Monitor"
fork {
function ()
-- Notice: be aware that signals are READ-ONLY when using @wave_vpi backend
-- so we can't use the following code to reset the dut
-- reset the dut
-- dut.reset:set(1)
-- dut.clock:posedge(10)
-- dut.reset:set(0)
dut.clock:posedge(10)
-- create a monitor for monitoring the dut.count signal
-- this monitor has been reused
local monitor = Monitor("MonitorForSimWave", dut.count:chdl())
monitor:start()
-- run the simulation for 100 clock cycles
dut.clock:posedge(20)
-- finish the simulation
sim.finish()
end
}
main_for_wal.lua 中的 Monitor 模块复用了前面生成波形时候的 Monitor.luaset 等,否则会导致报错,目前 WAL 场景下所有的信号都是只读的!2.3. 创建 xmake.lua 文件
target("sim_wave", function()
add_rules("verilua")
-- WAL 场景下这里必须是 @wave_vpi
add_toolchains("@wave_vpi")
-- 输入文件不是 Verilog/SystemVerilog,而是波形文件
-- 这里的波形文件路径指向的是前面生成波形时候的路径
add_files("./build/verilator/Counter/wave/test.vcd")
-- 这个复用了前面生成波形时候创建的模块
add_files("./Monitor.lua")
set_values("cfg.lua_main", "./main_for_wal.lua")
-- 设计的顶层还是需要手动指定
set_values("cfg.top", "tb_top")
end)
本示例中,波形文件是通过 gen_wave target 使用 Verilator 生成的。
Verilua 会自动调用 testbench_gen 工具生成一个名为 tb_top 的 testbench 模块,
该模块会:
- 实例化 Counter 作为 DUT(
uut子模块) - 提供时钟和复位生成
- 导出 DUT 端口
- 添加 DPI 接口
因此波形文件的顶层是 tb_top,而不是 Counter。
访问方式:
cfg.top = "tb_top"→ 从 testbench 层访问(当前设置)- 可访问:
tb_top.clk,tb_top.count,tb_top.uut.count_reg等
- 可访问:
cfg.top = "Counter"→ 从 DUT 层访问- 仅可访问:Counter 模块内部的信号
2.4. 编译
在创建好 xmake.lua 文件之后,我们就可以开始编译了,只需要执行下面的命令即可进行编译:
xmake build -P . sim_wave
2.5. 运行仿真
编译完成后,可以执行下面的命令运行仿真:
xmake run -P . sim_wave
此时查看命令行输出会发现 Monitor 的模块会在命令行中打印出下面的信息:
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x00
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x01
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x02
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x03
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x04
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x05
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x06
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x07
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x08
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x09
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x0a
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x00
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x01
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x02
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x03
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x04
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x05
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x06
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x07
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x08
[Monitor] [MonitorForSimWave] [tb_top.count] => 0x09
这和前面我们运行 RTL 仿真时候的输出是一样的。