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

欢迎来到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零基础学习之旅#17】搭建串口收发与储存双口RAM系统_第1张图片
按下第一次按键,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设计的系统框图:
【FPGA零基础学习之旅#17】搭建串口收发与储存双口RAM系统_第2张图片
通过串口发送数据到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;
	
//----------------	
//得到不同计数周期的计数器
	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
//----------------	
	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
	
//----------------		
	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
	
//----------------	
	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

//----------------	
	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;//数据寄存器
	
//----------------	
	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
	
//----------------	
//得到不同计数周期的计数器
	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
//----------------	
	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
	
//----------------		
	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

//----------------	
	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地址操作的有效性,在写地址和读地址代码部分加上范围限制

//----------------	
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)
		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;

//----------------	
	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

//----------------		
	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的输出延迟两个系统时钟周期;
	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视图:

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

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视图:

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

五、仿真测试激励文件

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 仿真结果

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

六、板级验证

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

输出RAM中的数据:

csdn

结尾


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

你可能感兴趣的:(FPGA学习之旅,1024程序员节,fpga开发,学习,Verilog,HDL,串口收发,双口RAM)