weiqi7777

使用乘法器实现各种移位操作

0
阅读(3168)

       

昨天看了一本书,使用乘法器来实现各种移位操作。包括逻辑左移,逻辑右移,算术右移,循环右移。

实现的框图如下所示:考虑对32位数据处理

clip_image002

核心器件就是一个乘法器。

a是输入的32位数据,

num是移位的5位数据

b是乘法器的64位输出

下面是实现各种移位的算法:

1、逻辑左移:结果取D

2、逻辑右移:num取反加1,结果取C

3、算术右移:num取反加1,结果取C。如果A[31]1A取反,在对结果C取反即可。为0,结果取C

4、循环右移:num取反加1,结果取C|D

看似是很简单的,而且算法也还是挺奇特的。以前还没有发现,还可以用乘法器来实现移位。

下面用verilog写程序进行验证。

代码也比较简单,对输入进行选择,对输出进行选择,然后中间一个乘法器。硬件电路大致为:

clip_image004

有了这个图,写代码就更容易了。

module shitf_according_mul( input [31:0] a, input [4:0] num, input [1:0] shift_mode, output reg[31:0] out ); localparam logic_left_shift = 2'b00; localparam logic_right_shift = 2'b01; localparam cycle_right_shift = 2'b10; localparam airth_right_shift = 2'b11; //data reg [31:0] reg_a; always@(*) begin case(shift_mode) logic_left_shift: reg_a = a; logic_right_shift: reg_a = a; cycle_right_shift: reg_a = a; airth_right_shift: reg_a = a[31] ? ~a:a; endcase end //shift number reg [31:0] reg_num; always@* begin case(shift_mode) logic_left_shift: reg_num = 32'b1<
       

纯组合逻辑设计,设计好了,需要写testbench验证吧。Testbench编写,也比较简单,就给输入和移位的值以及移位模式随便赋值就行了。

核心代码就是

shift_mode = logic_left_shift; for(j=0; j<=10; j=j+1) begin a = {$random()}; for(i=0; i<=31; i=i+1) begin num = i; #100; end end shift_mode = logic_right_shift; for(j=0; j<=10; j=j+1) begin a = {$random()}; for(i=0; i<=31; i=i+1) begin num = i; #100; end end shift_mode = cycle_right_shift; for(j=0; j<=10; j=j+1) begin a = {$random()}; for(i=0; i<=31; i=i+1) begin num = i; #100; end end shift_mode = airth_right_shift; for(j=0; j<=10; j=j+1) begin a = {$random()}; for(i=0; i<=31; i=i+1) begin num = i; #100; end end

每组移位测试10次,输入使用随机数产生,移位的值使用for循环从0遍历到31。好,开仿。

波形图如下所示:

clip_image006

这有输出了,但是这看着也太蛋疼了。这怎么验证,32位数据。对比输出和输入都对比得要死了。

这个时候,就要用到自动比对了。因为电路的功能是确定的,所以我们肯定知道对应不同的输入,输出应该是什么。我们可以通过其他方法,得到输出的值,然后和仿真得到的结果进行比对,如果是一样的话,就说明电路功能是正确的,如果不一样的话,就说明电路设计就有问题了。

testbench中加入自动比对的代码。既然要自动比对,首先要得到输入对应的输出。移位,用代码写是比较简单的。

这样,就得到了输出值了。

然后是比对,比对的话,就比较计算出来的输出和电路的输出是否一样,一样的话说明正确。这里就用一个信号来表示。

//`define no_delay_detect `ifdef no_delay_detect wire error_flag; assign error_flag = ((out != shift_out )? 1 : 0); `else reg error_flag; always@(*) begin #1; if(out != shift_out) error_flag = 1; else error_flag = 0; end `endif always@(posedge error_flag) begin if(error_flag==1) $display("a=%b j=%d num=%d shift_mode=%d out=%b",a,j,num,shift_mode,out); end

这里,用到了verilog的条件编译的一些知识。`ifdef这个和c语言中的#ifdef一样,`else#else一样,最后的`endif#endif是一样的。这个地方用到条件编译,是因为对应这两种比对方法,结果不一样的。第一种是没有加延时比对,第二种是加延时比对的。

最后一个always对标志进行判断,如果标志为1的话,就说明输出有错,将信息打印。否则就不打印。这里always的信号列表使用的是标志的上升沿。这样就保证了一次输入就检测一次。

先看一下使用第一个条件编译的仿真结果。就是不带延时的仿真。

clip_image008

波形中有标志信号有1,说明输出结果有的地方不对。

在看看打印信息。

clip_image010

发现,打印信息竟然在每一次输入变化的时候,都会有。这明显不对了。从波形图中看出,只是有的地方输出不正确,大多数地方是正确的。那么正确的地方应该是不会有打印信息的。


在来看看第二种条件编码仿真。即带延时的比对仿真。

clip_image012

波形图和上面那个一样,标志信号有的地方为1,说明输出不对。

但是看看打印信息。

clip_image014

发现,打印信息变少了,而且是在输出错误的地方才打印,输出正确的地方是没有打印的。

造成这两种不同的打印情况,是为什么了?这个和仿真器的仿真机制有关系的。Verilog的仿真是事件型的仿真,因为这是仿真硬件,而硬件是并行执行的。

在检测的always块中设置断点,看第一种条件编译仿真。

clip_image016

clip_image018

运行后,会在输入第一次变化的地方仿真暂停。看波形图。右边是波形,左边value是右边黄色标线出各个信号的值。

看出,在输入没有变化的时候,电路的输出和预想的输出是一样的,所以标志信号为0。所以仿真继续,不会停。但是在输入变化的时候,发现,电路的输出没有变,但是预想的输出已经变了,因为这个时候输入已经变了,所以一比对,发现电路输出和预想的输出不一样,然后标志置1。所以就触发了错误检测always块,所以程序就暂停了,然后就打印信息了。

原来问题就是在这里。在testbench中,信号的变化可以认为是没有延时的,输入一变化,输出马上就变化。但是在verilog的电路模块中,输出是有延时的,即不是输入一变化,输出就马上有。而是要经历一个很短暂的时间,输出才变化。

所以说,检测的时候,应该是在输入变化后的延时一段时间后检测才是正确的。所以才需要第二种条件编译仿真。带延时的检测。

有了这样一个自动比对,就不用一个一个的去看波形验证功能是否正确了。只需要去看标志为1的地方就行了。

clip_image020

定位到标志为1的地方。看出,移位的位数是0。移位方式是逻辑右移。电路的输出竟然是0。其实后面每一个出错的地方都是在移位的位数是0的地方。这是什么原因了?

其实是出现在以下的代码中

logic_right_shift:

reg_num=32'b1<<(~num + 1'b1);

仿真的时候看到,当num0的时候,reg_num1。所以乘法的结果的高32位是为0的,所以输出才是0

其实,当移位的位数为0的时候,输出就是输入本身。都不用对数据进行操作。所以使用乘法器构造移位,移位位数为0是比较特殊要考虑的。

可能大家觉得奇怪,为什么要用乘法器去构造移位器了,明显乘法器用的资源比普通的移位器用的资源多去了。而且还是32位乘法器。这个就得看具体的应用了。假设在一个设计中,乘法器是必须要使用的,那么用乘法器来实现移位就很有必要了。因为乘法器是必须要用的,那为什么还要花另外的逻辑去实现移位了。就好比夏天在一个空调房里面,空调是必须要开的,那肯定就不会有人傻到再去买一个风扇来用吧。

Baidu
map