verilog实现STFT

  短时傅里叶变换(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 以内,读者也可自行运行上述程序验证。

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今朝无言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值