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_oempty_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, 包引用等)?还是继续下一个模块分析?

posted @ 2025-05-04 16:17  江左子固  阅读(48)  评论(0)    收藏  举报