Skip to main content

时钟驱动策略

在 Verilua 中,有多种方式来驱动时钟信号。本文将介绍三种主要的时钟驱动策略,并帮助你根据具体场景选择最合适的方式。

三种时钟驱动策略

策略说明适用场景
Internal ClockRTL 中的 always 块生成时钟大多数常规验证场景
Lua ClockLua 协程驱动时钟需要精确控制时钟的场景
NativeClockRust 原生驱动时钟高性能时钟驱动场景

1. Internal Clock(内部时钟)

这是 Verilua 的默认时钟驱动方式。时钟由 Testbench Generator 自动在 RTL 中生成一个 always 块来驱动。

工作原理

Verilua 的 Testbench Generator 会自动在生成的 testbench 中添加类似以下的 Verilog 代码:

initial begin
clock = 0;
end

always #10 clock = ~clock; // 默认周期 20ns(半周期 10ns)

配置方式

默认情况下,Testbench Generator 会自动生成内部时钟。你可以在 xmake.lua 中通过 cfg.tb_gen_flags 配置时钟参数:

target("my_test", function()
add_rules("verilua")

-- 设置时钟周期为 10ns(默认为 20ns)
add_values("cfg.tb_gen_flags", "--period", "10")

-- 如果时钟信号名不是默认的 clock,需要指定
add_values("cfg.tb_gen_flags", "--clock-signal", "clk")
end)

时钟信号自动识别

如果不指定 --clock-signal,Testbench Generator 会按以下优先级自动检测时钟信号(不区分大小写):

  1. clock
  2. clock_i
  3. clk
  4. clk_i
  5. i_clk

主要参数

参数说明默认值
-p, --period时钟周期(单位由 timescale 决定,通常为 ns)20
--clock-signal, --cs时钟信号名称自动检测

优点

  • 简单易用: 无需手动驱动时钟
  • 性能最优: 时钟在仿真器内部生成,无额外开销
  • 稳定可靠: 由仿真器原生支持

缺点

  • 灵活性较低: 无法在运行时动态调整时钟参数
  • 功能受限: 无法实现时钟门控、动态频率调整等高级功能

适用场景

  • 大多数常规验证场景
  • 不需要动态控制时钟的测试

2. Lua Clock(Lua 协程时钟)

当需要精确控制时钟时,可以禁用内部时钟,改用 Lua 协程来驱动时钟。

配置方式

首先需要禁用内部时钟:

xmake.lua
target("my_test", function()
add_rules("verilua")
-- 禁用内部时钟
set_values("cfg.no_internal_clock", "1")
end)

然后在 Lua 代码中手动驱动时钟:

main.lua
local clock = dut.clock:chdl()

-- 时钟驱动任务
fork {
function()
while true do
clock:set(1)
await_time(5) -- 高电平持续 5 个时间步(默认 timescale 1ns/1ps,即 5ns)
clock:set(0)
await_time(5) -- 低电平持续 5 个时间步
end
end,

-- 主测试任务
function()
-- 你的测试代码
for i = 1, 100 do
dut.clock:posedge()
end
sim.finish()
end
}

优点

  • 完全控制: 可以实现任意时钟波形
  • 动态调整: 可以在运行时改变时钟频率
  • 高级功能: 支持时钟门控、相位调整等

缺点

  • 性能开销: 每个时钟周期都需要 Lua 和仿真器之间的上下文切换
  • 代码复杂: 需要手动编写时钟驱动逻辑

使用示例

基本时钟驱动

local clock = dut.clock:chdl()

fork {
function()
while true do
clock:set(1)
await_time_ns(5)
clock:set(0)
await_time_ns(5)
end
end
}

可变频率时钟

local clock = dut.clock:chdl()
local clock_period = 10 -- 初始周期 10ns

fork {
function()
while true do
clock:set(1)
await_time_ns(clock_period / 2)
clock:set(0)
await_time_ns(clock_period / 2)
end
end,

function()
-- 运行一段时间后改变频率
dut.clock:posedge(100)
clock_period = 20 -- 改为 20ns 周期

dut.clock:posedge(100)
sim.finish()
end
}

适用场景

  • 需要动态调整时钟频率的测试
  • 需要实现时钟门控的场景
  • 需要精确控制时钟相位的场景

3. NativeClock(原生时钟)

NativeClock 是专为高性能时钟驱动设计的解决方案。它在 Rust 原生代码中实现时钟切换,避免了每个时钟周期的 Lua 上下文切换开销。

配置方式

首先需要禁用内部时钟,并且对于 Verilator 需要启用 TIMING_MODE:

xmake.lua
target("my_test", function()
add_rules("verilua")
set_values("cfg.no_internal_clock", "1")

-- Verilator 需要 TIMING_MODE
add_defines("TIMING_MODE")
add_verilator_flags("--timing")
end)

然后在 Lua 代码中使用 NativeClock:

main.lua
local NativeClock = require "verilua.utils.NativeClock"

fork {
function()
local clk = NativeClock(dut.clock:chdl())

-- 启动 10ns 周期时钟
clk:start(10, "ns")

-- 运行测试
for i = 1, 1000 do
dut.clock:posedge()
end

clk:stop()
clk:destroy()
sim.finish()
end
}

优点

  • 高性能: 相比 Lua 时钟有显著的性能提升
  • 低延迟: 时钟切换完全在原生代码中完成
  • 易用性: API 简洁,配置简单

缺点

  • 灵活性较低: 仅支持固定波形,不支持动态调整(需要 stop/restart)
  • 依赖限制: Verilator 需要 TIMING_MODE
  • 仅限 HVL: 不支持 HSE 和 WAL 模式

API 简介

local NativeClock = require "verilua.utils.NativeClock"

-- 创建实例
local clk = NativeClock(dut.clock:chdl())

-- 启动时钟:周期、单位、可选配置
clk:start(10, "ns") -- 10ns 周期,50% 占空比
clk:start(10, "ns", { high = 3 }) -- 30% 占空比
clk:start(10, "ns", { start_high = false }) -- 从低电平开始

-- 控制
clk:stop() -- 停止时钟
clk:restart(20, "ns") -- 重启为新周期
clk:is_running() -- 检查是否运行中

-- 清理
clk:destroy()

适用场景

  • 需要高性能时钟驱动的大规模仿真
  • 时钟参数相对固定的测试
  • 性能敏感的回归测试

性能对比

三种时钟驱动策略的性能排序:

Internal Clock > NativeClock > Lua Clock

  • Internal Clock: 性能最优,时钟在仿真器内部生成,无额外开销
  • NativeClock: 性能次优,避免了 Lua 上下文切换开销
  • Lua Clock: 性能最低,每个时钟周期都需要 Lua 和仿真器之间的上下文切换
tip

如果不需要动态控制时钟,优先使用 Internal Clock。如果需要禁用内部时钟并追求性能,使用 NativeClock。

选择建议

场景推荐策略
常规验证,时钟参数固定Internal Clock
需要动态频率调整Lua Clock
需要高性能,参数固定NativeClock
时钟门控、相位控制Lua Clock

相关文档