如何用Verilog设计一个复数乘法器

如何用Verilog设计一个复数乘法器

复数乘法原理

1
2
3
4
5
6
7
8
9
a = a_i + j * a_q
b = b_i + j * b_q

(a_i + j * a_q) * (b_i + j * b_q)
= a_i * b_i + j * a_q * b_i + j * a_i * b_q - a_q * b_q
= (a_i * b_i - a_q * b_q) + j * (a_i * b_q + a_q * b_i)

result_i = a_i * b_i - a_q * b_q
result_q = a_i * b_q + a_q * b_i

如上式子化简,需要四个乘法器。先算出乘法再算出加法。

该式子还可以进一步化简

1
2
3
4
5
6
7
8
9
10
result_i = a_i * b_i - a_q * b_q
= a_i * b_i - a_q * b_q+ a_i * b_q - a_i * b_q
= a_i * (b_i + b_q) - b_q * (a_i + a_q)

result_q = a_i * b_q + a_q * b_i
= a_i * b_q + a_q * b_i + a_i * b_i - a_i * b_i
= a_i * (b_i + b_q) - b_i * (a_i - a_q)

result_i = a_i * (b_i + b_q) - b_q * (a_i + a_q)
result_q = a_i * (b_i + b_q) - b_i * (a_i - a_q)

上式多用了加法器,但只用了三个乘法器,优化了资源

两种结构的比较

复数乘法比较

在常规方法下,ac - bd 先乘后减,比如两个输入的实部和虚部都是8bit。那么最后相乘结果的实部和虚部就是16bit,相减最后为17bit。

但是在第二种结构2下,需要先预加操作,相乘,再相加。两个8bit数相加为9bit,8bit和9bit相乘后为17bit,最后再相加结果为18bit。

从电路的运算结构上来说,结构2最后输出比结构1多出1bit,实际上这1bit也是多余的,工程中在后级模块的运算中可以将多余的符号为截位饱和掉。

实部和虚部是8bit,复数乘法后的结果我们会取16bit。最高位1bit舍去,这是因为最后在两个数为负的最大值时才会出现溢出的情况,比如 -8的二进制补码为1000,但实际工程中,我们不会取-8这个值,也就是说4bit的有符号数,我们取能表示的最大范围为 -7 —7,而不是-8—7。为的是让数的表示范围对称,避免运算的溢出问题。

Verilog实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//result_i = a_i * (b_i + b_q) - b_q * (a_i + a_q) 
//result_q = a_i * (b_i + b_q) - b_i * (a_i - a_q)

assign sumb[SUMB_WIDTH - 1:0] = dinb_i + dinb_q;
assign suma[SUMA_WIDTH - 1:0] = dina_i + dina_q;
assign suba[SUBA_WIDTH - 1:0] = dina_i - dina_q;

signed_mult
#(
.A_WIDTH (DINA_WIDTH),
.B_WIDTH (SUMB_WIDTH)
)
u0_signed_mult(/*autoinst*/
// Outputs
.product (product_a[PRODUCT_A_WIDTH-1:0]),
// Inputs
.multa (dina_i[DINA_WIDTH-1:0]),
.multb (sumb[SUMB_WIDTH-1:0]));

signed_mult
#(
.A_WIDTH (DINB_WIDTH),
.B_WIDTH (SUMA_WIDTH)
)
u1_signed_mult(/*autoinst*/
// Outputs
.product (product_b[PRODUCT_B_WIDTH-1:0]),
// Inputs
.multa (dinb_q[DINB_WIDTH-1:0]),
.multb (suma[SUMA_WIDTH-1:0]));

signed_mult
#(
.A_WIDTH (DINB_WIDTH),
.B_WIDTH (SUBA_WIDTH)
)
u2_signed_mult(/*autoinst*/
// Outputs
.product (product_c[PRODUCT_C_WIDTH-1:0]),
// Inputs
.multa (dinb_i[DINB_WIDTH-1:0]),
.multb (suba[SUBA_WIDTH-1:0]));

assign mult_i[MULT_WIDTH-1:0] = product_a - product_b;
assign mult_q[MULT_WIDTH-1:0] = product_a - product_c;

Testbench平台搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
integer i;
integer j;

initial begin
for(i = 4; i <= 15; i= i+1)begin
for(j = 4; j <= 15; j = j+1)begin
dina_i = $signed(i);
dina_q = $signed(j);
dinb_i = $signed(j);
dinb_q = $signed(i);
#50;
if ((mult_i != ((dina_i * dinb_i) - (dina_q * dinb_q))) ||
(mult_q != ((dina_i * dinb_q) + (dina_q * dinb_i))) ) begin
$display("***ERROR at time = %0d ***", $time);
$display("dina_i =%d, dina_q =%d, dinb_i =%d, dinb_q =%d, mult_i =%d, mult_q =%d",
dina_i, dina_q, dinb_i, dinb_q, mult_i, mult_q);

$stop;
end
#50;
end
end

$display("****** Testbench Successfully completed! ****** ");

$display("*** ###### ### ##### ##### *** ");
$display("*** # # ## ## # # *** ");
$display("*** # # # # # # *** ");
$display("*** ######## ####### ##### ##### *** ");
$display("*** # # # # # *** ");
$display("*** # # # # # *** ");
$display("*** # # # ##### ##### *** ");
$finish;
end

在testbench中有一些技巧可以提高仿真效率,自动化对比仿真,对比错误将信息打印出来再回到波形中去查看,对比通过输出pass。

test_error

testerror信息

test_pass

testpass信息

将设计电路参数化可重复使用,构建自己的代码库,搭建起你的数字积木,本设计博主放在了GitHub上,可以在订阅号后台回复 “ip_lib”得到GitHub路径进行交流。

NingHeChuan wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!