目录
1.前言
在数字信号处理种复数乘法去使用的非常多,今天分享一个自己设计的复数乘法器,并将设计参数化,放入自己的代码库,供有需要时直接使用。相比于官方提供的封闭的IP核,自己设计的IP核虽然性能比不过,但是更灵活,方便进行个性化修改。FPGA其实就像搭积木一样,只要自己的代码库够丰富,设计只会越来越轻松!今天学习了在testbench中自动化对比仿真的技巧,对比错误将信息打印出来,方便回到波形中去查看,对比通过输出pass。
2.原理
两个复数相乘有:
C A ⋅ C B = ( a + b j ) ( c + d j ) C_A \cdot C_B = (a + bj)(c + dj) CA⋅CB=(a+bj)(c+dj) = ( a c − b d ) + ( a d + b c ) j =(ac - bd) + (ad + bc)j =(ac−bd)+(ad+bc)j
那么,
R e = a c − b d Re = ac - bd Re=ac−bd
I m = a d + b c Im = ad + bc Im=ad+bc
如果直接计算需要使用四个乘法器,外加两个加法器。为了减少资源使用:
令
m 1 = d ( a − b ) m_1 = d(a-b) m1=d(a−b)
m 2 = a ( c − d ) m_2 = a(c-d) m2=a(c−d)
m 3 = b ( c + d ) m_3 = b(c+d) m3=b(c+d)
代入上面式子可得
R e = a c − b d = m 1 + m 2 Re = ac - bd = m_1 + m_2 Re=ac−bd=m1+m2
I m = a d + b c = m 1 + m 3 Im = ad + bc = m_1 + m_3 Im=ad+bc=m1+m3
这样变换之后改为使用3个乘法器,5个加法器,相比减少了1个乘法器的使用,虽然多使用了3个加法器,但是3个加法器所消耗的资源远远不及一个乘法器。
电路结构如下:
3.代码
将设计参数化,形成自己的IP核,参考如下:
c
module complexX #(
parameter WIDTH1 = 16,
parameter WIDTH2 = 16
)(
input clk,
//输入(a+bj)(c+dj)
input signed [WIDTH1-1:0] a,
input signed [WIDTH1-1:0] b,
input signed [WIDTH2-1:0] c,
input signed [WIDTH2-1:0] d,
output signed [WIDTH1+WIDTH2-1:0] Re,
output signed [WIDTH1+WIDTH2-1:0] Im
);
//m1 = d(a-b)
//m2 = a(c-d)
//m3 = b(c+d)
//Re = ac - bd = m1 + m2
//Im = ad + bc = m1 + m3
wire [WIDTH1:0] aSb;
wire [WIDTH2:0] cSd;
wire [WIDTH2:0] cAd;
reg [WIDTH1+WIDTH2-1:0] m1 = 'd0;
reg [WIDTH1+WIDTH2-1:0] m2 = 'd0;
reg [WIDTH1+WIDTH2-1:0] m3 = 'd0;
assign aSb = $signed(a) - $signed(b);
assign cSd = $signed(c) - $signed(d);
assign cAd = $signed(c) + $signed(d);
always@(posedge clk)begin
m1 <= $signed(d) * $signed(aSb);
m2 <= $signed(a) * $signed(cSd);
m3 <= $signed(b) * $signed(cAd);
end
assign Re = $signed(m1) + $signed(m2);
assign Im = $signed(m1) + $signed(m3);
endmodule
4.仿真
编写仿真代码,遍历范围内的数据对乘法器进行验证。在testbench中有一些技巧可以提高仿真效率,自动化对比仿真,对比错误将信息打印出来再回到波形中去查看,对比通过输出pass。因为遍历16位宽的数据仿真时间太长,这里将参数改为4位。
c
module complexX_tb;
parameter T = 10;
parameter WIDTH1 = 4;
parameter WIDTH2 = 4;
reg clk ;
reg signed [WIDTH1 -1 : 0] a,b ;
reg signed [WIDTH2 -1 : 0] c,d ;
wire signed [WIDTH1+WIDTH2-1 : 0] Re,Im ;
complexX
#(.WIDTH1(WIDTH1),
.WIDTH2(WIDTH2))
u_complexX(
.clk(clk),
.a (a ),
.b (b ),
.c (c ),
.d (d ),
.Re (Re ),
.Im (Im )
);
always #(T/2) clk = ~clk;
reg signed [WIDTH1 -1 : 0] i;
reg signed [WIDTH2 -1 : 0] j;
initial begin
clk = 1'b0;
#(10*T);
for(i = -$pow(2,WIDTH1-1); i < $pow(2,WIDTH1-1)-1; i= i+1)begin
for(j = -$pow(2,WIDTH2-1); j < $pow(2,WIDTH2-1)-1; j = j+1)begin
a = $signed(i);
b = $signed(j);
c = $signed(i);
d = $signed(j);
#(T);
if ((Re != ((a * c) - (b * d)))||(Im != ((a * d) + (b * c))) ) begin
$display("***ERROR at time = %0d ***", $time);
$display("a =%d, b =%d, c =%d, d =%d, Re =%d, Im =%d",a, b, c, d, Re, Im);
$stop;
end
#(T);
end
end
$display("****** Testbench Successfully completed! ****** ");
$display("*** ###### ### ##### ##### *** ");
$display("*** # # ## ## # # *** ");
$display("*** # # # # # # *** ");
$display("*** ######## ####### ##### ##### *** ");
$display("*** # # # # # *** ");
$display("*** # # # # # *** ");
$display("*** # # # ##### ##### *** ");
$stop;
end
endmodule
如果仿真出现计算错误,会立即停止仿真,并显示下图所示的信息:
如果仿真完全正确,则会打印如下信息:
最终仿真结果如下图所示:
学习FPGA的时候很多常用的模块可以将其参数化,形成自己的ip,以后方便调用。做FPGA设计是一个逐渐积累的过程。相比于官方提供的封闭的IP核,自己设计的IP核虽然性能比不过,但是更灵活,方便进行个性化修改。FPGA其实就像搭积木一样,只要自己的代码库够丰富,设计只会越来越轻松!点击下面链接查看合集