verilog实现STFT

news2024/9/19 10:37:12

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2055269.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue2使用天地图

需求&#xff1a;用vue2使用天地图展示对应点位数据以及开发中出现的问题等&#xff0c;其实天地图的写法和百度地图差不多 注意&#xff01;&#xff01;&#xff01;天地图的接口不稳定&#xff0c;时常报错418&#xff0c;官网也是一样的情况&#xff0c;推荐还是使用百度或…

一种基于物联网(IoT)的生物多样性监测系统

目录 摘要 第一部分&#xff1a;引言 第二部分&#xff1a;相关工作 第三部分&#xff1a;贡献 第四部分&#xff1a;提出的系统架构 第五部分&#xff1a;BN使用的消息框架 第六部分&#xff1a;系统实施 第七部分&#xff1a;实验场景和结果 第八部分&#xff1a;结…

游戏开发之性能优化

游戏开发中的性能优化是一个复杂且多方面的过程&#xff0c;涉及到多个层面的改进和调整。以下是一些主要的优化技巧和方法&#xff1a; 代码优化&#xff1a; 缓存计算结果&#xff1a;对于那些耗费大量CPU计算而计算结果无需每帧变化的逻辑&#xff0c;使用缓存可以显著提高性…

Java——反射(4/4):反射的作用、应用场景(案例需求、实现步骤、代码实现)

目录 作用 应用场景 案例需求 实现步骤 代码实现 作用 基本作用&#xff1a;可以得到一个类的全部成分然后操作。可以破坏封装性。最重要的用途是&#xff1a;适合做Java的框架&#xff0c;基本上&#xff0c;主流的框架都会基于反射设计出一些通用的功能。 通过反射能够…

Springboot+vue 建筑资质证书管理系统

建筑企业资证管理系统&#xff0c;简称证书管理软件&#xff0c;基于springbootvue开发&#xff0c;数据库采用mysql&#xff0c;。以建筑企业证书管理为核心&#xff0c;包含投证书管理、证书过期提醒&#xff0c;辅助建筑企业高效完成证书管理、人员信息、投标经营活动等管理…

全功能知识付费小程序源码系统 支持视频课程、音频课程、图文课程 附带代码包以及搭建部署教程

系统概述 “全功能知识付费小程序源码系统”是一款专为知识创作者、教育机构及企业内训部门设计的全方位解决方案。该系统基于当前最流行的小程序技术构建&#xff0c;无需下载安装&#xff0c;用户只需通过微信等社交平台即可轻松访问&#xff0c;极大地降低了使用门槛。同时…

CentOS 7 安装流程详细教程

目录 前言1. CentOS 7 概述2. 安装环境准备2.1 硬件要求2.2 安装介质准备 3. CentOS 7 安装步骤3.1 引导安装程序3.2 选择语言和键盘布局3.3 配置安装源和软件包3.4 配置分区3.5 设置网络和主机名3.6 设置时间和日期3.7 设置 root 密码和创建用户3.8 开始安装并完成配置 4. 安装…

8-11章节复习总结

文章目录 数据库技术三级模式两级映射数据设计数据模型E-R模型关系模型练习题 关系代数关系代数运算符练习题 SQL语句练习题 数据库控制练习题 数据库故障和备份函数依赖函数依赖的公理系统键与约束 规范化第一范式1NF第二范式2NF第三范式3NF练习题 模式分解练习题 标准化与知识…

R语言统计分析——回归诊断2

参考资料&#xff1a;R语言实战【第2版】 R语言的car包提供的大量函数&#xff0c;大大增强了拟合和评价回归模型的能力。如下&#xff1a; 函数目的qqPlot()分位数比较图durbinWatsonTest()对误差自相关性做Durbin-Watson检验crPlots()成分与残差图ncvTest()对非恒定的误差方…

如何构建Java SpringBoot+Vue的宽带业务管理系统:一步一脚印教程

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

贝叶斯推理:分步指南

一、说明 让我们深入了解贝叶斯推理的迷人世界。我将通过易于遵循的示例向您介绍其实际应用。 贝叶斯推理为统计分析提供了一个强大而灵活的框架&#xff0c;特别是在存在不确定性和先验知识的情况下。通过结合先前的分布并使用贝叶斯定理根据新证据更新这些信念&#xff0c;贝…

可视耳勺值不值得买?精心总结六大选购技巧

随着生活品质逐渐提高&#xff0c;很多家庭越来越关注个护健康&#xff0c;其中包括耳道的清洁方式。以前人们常用棉签或者挖耳勺进行掏耳朵&#xff0c;但这种方式很容易导致清洁不干净引发中耳炎。可视耳勺应运而生&#xff0c;能通过可视化的操作帮助用户看清耳垢位置进行精…

python之matplotlib (4 图例)

图例 方法一 import matplotlib.pyplot as plt import numpy as npxnp.arange(1,10) yx2 yy2*x1figplt.figure() axplt.gca() ax.plot(x,y,labela,ccoral) ax.plot(x,yy,labelb,cy) plt.legend() plt.show() 在plot中的参数label注明标注名称即可实现图例的绘制&#xff0c;但…

【深度学习】直观理解AUROC

文章目录 前言如何计算直观解释常用计算方式 前言 AUROC常用于衡量二分类分类器的性能&#xff0c;本文旨在详解该指标计算过程 如何计算 设想我们有一个分类器&#xff0c;对数据做二分类。我们设输入数据为 x x x, 预测标签为 y y y, ground-truth标签为 y ^ \hat{y} y…

JAVA多场景多项目排队叫号系统小程序源码

&#x1f525;【告别长龙&#xff0c;智能排队新风尚】多场景多项目排队叫号系统大揭秘&#x1f50d; &#x1f680;【一码在手&#xff0c;轻松畅游各大场景】 还在为医院挂号、银行办事、餐厅等号的长队头疼吗&#xff1f;&#x1f629; 多场景多项目排队叫号系统&#xff0…

关于nginx标准配置参数介绍

标准配置参数&#xff1a; user root;#配置用户或者组&#xff0c;默认为nobody worker_processes 4;#允许生成的进程数&#xff0c;默认为1 项目中nginx.conf配置文件 user root; worker_processes 4; //最大的进程数&#xff0c;要看服务器的内核是多少核的&#xff0…

IEEE-802.3总线局域网

关于以太网相关基础知识&#xff0c;这批文章说的挺好的。

MacOS 阿里云docker镜像仓库无法登录的解决办法

问题情况 Error response from daemon: Get “https://registry.cn-hangzhou.aliyuncs.com/v2/”: Method Not Allowed 解决办法 MacOS下&#xff0c;打开该文件 open ~/.docker删除config.json文件 重新登录 已经提示Login Successed 接下去进行其他操作即可。 PS&…

MiDaS、ZoeDepth、Depth-Anything ai算法深度图估计

1、MiDaS 参考&#xff1a; https://github.com/isl-org/MiDaS https://pytorch.org/hub/intelisl_midas_v2/ https://colab.research.google.com/github/pytorch/pytorch.github.io/blob/master/assets/hub/intelisl_midas_v2.ipynb#scrollTo5A32CL3tocrZ 代码 import cv2 i…

如何使用midjourney?MidJourney订阅计划及国内订阅教程

国内如何订阅MidJourney 第三方代理 参考&#xff1a; zhangfeidezhu.com/?p474 使用信用卡订阅教程 办理国外信用卡&#xff1a; 这个各自找国外的银行办理就好了。 登录MidJourney&#xff1a; 登录MidJourney网站&#xff0c;进入订阅中心。如果是在Discord频道&#x…