八位“Booth二位乘算法”乘法器
八位“Booth二位乘算法”乘法器
原理
补码乘法器
之前介绍了几篇无符号乘法器或加法器的写法,当然,稍作修改也就可以改成符合有符号数的乘法器或加法器。
但是呢,我们之前写的乘法器或加法器,其实都是默认是正数来写的,而且是以正数的原码来写的,所以上面说稍作修改也就可以成为有符号数的乘法器或加法器,其实就是对我们以为的原码进行取补码,再进行乘法或加法的运算。
随着计算机硬件部件的升级,处理器技术的发展,现代处理器中的定点数(小数点位置固定)都是按照补码形式来存储的。
所以在之前写的无符号加法器中,只要利用:
\]
就可以轻易将原先的加法器改写成有符号加法器——只要对结果再取一次补码即可。
但是乘法器呢?稍作学习可以知道,补码的乘法是这样的:
\]
我们再考虑一下之前所说的:在现代处理器中的定点数都是按照补码形式来存储的。
所以我们要想得到两个数的乘法结果,首先应该知道被乘数的原码和补码,再对最终结果取补码,即可得到我们期望的乘法结果。
那么如何求“X*Y补
”呢?在处理器中,一个二进制数Y补
形如y7y6y5y4y3y2y1y0
,也就是表示一个数的补码,那么它的原码是多少呢?
补码的计算方法,除了“首位不变,余位取反再加一”的方式,还有一种就是“用溢出条件来减这个数”,在我们之前第一节课说二进制的时候,以钟表为例——“十二进制”,得到结论——“4
是-8
的补码”。
我们用第二种取补码的方式:-8的补码=12-8=4
(这里没有考虑符号问题,只是求了补码的值)
所以考虑一下符号的话,-8的补码=8-12=-4
同理:
十进制下,-4的补码=4-10=-6
二进制下,-101补码=1101补码=101-1000=-011=1011
这样解决求补码的方式在接下来的计算方面就更方便了,至于正数嘛,不变就好了。
回到上面的问题,一个二进制数Y补
形如y7y6y5y4y3y2y1y0
,它的原码是多少呢?根据:
\]
Y补
的原码Y
应该为:
\]
稍微化简一下:
\]
所以我们如果想求X*Y
,可以先求其补码:
\]
根据补码加法“X补+Y补=[X+Y]补
”再稍微化简一下:
\]
再引入一个定理:
\]
所以上式又可以换一种写法:
\]
哦这不就是上面介绍过的补码乘法嘛:
\]
如果令一个数Y1补=y6y6y5y4y3y2y1y0
,去掉了首位,那么上式是不是可以理解为:
\]
其中的Y1补
不就刚好是Y补
的后7位嘛?也就是说一个乘法可以分为两部分理解:首位的乘法和其他位的乘法。首位的乘法产生的部分积符号是减,其他位的部分积符号为加。
经过上面的推导大家应该会对补码乘法的原理有了一定的概念,我们来把它写成竖式的形式,以(-6)x(-7)
为例,原码乘应该是1110x1111
,在计算机中是以补码的形式存储,所以补码乘是1010x1001
,代入公式,令X补=1010
,Y补=1001
,其运算过程如下:
这里可能有一些迷惑的是:为什么第一步运算得到的结果是11111010
?为什么要在前面填充1111
?
这也就是所谓的符号填充,我们之前的设计中都没有涉及到符号位,所以默认都是填充0
,现在遇到了负数问题,也就需要填充符号了,但是这样看起来是不是一点都觉得很奇怪?如果没办法理解的话,我建议你可以尝试对它求补码,看看是不是可以保持首位符号位不变,余位取反加一。惊叹于设计师的机智。
补码乘法器的原理讲明白了,具体电路实现的话,大家可以尝试一下,本节重点不在于此。
Booth一位乘
在上面已经讨论了补码乘法器的原理,那么什么是Booth
乘法器呢?Booth
乘法器是由英国的Booth
夫妇提出的,并没有什么特殊含义,所以我们直接快进到内容。
经过补码乘法器的推导:
\]
参考中学数学:
\]
其核心计算思想是括号里的形式,也就是Y补
的原码Y
,所以我们对括号里的内容再进行分解合并,也就是对Y
分解合并。先分解:
\]
这样应该挺直观了吧:
\]
再合并:
\]
最后有个0-y0
的项,看起来有点不合群,所以令:
\]
代入上式,即:
\]
这也就是Booth
一位乘算法的原理。其优点就在于不用再像补码乘法器那样,不需要专门对最后一次部分积采用补码减法。
根据上式,还可以列出Booth
一位乘的规则:
y(i-1) | y(i) | y(i-1) - y(i) | 操作 |
---|---|---|---|
0 | 0 | 0 | 加0 |
0 | 1 | -1 | 减X补 |
1 | 0 | 1 | 加X补 |
1 | 1 | 0 | 加0 |
再举个例子来计算,仍以(-6)x(-7)
为例,补码乘是1010x1001
,列出竖式:
可是这里为什么还是有减法呢?和常规的补码乘法器相比,简直是老和尚抹洗头膏,大可不必。甚至由于每次判断两位数字,增大了电路的复杂度,那么为什么booth乘法器如此好用呢?
其实booth
一位乘算法并不常用,但是booth二位乘就不一样了,通过增加一定的空间复杂度,将运算周期减为一半!
Booth二位乘
还是根据补码乘法器,我们将Y
的表达式再进行变换——先分解:
\]
再整合:
\]
好了Booth
二位乘算法也完事了,类比于Booth
一位乘,我们也可以列出Booth
二位乘的规则:
y(i-1) | y(i) | y(i+1) | y(i-1) + y(i) - 2*y(i+1) | 操作 |
---|---|---|---|---|
0 | 0 | 0 | 0 | 加0 |
0 | 1 | 0 | 1 | 加X补 |
1 | 0 | 0 | 1 | 加X补 |
1 | 1 | 0 | 2 | 加2*X补 ,即X补<<1 |
0 | 0 | 1 | -2 | 减2*X补 ,即X补<<1 |
0 | 1 | 1 | -1 | 减X补 |
1 | 0 | 1 | -1 | 减X补 |
1 | 1 | 1 | 0 | 加0 |
再举个例子来计算,仍以(-6)x(-7)
为例,补码乘是1010x1001
,列出竖式:
运算周期减半了!
好了,那Booth
乘法器有没有三位乘呢?可以有,但是三位的时候就会出现加3*X补
,2*X补
可以通过左移一位得到,而3*X补
就有点麻烦了,所以不再介绍,至于四位乘、八位乘,想挑战的同学可以挑战一下。
设计思路
减法变加法
首先我们来解决一个问题,如何把减法消除?我们知道,减去一个数,等于加上这个数的相反数;减去一个数,也等于加上这个数的补码。这个过程中的减数也默认是正数,因为正数的补码还是正数,只有正数前面加一个符号再去补码才有用。那么如上面竖式所写,减去一个负补码,就应该等于加上“这个负补码的补码的相反数”,比如上面的补码乘法器竖式,就应该变换成如下形式:
再说明一下吧:减11010
,就相当于加11010
的补码的相反数,即加10110
的相反数,即00110
。
所以booth
一位乘算法的示例应该变成这样:
booth
二位乘算法的示例应该变成这样:
vivado特性
考虑到上述减法变加法的操作后,容易总结出:减法变加法,其实就是对补码的符号位取反,也就是对减数每一位取反后再加一。
再回读一边上述的理论部分,可能你会发现,在乘法运算中,只用到了补码和“负补码”两种概念的数字。而在vivado
中(相当于在处理器中),数字默认是以补码形式存储的,即输入的乘数默认就是补码形式,这样只需要再求出“负补码”即可。设X[3:0]
表示一个乘数,默认是以补码形式存储,则其“负补码”:
\]
至于其原码:
\]
其实根本用不着。
有了以上知识储备,我们就可以写代码啦~
设计文件
//由于实力不够,没能设计成改一个数字变一个规模的程序
`define size 8
module mul_booth_signed(
input wire [`size - 1 : 0] mul1,mul2,
input clk,
input wire [2:0] clk_cnt,//运算节拍,相当于状态机了,8位的话每次运算有4个拍
output wire [2*`size - 1 : 0] res
);
//由于传值默认就是补码,所以只需要再计算“负补码”即可
wire [`size - 1 : 0] bmul1,bmul2;
assign bmul1 = (~mul1 + 1'b1) ;
assign bmul2 = (~mul2 + 1'b1) ;//其实乘数2的负补码也没用到。
//其实可以把状态机的开始和结束状态都写出来,我懒得写了,同学们可以尝试一下啊~
parameter zeroone = 3'b00,
twothree = 3'b001,
fourfive = 3'b010,
sixseven = 3'b011;
//y(i-1),y(i),y(i+1)三个数的判断寄存器,由于有多种情况,也可以看成状态机(也可以改写成状态机形式,大家自己试试吧)
reg [2:0] temp;
//部分积
reg [2*`size-1 : 0] A;
//每个节拍下把相应位置的数据传给temp寄存器
always @ (posedge clk) begin
case(clk_cnt)
zeroone : temp <= {mul2[1:0],1'b0};
twothree : temp <= mul2[3:1];
fourfive : temp <= mul2[5:3];
sixseven : temp <= mul2[7:5];
default : temp <= 0;
endcase
end
always @(posedge clk) begin
if (clk_cnt == 3'b100) begin//如果节拍到4就让部分积归0,此时已经完成一次计算了
A <= 0;
end else case (temp)
3'b000,3'b111 : begin//这些是从高位到低位的判断,别看反了噢
A <= A + 0;
end
3'b001,3'b010 : begin//加法操作使用补码即可,倍数利用左移解决
A <= A + ({{8{mul1[`size-1]}},mul1} << 2*(clk_cnt-1));
end
3'b011 : begin
A <= A + ({{8{mul1[`size-1]}},mul1} << 2*(clk_cnt-1) + 1);
end
3'b100: begin//减法操作利用“负补码”改成加法操作,倍数利用左移解决
A <= A + ({{8{bmul1[`size-1]}},bmul1} << 2*(clk_cnt-1) + 1);
end
3'b101,3'b110 : begin
A <= A + ({{8{bmul1[`size-1]}},bmul1} << 2*(clk_cnt-1));
end
default: A <= 0;
endcase
end
//当节拍到4的时候写入结果寄存器。
assign res = (clk_cnt == 3'b100) ? A : 0;
endmodule
这是一个八位Booth
二位乘算法的乘法器,至于Booth
一位和Booth
四位的乘法器,大家各自尝试就好。
此外在这个文件当中,我用到了clk_cnt
这个寄存器,大家是不是以为我会多用一个模块用来产生clk_cnt
的波形?
身为一个懒人,我直接在测试文件里写了吼吼吼~
综合电路
37
个元件,36
个IO口,318
根线
测试文件
`timescale 1ns / 1ps
module mul_tb(
);
reg [7:0] mul1,mul2;
wire [15:0] res;
reg clk;
wire clk_en;
reg [2:0] clk_cnt;
initial begin
mul1 <= -8'd7;
mul2 <= -8'd3;
clk <= 0;
clk_cnt <= 3'b0;
end
always # 10 clk = ~clk;
//clk_cnt发生器,懒人版
always @(posedge clk) begin
clk_cnt <= clk_cnt + 1'b1;
if (clk_cnt == 3'b100)
clk_cnt <= 3'b00;
end
//每次运算结束后,让乘数变化,以便产生不同的数据用以观察
assign clk_en = (clk_cnt == 3'b100) ? 1'b1 : 1'b0;
always @ (posedge clk_en) begin
mul2 <= mul2 + 1'b1;
end
mul_booth_signed try(.mul1(mul1),.mul2(mul2),.res(res),.clk(clk),.clk_cnt(clk_cnt));
endmodule
仿真波形
将其改成有符号十进制数形式显示,可以验证电路设计正确。
八位“Booth二位乘算法”乘法器的更多相关文章
- Wellner 自适应阈值二值化算法
参考文档: Adaptive Thresholding for the DigitalDesk.pdf Adaptive Thresholding Using the Integral I ...
- Frequent Pattern 挖掘之二(FP Growth算法)
Frequent Pattern 挖掘之二(FP Growth算法) FP树构造 FP Growth算法利用了巧妙的数据结构,大大降低了Aproir挖掘算法的代价,他不需要不断得生成候选项目队列和不断 ...
- 【算法随记七】巧用SIMD指令实现急速的字节流按位反转算法。
字节按位反转算法,在有些算法加密或者一些特殊的场合有着较为重要的应用,其速度也是一个非常关键的应用,比如一个byte变量a = 3,其二进制表示为00000011,进行按位反转后的结果即为110000 ...
- php面试题之二——数据结构和算法(高级部分)
二.数据结构和算法 1.使对象可以像数组一样进行foreach循环,要求属性必须是私有.(Iterator模式的PHP5实现,写一类实现Iterator接口)(腾讯) <?php class T ...
- C++ 出现bug :二位数组的操作运算,求非对角线的元素的和
编写一个通用程序,求出二位数组(行数和列数必须相等)的非对角线的元素之和,试建立类MATRIX完成上述功能 #include<iostream> using namespace std; ...
- C语言生成32位和64位随机数算法
C语言生成32位和64位随机数算法 /** * randstd.h * * Standard definitions and types, Bob Jenkins * * 2015-01-19: re ...
- 从分布式一致性到共识机制(二)Raft算法
春秋五霸说开 春秋五霸,是指东周春秋时期相继称霸主的五个诸侯,“霸”,意为霸主,即是诸侯之领袖.典型的比如齐桓公,晋文公,春秋时期诸侯国的称霸,与今天要讨论的Raft算法很像. 一.更加直观的Raft ...
- C# 最大二叉堆算法
C#练习二叉堆算法. namespace 算法 { /// <summary> /// 最大堆 /// </summary> /// <typeparam name=&q ...
- JVM(二)GC算法和垃圾收集器
前言 垃圾收集器(Garbage Collection)通常被成为GC,诞生于1960年MIT的Lisp语言.上一篇介绍了Java运行时区域的各个部分,其中程序计数器.虚拟机栈.本地方法栈3个区域随线 ...
随机推荐
- JVM 第三篇:Java 类加载机制
本文内容过于硬核,建议有 Java 相关经验人士阅读. 1. 什么是类的加载? 类的加载指的是将类的 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 ...
- java之网络编程1-Tcp
一,了解之前先了解一下网络基础 首先理清一个概念:网络编程 != 网站编程,网络编程现在一般称为TCP/IP编程 一般的网络编程都称为Socket编程,Socket的英文意思是"插座&quo ...
- LiteOS-任务篇-源码分析-任务调度函数
目录 前言 笔录草稿 核心源码分析 osTaskSchedule函数源码分析 osPendSV函数源码分析 TaskSwitch函数源码分析 调度上层源码分析 osSchedule函数源码分析 LOS ...
- 【dos】wmic命令
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 磁盘 查看硬盘信息:wmic diskdrive 查看逻辑盘信息:wmic l ...
- 多测师讲解selenium_iframe框定位_高级讲师肖sir
iframe 框定位方法: 查看iframe框 京东点击登录定位元素 定位qq: qq登录定位的元素 查找iframe框 定位iframe框 from selenium import webdrive ...
- MySQL 8 新特性之Clone Plugin
Clone Plugin是MySQL 8.0.17引入的一个重大特性,为什么要实现这个特性呢?个人感觉,主要还是为Group Replication服务.在Group Replication中,添加一 ...
- 用Pycharm创建指定的Django版本
最近在学习胡阳老师(the5fire)的<Django企业级开发实战>,想要使用pycharm创建django项目时,在使用virtualenv创建虚拟环境后,在pycharm内,无论如何 ...
- Java常见的一些经典面试题(附答案解析)
前言: 我想每个程序员比较头疼的事情都是:工作拧螺丝,面试造火箭吧.但是又必须经历这个过程,尤其是弄不清面试官问的问题,如果你准备的不是很充分,会导致面试的时候手足无措.今天这篇文章是从已工作5年的程 ...
- 置Hugo的代码高亮
+++ date="2020-10-17" title="设置Hugo的代码高亮" tags=["hugo"] categories=[&q ...
- spring boot:多个filter/多个interceptor/多个aop时设置调用的先后顺序(spring boot 2.3.1)
一,filter/interceptor/aop生效的先后顺序? 1,filter即过滤器,基于servlet容器,处于最外层, 所以它会最先起作用,最后才停止 说明:filter对所有访问到serv ...