原理分析
加减乘除是运算的基础,也是我们在小学课堂里的重点必修课。乘除运算虽然对于我们今天来说还是小菜一碟,让计算机做起来也是九牛一毛不足挂齿,但是要真探究一下计算机是如何完乘除运算的,可还真有些学问和技巧,并不是人脑那么9*9一闪而过81出来了,计算机虽然得到结果的时间可能比人要快上不知道多少个数量级,但它怎么说还是需要一个过程的。
可能不同的CPU内部的运算原理和机制略有差异,我们也无法完全去把这些运算方式搞清楚,这个例程我们就老老实实的用移位累加的方式完成两个8位无符号数的乘法运算。这里先随便举个例子来说明我们的运算原理,例如8位无符号数189和25相乘。
因为计算机只认识0和1,因此一切运算的基础都是0和1,所以我们的运算也必须是基于2进制来进行的。因此,我们首先要完成机制的转换。乘数189对应的2进制数为10111101,被乘数25对应的2进制数为00011001。
按照我们最常用的10进制乘法运算的方式,我们可以得到如图1所示的2进制乘法。在这个运算过程中,我们从被乘数的最低位到最高位依次判断其取值是1还是0,如果是1则对乘数累加,否则不累加(即取0),需要累加的乘数根据当前被乘数位需要进行相应的移位,如被乘数的bit3为1,则乘数相应左移3次(即放大8倍)作为累加数。依据此原理,我们要设计的8位无符号乘法也是通过对被乘数进行逐位判断后累加进行左移的乘数而得到最终的结果。
图1 2进制乘法
在我们的8位无符号乘法运算中,一些基本的接口信号及其功能为:8位无符号数ain和bin是需要进行运算的两个乘数;输出的结果用16位无符号数yout表示;enable信号为运算使能信号;ready信号为运算完成标志位。用户先给ain和bin赋值,然后将enable信号拉高后即开始运算,大约8个时钟周期后运算输出结果,ready信号输出高电平表示运算结果有效,此后如果enable信号被用户拉低则ready信号也随后拉低,表示完成一次运算。接着用户可以给ain和bin赋新的运算值,然后拉高enable信号继续一次新的运算。
Verilog参考实例
module mux(
clk,rst_n,
enable,ain,bin,yout,ready
);
input clk;
input rst_n;
input enable; (1)
input[7:0] ain;(2)
input[7:0] bin;(3)
output reg[15:0] yout;(4)
output reg ready;(5)
reg[4:0] i;(6)
always@(posedge clk)
if(!rst_n) begin
ready <= 1'b0;
yout <= 16'h0000;
i <= 4'd0;
end
else if(enable) begin
if(i < 4'd8) i <= i+1'b1;
else ;
if(i < 4'd7) begin(7)
ready <= 1'b0;
if(ain[i]) yout <= (yout+{1'b0,bin,7'd0})>>1; (8)
else yout <= yout>>1; (9)
end
else if(i == 4'd7) begin(10)
if(ain[i]) yout <= yout+{1'b0,bin,7'd0}; (11)
else ;(12)
ready <= 1'b1;(13)
end
else ready <= 1'b0;
end
else begin
i <= 4'd0;
yout <= 16'h0000;
end
endmodule
(1)运算使能信号。0表示无操作;1表示将对当前输入的ain和bin进行乘法运算。
(2)8位无符号数,他将和bin进行相乘操作。
(3)8位无符号数,他将和ain进行相乘操作。
(4)16位的无符号数,用于存储2个8位无符号数相乘的运算结果。
(5)乘法运算完成标志位。当前运算完成后输出高电平,此后如果enable信号拉低则该信号也拉低无效。
(6)移位计数器,在enable=1时,每个时钟周期i会递增直到i=8停止。i=0~7时,对应进行移位累加计算。
(7)进行7次的移位累加运算(不包括最后一次最高位的累加运算)。我们这里的累加,并不是完全仿照原理中示意的方式进行移位然后累加,而是先将累加的乘数左移7位,然后每次累加完右移1位,对应7次累加完成后,最低位就回到了运算结果的最低位,而第8次累加即最高位的累加运算是不进行移位的。
(8)被乘数的相应位为1,则进行累加并右移1位。
(9)被乘数的相应位为0,则值移位不累加。
(10)第8次累加运算不进行移位操作,
(11)被乘数的相应位为1,则进行累加但不移位。
(12)被乘数的相应位为0,则值不累加也不移位。
(13)最后一次移位,则拉高ready信号表示运算完成,输出结果有效。
仿真验证
这里的验证专门编写了一个小任务,入口参数是给ain和bin的赋值,然后使能enable信号,发起一次运算操作,待ready信号拉高后比对运算结果yout是否正确,打印结果,然后撤销(拉低)enable信号完成当前运算。在initial里面,通过256*256次调用这个小任务,完成对该乘法器的验证。
`timescale 1 ns/ 1 ps
module mux_vlg_tst();
reg [7:0] ain;
reg [7:0] bin;
reg clk;
reg enable;
reg rst_n;
wire ready;
wire [31:0]yout;
reg[8:0] i,j;
mux i1 (
.ain(ain),
.bin(bin),
.clk(clk),
.enable(enable),
.ready(ready),
.rst_n(rst_n),
.yout(yout)
);
initial begin
$display("mux example simulation is running.\n");
rst_n = 0;
clk = 0;
enable = 0;
ain = 8'hzz;
bin = 8'hzz;
#1000;
@(posedge clk);
rst_n = 1;
for(i=0;i<256;i=i+1) begin(1)
for(j=0;j<256;j=j+1) begin(2)
mux_ab(i,j);(3)
end
end
$display("mux example simulation is over.All right.\n");(4)
$stop;
end
always #10 clk = ~clk;
task mux_ab;(5)
input[7:0] a;
input[7:0] b;
begin
@(posedge clk); #3;
ain = a;
bin = b;
enable = 1;(6)
@(posedge ready);(7)
@(posedge clk); #3;
if(a*b == yout) $display("%3d * %3d = %5d, it is right.",a,b,yout);
(8)
else begin(9)
$display("%3d * %3d = %5d, it is wrong.",a,b,yout);
$stop;
end
@(posedge clk); #3;
enable = 0;(10)
ain = 8'hzz;
bin = 8'hzz;
end
endtask
endmodule
(1)乘数ain从0到255递增。
(2)被乘数bin从0到255递增,以此完成全便利测试。
(3)调用乘法运算任务,输入参数i和j分别会赋值给ain和bin用于运算。
(4)所有测试成功完成,则最终打印“mux example simulation is over.All right.”的信息,如果测试中出现任何一个错误,则测试脚本会停止运行,也就意味着见不到这条语句。
(5)产生一次乘法运算任务的激励。输入参数a和b分别为运算的输出乘数和被乘数。
(6)使能信号拉高,发起一次乘法运算。
(7)等待ready信号拉高,表示乘法运算完成,输出结果有效。
(8)测试模块输出的乘法运算结果正确,打印相关信息。
(9)测试模块输出的乘法运算结果错误,打印错误提示并停止测试脚本的运行。
(10)拉低enable信号完成当前运算。
如图2所示,为当前测试结果,我们看到了最后的“mux example simulation is over.All right.”提示信息,表示测试通过。
图2 mux工程测试结果