lzc
我们开始逐行分析你提供的 lzc.sv
模块的前 20 行,这段代码实现了一个 Leading Zero Counter(LZC)或 Trailing Zero Counter(TZC),功能上非常关键,常用于:
- Posit 或浮点尾数规格化(前导零计数)
- 非零检测与对齐
- 位串压缩/快速编码等
📄 文件前几行:版权信息
// Copyright (c) 2018 - 2019 ETH Zurich, University of Bologna
// All rights reserved.
// ...
这些行是由 ETH Zürich 和 Bologna 大学联合开发的 PULP 平台(开放硬件平台)代码的版权声明。
🔍 行 15–20:模块功能说明
/// A trailing zero counter / leading zero counter.
/// Set MODE to 0 for trailing zero counter => cnt_o is the number of trailing zeros (from the LSB)
/// Set MODE to 1 for leading zero counter => cnt_o is the number of leading zeros (from the MSB)
/// If the input does not contain a zero, `empty_o` is asserted. Additionally `cnt_o` contains
/// the maximum number of zeros - 1. For example:
/// in_i = 000_0000, empty_o = 1, cnt_o = 6 (mode = 0)
这些是 模块文档注释(///
),说明如下几点:
特性 | 说明 |
---|---|
MODE=0 | Trailing Zero Counter:从最低有效位(LSB)向高数,数 0 的个数 |
MODE=1 | Leading Zero Counter:从最高有效位(MSB)向低数,数 0 的个数 |
empty_o | 如果输入中没有 0,则置 1,cnt_o 输出最大值(例如全 0 输出最大计数) |
✅ 举例说明(第 20 行)
in_i = 000_0000, empty_o = 1, cnt_o = 6 (mode = 0)
即:输入为全 0,则 trailing 模式下有 6 个零位,cnt_o=6
,并且 empty_o=1
。
我们将继续读取并分析 module
定义与端口,是否继续往下查看结构与逻辑实现?
我们继续对 lzc.sv
第 21–40 行逐行分析。这一段包含模块声明、参数定义与端口接口。
📄 行 21–23:更多使用示例注释
/// in_i = 000_0001, empty_o = 0, cnt_o = 0 (mode = 0)
/// in_i = 000_1000, empty_o = 0, cnt_o = 3 (mode = 0)
这两行举了具体例子来说明 Trailing Zero Counter 模式:
- 输入为
000_0001
,最右边一位是 1,故 trailing zero 为 0 - 输入为
000_1000
,最后一个 1 前有 3 个 0,因此输出cnt_o = 3
📄 行 24–25:模拟优化说明
/// Furthermore, this unit contains a more efficient implementation for Verilator (simulation only).
/// This speeds up simulation significantly.
说明:该模块内置了一个模拟优化路径,用于 Verilator 仿真器(通过 verilator lint_off
等方式加速 zero count 运算)。
🧱 行 26:模块开始
module lzc #(
SystemVerilog 的参数化模块声明开始。
⚙️ 行 27–33:模块参数定义
parameter int unsigned WIDTH = 2,
parameter bit MODE = 1'b0,
parameter int unsigned CNT_WIDTH = cf_math_pkg::idx_width(WIDTH)
参数名 | 含义 |
---|---|
WIDTH |
输入向量 in_i 的位宽 |
MODE |
0 = trailing zero counter,1 = leading |
CNT_WIDTH |
计数输出的位宽,自动由 cf_math_pkg::idx_width(WIDTH) 推导得出(log2向上取整) |
🧩 行 34:端口列表开始
) (
结束参数块,开始端口定义。
🧮 行 35–37:输入输出端口声明
input logic [WIDTH-1:0] in_i,
output logic [CNT_WIDTH-1:0] cnt_o,
output logic empty_o
端口 | 类型 | 含义 |
---|---|---|
in_i |
输入 | 要统计的输入向量 |
cnt_o |
输出 | 统计到的前导(或尾部)零的数量 |
empty_o |
输出 | 如果输入中没有任何 1 ,则输出为 1 |
✅ 小结:到目前为止我们得到了:
- 模块功能是双模式的 zero counter(leading/trailing)
- 参数化设计,可支持任意宽度输入
- 使用了包中函数
idx_width
计算最小输出位宽
是否继续分析模块实现部分?接下来很可能包含 generate、for loop 或递归结构来高效实现 zero count。
我们继续分析第 61–80 行,开始进入 反转输入向量(用于 leading vs trailing 模式) 以及 构造递归归约树 的核心逻辑。
🔁 行 61–63:声明反转后的临时向量
logic [WIDTH-1:0] in_tmp;
🔸 为了统一后续逻辑,无论是 LZC(从高位扫)还是 TZC(从低位扫),都将输入向量 in_i
统一转换为一个内部向量 in_tmp
,即:
- 如果是 LZC:反转向量(高位对准低位)
- 如果是 TZC:保留原顺序
🔄 行 64–68:反转操作
always_comb begin : flip_vector
for (int unsigned i = 0; i < WIDTH; i++) begin
in_tmp[i] = (MODE) ? in_i[WIDTH-1-i] : in_i[i];
end
end
🧠 逻辑说明:
-
对于每一位索引
i
:- 若
MODE == 1
(即 LZC 模式),将输入反转 - 若
MODE == 0
(即 TZC 模式),保留原顺序
- 若
这一步确保后续统一用“从 index 0 向上扫”来处理,不用区分高低位。
🔣 行 70–72:构造 index LUT
for (genvar j = 0; unsigned'(j) < WIDTH; j++) begin : g_index_lut
assign index_lut[j] = (NumLevels)'(unsigned'(j));
end
每个位都赋一个静态索引值:
index_lut[j] = j
(强制转换为 NumLevels 宽度)
这些索引值将被用于后续二叉归约过程中挑选出最靠前的 1 所在位的位置。
🧱 行 74–75:构造归约层
for (genvar level = 0; unsigned'(level) < NumLevels; level++) begin : g_levels
if (unsigned'(level) == NumLevels - 1) begin : g_last_level
构建一个 多层二叉归约树:
- 每一层
level
表示当前归约深度 NumLevels
是整棵树的高度g_last_level
是处理树最底层的节点(用于最终合并)
🔁 行 76–80:具体树节点归约(部分)
for (genvar k = 0; k < 2 ** level; k++) begin : g_level
if (unsigned'(k) * 2 < WIDTH - 1) begin : g_reduce
assign sel_nodes[2 ** level - 1 + k] = in_tmp[k * 2] | in_tmp[k * 2 + 1];
🧠 核心逻辑:
- 每一层将相邻的两个 bit 分组,例如
in_tmp[0]
与in_tmp[1]
构成一组 - 用
OR
判断该组中是否有 “1” - 如果有,则当前节点
sel_nodes[idx] = 1
,表示该子树路径有效
📌 注意:
- 节点编号:
2^level - 1 + k
是满二叉树的编号方式(从上往下,左往右编号)
到此,我们处理了:
- 输入反转
- 每位构建索引表
- 二叉归约逻辑的第一层
是否继续分析剩余归约过程(index_nodes
构造、最终输出 cnt_o
和 empty_o
)?
继续分析第 81–100 行,这部分是归约树中的核心逻辑:选择最先出现的有效位(从输入向量中第一个为 1 的位)并记录其索引。分为最底层的输入归约(leaf level)和非最底层的索引归约(internal levels)。
🍃 最底层归约(处理输入向量)
assign index_nodes[2 ** level - 1 + k] = (in_tmp[k * 2] == 1'b1)
? index_lut[k * 2] :
index_lut[k * 2 + 1];
🔍 若 in_tmp[k*2]
为 1,就选择它的索引作为结果;否则选择右边的。
📌 对应输入中两个比特中,哪个先为 1,就将其索引存入 index_nodes
。
⚠️ 边界处理:只有一个元素时
if (unsigned'(k) * 2 == WIDTH - 1) begin : g_base
assign sel_nodes[2 ** level - 1 + k] = in_tmp[k * 2];
assign index_nodes[2 ** level - 1 + k] = index_lut[k * 2];
end
🎯 说明:
- 若当前层中,只剩下一个元素(即
k*2 == WIDTH-1
),就不能再成对比较。 - 直接将该位作为有效性和索引输出。
⚠️ 超出范围处理
if (unsigned'(k) * 2 > WIDTH - 1) begin : g_out_of_range
assign sel_nodes[2 ** level - 1 + k] = 1'b0;
assign index_nodes[2 ** level - 1 + k] = '0;
end
🧱 若 k*2 > WIDTH-1
,说明越界,当前节点应标记为无效,索引设为 0。
🌳 非最后层(递归合并)
end else begin : g_not_last_level
for (genvar l = 0; l < 2 ** level; l++) begin : g_level
assign sel_nodes[2 ** level - 1 + l] =
sel_nodes[2 ** (level + 1) - 1 + l * 2] |
sel_nodes[2 ** (level + 1) - 1 + l * 2 + 1];
assign index_nodes[2 ** level - 1 + l] =
(sel_nodes[2 ** (level + 1) - 1 + l * 2] == 1'b1)
🧠 核心递归:
- 合并两棵子树的有效性:若有任一子树有效(有 1),则当前节点有效。
- 若左子树有效,则当前索引为左索引;否则取右。
这一过程持续到整个树顶端(level 0)为止,最终 index_nodes[0]
就是整个输入中最先为 1 的位的位置。
是否继续分析最后一部分逻辑(根节点输出赋值 cnt_o
, empty_o
),以及结束模块?
我们现在分析 lzc.sv
的最后部分,主要完成归约树的最终输出:即得到前导/尾部零的个数 cnt_o
和是否为空 empty_o
。
✅ 行 101–102:选择最终索引节点
? index_nodes[2 ** (level + 1) - 1 + l * 2] :
index_nodes[2 ** (level + 1) - 1 + l * 2 + 1];
🔍 若左子树有效,则选择左侧节点的 index,否则选择右侧的。
这完成了对 index_nodes
的归并,确保最终 index_nodes[0]
是整个输入中最先为 1 的位置索引。
🟢 行 106:输出 cnt_o
assign cnt_o = NumLevels > unsigned'(0) ? index_nodes[0] : {(cf_math_pkg::clog(WIDTH)) {1'b0}};
🧠 输出说明:
- 若树深大于 0(即 WIDTH > 1),
cnt_o
输出最终合并的索引。 - 否则设为全 0。
这里使用 cf_math_pkg::clog(WIDTH)
表示按最大位宽初始化。
🚩 行 107:输出 empty_o
assign empty_o = NumLevels > unsigned'(0) ? ~sel_nodes[0] : ~(|in_i);
- 若树深 > 0,则输出树根节点的
sel_nodes[0]
是否无效(即输入全 0) - 否则直接对输入
in_i
求或,判断是否为空
🔚 行 109–111:模块结构结尾
end : gen_lzc
endmodule : lzc
结束 gen_lzc
generate 块与整个模块定义。
✅ 总结:模块功能
输出 | 含义 |
---|---|
cnt_o |
输入中第一个 1 的位置索引 |
empty_o |
输入是否全为 0 |
该模块通过层层归约构建出前导/尾部零的精确位置,并兼容任意位宽,具备高度可综合性和可配置性。
是否将该 lzc.sv
模块转换为标准 Verilog-2001(去除 logic
, generate
, 包引用等)?还是继续下一个模块分析?