Я продолжаю делить задачу расчета БПФ на части. В наличии уже есть сдвиговый регистр, внутри которого реализовано умножение на оконную функцию. Теперь реализуем бабочку БПФ. Смысл очень прост: бабочка БПФ это есть ДПФ, по скольку основание БПФ кратно степени 2, то БПФ можно разбить на ДПФ (бабочки) по основанию 2 и затем объединить их.
ДПФ2 на pyhon:
ДПФ2 на pyhon:
def FSM_BF2(self, i_clk, i_en, o_rd_addr, i_data, o_we, o_data, o_wr_addr, FFT_SIZE):
LEN = len(i_data)
MY_LEN = LEN/2
WIDTH = MY_LEN - 1
WIDTH_SUM = WIDTH + 1
r_rd_addr = [Signal(modbv(0,0,FFT_SIZE)) for i in range(2)]
r_wr_addr = Signal(modbv(0,0,FFT_SIZE))
#r_addr = Signal(modbv(0,0,DEPTH))
r_we = [Signal(bool(0)) for i in range(3)]
r_data = Signal(intbv(0,-2**WIDTH, 2**WIDTH))
r_in_data = Signal(intbv(0,-2**WIDTH, 2**WIDTH))
r_out_data = [Signal(intbv(0,-2**WIDTH, 2**WIDTH)) for i in range(2)]
w_sum = Signal(intbv(0, -2**WIDTH_SUM, 2**WIDTH_SUM))
w_sub = Signal(intbv(0, -2**WIDTH_SUM, 2**WIDTH_SUM))
@always_comb
def f_sum():
w_sum.next = r_in_data + i_data[LEN:MY_LEN].signed()
@always_comb
def f_sub():
w_sub.next = r_in_data - i_data[LEN:MY_LEN].signed()
@always_comb
def f_data():
o_data.next = r_out_data[r_wr_addr[0]]
@always_comb
def f_rd_addr():
o_rd_addr.next = r_rd_addr[0]
@always_comb
def f_wr_addr():
o_wr_addr.next = r_wr_addr
@always_comb
def f_we():
o_we.next = r_we[0]
@always(i_clk.posedge)
def seq():
if i_en == 1:
r_rd_addr[1].next = r_rd_addr[0]
if r_rd_addr[0] != FFT_SIZE - 1:
r_rd_addr[0].next = r_rd_addr[0] + 1
# if r_rd_addr[0] != r_rd_addr[0].max-1:
# r_rd_addr[0].next = r_rd_addr[0] + 1
#
if r_rd_addr[1] == 1:
r_we[0].next = 1
if r_wr_addr == r_wr_addr.max - 1:
r_we[0].next = 0
#
if r_we[0] == 1:
r_wr_addr.next = r_wr_addr + 1
if r_rd_addr[1][0] == 0:
r_in_data.next = i_data[LEN:MY_LEN].signed()
else:
r_out_data[0].next = w_sum[:1].signed()
r_out_data[1].next = w_sub[:1].signed()
else:
r_rd_addr[0].next = 0
r_rd_addr[1].next = 0
r_out_data[0].next = 0
r_out_data[1].next = 0
r_we[0].next = 0
r_we[1].next = 0
return f_sum, f_sub, f_data, f_rd_addr, f_wr_addr, f_we, seq
Поехали сверху вниз:
i_clk - тактовый вход
i_en - в данном случае выполняет функцию синхронного сброса
o_rd_addr - адрес чтения из памяти
i_data - вход данных (берутся из памяти)
o_data - выходные данные (пишутся в память)
o_we - выход разрешения записи в память
o_wr_addr - адрес записи в память
FFT_SIZE - константа не используется
константы:
LEN - длины входных данных, вида {RE,IM}. В моем случае 32 бита
MY_LEN - длина RE, 16 бит
WIDTH - количество бит для знакового представления числа
WIDTH_SUM - количество бит для представления суммы 2 знаковых чисел
Тут стоить сказать о нюансах. Если посчитать ДПФ8 от синусоиды с амплитудой 1, то мы получим 8. Если ДПФ16 - 16. То есть в зависимости от основания ДПФ меняется его результат. Чтобы результаты не менялись я буду делить на 2 результаты после каждой стадии, что аналогично умножению на 1/N в формуле ДПФ.
r_rd_addr[][2] - регистр адреса чтения из памяти (2 переменных). Первый регистр используется, чтобы выставить адрес на линии для ram, второй регистр (на один такт задержанный первый) используется, чтобы правильно интерпретировать входные данные.
r_wr_addr - регистр адреса записи
r_we[2] - регистр записи в память. Второй бит не используется
r_in_data - регистр входных данных
r_out_data[][2] - выход бабочки по основанию 2
w_sum, w_sub - комбинаторные выходы бабочки
Дальше требует пояснения строка 27: r_out_data[r_wr_addr[0]]. Есть два регистра r_out_data[], мультиплексором управляет нулевой бит r_wr_addr.
[LEN:MY_LEN] в строках 20,23, 57 вытаскивает из входных данных RE
Порт на verilog:
assign fsm_bf2_w_sum = (fsm_bf2_r_in_data + $signed(o_w_dualram_data[32-1:16]));
assign fsm_bf2_w_sub = (fsm_bf2_r_in_data - $signed(o_w_dualram_data[32-1:16]));
assign o_w_bf2_data = fsm_bf2_r_out_data[fsm_bf2_r_wr_addr[0]];
assign o_w_bf2_rd_addr = fsm_bf2_r_rd_addr[0];
assign o_w_bf2_wr_addr = fsm_bf2_r_wr_addr;
assign o_w_bf2_we = fsm_bf2_r_we[0];
always @(posedge i_clk) begin: FFT_FSM_FSM_BF2_SEQ
if ((r_en_bf2 == 1)) begin
fsm_bf2_r_rd_addr[1] <= fsm_bf2_r_rd_addr[0];
if (($signed({1'b0, fsm_bf2_r_rd_addr[0]}) != (16 - 1))) begin
fsm_bf2_r_rd_addr[0] <= (fsm_bf2_r_rd_addr[0] + 1);
end
if ((fsm_bf2_r_rd_addr[1] == 1)) begin
fsm_bf2_r_we[0] <= 1;
end
if (($signed({1'b0, fsm_bf2_r_wr_addr}) == (16 - 1))) begin
fsm_bf2_r_we[0] <= 0;
end
if ((fsm_bf2_r_we[0] == 1)) begin
fsm_bf2_r_wr_addr <= (fsm_bf2_r_wr_addr + 1);
end
if ((fsm_bf2_r_rd_addr[1][0] == 0)) begin
fsm_bf2_r_in_data <= $signed(o_w_dualram_data[32-1:16]);
end
else begin
fsm_bf2_r_out_data[0] <= $signed(fsm_bf2_w_sum[17-1:1]);
fsm_bf2_r_out_data[1] <= $signed(fsm_bf2_w_sub[17-1:1]);
end
end
else begin
fsm_bf2_r_rd_addr[0] <= 0;
fsm_bf2_r_rd_addr[1] <= 0;
fsm_bf2_r_out_data[0] <= 0;
fsm_bf2_r_out_data[1] <= 0;
fsm_bf2_r_we[0] <= 0;
fsm_bf2_r_we[1] <= 0;
end
end