Я продолжаю делить задачу расчета БПФ на части. В наличии уже есть сдвиговый регистр, внутри которого реализовано умножение на оконную функцию. Теперь реализуем бабочку БПФ. Смысл очень прост: бабочка БПФ это есть ДПФ, по скольку основание БПФ кратно степени 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