FPGA应用笔记(一)——除法器
文章目录
0.除法器简介
FPGA可以实现几乎所有的数字逻辑和时序,FPGA的速度很快,但是如果在用Verilog HDL编程时,把其加减乘除当作给STM32编程时的加减乘除的话,那就很有可能会被BUG缠绕。单片机编程和计算机编程都是在体系结构层编程,加减乘除还有浮点操作的具体实现都是不用去管的。但是FPGA则是从逻辑层面去编程,乘法(不包括与二的次幂相乘的乘法)和除法的使用都是要谨慎的,因为它在时序逻辑中,占用的时钟周期太多了,就算如果用组合逻辑去实现,那么除法完成时间也是不能忽略不计的。
作为EE专业的学生,总是需要用到FPGA来进行高速信号处理,正好在这段时间来复习一下之前的Verilog HDL编程的知识。由于Verilog HDL从语法上看是一种类C语言,由于笔者有丰富的单片机编程经验,故也对C语言十分熟悉,所以很多语法相关的知识都不需要在文章中回顾。文章主要从编程方法和思路的角度来复习Verilog HDL.本项目基于Vivado 2018.01实现,没有用到FPGA做实物验证(这个行为不大对,但是考虑到本项目的目的,过程就从简了)
1.除法器的设计
1.1 需求分析
我们要设计的是一款8-bit时序整数除法器,输入包括被除数、除数、时间信号和复位信号,得到的结果包括商和余数,以及结果有效信号,其中被除数和除数都是八位并行输入,商和余数也是八位并行输出。
1.2 算法设计
我们可以先研究长除法的过程,从中提炼出除法器的实现算法。
图一 长除法1010÷0011
如图一,对于长除法1010÷0011,我们可以把这个过程分解为这样:
1、先拿0011和0001比较,发现0011大于0001,故上0;
2、再把0011和0010比较,发现依然大于,则还是上0;
3、把0010和0101比较,0010小于0101,故我们在第三位上1,并用0011去减101得到100;
4、再拿0011和0100比较,同3,第四位上1并算出0100-0011,得到的即为余数。
从以上过程中,对于一个N-bit除以N-bit的除法,我们可以归纳出这样的算法:
1、将被除数扩展为2N位记作A,除数也扩展为2N位并左移N位记作B,计数变量cnt = 0;
2、将A左移一位;
3、比较A和B高N位的大小,若[2N-1:N]A>[2N-1:N]B,则令A = A - B + 1。若cnt!=0x0F,则令cnt =
cnt+1,并回到第二步,若cnt == 0x0F,则进入第四步;
4、计算结束,A的高N位即为余数,低N位为商。
1.3 时序设计
因为是设计时序电路,所以一定要设计好到:“到了哪个时间节拍,做什么事情。”由于涉及到了算法,所以我们需要一个reg变量state来记录状态,确定什么状态执行什么操作。通过分析1.2中的算法,我们可以分析得到,state需要五种状态:
1、空闲(idle)状态,在rst信号有效以及除数为0(运算无效)的时候处于此状态;
2、初始化(init)状态。在除数或被除数发生变化或者rst失能时,需要重新计算结果,在初始化状态下给A、B重新
赋值。
3、计算状态1(cal1)。执行将A左移的操作;
4、计算状态2(cal2)。执行算法的第三步中断步骤。
5、完成状态(done)。在计算完成后,将结果输出,并使输出有效信号使能。
在此我们使用单热码(single-hot code)来表示这五种状态,即0b00001,0b00010,0b00100,0b01000,
0b10000. 保证有记录状态的寄存器后,后续的编程工作也相对好做了。
2. 基于Verilog HDL的实现
parameter state_init = 4'b0000;
parameter state_idle = 4'b0001;
parameter state_cal1 = 4'b0010;
parameter state_cal2 = 4'b0011;
parameter state_done = 4'b0100;
module divider(clk, rst_n, A,B,C,D,ov);
input clk;
input rst_n;
input [15:0]A;
input [15:0]B;
output [15:0]C;
output [15:0]D;
output ov;
/*寄存器需要初始化*/
reg output_valid = 0;
reg [31:0] temp_a = 32'b0 ,temp_b = 32'b0;
reg [15:0] bufa = 16'b0,bufb = 16'b0;
reg [15:0] TMPA = 16'b0,TMPB = 16'b0;
reg [4:0] state = state_idle;
reg [3:0] cnt = 4'b0000;
assign ov = output_valid;
assign C = temp_a[15:0] & {16{output_valid}};
assign D = temp_a[31:16] & {16{output_valid}};
always@(A or B)
begin
bufa <= A;
bufb <= B;
end
always@(posedge clk)
begin
if(rst_n)
begin
case(state)
state_idle:
begin
if(bufa===TMPA&&bufb===TMPB)
begin
state <= state_idle;
end
else
begin
TMPA <= bufa;
TMPB <= bufb;
state <= state_init;
output_valid <= 0;
end
end
state_init:
begin
temp_a[15:0] <= bufa;
temp_b[31:16] <= bufb;
state <= state_cal1;
cnt <= 4'b0;
end
state_cal1:
begin
temp_a <= temp_a<<1;
state <= state_cal2;
end
state_cal2:
begin
if(temp_a[31:16]>=temp_b[31:16])
begin
temp_a <= temp_a - temp_b + 1;
end
if(cnt === 4'b1111)
begin
state <= state_done;
end
else
begin
cnt <= cnt + 1;
state <= state_cal1;
end
end
state_done:
begin
output_valid <= 1;
end
default: state <= state_idle;
endcase
end
else
output_valid <= 0;
end
endmodule
3. 基于Vivado HLx的仿真结果
如图,Testbench输入: 被除数 A=12,除数B=3.
最后除法器算出了结果:商4余0.
4. 注意事项
1、用时序逻辑实现功能前,先用FSM将过程建模,再去用Verilog实现;
2、每一个寄存器一定都需要在定义的时候初始化!!!不然在仿真时会有玄学问题;
3、仿真结果可以证明用时序逻辑实现除法器效率极低,建议采用组合逻辑或者其它更高效的方式实现除法器。本次项目的目的只是为一个FPGA菜鸟进一步熟悉FSM编程。