1. 背景
前导0检测是浮点乘法运算中常用的计算,其目的是对前导0进行计数,以二进制数输出前导零个数。使用多路选择器直接从所有可能结果中选出是比较直观的实现方法,但如果直接使用多路选择器进行枚举与选择,代码实现比较复杂,面积和延时也都比较大。而如果采用按位检测与计数,则需要引入时钟,这将会导致不确定的执行延时。
事实上,前导0检测可以通过少量逻辑门(\(O(log_2n)\)级别)完成。
2. 原理
2.1 LZD定义
我们先将前导零检测部件(LZD)的端口定义如下:
- A[7:0]:输入数据
- B[2:0]:前导0计数
- full:A全部为0
功能的伪码表示如下:
if A == 8'b0
B = 3'b0
full = 1
else
B = A的前导0数量
full = 0
2.2 理论推导
首先我们考虑一种简单的情况,前导0之后全部为1,例如对一个8bits数据A=00000111,前导0检测的结果应为:B=101,怎么理解这个结果呢?
从高位至低位考虑:
- B的最高位为1,代表结果大于等于4,即第一个1在A[3:0](低4位)中;
- B的下一位为0,代表结果小于\(4 * B[2] + 2 = 6\),即第一个1在A[3:2](低4位的高2位);
- B的最后一位为1,代表最终确定结果等于5,即第一个1在A[2](低4位的高2位的低1位)
可以发现,从高位至低位考虑,第n位为0/1,代表第一个1出现在A的剩余可能区间的高/低2n位。反过来,从长度为1位的小区间到长度为2m的大区间,第一个1可以依次从低位至高位确定B。
2.3 硬件结构
怎么实现呢?我们可以通过建立一棵二叉树进行实现,以期达到\(O(log_2n)\)的延时。
考虑A的特征,我们发现,第一个1往前都是0,往后都是1,我们需要两种基本操作来完成上面推理出的二分的LZD:
- “保留”第一个1的位置信息,对应或运算;
- 决定第一个1是在左侧还是在右侧,对应异或运算。
2.3.1建立二叉树
还是以A=00000111,B=101为例,第一步,建立二叉树,先看下图:
我们发现,从上往下,每一个节点分别代表8bits、4bits、2bits、1bit的数据,如果该节点的值为1,则代表这一段区间中有1,否则全为0。父节点的值由子节点通过或运算得到。
2.3.2 分析第一个1的位置
我们还希望从中提取出第一个1是左区间还是右区间。由于0一定在1的左侧,我们可以用异或运算来提取:
- 当左为0,右为1,异或得到1,代表第一个1在右区间;
- 当左为1,右为1,异或得到0,代表第一个1在左区间;
- 当左右都为0,我们不在乎得到什么,姑且与第二种情况合并。
如下图所示:
两个兄弟节点之间的数字代表了异或运算的结果,1代表第一个1在右边,0代表第一个1在左边或者不存在第一个1。
2.3.3 传递有效结果
尽管每对兄弟节点都会产生一个判断值,但大多数值都是无效的,我们希望只提取出有效的值。我们自顶向下考虑,还是分三种情况:
- 左、右节点均为1:这种情况下,第一个1在左子树,所以B的更低位应采用左子树的异或结果,对应本级的异或结果为0;
- 左节点为0右节点为1:这种情况下,第一个1在右子树,所以B的更低位应采用右子树的异或结果,对应本级的异或结果为1;
- 左、右节点均为0:这种情况下,左右区间都没有1,无论哪一边子树的异或结果都是无效的,为了实现简单,我们不妨归类为本级异或结果同样为1的第一种情况。
那么效果如下:
橙色标注的是第一个1的传递路径,蓝色标注的是B的逐位逐级的选择,绿色标注的是本级异或结果对下一级异或结果的选择。
2.4 一般情况
这种方法看起来好像很不错,不过到这里还没有结束。还记得吗,我们在开始的时候做了一个假设,从第一个1往后都是1,但这显然不是一般情况。
更一般的情况是有可能出现左节点为1,右节点为0。这种情况下显然第一个1在左区间,但按现在的方法会将其归到右区间。不过不用担心,这不是什么大问题,我们只需要将它修正为左右节点都为1的情况即可。方法是将右节点用于异或运算的值进行修正:
只有左节点为1,右节点为0的情况会受到这种修正的影响。至此,我们已经从理论上完成了\(O(log_2n)\)的前导零检测,下面附上verilog实现.
3. 代码
4. 评估
4.1 延时
每一级生成下一级节点的值的延时为1个或门的延时;
每一级生成本级异或值的延时为1个或门与1个异或门的延时,选择下一级异或值的延时为1个MUX的延时;
综上,对2n位的数据进行前导零检测,生成B的第i位的延时为:
总的延时为:
4.2 面积
对2n位的数据进行前导零检测,生成B的第i位所需的硬件结构为:
总的面积为:
可见,面积和延时都是\(O(log_2n)\)级别的。
转载请注明出处: 原文地址