【FPGA零基础学习之旅#17】搭建串口收发与储存双口RAM系统

news2024/9/25 17:16:13

🎉欢迎来到FPGA专栏~搭建串口收发与储存双口RAM系统


  • ☆* o(≧▽≦)o *☆~我是小夏与酒🍹
  • 博客主页:小夏与酒的博客
  • 🎈该系列文章专栏:FPGA学习之旅
  • 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
  • 📜 欢迎大家关注! ❤️
    FPGQ2

CSDN

🎉 目录-串口收发与储存双口RAM系统

  • 一、效果演示
  • 二、基础知识
    • 2.1 实现目标
    • 2.2 所需基础模块
  • 三、系统分析
  • 四、代码编写
    • 4.1 控制模块
    • 4.2 顶层模块
  • 五、仿真测试激励文件
    • 5.1 key_model
    • 5.2 testbench编写
    • 5.3 仿真结果
  • 六、板级验证

遇见未来

一、效果演示

🥝输入数据:
输入

🥝输出数据:
输出

🥝串口助手分析:
输出数据
按下第一次按键,FPGA开始连续发送数据,按下第二次按键,FPGA停止发送数据。

二、基础知识

2.1 实现目标

使用按键消抖串口发送与接收模块,以及双端口RAM模块实现串口发送数据到FPGA中,FPGA接收到数据后将数据存储在双口RAM中,当按下按键时FPGA将RAM中存储的数据再通过串口发送出去,再次按下按键后,FPGA停止发送数据。

2.2 所需基础模块

相关模块学习文章:
🥝【FPGA零基础学习之旅#10】按键消抖模块设计与验证(一段式状态机实现);
🥝【FPGA零基础学习之旅#13】串口发送模块设计与验证;
🥝【FPGA零基础学习之旅#14】串口发送字符串;
🥝【FPGA零基础学习之旅#15】串口接收模块设计与验证(工业环境);
🥝【FPGA零基础学习之旅#16】嵌入式块RAM-双口ram的使用。

三、系统分析

参考小梅哥FPGA设计的系统框图:
sysy
通过串口发送数据到FPGA中,FPGA接收到数据后将数据存储在双口RAM一段连续空间中。当需要时,按下按键S0,则FPGA将RAM中存储的数据通过串口发送出去;再次按下S0,则停止数据发送。

进行功能划分:串口接收模块按键消抖模块RAM模块串口发送模块以及控制模块

在此给出所需使用的模块代码:

按键消抖模块

//
//模块:按键消抖模块
//key_state:输出消抖之后按键的状态
//key_flag:按键消抖结束时产生一个时钟周期的高电平脉冲
//
module KeyFilter(
	input 		Clk,
	input 		Rst_n,
	input 		key_in,
	output reg 	key_flag,
	output reg 	key_state
);

	//按键的四个状态
	localparam
		IDLE 		= 4'b0001,
		FILTER1 	= 4'b0010,
		DOWN 		= 4'b0100,
		FILTER2 	= 4'b1000;

	//状态寄存器
	reg [3:0] curr_st;
	
	//边沿检测输出上升沿或下降沿
	wire pedge;
	wire nedge;
	
	//计数寄存器
	reg [19:0]cnt;
	
	//使能计数寄存器
	reg en_cnt;
	
	//计数满标志信号
	reg cnt_full;//计数满寄存器
	
//------<边沿检测电路的实现>------
	//边沿检测电路寄存器
	reg key_tmp0;
	reg key_tmp1;
	
	//边沿检测
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)begin
			key_tmp0 <= 1'b0;
			key_tmp1 <= 1'b0;
		end
		else begin
			key_tmp0 <= key_in;
			key_tmp1 <= key_tmp0;
		end	
	end
		
	assign nedge = (!key_tmp0) & (key_tmp1);
	assign pedge = (key_tmp0)  & (!key_tmp1);

//------<状态机主程序>------	
	//状态机主程序
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)begin
			curr_st <= IDLE;
			en_cnt <= 1'b0;
			key_flag <= 1'b0;
			key_state <= 1'b1;
		end
		else begin
			case(curr_st)
				IDLE:begin
					key_flag <= 1'b0;
					if(nedge)begin
						curr_st <= FILTER1;
						en_cnt <= 1'b1;
					end
					else
						curr_st <= IDLE;
				end
				
				FILTER1:begin
					if(cnt_full)begin
						key_flag <= 1'b1;
						key_state <= 1'b0;
						curr_st <= DOWN;
						en_cnt <= 1'b0;
					end	
					else if(pedge)begin
						curr_st <= IDLE;
						en_cnt <= 1'b0;
					end
					else
						curr_st <= FILTER1;
				end
				
				DOWN:begin
					key_flag <= 1'b0;
					if(pedge)begin
						curr_st <= FILTER2;
						en_cnt <= 1'b1;
					end
					else
						curr_st <= DOWN;
				end
				
				FILTER2:begin
					if(cnt_full)begin
						key_flag <= 1'b1;
						key_state <= 1'b1;
						curr_st <= IDLE;
						en_cnt <= 1'b0;
					end	
					else if(nedge)begin
						curr_st <= DOWN;
						en_cnt <= 1'b0;
					end
					else
						curr_st <= FILTER2;
				end
				
				default:begin
					curr_st <= IDLE;
					en_cnt <= 1'b0;
					key_flag <= 1'b0;
					key_state <= 1'b1;
				end
			endcase
		end
	end
	
//------<20ms计数器>------		
	//20ms计数器
	//Clk 50_000_000Hz
	//一个时钟周期为20ns
	//需要计数20_000_000 / 20 = 1_000_000次
	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			cnt <= 20'd0;
		else if(en_cnt)
			cnt <= cnt + 1'b1;
		else
			cnt <= 20'd0;
	end
	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			cnt_full <= 1'b0;
		else if(cnt == 999_999)
			cnt_full <= 1'b1;
		else
			cnt_full <= 1'b0;
	end
	
endmodule

串口接收模块

//
//模块名称:串口接收模块(工业环境)
//
module uart_byte_rx(
	input 					Clk,//50M
	input 					Rst_n,
	input 			[2:0]	baud_set,
	input 					data_rx,
	output 	reg 	[7:0]	data_byte,
	output 	reg			Rx_Done
);

	reg s0_Rx,s1_Rx;//同步寄存器
	
	reg tmp0_Rx,tmp1_Rx;//数据寄存器
	
	reg [15:0]bps_DR;//分频计数器计数最大值
	reg [15:0]div_cnt;//分频计数器
	reg bps_clk;//波特率时钟
	reg [7:0]bps_cnt;
	
	reg uart_state;
	
	reg [2:0] r_data_byte [7:0];
	
	reg [2:0]START_BIT;
	reg [2:0]STOP_BIT;
	
	wire nedge;
	
//--------<同步寄存器处理>--------		
//用于消除亚稳态
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)begin
			s0_Rx <= 1'b0;
			s1_Rx <= 1'b0;
		end
		else begin
			s0_Rx <= data_rx;
			s1_Rx <= s0_Rx;
		end
	end
	
//--------<数据寄存器处理>--------		
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)begin
			tmp0_Rx <= 1'b0;
			tmp1_Rx <= 1'b0;
		end
		else begin
			tmp0_Rx <= s1_Rx;
			tmp1_Rx <= tmp0_Rx;
		end
	end
	
//--------<下降沿检测>--------	
	assign nedge = !tmp0_Rx & tmp1_Rx;
	
//--------<div_cnt模块>--------	
//得到不同计数周期的计数器
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			div_cnt <= 16'd0;
		else if(uart_state)begin
			if(div_cnt == bps_DR)
				div_cnt <= 16'd0;
			else
				div_cnt <= div_cnt + 1'b1;
		end
		else
			div_cnt <= 16'd0;
	end
//--------<bps_clk信号的产生>--------	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			bps_clk <= 1'b0;
		else if(div_cnt == 16'd1)
			bps_clk <= 1'b1;
		else
			bps_clk <= 1'b0;
	end
	
//--------<bps_clk计数模块>--------		
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			bps_cnt <= 8'd0;
		else if(bps_cnt == 8'd159 || (bps_cnt == 8'd12 && (START_BIT > 2)))
			bps_cnt <= 8'd0;
		else if(bps_clk)
			bps_cnt <= bps_cnt + 1'b1;
		else
			bps_cnt <= bps_cnt;
	end
	
//--------<Rx_Done模块>--------	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			Rx_Done <= 1'b0;
		else if(bps_cnt == 8'd159)
			Rx_Done <= 1'b1;
		else
			Rx_Done <= 1'b0;
	end	
	
//--------<波特率查找表>--------		
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			bps_DR <= 16'd324;
		else begin
			case(baud_set)
				0:bps_DR <= 16'd324;
				1:bps_DR <= 16'd162;
				2:bps_DR <= 16'd80;
				3:bps_DR <= 16'd53;
				4:bps_DR <= 16'd26;
				default:bps_DR <= 16'd324;
			endcase
		end	
	end

//--------<采样数据接收模块>--------	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)begin
			START_BIT <= 3'd0;
			r_data_byte[0] <= 3'd0; 
			r_data_byte[1] <= 3'd0;
			r_data_byte[2] <= 3'd0; 
			r_data_byte[3] <= 3'd0;
			r_data_byte[4] <= 3'd0; 
			r_data_byte[5] <= 3'd0;
			r_data_byte[6] <= 3'd0; 
			r_data_byte[7] <= 3'd0;
			STOP_BIT <= 3'd0;
		end
		else if(bps_clk)begin
			case(bps_cnt)
				0:begin
					START_BIT <= 3'd0;
					r_data_byte[0] <= 3'd0;
					r_data_byte[1] <= 3'd0;
					r_data_byte[2] <= 3'd0;
					r_data_byte[3] <= 3'd0;
					r_data_byte[4] <= 3'd0;
					r_data_byte[5] <= 3'd0;
					r_data_byte[6] <= 3'd0;
					r_data_byte[7] <= 3'd0;
					STOP_BIT <= 3'd0; 
				end
				6,7,8,9,10,11:START_BIT <= START_BIT + s1_Rx;
				22,23,24,25,26,27:r_data_byte[0] <= r_data_byte[0] + s1_Rx;
				38,39,40,41,42,43:r_data_byte[1] <= r_data_byte[1] + s1_Rx;
				54,55,56,57,58,59:r_data_byte[2] <= r_data_byte[2] + s1_Rx;
				70,71,72,73,74,75:r_data_byte[3] <= r_data_byte[3] + s1_Rx;
				86,87,88,89,90,91:r_data_byte[4] <= r_data_byte[4] + s1_Rx;
				102,103,104,105,106,107:r_data_byte[5] <= r_data_byte[5] + s1_Rx;
				118,119,120,121,122,123:r_data_byte[6] <= r_data_byte[6] + s1_Rx;
				134,135,136,137,138,139:r_data_byte[7] <= r_data_byte[7] + s1_Rx;
				150,151,152,153,154,155:STOP_BIT <= STOP_BIT + s1_Rx;
				default:begin
					START_BIT <= START_BIT;
					r_data_byte[0] <= r_data_byte[0];
					r_data_byte[1] <= r_data_byte[1];
					r_data_byte[2] <= r_data_byte[2];
					r_data_byte[3] <= r_data_byte[3];
					r_data_byte[4] <= r_data_byte[4];
					r_data_byte[5] <= r_data_byte[5];
					r_data_byte[6] <= r_data_byte[6];
					r_data_byte[7] <= r_data_byte[7];
					STOP_BIT <= STOP_BIT;
				end
			endcase
		end
	end

//--------<数据状态判定模块>--------	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			data_byte <= 8'd0;
		else if(bps_cnt == 8'd159)begin
			data_byte[0] <= r_data_byte[0][2];
			data_byte[1] <= r_data_byte[1][2];
			data_byte[2] <= r_data_byte[2][2];
			data_byte[3] <= r_data_byte[3][2];
			data_byte[4] <= r_data_byte[4][2];
			data_byte[5] <= r_data_byte[5][2];
			data_byte[6] <= r_data_byte[6][2];
			data_byte[7] <= r_data_byte[7][2];
		end
		else
			;
	end

//--------<uart_state模块>--------	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			uart_state <= 1'b0;
		else if(nedge)
			uart_state <= 1'b1;
		else if(Rx_Done || (bps_cnt == 8'd12 && (START_BIT > 2)))
			uart_state <= 1'b0;
		else
			uart_state <= uart_state;
	end

endmodule

串口发送模块

//
//模块名称:串口发送模块
//
module uart_byte_tx(
	input 		Clk,
	input 		Rst_n,
	input [7:0]	data_byte,
	input 		send_en,
	input [2:0]	baud_set,
	
	output reg uart_tx,
	output reg Tx_Done,
	output reg uart_state
);

	reg bps_clk;//波特率时钟
	
	reg [15:0]div_cnt;//分频计数器
		
	reg [15:0]bps_DR;//分频计数最大值
	
	reg [3:0]bps_cnt;//波特率计数时钟
		
	//定义数据的起始位和停止位
	localparam START_BIT = 1'b0;
	localparam STOP_BIT  = 1'b1;
	
	reg [7:0]r_data_byte;//数据寄存器
	
//--------<uart状态模块>--------	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			uart_state <= 1'b0;
		else if(send_en)
			uart_state <= 1'b1;
		else if(bps_cnt == 4'd11)//bps_cnt计数达到11次,即发送结束
			uart_state <= 1'b0;
		else
			uart_state <= uart_state;
	end

//--------<使能分频计数模块>-------	
//	assign en_cnt = uart_state;
	
//--------<寄存待发送的数据,使数据保持稳定>--------
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			r_data_byte <= 8'd0;
		else if(send_en)
			r_data_byte <= data_byte;
		else
			r_data_byte <= r_data_byte;
	end
	
//--------<波特率查找表>--------		
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			bps_DR <= 16'd5207;
		else begin
			case(baud_set)
				0:bps_DR <= 16'd5207;
				1:bps_DR <= 16'd2603;
				2:bps_DR <= 16'd1301;
				3:bps_DR <= 16'd867;
				4:bps_DR <= 16'd433;
				default:bps_DR <= 16'd5207;
			endcase
		end	
	end
	
//--------<Div_Cnt模块>--------	
//得到不同计数周期的计数器
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			div_cnt <= 16'd0;
		else if(uart_state)begin	//	assign en_cnt = uart_state;
			if(div_cnt == bps_DR)
				div_cnt <= 16'd0;
			else
				div_cnt <= div_cnt + 1'b1;
		end
		else
			div_cnt <= 16'd0;
	end
//--------<bps_clk信号的产生>--------	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			bps_clk <= 1'b0;
		else if(div_cnt == 16'd1)
			bps_clk <= 1'b1;
		else
			bps_clk <= 1'b0;
	end
	
//--------<bps_cnt计数模块>--------		
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			bps_cnt <= 4'd0;
		else if(bps_cnt == 4'd11)//clr信号
			bps_cnt <= 4'd0;
		else if(bps_clk)
			bps_cnt <= bps_cnt + 1'b1;
		else
			bps_cnt <= bps_cnt;
	end

//--------<Tx_Done模块>--------	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			Tx_Done <= 1'b0;
		else if(bps_cnt == 4'd11)
			Tx_Done <= 1'b1;
		else
			Tx_Done <= 1'b0;
	end
	
//--------<数据位输出模块-10选1多路器>--------	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			uart_tx <= 1'b1;
		else begin
			case(bps_cnt)
				0:uart_tx <= 1'b1;
				1:uart_tx <= START_BIT;
				2:uart_tx <= r_data_byte[0];
				3:uart_tx <= r_data_byte[1];
				4:uart_tx <= r_data_byte[2];
				5:uart_tx <= r_data_byte[3];
				6:uart_tx <= r_data_byte[4];
				7:uart_tx <= r_data_byte[5];
				8:uart_tx <= r_data_byte[6];
				9:uart_tx <= r_data_byte[7];
				10:uart_tx <= STOP_BIT;
				default:uart_tx <= 1'b1;
			endcase
		end
	end
	
endmodule

四、代码编写

上述已给出按键消抖模块和串口收发模块,在本文中主要编写控制模块顶层模块

4.1 控制模块

为了实现FPGA将接收到的数据存储到双口RAM的一段连续空间中,就需要设计一个可以实现写地址数据自加的控制逻辑,且其控制信号为串口接收模块输出的Rx_Done信号。每来一个Rx_Done就表明接收成功一字节数,地址数进行加一:

assign wren = Rx_Done;

always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)
		wraddress <= 8'd0;
	else if(Rx_Done)
		wraddress <= wraddress + 1'b1;
	else
		wraddress <= wraddress;
end

当按下按键S0,FPGA将RAM中存储的数据通过串口发送出去。需要实现按键按下即启动连续读操作,再次按下可暂停读操作:

reg do_send;

always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)
		do_send <= 1'd0;
	else if(Key_flag && !Key_state)
		do_send <= ~do_send;
end

always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n) 
		rdaddress <= 8'd0;
	else if(do_send && Tx_Done)
		rdaddress <= rdaddress + 8'd1;
	else
		rdaddress <= rdaddress;
end

在仿真双端口RAM时发现其输出会延迟两个系统时钟周期这是为了保证数据变化稳定之后才进行数据输出,所以在此将驱动Send_en的信号接两级寄存器进行延迟两拍当按键按下后启动一次发送,然后判断上一字节是否发送结束,是则进行下一字节发送否则不进行下一次发送:

reg r0_send_done,r1_send_done;

always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)begin
		r0_send_done <= 1'b0;
		r1_send_done <= 1'b0;
	end
	else begin
		r0_send_done <= (do_send && Tx_Done);
		r1_send_done <= r0_send_done; 
	end
end

always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)
		Send_en <= 1'b0;
	else if(Key_flag && !Key_state)
		Send_en <= 1'b1;
	else if(r1_send_done)
		Send_en <= 1'b1;
	else
		Send_en <= 1'b0;
end

为了保证RAM地址操作的有效性,在写地址和读地址代码部分加上范围限制

//--------<dpram写地址加1>--------	
always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)
		wraddress <= 8'd0;
	else if(Rx_Done)
		wraddress <= wraddress + 1'b1;
	else if(wraddress > 8'd255) //当写地址大于配置ip核时的值时,返回到0地址;
		wraddress <= 8'd0;
	else
		wraddress <= wraddress;
end

//--------<dpram读地址加1>--------		
always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)
		rdaddress <= 8'd0;
	else if(do_send && Tx_Done)
		rdaddress <= rdaddress + 8'd1;
	else if(rdaddress > 8'd255)	//当读地址大于255时,返回到0地址;
		rdaddress <= 8'd0;
	else
		rdaddress <= rdaddress;
end

完整的控制模块代码:system_ctrl.v

module system_ctrl(
	input 				Clk,
	input 				Rst_n,
	input 				Key_flag,
	input 				Key_state,
	input 				Rx_Done,
	input 				Tx_Done,
	output 				wren,
	output reg			Send_en,
	output reg [7:0]	rdaddress,
	output reg [7:0]	wraddress
);

	assign wren = Rx_Done;
	
	reg do_send;
	
	reg r0_send_done;
	reg r1_send_done;

//--------<dpram写地址加1>--------	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			wraddress <= 8'd0;
		else if(Rx_Done)
			wraddress <= wraddress + 1'b1;
		else if(wraddress > 8'd255) //当写地址大于配置ip核时的值时,返回到0地址;
			wraddress <= 8'd0;
		else
			wraddress <= wraddress;
	end

//--------<翻转标志信号>--------
//按下一次按键开始连续发送数据,再按一次按键停止发送;
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			do_send <= 1'b0;
		else if(Key_flag && !Key_state)
			do_send <= ~do_send;
	end

//--------<dpram读地址加1>--------		
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			rdaddress <= 8'd0;
		else if(do_send && Tx_Done)
			rdaddress <= rdaddress + 8'd1;
		else if(rdaddress > 8'd255)	//当读地址大于255时,返回到0地址;
			rdaddress <= 8'd0;
		else
			rdaddress <= rdaddress;
	end

//--------<RAM的两拍延迟>--------	
//双端口RAM的输出延迟两个系统时钟周期;
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)begin
			r0_send_done <= 1'b0;
			r1_send_done <= 1'b1;
		end
		else begin
			r0_send_done <= (do_send && Tx_Done);
			r1_send_done <= r0_send_done;
		end
	end

//--------<按键控制与连续读操作>--------	
//Send_en由按键信号和r1_send_done信号同时控制;
//r1_send_done信号使得串口连续读取dpram的数据;
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			Send_en <= 1'b0;
		else if(Key_flag && !Key_state)
			Send_en <= 1'b1;
		else if(r1_send_done)
			Send_en <= 1'b1;
		else
			Send_en <= 1'b0;
	end

endmodule

控制模块的RTL视图:

RTL-CTRL

4.2 顶层模块

串口接收模块按键消抖模块RAM模块串口发送模块以及控制模块例化到顶层模块中。

uart_system_top.v:

module uart_system_top(
	input 	Clk,
	input 	Rst_n,
	input		key_in,
	input 	uart_rx,
	output 	uart_tx
);
	
	wire [7:0]rx_data;
	wire [7:0]tx_data;
	wire Key_flag;
	wire Key_state;
	wire Rx_Done;
	wire Tx_Done;
	wire wren;
	wire Send_en;
	wire [7:0]rdaddress;
	wire [7:0]wraddress;
	
	uart_byte_rx uart_byte_rx(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.baud_set(3'd0),
		.data_rx(uart_rx),
		.data_byte(rx_data),
		.Rx_Done(Rx_Done)
	);
	
	dpram dpram(
		.clock(Clk),
		.data(rx_data),
		.rdaddress(rdaddress),
		.wraddress(wraddress),
		.wren(wren),
		.q(tx_data)
	);
		
	KeyFilter KeyFilter(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.key_in(key_in),
		.key_flag(Key_flag),
		.key_state(Key_state)
	);

	uart_byte_tx uart_byte_tx(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.data_byte(tx_data),
		.send_en(Send_en),
		.baud_set(3'd0),
		.uart_tx(uart_tx),
		.Tx_Done(Tx_Done),
		.uart_state()
	);

	system_ctrl system_ctrl(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.Key_flag(Key_flag),
		.Key_state(Key_state),
		.Rx_Done(Rx_Done),
		.Tx_Done(Tx_Done),
		.wren(wren),
		.Send_en(Send_en),
		.rdaddress(rdaddress),
		.wraddress(wraddress)
	);

endmodule

顶层模块的RTL视图:

RTL

五、仿真测试激励文件

5.1 key_model

key_model仿真模型用于有按键控制信号的项目进行仿真测试,模拟实际情况中的按键抖动。 在仿真时将该模型也添加到工程中使用。

key_model.v:

`timescale 1ns/1ns

module key_model(press,key);
	
	input press;
	output reg key;
	
	reg [15:0]myrand;
	
	initial begin
		key = 1'b1;		
	end
	
	always@(posedge press)
		press_key;
		
	task press_key;
		begin
			repeat(50)begin
				myrand = {$random}%65536;//0~65535;
				#myrand key = ~key;			
			end
			key = 0;
			#25000000;
			
			repeat(50)begin
				myrand = {$random}%65536;//0~65535;
				#myrand key = ~key;			
			end
			key = 1;
			#25000000;		
		end	
	endtask

endmodule

5.2 testbench编写

完整的仿真测试激励文件:

uart_system_top_tb.v:

`timescale 1ns/1ns
`define clock_period 20

module uart_system_top_tb;
	
	reg Clk;
	reg Rst_n;
	wire Key_in;
	wire uart_rx;
	wire uart_tx;
	
	reg [7:0]data_byte_t;
	reg send_en;
	wire [2:0]baud_set;
	wire Tx_Done;
	reg press;
	
	assign baud_set = 3'd0;
	
	uart_system_top uart_system_top(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.key_in(Key_in),
		.uart_rx(uart_tx),
		.uart_tx(uart_rx)
	);
	
	uart_byte_tx uart_byte_tx(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.data_byte(data_byte_t),
		.send_en(send_en),
		.baud_set(baud_set),
		.uart_tx(uart_tx),
		.Tx_Done(Tx_Done),
		.uart_state()
	);
	
	key_model key_model(
		.press(press),
		.key(Key_in)
	);
	
	initial Clk = 1;
	always#(`clock_period / 2) Clk = ~Clk;

	initial begin
		Rst_n = 1'b0;
		press = 0;
		data_byte_t = 8'd0;
		send_en = 1'd0;
		#(`clock_period*20 + 1 );
		Rst_n = 1'b1;
		#(`clock_period*50);
		
		data_byte_t = 8'haa;
		send_en = 1'd1;
		#`clock_period;
		send_en = 1'd0;		
		@(posedge Tx_Done)
		
		#(`clock_period*5000);
		
		data_byte_t = 8'h55;
		send_en = 1'd1;
		#`clock_period;
		send_en = 1'd0;
		@(posedge Tx_Done)
		
		#(`clock_period*5000);
		
		data_byte_t = 8'h33;
		send_en = 1'd1;
		#`clock_period;
		send_en = 1'd0;		
		@(posedge Tx_Done)
		
		#(`clock_period*5000);
		
		data_byte_t = 8'haf;
		send_en = 1'd1;
		#`clock_period;
		send_en = 1'd0;
		@(posedge Tx_Done)
		
		#(`clock_period*5000);
		
		press = 1;
		#(`clock_period*3)
		press = 0;
		
		#(`clock_period*2000000)
		
		$stop;
	end

endmodule

5.3 仿真结果

仿真结果

六、板级验证

🥝输入数据储存到双口RAM中:
输入

🥝输出RAM中的数据:
输出

csdn

🧸结尾


  • ❤️ 感谢您的支持和鼓励! 😊🙏
  • 📜您可能感兴趣的内容:
  • 【FPGA零基础学习之旅#14】串口发送字符串
  • 【Python】串口通信-与FPGA、蓝牙模块实现串口通信(Python+FPGA)
  • 【Arduino TinyGo】【最新】使用Go语言编写Arduino-环境搭建和点亮LED灯
  • 【全网首发开源教程】【Labview机器人仿真与控制】Labview与Solidworks多路支配关系-四足爬行机器人仿真与控制
    遇见未来

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

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

相关文章

超声波清洗机品牌哪些好用?好评不断的超声波清洗机推荐

超声波清洗机目前的使用范围逐渐变广&#xff0c;一开始超声波清洗机只出现在大型的工业领域中的零件清洗&#xff0c;逐渐衍生到现在&#xff0c;出现了小型的超声波清洗机&#xff0c;可以让大家可以在家也使用上超声波清洗机。眼镜是现在大部分都离不开视线辅助的一个工具&a…

越流行的大语言模型越不安全

源自&#xff1a;GoUpSec “人工智能技术与咨询” 发布 安全研究人员用OpenSSF记分卡对GitHub上50个最流行的生成式AI大语言模型项目的安全性进行了评估&#xff0c;结果发现越流行的大语言模型越危险。 近日&#xff0c;安全研究人员用OpenSSF记分卡对GitHub上50个最流…

Powershell脚本自动备份dhcp数据库

文章目录 为什么要备份DHCP数据库呢&#xff1f;在PowerShell中自动备份DHCP数据库1&#xff0c;创建备份目录2&#xff0c;判断备份路径是否存在3&#xff0c;备份DHCP数据库4&#xff0c;完整自动备份脚本5&#xff0c;安排定期备份 推荐阅读 为什么要备份DHCP数据库呢&#…

故障解析丨Clone节点导致主从故障

1.背景概述 在一次主从复制架构中&#xff0c;由于主节点binlog损坏&#xff0c;导致从节点无法正常同步数据&#xff0c;只能重做从节点&#xff1b;因此使用MySQL 8.0.17开始提供的clone技术进行恢复&#xff0c;恢复后的2天都发生了主从报错数据冲突。 通过解析binlog发现…

网页轮播图

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>纯CSS实现轮播图(自动轮播)</title><style&…

Warning: ‘Destination Folder‘ contains 1 space.【Anaconda安装】

报错内容如下&#xff1a; 意思就是说你的安装路径下不要有空格哈哈&#xff0c;有空格就不行&#xff0c;比如&#xff1a; "D:\Program Files\Anaconda3"中间就有空格&#xff0c;Program与Files之间。 换个路径&#xff0c;例如&#xff1a; 就可以了。

EmbedChain:比LangChain更加轻量化的LLM框架

一、前言 在之前的文章中&#xff0c;我们研究了如何使用LangChain结合大型语言模型&#xff08;LLM&#xff09;API来构建用户友好且直观的聊天机器人。现在&#xff0c;我们将探索一个新的Python包来进一步简化LangChain的实现。只需3-4行代码&#xff0c;我们就可以轻松地与…

Runner GoUI自动化测试发布

构建自动化测试体系是当下每个项目团队愿意去做的&#xff0c;自动化测试减少重复操作节省人力成本。 RunnerGo UI自动化平台 RunnerGo提供从API管理到API性能再到可视化的API自动化、UI自动化测试功能模块&#xff0c;覆盖了整个产品测试周期。 RunnerGo UI自动化基于Selen…

APUS入驻百度灵境矩阵,普惠AI大模型插件能力

10月17日&#xff0c;APUS出席百度世界大会2023。会上&#xff0c;百度公布了灵境矩阵业务进展&#xff0c;APUS作为灵境矩阵首批合作伙伴正与百度携手拓展大模型能力边界、构建大模型应用生态。 百度认为&#xff0c;大模型将繁荣AI应用生态&#xff0c;在生态搭建过程中&…

springboot maven项目环境搭建idea

springboot maven项目环境搭建idea 文章目录 springboot maven项目环境搭建idea用到的软件idea下载和安装java下载和安装maven下载和安装安装maven添加JAVA_HOME路径&#xff0c;增加JRE环境修改conf/settings.xml&#xff0c;请参考以下 项目idea配置打开现有项目run或build打…

uview 1 uni-app表单 number digit 的输入框有初始化赋值后,但是校验失败

背景&#xff1a; 在onReady初始化规则 onReady() { this.$refs.uForm.setRules(this.rules); }, 同时&#xff1a;ref,model,rules,props都要配置好。 报错 当input框限定type为number&#xff0c;digit类型有初始值不做修改动作,直接提交会报错&#xff0c;验…

仿美团外卖微信小程序源码/美团外卖优惠券领劵小程序-自带流量主模式

源码简介&#xff1a; 仿美团外卖微信小程序源码&#xff0c;它是美团外卖优惠券领劵小程序&#xff0c;还自带流量主模式。可以领取外卖优惠券的小程序。实用方便。 美团优惠券小程序带举牌小人带菜谱流量主模式&#xff0c;挺多外卖小程序的&#xff0c;但是都没有搭建教程…

Leetcode每日一题6.05:二叉树搜索树BST

二叉搜索树&#xff08;BST&#xff09; 根节点大于等于左子树所有节点&#xff0c;小于等于右子树所有节点。 二叉搜索树中序遍历即为节点从小到大排序。 230. 二叉搜索树中第K小的元素 题目描述&#xff1a; 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &a…

tcpdump 异常错误

tcpdump 进行抓包的时候&#xff0c;-w 提示 Permission denied&#xff1a; sudo tcpdump -w test1.log tcpdump: test1.log: Permission denied 开始以为是用户权限的问题&#xff0c;后来换用 root 账户还是不行&#xff0c;经搜索&#xff0c;是 AppArmor 的问题。 解决方…

一台服务器成了哆啦A梦的神奇口袋

如果我有一台服务器&#xff0c;那简直就像打开了哆啦A梦的神奇口袋&#xff01;可以做的事情可太多啦&#xff0c;比如&#xff1a; 学习和探险 首先嘛&#xff0c;当然是用来学习和探险啦&#xff01;我可以安装和配置各种操作系统、编程语言和工具&#xff0c;深入了解计…

优优嗨聚集团:美团外卖,让美好儿童餐计划触手可及

在当今这个快节奏的社会&#xff0c;父母们对于孩子的饮食健康越来越关注。如何让孩子吃得健康、吃得安心&#xff0c;是每一个家长都非常关心的问题。而美团外卖&#xff0c;作为中国最大的外卖平台之一&#xff0c;一直在积极推动美好儿童餐计划&#xff0c;让家长们能够更方…

docker(2)部署前后端分离springboot+vue项目

前置知识 虚拟网桥 docker容器需要在同一个网段才能通信&#xff0c;当启动一个容器时会自动连接一个docker中默认网桥段但此默认网桥段非本容器固定&#xff0c;当下次容器启动分配的ip会变&#xff0c;并且不可用名称直接访问。 自定义网段将需要互通的容器放入&#xff0c…

容联七陌入选沙利文2023中国AI技术变革典型企业

近日&#xff0c;全球增长咨询公司弗若斯特沙利文&#xff08;Frost & Sullivan&#xff0c;简称“沙利文”&#xff09;发布《2023年中国AI技术变革企业服务白皮书》&#xff0c;白皮书显示&#xff0c;容联七陌以大模型为支撑&#xff0c;通过生成式一体化智能客服全方位…

首枚开源社正式成员纪念徽章来啦

各位尊敬的开源社正式成员&#xff1a; 2023年10月16日&#xff0c;开源社刚过完第九个成立生日&#xff0c;开源社与您共同迎接第十年的到来&#xff01; 首枚开源社正式成员纪念徽章&#xff0c;满载作为开源人的归属感和荣誉感&#xff0c;设计上我们采用开源社 logo 的经典…

【Java集合类面试十八】、ConcurrentHashMap是怎么分段分组的?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;ConcurrentHashMap是怎么…