Skip to main content

一个简单的 WAL 示例

WAL - Waveform Analysis Language

WALWaveform Analysis Language(波形分析语言)的缩写。

WAL 是一种利用编程语言自动化波形分析的方法。Verilua 采用 Lua 语言实现了 WAL,支持 VCD、FST、FSDB 等多种波形格式。WAL 场景下可以复用 HVL、HSE 中使用的验证组件,极大地提高了波形分析效率。

本节将通过一个简单的例子来展示 Verilua 在 WAL 场景下的基本使用方式。

本节所使用的示例代码可以在 这里 找到。

1. 生成波形

生成波形主要是使用到了 Verilua 的 HVL 场景,可以现查看这里的内容对 HVL 场景的使用有一个基本的了解。

1.1. 准备 RTL 文件

下面是本节使用到的 RTL 代码,是一个简单的 Counter 模块。

Counter.v
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 脚本文件

main.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
}
Monitor.lua
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 的介绍可以参考这里的内容。

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 模块会在命令行中打印出下面的信息:

Terminal
[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 波形分析场景的入口。

main_for_wal.lua
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.lua
WAL 场景下,不允许出现赋值的语句,例如 set 等,否则会导致报错,目前 WAL 场景下所有的信号都是只读的!

2.3. 创建 xmake.lua 文件

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)
为什么 cfg.top 设置为 "tb_top"?

本示例中,波形文件是通过 gen_wave target 使用 Verilator 生成的。 Verilua 会自动调用 testbench_gen 工具生成一个名为 tb_top 的 testbench 模块, 该模块会:

  1. 实例化 Counter 作为 DUT(uut 子模块)
  2. 提供时钟和复位生成
  3. 导出 DUT 端口
  4. 添加 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 模块内部的信号

参考:examples/WAL/xmake.lua 源码及详细注释

2.4. 编译

在创建好 xmake.lua 文件之后,我们就可以开始编译了,只需要执行下面的命令即可进行编译:

xmake build -P . sim_wave

2.5. 运行仿真

编译完成后,可以执行下面的命令运行仿真:

xmake run -P . sim_wave

此时查看命令行输出会发现 Monitor 的模块会在命令行中打印出下面的信息:

Terminal
[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 仿真时候的输出是一样的。