設(shè)計(jì)思想
首先需要把FIR最基本的結(jié)構(gòu)實(shí)現(xiàn),也就是每個(gè)FIR抽頭的數(shù)據(jù)與其抽頭系數(shù)相乘這個(gè)操作。由頂層文件對(duì)這個(gè)基本模塊進(jìn)行多次調(diào)用。
由于FIR抽頭系數(shù)是中心對(duì)稱的,為了減少乘法在FPGA內(nèi)的出現(xiàn),每個(gè)基本結(jié)構(gòu)同時(shí)會(huì)輸入兩個(gè)信號(hào),也是關(guān)于中心對(duì)稱的。
此外,為了防止后續(xù)相加的過程引起符號(hào)位溢出,F(xiàn)IR基本模塊需要對(duì)乘法結(jié)果進(jìn)行符號(hào)位擴(kuò)展。
擴(kuò)展完成后,如果同時(shí)對(duì)這些(71個(gè))加權(quán)結(jié)果相加,肯定會(huì)使得系統(tǒng)運(yùn)行速率上不去,而且設(shè)計(jì)的比較死板。這里需要引入流水線操作;何為流水線操作,簡(jiǎn)單地說,本來你一個(gè)人承擔(dān)一桌菜,你需要洗菜,切菜,炒菜,裝盤,上桌,不僅十分麻煩而且很耽誤時(shí)間;這個(gè)時(shí)候有人過來幫你,一個(gè)人洗菜,一個(gè)人切菜,一個(gè)人炒菜,一個(gè)人裝盤,你負(fù)責(zé)上桌,雖然費(fèi)了些人,但是每個(gè)人的任務(wù)都比較輕松所以做事速度也很快,這就是流水線操作,把一件很復(fù)雜的事情劃分成N個(gè)小事,雖然犧牲了面積但換取了系統(tǒng)運(yùn)行時(shí)鐘的提升。
前期準(zhǔn)備
除了Verilog模塊,我們還有幾樣?xùn)|西需要準(zhǔn)備。首先,需要將FIR抽頭系數(shù)定點(diǎn)化,上一文使用的FIR抽頭系數(shù)都是很小的浮點(diǎn)數(shù),為此,我們直接對(duì)每個(gè)系數(shù)乘以2的15次冪,然后取整數(shù),舍去小數(shù)位,設(shè)定FIR抽頭系數(shù)位寬為16bit;因?yàn)橄禂?shù)本身比較小,不擔(dān)心會(huì)溢出。注意,這里抽頭系數(shù)的位寬盡量不超過信號(hào)位寬,否則可能會(huì)有問題。
為了方便多個(gè)模塊同時(shí)調(diào)用FIR系數(shù),這里使用Python直接將定點(diǎn)化的系數(shù)生成為function,輸入為index,需要第N階的FIR系數(shù),就調(diào)用function,輸入?yún)?shù)為N,輸出為定點(diǎn)化的系數(shù)。
所謂定點(diǎn)化,這里使用的方法十分粗暴,直接對(duì)所有浮點(diǎn)數(shù),乘以一個(gè)2的n次冪。然后對(duì)參數(shù)向下取整,舍棄小數(shù)位。
FIR浮點(diǎn)系數(shù)轉(zhuǎn)化為定點(diǎn)數(shù)并生成function的代碼如下:
def coef2function(filename, exp, gain):
# :param filename: FIR抽頭系數(shù)文件名
# :param exp: 浮點(diǎn)數(shù)轉(zhuǎn)定點(diǎn)數(shù)的位寬
# :param gain: 浮點(diǎn)數(shù)整體的增益,增益為power(2, gain)
# :return:
coef = set_coef(filename)
with open('fir_coef.v', 'w') as f:
f.write('function [{}:0] get_coef;\\n'.format(exp-1))
f.write('input [7:0] index;\\n')
f.write('case (index)\\n')
for i in range(len(coef)):
f.write('{}: get_coef = {};\\n'.format(i,int(np.floor(coef[i] * np.power(2,gain)))))
f.write('default: get_coef = 0;\\n')
f.write('endcase\\nendfunction')
轉(zhuǎn)換生成的function示例如下:
function [15:0] get_coef;
input [7:0] index;
case (index)
0: get_coef = 0;
1: get_coef = 0;
2: get_coef = 2;
3: get_coef = 10;
...
69: get_coef = 10;
70: get_coef = 2;
71: get_coef = 0;
72: get_coef = 0;
default: get_coef = 0;
endcase
endfunction
這樣,當(dāng)多個(gè)基本模塊并行運(yùn)行時(shí),每個(gè)模塊的系數(shù)可以通過調(diào)用function獲取對(duì)應(yīng)的參數(shù)。
仿真需要有信號(hào)源供FIR濾波,所以直接將仿真用的信號(hào)源定點(diǎn)化;因?yàn)門estbench中使用readmemh或者readmemb讀取txt文檔數(shù)據(jù),只能讀取二進(jìn)制或16進(jìn)制數(shù)據(jù),所以需要對(duì)數(shù)據(jù)進(jìn)行二進(jìn)制或16進(jìn)制轉(zhuǎn)換。
信號(hào)源選取上一文的信號(hào)源,由于該信號(hào)源最大值為3,設(shè)定信號(hào)源的位寬為16位,為防止數(shù)據(jù)溢出,信號(hào)源整體乘以2的12次冪,然后取整舍去小數(shù)位。為了方便后續(xù)轉(zhuǎn)二進(jìn)制,這里需要將數(shù)據(jù)由16bit有符號(hào)轉(zhuǎn)為16bit無符號(hào);轉(zhuǎn)換的過程為,如果data[i]小于0,直接設(shè)定data[i] = 2^16 + data[i]。然后使用“{{:0>16b}}”.format(data[i])轉(zhuǎn)換為16bit二進(jìn)制,存入cos.txt。
浮點(diǎn)數(shù)轉(zhuǎn)換定點(diǎn)數(shù)并轉(zhuǎn)換二進(jìn)制數(shù)據(jù)存入txt轉(zhuǎn)換代碼如下:
def float2fix_point(data, exp, gain, size):
# '''
# :param data: 信號(hào)源數(shù)據(jù)
# :param exp: 浮點(diǎn)數(shù)轉(zhuǎn)定點(diǎn)數(shù)的位寬
# :param gain: 浮點(diǎn)數(shù)整體乘以增益,增益為power(2,15)
# :param size: 轉(zhuǎn)換多少點(diǎn)數(shù)
# :return:
# '''
if size > len(data):
print("error, size > len(data)")
return
data = [int(np.floor(data[i] * np.power(2, gain) )) for i in range(size)]
fmt = '{{:0 >{}b}}'.format(exp)
n = np.power(2, exp)
for i in range(size):
if data[i] > (n //2 - 1):
print("error")
if data[i] < 0:
d = n + data[i]
else:
d = data[i]
data[i] = fmt.format(d)
# data = [bin(data[i]) for i in range(4096)]
np.savetxt('cos.txt', data, fmt='%s')
實(shí)現(xiàn)方法
為了方便看示例代碼,這里假定信號(hào)位寬DATA_BITS為16,系數(shù)位寬為COEF_BITS為16,擴(kuò)展符號(hào)位寬EXTEND_BITS為5, 階數(shù)FIR_ORDER為72。
設(shè)計(jì)思路還是從底層開始設(shè)計(jì),首先需要實(shí)現(xiàn)FIR的基本模塊。前面提到,為了節(jié)省乘法器,每個(gè)模塊輸入兩個(gè)信號(hào)和一個(gè)FIR抽頭系數(shù),兩個(gè)參數(shù)相加,相加結(jié)果直接乘以系數(shù),最后做符號(hào)位擴(kuò)展,防止后續(xù)操作導(dǎo)致符號(hào)位溢出。
fir_base.v 主要代碼:
reg signed [DATA_BITS + COEF_BITS - 1:0] data_mult;
// 因?yàn)镕IR系數(shù)是中心對(duì)稱的,所以直接把中心對(duì)稱的數(shù)據(jù)相加乘以系數(shù)
// 相加符號(hào)位擴(kuò)展一位
wire signed [DATA_BITS:0] data_in ;
assign data_in = {data_in_A[DATA_BITS-1], data_in_A} + {data_in_B[DATA_BITS-1], data_in_B};
// 為了防止后續(xù)操作導(dǎo)致符號(hào)位溢出,這里擴(kuò)展符號(hào)位,設(shè)計(jì)位操作知識(shí)
assign data_out = {{EXTEND_BITS{data_mult[DATA_BITS + COEF_BITS - 1]}},data_mult };
always @(posedge clk or posedge rst) begin
if (rst) begin
// reset
fir_busy <= 1'b0;
data_mult <= 0 ;
output_vld <= 1'b0;
end
else if (en) begin
//如果coef為0,不需要計(jì)算直接得0
data_mult <= coef != 0 ? data_in * coef : 0;
output_vld <= 1'b1;
end
else begin
data_mult <= 'd0;
output_vld <= 1'b0;
end
end
完成了基本模塊后,頂層模塊就是調(diào)用基本模塊,然后對(duì)運(yùn)算結(jié)果進(jìn)行相加操作。但這里需要注意,頂層首先需要73個(gè)16bit的寄存器,用來保存?zhèn)魅氲男盘?hào)并實(shí)現(xiàn)每時(shí)鐘周期上升沿,73個(gè)數(shù)據(jù)整體前移;學(xué)過數(shù)據(jù)結(jié)構(gòu)的同學(xué)可以把這個(gè)想象成隊(duì)列結(jié)構(gòu),每次信號(hào)上升沿時(shí),隊(duì)首信號(hào)出隊(duì),隊(duì)尾補(bǔ)進(jìn)新的信號(hào)。
實(shí)現(xiàn)方法如下:
// FIR輸入數(shù)據(jù)暫存寄存器組
reg signed [DATA_BITS-1:0] data_tmp [FIR_ORDER:0] ;
always @(posedge clk or posedge rst) begin
if (rst) begin
// reset
data_tmp[0] <= 0;
end
else if (data_in_vld) begin
data_tmp[0] <= data_in;
end
end
generate
genvar j;
for (j = 1; j <= FIR_ORDER; j = j + 1)
begin: fir_base
//這里無法兼顧0,F(xiàn)IR_HALF_ORDER
always @(posedge clk or posedge rst) begin
if (rst) begin
// reset
data_tmp[j] <= 0;
end
else if (data_in_vld) begin
data_tmp[j] <= data_tmp[j-1];
end
end
endgenerate
這里實(shí)現(xiàn)了從0-72共73個(gè)寄存器,使用了Verilog的類似二維數(shù)組的寄存器定義用法。可以從代碼看到,0號(hào)data_tmp過于特殊,需要保存輸入的信號(hào),而其他data_tmp直接使用generate for語(yǔ)法實(shí)現(xiàn)前面提到的“隊(duì)列”功能。generate for語(yǔ)法是可以綜合的, 其中for循環(huán)的參數(shù)必須是常數(shù) ,其作用就是直接在電路上復(fù)制循環(huán)體的內(nèi)容。對(duì)于像這樣需要規(guī)律性地賦值操作很方便,下面還會(huì)出現(xiàn)generate for語(yǔ)法。
寄存器組的問題解決后,需要與FIR參數(shù)進(jìn)行乘加,這里同樣適用generate for語(yǔ)句簡(jiǎn)化設(shè)計(jì):
localparam FIR_HALF_ORDER = FIR_ORDER / 2; //36
wire signed [OUT_BITS-1:0] data_out_tmp [FIR_HALF_ORDER:0] ;
// FIR輸出數(shù)據(jù)后流水線相加的中間變量,多出部分變量,防止下一級(jí)相加過程中index越界
reg signed [OUT_BITS-1:0] dat_out_reg [FIR_HALF_ORDER+4:0] ; //40-0
always @(posedge clk or posedge rst) begin
if (rst) begin
// reset
dat_out_reg[FIR_HALF_ORDER] <= 0;
end
else if (output_vld_tmp[FIR_HALF_ORDER]) begin
dat_out_reg[FIR_HALF_ORDER] <= data_out_tmp[FIR_HALF_ORDER];
end
end
fir_base
#(
.DATA_BITS(DATA_BITS),
.COEF_BITS(COEF_BITS),
.EXTEND_BITS(EXTEND_BITS)
)
fir_inst_FIR_HALF_ORDER(
.clk (clk),
.rst (rst),
.en (data_in_vld),
.data_in_A (data_tmp[FIR_HALF_ORDER]),
.data_in_B (12'd0),
.coef (get_coef(FIR_HALF_ORDER)),
.fir_busy (),
.data_out (data_out_tmp[FIR_HALF_ORDER]),
.output_vld (output_vld_tmp[FIR_HALF_ORDER])
);
generate
genvar j;
for (j = 1; j < FIR_HALF_ORDER; j = j + 1)
begin: fir_base
fir_base
#(
.DATA_BITS(DATA_BITS),
.COEF_BITS(COEF_BITS),
.EXTEND_BITS(EXTEND_BITS)
)
fir_inst_NORMAL
(
.clk (clk),
.rst (rst),
.en (data_in_vld),
.data_in_A (data_tmp[j]),
.data_in_B (data_tmp[FIR_ORDER-j]),
.coef (get_coef(j)),
.fir_busy (),
.data_out (data_out_tmp[j]),
.output_vld (output_vld_tmp[j])
);
always @(posedge clk or posedge rst) begin
if (rst) begin
// reset
dat_out_reg[j] <= 0;
end
else if (output_vld_tmp[j]) begin
dat_out_reg[j] <= data_out_tmp[j];
end
end
endgenerate
首先由于中心點(diǎn)(第36階)的系數(shù)是只乘中心點(diǎn),并不像其他系數(shù)可以傳入關(guān)于中心對(duì)稱的兩個(gè)信號(hào)。所以FIR_HALF_ORDER需要單獨(dú)例化。同樣,dat_out_reg也需要單獨(dú)復(fù)制;其他的信號(hào)在generate for循環(huán)體完成操作,由于0號(hào)系數(shù)在階數(shù)為偶數(shù)的情況下為0,這里跳過0號(hào)系數(shù)直接從1號(hào)系數(shù)開始,所以for循環(huán)是從1 - FIR_HALF_ORDER。
加權(quán)結(jié)果出來后,需要對(duì)結(jié)果相加,為了提升系統(tǒng)運(yùn)行速率,這里采用三級(jí)流水線操作。每次進(jìn)行4位數(shù)據(jù)相加傳遞給下一級(jí)流水線,所以示例代碼里FIR最高階數(shù)為4 * 4 * 4 * 2 = 128。
流水線操作過程如下:
// 流水線第一級(jí)相加,計(jì)算公式ceil(N/4)
localparam FIR_ADD_ORDER_ONE = (FIR_HALF_ORDER + 3) / 4; //
// 流水線第二級(jí)相加,計(jì)算公式ceil(N/4)
localparam FIR_ADD_ORDER_TWO = (FIR_ADD_ORDER_ONE + 3) / 4; //3
reg signed [OUT_BITS-1:0] dat_out_A [FIR_ADD_ORDER_ONE+3:0] ; //12-0
reg signed [OUT_BITS-1:0] dat_out_B [FIR_ADD_ORDER_TWO+3:0] ; //6-0
// 這些多余的reg直接設(shè)為0就可以了
always @ (posedge clk) begin
dat_out_reg[FIR_HALF_ORDER+1] = 0;
dat_out_reg[FIR_HALF_ORDER+2] = 0;
dat_out_reg[FIR_HALF_ORDER+3] = 0;
dat_out_reg[FIR_HALF_ORDER+4] = 0;
dat_out_A[FIR_ADD_ORDER_ONE] = 0;
dat_out_A[FIR_ADD_ORDER_ONE+1] = 0;
dat_out_A[FIR_ADD_ORDER_ONE+2] = 0;
dat_out_A[FIR_ADD_ORDER_ONE+3] = 0;
dat_out_B[FIR_ADD_ORDER_TWO] = 0;
dat_out_B[FIR_ADD_ORDER_TWO + 1] = 0;
dat_out_B[FIR_ADD_ORDER_TWO + 2] = 0;
dat_out_B[FIR_ADD_ORDER_TWO + 3] = 0;
end
// 判定所有FIR_BASE模塊完成轉(zhuǎn)換
assign data_out_vld = (&output_vld_tmp[FIR_HALF_ORDER:1] == 1'b1) ? 1'b1 : 1'b0;
//最后一級(jí)流水線
always @(posedge clk or posedge rst) begin
if (rst) begin
// reset
data_out <= 0;
end
else if (data_out_vld) begin
data_out <= dat_out_B[0] + dat_out_B[1] + dat_out_B[2] + dat_out_B[3];
end
end
generate
genvar j;
for (j = 1; j < FIR_HALF_ORDER; j = j + 1)
if (j <= FIR_ADD_ORDER_ONE)
begin
//流水線相加 第一級(jí)
//注意j 的范圍是[1,FIR_HALF_ORDER]
//所以dat_out_A[j-1]
always @(posedge clk or posedge rst) begin
if (rst) begin
// reset
dat_out_A[j-1] <= 0;
end
else begin
dat_out_A[j-1] <= dat_out_reg[4*j-3] + dat_out_reg[4*j-2] + dat_out_reg[4*j-1] + dat_out_reg[4*j];
end
end
end
if (j <= FIR_ADD_ORDER_TWO)
begin
// 流水線相加 第二級(jí)
always @(posedge clk or posedge rst) begin
if (rst) begin
// reset
dat_out_B[j-1] <= 0;
end
else begin
dat_out_B[j-1] <= dat_out_A[4*j - 4] + dat_out_A[4*j- 3] + dat_out_A[4*j - 2] + dat_out_A[4*j - 1];
end
end
end
end
endgenerate