短时傅里叶变换(STFT, Short Time Fourier Transform),是处理采样数据、获取信号时频特征的一种常用方法。然而其消耗的计算资源较为庞大,在数据采集设备全速运行时,若在上位机进行 STFT 的计算,则很难做到实时性。因此尝试将 STFT 步骤迁移到 FPGA 端进行,从而减轻上位机的计算压力。
STFT 的操作流程上,就是对滑动窗口中的数据做 FFT,这一工作可以交给 FFT IP 核进行,因此我们实际只需要实现对采样数据流的控制,以实现滑动窗口。
采样数据存储容器设计
FIFO 设计
由于采样数据由 ADC 源源不断地采集而来,因此需要类似于 FIFO 这样的数据存储模块。然而在 STFT 过程中,需要对数据进行重叠读取,用常规的 FIFO 是无法实现这一目标的,因此需要自己用 RAM 搭建一个数据存储模块,其采样数据以 FIFO 接口的形式顺序传入,而以 RAM 的接口形式随机读取。
因此利用真双口 Block RAM 搭建同步 FIFO,并利用真双口 RAM 的两个输入输出接口,实现 FIFO 接口和 RAM 接口;通过真双口 RAM,可以同时进行两个操作,这就为实时 STFT 过程中,采样数据的顺序写入以及 STFT 随机读取的同时进行提供了基础。同时,为了模拟 STFT 窗口的移动,因此也设计了 FIFO 首尾指针的配置功能,从而可以通过直接修改头指针来滑动数据窗口:
/*
* file : FIFO_BRAM_sync.v
* author : 今朝无言
* lab : WHU-EIS-LMSWE
* date : 2024-07-20
* version : v3.0
* description : 利用真双口 Block RAM 搭建同步 FIFO,从而同时支持随机存取功能
*/
`default_nettype none
module FIFO_BRAM_sync(
input wire clk,
input wire rst_n,
//FIFO Interface
input wire [DataWidth-1:0] FIFO_data_in,
input wire FIFO_wren,
output reg [DataWidth-1:0] FIFO_data_out,
input wire FIFO_rden,
output wire FIFO_out_vaild,
output wire FIFO_empty, //空标志
output wire FIFO_full, //满标志
output wire [AddrWidth-1:0] FIFO_element_cnt, //FIFO中元素个数
output wire [AddrWidth-1:0] FIFO_head, //当前首指针,指向下一个被读取的元素
output wire [AddrWidth-1:0] FIFO_tail, //当前尾指针,指向下一个被写入的位置
//RAM Interface
input wire [AddrWidth-1:0] RAM_addr_w,
input wire [DataWidth-1:0] RAM_data_in,
input wire RAM_wren,
input wire [AddrWidth-1:0] RAM_addr_r,
output reg [DataWidth-1:0] RAM_data_out,
input wire RAM_rden,
output wire RAM_out_vaild,
//Config Interface
input wire [AddrWidth-1:0] config_set_Head, //设置首指针
input wire [AddrWidth-1:0] config_set_Tail, //设置尾指针
input wire [1:0] config_vaild, //bit0控制修改Head,bit1控制修改Tail
output reg config_ready,
//异常告警
output reg event_wr_collision, //发生写入冲突
output reg event_FIFO_wr_failed, //写FIFO失败
output reg event_FIFO_rd_failed, //读FIFO失败
output reg event_RAM_rd_failed //读RAM失败
);
parameter DataWidth = 32; //这两个 param 注意与 BRAM IP 的配置相一致
parameter FIFO_Depth = 65536;
localparam AddrWidth = (FIFO_Depth>1)? clogb2(FIFO_Depth - 1) : 1;
//------------------------log2---------------------------------
function integer clogb2 (input integer depth);
begin
for (clogb2 = 0; depth > 0; clogb2 = clogb2 + 1) begin
depth = depth >> 1;
end
end
endfunction
//----------------------FIFO interface--------------------------
reg FIFO_empty_r;
reg FIFO_full_r;
reg [AddrWidth-1:0] FIFO_element_cnt_r = 'd0;
reg [AddrWidth-1:0] FIFO_head_r = 'd0; //当前首指针,指向下一个被读取的元素
reg [AddrWidth-1:0] FIFO_tail_r = 'd0; //当前尾指针,指向下一个被写入的位置
assign FIFO_empty = FIFO_empty_r;
assign FIFO_full = FIFO_full_r;
assign FIFO_element_cnt = FIFO_element_cnt_r;
assign FIFO_head = FIFO_head_r;
assign FIFO_tail = FIFO_tail_r;
assign FIFO_out_vaild = FIFO_out_vaild_r[3];
assign RAM_out_vaild = RAM_out_vaild_r[3];
//----------------------BRAM IP Core----------------------------
reg BRAM_wea = 1'b0;
reg [AddrWidth-1:0] BRAM_addra = 'd0;
reg [DataWidth-1:0] BRAM_dina = 'd0;
wire [DataWidth-1:0] BRAM_douta;
reg BRAM_web = 1'b0;
reg [AddrWidth-1:0] BRAM_addrb = 'd0;
reg [DataWidth-1:0] BRAM_dinb = 'd0;
wire [DataWidth-1:0] BRAM_doutb;
//利用真双口RAM(True Dual RAM)进行功能实现
RAM_TrueDual_0 RAM_inst(
.clka (clk),
.ena (1'b1),
.wea (BRAM_wea),
.addra (BRAM_addra),
.dina (BRAM_dina),
.douta (BRAM_douta),
.clkb (clk),
.enb (1'b1),
.web (BRAM_web),
.addrb (BRAM_addrb),
.dinb (BRAM_dinb),
.doutb (BRAM_doutb)
);
//对于两个独立的输入输出端口,要满足 FIFO 读写、RAM 读写四种操作,因此需要合理设计优先级,
//本设计优先满足 FIFO 要求、优先满足写入要求,即:FIFO写 > RAM 写 > FIFO读 > RAM读,
//并根据优先级依次分配 port A/B。
//注意,由于RAM的特性,在addr改变后的第二个时钟上升沿才输出响应的数据;而写则是发生在同一时钟沿。
//这就导致本模块进行FIFO读时,出现3个clk的延迟(本模块里还额外打一拍)
//-------------------------CTRL---------------------------------
//BRAM_wea
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
BRAM_wea <= 1'b0;
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b1xxx: begin
BRAM_wea <= 1'b1;
end
4'b01xx: begin
BRAM_wea <= 1'b1;
end
default: begin
BRAM_wea <= 1'b0;
end
endcase
end
end
//BRAM_addra
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
BRAM_addra <= 'd0;
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b1xxx: begin
BRAM_addra <= FIFO_tail;
end
4'b01xx: begin
BRAM_addra <= RAM_addr_w;
end
4'b001x: begin
BRAM_addra <= FIFO_head;
end
4'b0001: begin
BRAM_addra <= RAM_addr_r;
end
default: begin
BRAM_addra <= 'd0;
end
endcase
end
end
//BRAM_dina
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
BRAM_dina <= 'd0;
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b1xxx: begin
BRAM_dina <= FIFO_data_in;
end
4'b01xx: begin
BRAM_dina <= RAM_data_in;
end
default: begin
BRAM_dina <= 'd0;
end
endcase
end
end
//BRAM_web
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
BRAM_web <= 1'b0;
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b11xx: begin
BRAM_web <= 1'b1;
end
default: begin
BRAM_web <= 1'b0;
end
endcase
end
end
//BRAM_addrb
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
BRAM_addrb <= 'd0;
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b11xx: begin
BRAM_addrb <= RAM_addr_w;
end
4'b101x, 4'b011x: begin
BRAM_addrb <= FIFO_head;
end
4'b1001, 4'b0101, 4'b0011: begin
BRAM_addrb <= RAM_addr_r;
end
default: begin
BRAM_addrb <= 'd0;
end
endcase
end
end
//BRAM_dinb
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
BRAM_dinb <= 'd0;
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b11xx: begin
BRAM_dinb <= RAM_data_in;
end
default: begin
BRAM_dinb <= 'd0;
end
endcase
end
end
//FIFO_data_out
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
FIFO_data_out <= 'd0;
end
else begin
casex(mod_buf_d2)
4'b001x: begin
FIFO_data_out <= BRAM_douta;
end
4'b101x, 4'b011x: begin
FIFO_data_out <= BRAM_doutb;
end
default: begin
FIFO_data_out <= 'd0;
end
endcase
end
end
//RAM_data_out
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
RAM_data_out <= 'd0;
end
else begin
casex(mod_buf_d2)
4'b0001: begin
RAM_data_out <= BRAM_douta;
end
4'b1001, 4'b0101, 4'b0011: begin
RAM_data_out <= BRAM_doutb;
end
default: begin
RAM_data_out <= 'd0;
end
endcase
end
end
//FIFO_head_r
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
FIFO_head_r <= 'd0;
end
else begin
if(config_ready & config_vaild[0]) begin
FIFO_head_r <= config_set_Head; //配置head、tail时,应暂停读写操作,以避免出现错误
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b101x, 4'b011x, 4'b001x: begin
if(~FIFO_empty) begin
if(FIFO_head_r < FIFO_Depth - 1'b1) begin
FIFO_head_r <= FIFO_head_r + 1'b1;
end
else begin
FIFO_head_r <= 16'd0;
end
end
else begin
FIFO_head_r <= FIFO_head_r;
end
end
default: begin
FIFO_head_r <= FIFO_head_r;
end
endcase
end
end
end
//FIFO_tail_r
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
FIFO_tail_r <= 'd0;
end
else begin
if(config_ready & config_vaild[1]) begin
FIFO_tail_r <= config_set_Tail;
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b1xxx: begin
if(~FIFO_full) begin
if(FIFO_tail_r < FIFO_Depth - 1'b1) begin
FIFO_tail_r <= FIFO_tail_r + 1'b1;
end
else begin
FIFO_tail_r <= 16'd0;
end
end
else begin
FIFO_tail_r <= FIFO_tail_r;
end
end
default: begin
FIFO_tail_r <= FIFO_tail_r;
end
endcase
end
end
end
//FIFO_empty_r
always @(*) begin
FIFO_empty_r <= (FIFO_head==FIFO_tail)? 1'b1 : 1'b0;
end
//FIFO_full_r
always @(*) begin
FIFO_full_r <= ((FIFO_tail + 1'b1 == FIFO_head) ||
((FIFO_head == 16'd0) && (FIFO_tail == FIFO_Depth - 1'b1)))?
1'b1 : 1'b0;
end
//FIFO_element_cnt
always @(*) begin
if(FIFO_tail >= FIFO_head) begin
FIFO_element_cnt_r <= FIFO_tail - FIFO_head;
end
else begin
FIFO_element_cnt_r <= FIFO_Depth + FIFO_tail - FIFO_head;
end
end
//config_ready
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
config_ready <= 1'b0;
end
else begin
config_ready <= 1'b1;
end
end
//event_wr_collision
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
event_wr_collision <= 1'b0;
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b11xx: begin
if(FIFO_tail == RAM_addr_w) begin //发生写入地址冲突,将无法确定写入对应地址的数据时是来自于哪个Port
event_wr_collision <= 1'b1;
end
else begin
event_wr_collision <= 1'b0;
end
end
default: begin
event_wr_collision <= 1'b0;
end
endcase
end
end
//event_FIFO_wr_failed
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
event_FIFO_wr_failed <= 1'b0;
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b1xxx: begin
if(FIFO_full) begin
event_FIFO_wr_failed <= 1'b1;
end
else begin
event_FIFO_wr_failed <= 1'b0;
end
end
default: begin
event_FIFO_wr_failed <= 1'b0;
end
endcase
end
end
//event_FIFO_rd_failed
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
event_FIFO_rd_failed <= 1'b0;
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b111x: begin
event_FIFO_rd_failed <= 1'b1;
end
4'b001x, 4'b101x, 4'b011x: begin
if(FIFO_empty) begin
event_FIFO_rd_failed <= 1'b1;
end
else begin
event_FIFO_rd_failed <= 1'b0;
end
end
default: begin
event_FIFO_rd_failed <= 1'b0;
end
endcase
end
end
//event_RAM_rd_failed
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
event_RAM_rd_failed <= 1'b0;
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b1101, 4'b1011, 4'b1111, 4'b0111: begin
event_RAM_rd_failed <= 1'b1;
end
default: begin
event_RAM_rd_failed <= 1'b0;
end
endcase
end
end
//FIFO_out_vaild_r
reg [3:0] FIFO_out_vaild_r = 4'b0;
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
FIFO_out_vaild_r <= 4'b0000;
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b101x, 4'b011x, 4'b001x: begin
FIFO_out_vaild_r <= {FIFO_out_vaild_r[2:0], ~FIFO_empty};
end
default: begin
FIFO_out_vaild_r <= {FIFO_out_vaild_r[2:0], 1'b0};
end
endcase
end
end
//RAM_out_vaild_r
reg [3:0] RAM_out_vaild_r = 4'b0;
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
RAM_out_vaild_r <= 4'b0000;
end
else begin
casex({FIFO_wren, RAM_wren, FIFO_rden, RAM_rden})
4'b0001, 4'b1001, 4'b0101, 4'b0011: begin
RAM_out_vaild_r <= {RAM_out_vaild_r[2:0], 1'b1};
end
default: begin
RAM_out_vaild_r <= {RAM_out_vaild_r[2:0], 1'b0};
end
endcase
end
end
//mod_buf
reg [3:0] mod_buf_d0 = 4'b0000;
reg [3:0] mod_buf_d1 = 4'b0000;
reg [3:0] mod_buf_d2 = 4'b0000;
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
mod_buf_d0 <= 4'b0000;
mod_buf_d1 <= 4'b0000;
mod_buf_d2 <= 4'b0000;
end
else begin
mod_buf_d0 <= {FIFO_wren, RAM_wren, FIFO_rden, RAM_rden};
mod_buf_d1 <= mod_buf_d0;
mod_buf_d2 <= mod_buf_d1;
end
end
endmodule
testbench
编写 Testbench 如下,进行本 FIFO_RAM 的测试
`timescale 1ns/100ps
`default_nettype none
module FIFO_BRAM_sync_tb();
reg clk_100M = 1'b1;
reg rst_n = 1'b1;
always #5 begin
clk_100M <= ~clk_100M;
end
//FIFO Interface
reg [15:0] FIFO_data_in = 16'd0;
reg FIFO_wren = 1'b0;
wire [15:0] FIFO_data_out;
reg FIFO_rden = 1'b0;
wire FIFO_out_vaild;
wire FIFO_empty;
wire FIFO_full;
wire [6:0] FIFO_element_cnt;
wire [6:0] FIFO_head;
wire [6:0] FIFO_tail;
//RAM Interface
reg [15:0] RAM_addr_w = 16'd0;
reg [15:0] RAM_data_in = 16'd0;
reg RAM_wren = 1'b0;
reg [15:0] RAM_addr_r = 16'd0;
wire [15:0] RAM_data_out;
reg RAM_rden = 1'b0;
wire RAM_out_vaild;
//Config Interface
reg [15:0] config_set_Head = 16'd0;
reg [15:0] config_set_Tail = 16'd0;
reg [1:0] config_vaild = 2'b00;
wire config_ready;
//异常告警
wire event_wr_collision;
wire event_FIFO_wr_failed;
wire event_FIFO_rd_failed;
wire event_RAM_rd_failed;
FIFO_BRAM_sync #(
.DataWidth (16),
.FIFO_Depth (16'd128) //参数设置注意和 BRAM IP 的配置一致
)
FIFO_BRAM_sync_inst(
.clk (clk_100M),
.rst_n (rst_n),
//FIFO Interface
.FIFO_data_in (FIFO_data_in),
.FIFO_wren (FIFO_wren),
.FIFO_data_out (FIFO_data_out),
.FIFO_rden (FIFO_rden),
.FIFO_out_vaild (FIFO_out_vaild),
.FIFO_empty (FIFO_empty),
.FIFO_full (FIFO_full),
.FIFO_element_cnt (FIFO_element_cnt),
.FIFO_head (FIFO_head),
.FIFO_tail (FIFO_tail),
//RAM Interface
.RAM_addr_w (RAM_addr_w),
.RAM_data_in (RAM_data_in),
.RAM_wren (RAM_wren),
.RAM_addr_r (RAM_addr_r),
.RAM_data_out (RAM_data_out),
.RAM_rden (RAM_rden),
.RAM_out_vaild (RAM_out_vaild),
//Config Interface
.config_set_Head (config_set_Head),
.config_set_Tail (config_set_Tail),
.config_vaild (config_vaild),
.config_ready (config_ready),
//异常告警
.event_wr_collision (event_wr_collision), //发生写入冲突
.event_FIFO_wr_failed (event_FIFO_wr_failed), //写FIFO失败
.event_FIFO_rd_failed (event_FIFO_rd_failed), //读FIFO失败
.event_RAM_rd_failed (event_RAM_rd_failed) //读RAM失败
);
//---------------------------Ctrl-------------------------------
//FIFO_data_in
always @(posedge clk_100M) begin
if(~rst_n) begin
FIFO_data_in <= 16'd0;
end
else begin
if(FIFO_wren) begin
FIFO_data_in <= FIFO_data_in + 1'b1;
end
else begin
FIFO_data_in <= FIFO_data_in;
end
end
end
//RAM_data_in
always @(posedge clk_100M) begin
if(~rst_n) begin
RAM_data_in <= 16'd66;
end
else begin
if(RAM_wren) begin
RAM_data_in <= RAM_data_in + 1'b1;
end
else begin
RAM_data_in <= RAM_data_in;
end
end
end
//FIFO Write
task FIFO_write;
input [15:0] Len;
integer i;
begin
for(i=0; i<Len; i=i+1) begin
wait(clk_100M);
FIFO_wren <= 1'b1;
wait(~clk_100M);
end
wait(clk_100M);
FIFO_wren <= 1'b0;
end
endtask
//FIFO Read
task FIFO_read;
input [15:0] Len;
integer i;
begin
for(i=0; i<Len; i=i+1) begin
wait(clk_100M);
FIFO_rden <= 1'b1;
wait(~clk_100M);
end
wait(clk_100M);
FIFO_rden <= 1'b0;
end
endtask
//RAM wr
task RAM_write;
input [15:0] addr;
begin
wait(clk_100M);
RAM_wren <= 1'b1;
RAM_addr_w <= addr;
wait(~clk_100M);
wait(clk_100M);
RAM_wren <= 1'b0;
end
endtask
//RAM rd
task RAM_read;
input [15:0] addr;
begin
wait(clk_100M);
RAM_rden <= 1'b1;
RAM_addr_r <= addr;
wait(~clk_100M);
wait(clk_100M);
RAM_rden <= 1'b0;
end
endtask
//set FIFO head & tail
task set_FIFO_head_tail;
input [15:0] head;
input [15:0] tail;
begin
wait(clk_100M);
config_vaild <= 2'b11;
config_set_Head <= head;
config_set_Tail <= tail;
wait(~clk_100M);
wait(clk_100M);
config_vaild <= 2'b00;
config_set_Head <= 16'd0;
config_set_Tail <= 16'd0;
end
endtask
//main
integer i;
initial begin
rst_n <= 1'b0;
#100;
rst_n <= 1'b1;
#100;
//分别进行FIFO的读写
FIFO_write(16'd64);
FIFO_read(16'd72); //测试读空
FIFO_write(16'd150); //测试写满
FIFO_read(16'd150);
//同时进行FIFO读写
#200;
fork
begin: join_FIFO_wr
FIFO_write(16'd64);
end
begin: join_FIFO_rd
FIFO_read(16'd72);
end
join
//测试RAM端口
RAM_write(16'd12);
RAM_read(16'd12);
//FIFO/RAM依次读写
FIFO_write(16'd64);
for(i=0; i<10; i=i+1) begin
RAM_read(i);
end
for(i=15; i<20; i=i+1) begin
RAM_write(i);
end
FIFO_read(16'd64);
//设置FIFO head & tail
set_FIFO_head_tail(16'd10, 16'd20);
FIFO_read(16'd20);
//测试读写异常处理机制
#200;
fork
begin: join_FIFO_wr2
FIFO_write(16'd64);
end
begin: join_RAM_wr2
#0;
for(i=32; i<40; i=i+1) begin
RAM_write(i);
end
end
begin: join_FIFO_rd2
#0;
FIFO_read(16'd32);
end
begin: join_RAM_rd2
#160;
for(i=0; i<32; i=i+1) begin
RAM_read(i);
end
end
join
//测试FIFO/RAM同时写
#200;
fork
begin: join_FIFO_wr3
FIFO_write(16'd64);
end
begin: join_RAM_wr3
#200;
for(i=32; i<40; i=i+1) begin
RAM_write(i);
end
end
join
//测试FIFO/RAM同时读
#200;
fork
begin: join_FIFO_rd3
FIFO_read(16'd127);
end
begin: join_RAM_rd3
#200;
for(i=0; i<32; i=i+1) begin
RAM_read(i);
end
end
join
//测试写地址冲突
#200;
set_FIFO_head_tail(16'd0, 16'd0);
fork
begin: join_FIFO_wr4
FIFO_write(16'd64);
end
begin: join_RAM_wr4
#0;
for(i=0; i<32; i=i+1) begin
RAM_write(i);
end
end
join
//查看写入冲突下写入了什么数据
#100;
FIFO_read(16'd64); //其实也没啥必要看,BRAM手册上说这种情况是无法确定写入数据的
//仿真结果是,在发生写入冲突时,内容为 port B 写入的数据
//finish
#1000;
$stop;
end
endmodule
STFT顶层模块实现
设计实现
进一步地,利用我们上面实现的 FIFO 模块,实现 STFT 模块。该模块用到了 FFT IP 核,65536 point,启用 Config Transform Length 功能,数据位宽 16bit,Pipelined Stream I/O,Natural Order,固定缩放,具体如何使用 FFT IP 可以参考我之前写的这篇博客。
/*
* file : STFT.v
* author : 今朝无言
* lab : WHU-EIS-LMSWE
* date : 2024-07-20
* version : v1.0
* description : Short Time Fourier Transform (STFT)
*/
`default_nettype none
module STFT(
input wire clk_100M,
input wire rst_n,
//data in
input wire signed [15:0] data_in_Real,
input wire signed [15:0] data_in_Img,
input wire data_in_vaild,
output reg data_in_ready,
//data out
output reg signed [15:0] data_out_Real,
output reg signed [15:0] data_out_Img,
output reg [15:0] data_out_Idx,
output reg data_out_vaild,
input wire data_out_ready,
//Config
input wire [4:0] config_NFFT, //STFT点数(以二为底的指数,最大16)
input wire [15:0] config_overlap, //STFT步进
input wire [15:0] config_SCALE_SCH, //缩放系数,每2bit控制一个蝶形单元(Radix-4)的缩放
input wire config_FWD_INV, //FFT(1) or IFFT(0)
input wire config_vaild,
output reg config_ready
);
// 利用 Block RAM 构成同步循环队列(从而也支持随机读取),构成 data_in 的存储空间,并根据步进及 NFFT 读取数据供入 FFT IP
//--------------------------FFT IP------------------------------------
//config
reg [15:0] SCALE_SCH = {2'd2, 2'd2, 2'd2, 2'd2,
2'd2, 2'd2, 2'd2, 2'd2}; //16'haaaa;
reg FWD_INV = 1'b1; //1:FFT, 0:IFFT
reg [4:0] NFFT = 5'd10; //默认 N=1024
wire [31:0] s_axis_config_tdata;
wire s_axis_config_tvalid;
wire s_axis_config_tready;
assign s_axis_config_tdata = {7'b0, SCALE_SCH, FWD_INV, 3'b0, NFFT};
assign s_axis_config_tvalid = config_ready & config_vaild; //event_frame_started;
//data in
reg signed [15:0] XN_Real = 16'sd0;
reg signed [15:0] XN_Img = 16'sd0;
wire [31:0] s_axis_data_tdata;
reg s_axis_data_tvalid = 1'b0;
wire s_axis_data_tready;
reg s_axis_data_tlast = 1'b0;
assign s_axis_data_tdata = {XN_Img, XN_Real};
//data out
wire signed [15:0] XK_Real;
wire signed [15:0] XK_Img;
wire [15:0] XK_INDEX;
wire [31:0] m_axis_data_tdata;
wire [15:0] m_axis_data_tuser;
wire m_axis_data_tvalid;
reg m_axis_data_tready = 1'b0;
wire m_axis_data_tlast;
assign XK_Real = m_axis_data_tdata[15:0];
assign XK_Img = m_axis_data_tdata[31:16];
assign XK_INDEX = m_axis_data_tuser[15:0];
//events
wire event_frame_started;
wire event_tlast_unexpected;
wire event_tlast_missing;
wire event_status_channel_halt;
wire event_data_in_channel_halt;
wire event_data_out_channel_halt;
//FFT IP,65536 point,启用 Config Transform Length 功能,数据位宽 16bit,Pipelined Stream I/O,Natural Order,固定缩放
FFT_1 FFT_inst(
.aclk (clk_100M),
.aresetn (rst_n),
//Config
.s_axis_config_tdata (s_axis_config_tdata),
.s_axis_config_tvalid (s_axis_config_tvalid),
.s_axis_config_tready (s_axis_config_tready),
//DATA INPUT
.s_axis_data_tdata (s_axis_data_tdata),
.s_axis_data_tvalid (s_axis_data_tvalid),
.s_axis_data_tready (s_axis_data_tready),
.s_axis_data_tlast (s_axis_data_tlast),
//DATA OUTPUT
.m_axis_data_tdata (m_axis_data_tdata),
.m_axis_data_tuser (m_axis_data_tuser),
.m_axis_data_tvalid (m_axis_data_tvalid),
.m_axis_data_tready (m_axis_data_tready),
.m_axis_data_tlast (m_axis_data_tlast),
//event signal
.event_frame_started (event_frame_started),
.event_tlast_unexpected (event_tlast_unexpected),
.event_tlast_missing (event_tlast_missing),
.event_status_channel_halt (event_status_channel_halt),
.event_data_in_channel_halt (event_data_in_channel_halt),
.event_data_out_channel_halt (event_data_out_channel_halt)
);
//----------------------FIFO_RAM_sync---------------------------------
//FIFO Interface
reg [31:0] FIFO_data_in = 32'd0;
reg FIFO_wren = 1'b0;
wire [31:0] FIFO_data_out;
reg FIFO_rden = 1'b0;
wire FIFO_out_vaild;
wire FIFO_empty;
wire FIFO_full;
wire [15:0] FIFO_element_cnt;
wire [15:0] FIFO_head;
wire [15:0] FIFO_tail;
//RAM Interface
reg [15:0] RAM_addr_w = 16'd0;
reg [31:0] RAM_data_in = 32'd0;
reg RAM_wren = 1'b0;
reg [15:0] RAM_addr_r = 16'd0;
wire [31:0] RAM_data_out;
reg RAM_rden = 1'b0;
wire RAM_out_vaild;
//Config Interface
reg [15:0] FIFO_config_set_Head = 16'd0;
reg [15:0] FIFO_config_set_Tail = 16'd0;
reg [1:0] FIFO_config_vaild = 2'b00;
wire FIFO_config_ready;
//异常告警
wire event_wr_collision;
wire event_FIFO_wr_failed;
wire event_FIFO_rd_failed;
wire event_RAM_rd_failed;
localparam FIFO_Depth = 65536;
FIFO_BRAM_sync #(
.DataWidth (32), //16bit Real + 16bit Img
.FIFO_Depth (FIFO_Depth)
)
FIFO_BRAM_sync_inst(
.clk (clk_100M),
.rst_n (rst_n),
//FIFO Interface
.FIFO_data_in (FIFO_data_in),
.FIFO_wren (FIFO_wren),
.FIFO_data_out (FIFO_data_out),
.FIFO_rden (FIFO_rden),
.FIFO_out_vaild (FIFO_out_vaild),
.FIFO_empty (FIFO_empty),
.FIFO_full (FIFO_full),
.FIFO_element_cnt (FIFO_element_cnt),
.FIFO_head (FIFO_head),
.FIFO_tail (FIFO_tail),
//RAM Interface
.RAM_addr_w (RAM_addr_w),
.RAM_data_in (RAM_data_in),
.RAM_wren (RAM_wren),
.RAM_addr_r (RAM_addr_r),
.RAM_data_out (RAM_data_out),
.RAM_rden (RAM_rden),
.RAM_out_vaild (RAM_out_vaild),
//Config Interface
.config_set_Head (FIFO_config_set_Head),
.config_set_Tail (FIFO_config_set_Tail),
.config_vaild (FIFO_config_vaild),
.config_ready (FIFO_config_ready),
//异常告警
.event_wr_collision (event_wr_collision), //发生写入冲突
.event_FIFO_wr_failed (event_FIFO_wr_failed), //写FIFO失败
.event_FIFO_rd_failed (event_FIFO_rd_failed), //读FIFO失败
.event_RAM_rd_failed (event_RAM_rd_failed) //读RAM失败
);
//-------------------------------参数配置-------------------------------------
reg [15:0] STFT_overlap = 16'd1024; //STFT的步进点数,步进点数 = FFT点数 - 重叠点数
//NFFT
always @(posedge clk_100M or negedge rst_n) begin
if(~rst_n) begin
NFFT <= 5'd10;
end
else begin
if(config_ready & config_vaild) begin
NFFT <= config_NFFT;
end
else begin
NFFT <= NFFT;
end
end
end
//STFT_overlap
always @(posedge clk_100M or negedge rst_n) begin
if(~rst_n) begin
STFT_overlap <= 16'd1024;
end
else begin
if(config_ready & config_vaild) begin
STFT_overlap <= config_overlap;
end
else begin
STFT_overlap <= STFT_overlap;
end
end
end
//config_ready
always @(posedge clk_100M or negedge rst_n) begin
if(~rst_n) begin
config_ready <= 1'b0;
end
else begin
config_ready <= 1'b1;
end
end
//SCALE_SCH
always @(posedge clk_100M or negedge rst_n) begin
if(~rst_n) begin
SCALE_SCH <= {2'd2, 2'd2, 2'd2, 2'd2,
2'd2, 2'd2, 2'd2, 2'd2};
end
else begin
if(config_ready & config_vaild) begin
SCALE_SCH <= config_SCALE_SCH;
end
else begin
SCALE_SCH <= SCALE_SCH;
end
end
end
//FWD_INV
always @(posedge clk_100M or negedge rst_n) begin
if(~rst_n) begin
FWD_INV <= 1'b1;
end
else begin
if(config_ready & config_vaild) begin
FWD_INV <= config_FWD_INV;
end
else begin
FWD_INV <= FWD_INV;
end
end
end
//----------------------------数据输入流控制-------------------------------------
//RAM_addr_w
always @(*) begin
RAM_addr_w <= 16'd0;
end
//RAM_data_in
always @(*) begin
RAM_data_in <= 16'd0;
end
//RAM_wren
always @(*) begin
RAM_wren <= 1'b0;
end
//FIFO_data_in
always @(posedge clk_100M or negedge rst_n) begin
if(~rst_n) begin
FIFO_data_in <= 32'd0;
end
else begin
if(data_in_vaild & data_in_ready) begin
FIFO_data_in <= {data_in_Img, data_in_Real};
end
else begin
FIFO_data_in <= FIFO_data_in;
end
end
end
//FIFO_wren
always @(*) begin
FIFO_wren <= data_in_vaild & data_in_ready;
end
//data_in_ready
always @(*) begin
data_in_ready <= ~FIFO_full;
end
//----------------------------数据输出流控制-------------------------------------
//data_out_Real
always @(*) begin
data_out_Real <= XK_Real;
end
//data_out_Img
always @(*) begin
data_out_Img <= XK_Img;
end
//data_out_Idx
always @(*) begin
data_out_Idx <= XK_INDEX;
end
//data_out_vaild
always @(*) begin
data_out_vaild <= m_axis_data_tvalid;
end
//m_axis_data_tready
always @(*) begin
m_axis_data_tready <= data_out_ready;
end
//-----------------------------STFT 流程控制-------------------------------------
localparam S_IDLE = 8'h01; //等待FIFO中数据足够一次FFT
localparam S_PUSH_DATA = 8'h02; //向FFT IP推送数据
localparam S_OVERLAP = 8'h04; //滑动STFT窗口
localparam S_END = 8'h08;
reg [7:0] state = S_IDLE;
reg [7:0] next_state;
always @(posedge clk_100M or negedge rst_n) begin
if(~rst_n) begin
state <= S_IDLE;
end
else begin
state <= next_state;
end
end
always @(*) begin
case(state)
S_IDLE: begin
if(FIFO_element_cnt >= (1 << NFFT)) begin
next_state <= S_PUSH_DATA;
end
else begin
next_state <= S_IDLE;
end
end
S_PUSH_DATA: begin
if(cnt >= (1 << NFFT) + 4) begin //NFFT个clk使能读RAM数据,以及4个clk的读数据输出延迟
next_state <= S_OVERLAP;
end
else begin
next_state <= S_PUSH_DATA;
end
end
S_OVERLAP: begin
next_state <= S_END;
end
S_END: begin
next_state <= S_IDLE;
end
default: begin
next_state <= S_IDLE;
end
endcase
end
//cnt
reg [15:0] cnt = 16'd0; //S_PUSH_DATA阶段进行计数,以控制正确的数据流输入FFT_IP
always @(posedge clk_100M) begin
case(state)
S_PUSH_DATA: begin
cnt <= cnt + 1'b1;
end
default: begin
cnt <= 16'd0;
end
endcase
end
//FIFO_rden
always @(*) begin
FIFO_rden <= 1'b0;
end
//RAM_addr_r
always @(posedge clk_100M) begin
case(state)
S_IDLE: begin
RAM_addr_r <= FIFO_head;
end
S_PUSH_DATA: begin
if(cnt <= (1 << NFFT) - 1'b1) begin
RAM_addr_r <= (RAM_addr_r == FIFO_Depth - 1'b1)? 16'd0 : RAM_addr_r + 1'b1;
end
else begin
RAM_addr_r <= RAM_addr_r;
end
end
default: begin
RAM_addr_r <= RAM_addr_r;
end
endcase
end
//RAM_rden
always @(posedge clk_100M) begin
case(state)
S_PUSH_DATA: begin
if(cnt <= (1 << NFFT) - 1'b1) begin
RAM_rden <= 1'b1;
end
else begin
RAM_rden <= 1'b0;
end
end
default: begin
RAM_rden <= 1'b0;
end
endcase
end
//XN_Real & XN_Img
always @(posedge clk_100M) begin
case(state)
S_PUSH_DATA: begin
if(RAM_out_vaild) begin
XN_Real <= RAM_data_out[15:0];
XN_Img <= RAM_data_out[31:16];
end
else begin
XN_Real <= XN_Real;
XN_Img <= XN_Img;
end
end
default: begin
XN_Real <= 16'd0;
XN_Img <= 16'd0;
end
endcase
end
//s_axis_data_tvalid
always @(posedge clk_100M) begin
case(state)
S_PUSH_DATA: begin
if(RAM_out_vaild) begin
s_axis_data_tvalid <= 1'b1;
end
else begin
s_axis_data_tvalid <= 1'b0;
end
end
default: begin
s_axis_data_tvalid <= 1'b0;
end
endcase
end
//s_axis_data_tlast
always @(posedge clk_100M) begin
case(state)
S_PUSH_DATA: begin
if(RAM_out_vaild && (cnt == (1 << NFFT) + 4)) begin
s_axis_data_tlast <= 1'b1;
end
else begin
s_axis_data_tlast <= 1'b0;
end
end
default: begin
s_axis_data_tlast <= 1'b0;
end
endcase
end
//FIFO_config_set_Head
always @(posedge clk_100M) begin
case(state)
S_OVERLAP: begin
if(FIFO_config_ready) begin
FIFO_config_set_Head <= FIFO_head + STFT_overlap;
end
else begin
FIFO_config_set_Head <= FIFO_head;
end
end
default: begin
FIFO_config_set_Head <= FIFO_head;
end
endcase
end
//FIFO_config_set_Tail
always @(*) begin
FIFO_config_set_Tail <= FIFO_tail;
end
//FIFO_config_vaild
always @(posedge clk_100M) begin
case(state)
S_OVERLAP: begin
FIFO_config_vaild <= 2'b01;
end
default: begin
FIFO_config_vaild <= 2'b00;
end
endcase
end
endmodule
testbench
//测试STFT
`timescale 1ns/1ns
`default_nettype none
module STFT_tb();
reg clk_100M = 1'b1;
reg rst_n = 1'b1;
always #5 begin
clk_100M <= ~clk_100M;
end
//------------------------STFT-------------------------------
//data in
reg signed [15:0] data_in_Real = 16'sd0;
reg signed [15:0] data_in_Img = 16'sd0;
reg data_in_vaild = 1'b0;
wire data_in_ready;
//data out
wire signed [15:0] data_out_Real;
wire signed [15:0] data_out_Img;
wire [15:0] data_out_Idx;
wire data_out_vaild;
reg data_out_ready = 1'b0;
//Config
reg [4:0] config_NFFT = 5'd10; //STFT点数(以二为底的指数,最大16)
reg [15:0] config_overlap = 16'd128; //STFT步进
reg [15:0] config_SCALE_SCH = 16'haaaa; //缩放系数,每2bit控制一个蝶形单元(Radix-4)的缩放,2'b2
reg config_FWD_INV = 1'b1; //FFT(1) or IFFT(0)
reg config_vaild = 1'b0;
wire config_ready;
STFT STFT_inst(
.clk_100M (clk_100M),
.rst_n (rst_n),
//data in
.data_in_Real (data_in_Real),
.data_in_Img (data_in_Img),
.data_in_vaild (data_in_vaild),
.data_in_ready (data_in_ready),
//data out
.data_out_Real (data_out_Real),
.data_out_Img (data_out_Img),
.data_out_Idx (data_out_Idx),
.data_out_vaild (data_out_vaild),
.data_out_ready (data_out_ready),
//Config
.config_NFFT (config_NFFT), //STFT点数(以二为底的指数,最大16)
.config_overlap (config_overlap), //STFT步进
.config_SCALE_SCH (config_SCALE_SCH), //缩放系数,每2bit控制一个蝶形单元(Radix-4)的缩放,2'b2
.config_FWD_INV (config_FWD_INV), //FFT(1) or IFFT(0)
.config_vaild (config_vaild),
.config_ready (config_ready)
);
//------------------------------------------------------------
//data in
always @(posedge clk_100M) begin
if(data_in_ready & rst_n) begin
data_in_Real <= data_in_Real + 16'sd1;
data_in_Img <= data_in_Img + 16'sd0;
data_in_vaild <= 1'b1;
end
else begin
data_in_Real <= data_in_Real;
data_in_Img <= data_in_Img;
data_in_vaild <= 1'b0;
end
end
//data out
always @(posedge clk_100M) begin
data_out_ready <= 1'b1;
end
//Config
always @(posedge clk_100M) begin
if(config_ready) begin
config_NFFT <= 5'd10; //1024点
config_overlap <= 16'd128; //noverlap 128
config_SCALE_SCH <= 16'haaaa; //缩放因子 1/NFFT
config_FWD_INV <= 1'b1; //执行FFT
config_vaild <= 1'b1;
end
else begin
config_NFFT <= config_NFFT;
config_overlap <= config_overlap;
config_SCALE_SCH <= config_SCALE_SCH;
config_FWD_INV <= config_FWD_INV;
config_vaild <= 1'b0;
end
end
//main
initial begin
rst_n <= 1'b0;
#100;
rst_n <= 1'b1;
#100;
#1000000;
$stop;
end
endmodule
与 MATLAB 结果比对
%--------------------验证STFT结果---------------------------
clc,clear,close all
%% data
datas = 1:65535; %testbench里STFT的数据流
%% STFT
fs=1;
nfft=1024;
window_len=nfft;
noverlap=128;
overlap=window_len-noverlap;
[s,f,t] = stft(datas,fs,'window',ones(window_len,1),'OverlapLength',overlap,'FFTLength',nfft);
s = s/nfft;
%% plot
figure('color','w')
mesh(t,f,real(s))
xlabel('$t/s$','interpreter','latex')
ylabel('$f/Hz$','interpreter','latex')
figure('color','w')
mesh(t,f,imag(s))
xlabel('$t/s$','interpreter','latex')
ylabel('$f/Hz$','interpreter','latex')
可以对比 MATLAB 计算结果与 FPGA 计算结果,误差在 ± 3 \pm3 ±3 以内,读者也可自行运行上述程序验证。