• Verilog入门实例2——双端口RAM,单按键控制多样式流水灯


    verilog入门实例2——双端口RAM,单按键控制多样式流水灯

    一. 双端口RAM

    1. 设计一个位宽8bit,地址深度为128,可以同时读写的双端口RAM

    2. 模块名字:ram_dual

    3. 功能说明:当外部给出写使能时,写地址和写数据有效,将数据存放在对应地址中。当外部给出读使能时,通过读地址读取数据。读写可同时进行。

    4. 输入端口:rst, clk_r, clk_w, addr_r[7:0], addr_w[7:0], data_w[7:0], rd_en, wr_en(_r表示读,_w表示写,_en使能)

    5. 输出端口:data_rd[7:0](为读取数据)

    6. 测试激励要求:向第一个地址写入数据0x01,第十个地址写入数据0x0a,第二十个地址写入数据0x20,第一百个地址写入数据0x64。然后依次读取这四个地址的数据。

    7. 使用define语句给以上地址起个名字,将命名单独放到define.v文件,激励中地址用别名替代。

    8.  首先了解RAM的具体概念,FPGA中的RAM有单端口,双端口和伪双端口之分。

      • 单端口RAM(Single-Port RAM)

        输入只有一组数据线和一组地址线,只有一个时钟,读写公用地址线。

        输出只有一个端口。

        所以但端口RAM的读写操作不能同时进行。当wea拉高时,会将数据写入对应的地址,同时douta输出的数据与此时写入的数据是一致的,因此在读的时候需要重新生成对应的读地址给addra,并且将wea变为低电平。

      • 伪双端口RAM(Simple Dual-Port RAM)

        输入有一组数据线,两组地址线,两个时钟。

        提供了两个端口A和B,通过端口A进行写访问,通过端口B进行读访问。互相不影响。

      • 双端口RAM(True Dual-Port RAM)

        提供两个端口A和B,这两个端口都可以对存储进行写读访问。
        从以上概念可知,题目要求是需要一个伪双端口RAM

    src

    module ram_dual(
    	input rst_n,
    	input clk_r,
    	input clk_w,
    	input [7:0]addr_r,
    	input [7:0]addr_w,
    	input [7:0]data_w,
    	input rd_en,
    	input wr_en,//_r表示读,_w表示写,_en使能
    	output reg[7:0]data_rd //为读取数据
    );
    
    	reg [7:0] ram[127:0];
    	
    	//  Port read
    	always@(posedge clk_r or negedge rst_n)
    		begin
    			if(!rst_n)
    				data_rd <= 1'b0;
    			else if (rd_en)
    					data_rd <= ram[addr_r];
    				else 
    					data_rd <= 8'b00000000;
    				
    		end
    	
    	// Port write
    	always@(posedge clk_w or negedge rst_n)
    		begin
    			if (!rst_n)
    				ram[addr_w] <= ram[addr_w];
    			else if (wr_en)
    					ram[addr_w] <= data_w;
    				else 
    					ram[addr_w] <= ram[addr_w];
    			
    		end
    		
    endmodule
    	
    
    

    tb

    `timescale 1ns/1ps
    `include "define.v"
    module tb_ram_dual;
    	reg clk_w,clk_r,rst_n;
    	reg [7:0] addr_w;
    	reg [7:0] addr_r;
    	reg [7:0] data_w;
    	reg wr_en,rd_en;
    	
    	wire [7:0] data_rd;
    	
    	ram_dual uut(
    		.clk_r(clk_r),
    		.clk_w(clk_w),
    		.rst_n(rst_n),
    		.addr_r(addr_r),
    		.addr_w(addr_w),
    		.data_w(data_w),
    		.wr_en(wr_en),
    		.rd_en(rd_en),
    		.data_rd(data_rd)
    	);
    	
    	always #10 clk_r = ~clk_r;
    	always #10 clk_w = ~clk_w;
    	
    	
    	initial
    		begin
    			clk_r = 0;
    			rst_n = 0;
    			clk_w = 0;
    			wr_en = 0;
    			rd_en = 0;
    			addr_r= 0;
    			addr_w= 0;
    			data_w= 0;
    			#20 
    				begin
    				rst_n = 1;
    				wr_en = 1;
    				addr_w = `ADDR_W_ONE;
    				data_w = 8'h01;
    				end 
    			#20
    				begin
    				addr_w = `ADDR_W_TWO;
    				data_w = 8'h0a;				
    				end
    			#20
    				begin
    				addr_w = `ADDR_W_THREE;
    				data_w = 8'h20;
    				end
    			#20
    				begin
    				addr_w = `ADDR_W_FOUR;
    				data_w = 8'h64;
    				end
    			#100
    				begin
    				wr_en = 0;
    				rd_en = 1;
    				addr_w = 8'b0;
    				addr_r = 8'h00;
    				data_w = 1'b0;
    				end
    			#20
    				begin
    				addr_r = 8'h09;
    				end
    			#20 addr_r = 8'h13;
    			#20 addr_r = 8'h63;
    			#20 
    			begin
    			addr_r = 8'h00;
    			rd_en = 0;
    			end
    			
    		end
    		endmodule
    

    define.v

    `define ADDR_W_ONE    8'h0
    `define ADDR_W_TWO    8'h09
    `define ADDR_W_THREE  8'h13
    `define ADDR_W_FOUR   8'h63
    

    仿真截图

    设计一个流水灯控制器

    ​ 要求:

    1. 有多种变化方式
    2. 设置复位键
    3. 有按键控制流水灯的变化方式
    4. 具有按键消抖功能
    5. 下载到FGPA上观察实际结果是否符合设计
    6. 报告提交仿真结果并说明设计的正确性

    设计一个流水灯控制器

    ​ 要求:

    1. 有多种变化方式
    2. 设置复位键
    3. 有按键控制流水灯的变化方式
    4. 具有按键消抖功能
    5. 下载到FGPA上观察实际结果是否符合设计
    6. 报告提交仿真结果并说明设计的正确性

    思路:分两块部分,一个是按键消抖,一个是单个按键控制流水灯变化。按键消抖:由于按键的抖动不会超过20ms,只要按键输入的电平变化了,立即计时20ms后取反。按键控制流水灯,对消抖后的按键输入进行计数,根据需求的变化种数设置计数的大小,本人是设置了四种,只是计数至3就重新计数,根据计数来判断是何种变化形式;具体流水灯变化是,先设置一个led_control值,再运用case语句,led_control值是每过led灯的闪烁时间就变化,然后不同ked_control值使不同的led灯亮起,这样就可以随意设置流水灯的样式了。

    scr

    `timescale 1ns / 1ps
    //////////////////////////////////////////////////////////////////////////////////
    // Company: 
    // Engineer: 
    // 
    // Create Date:    17:57:17 08/04/2020 
    // Design Name: 
    // Module Name:    led 
    // Project Name: 
    // Target Devices: 
    // Tool versions: 
    // Description: 
    //
    // Dependencies: 
    //
    // Revision: 
    // Revision 0.01 - File Created
    // Additional Comments: 
    //
    //////////////////////////////////////////////////////////////////////////////////
    module led(
    	input clk,
    	input rst_n,
    	input wire key_in,
    	output reg [3:0]led,
    	
    	//output reg key_cnt,
    	//output reg [20:0]cnt1,
    	
    	output reg key_out
    	);
    	reg [26:0]cnt;
    	reg [1:0]led_control;
    	
    	reg key_cnt;
    	reg [20:0]cnt1;
    	reg [1:0]cnt2;
    	//reg key_out;
    	parameter TIME = 500;//just for test
    	//parameter TIME = 50000000
    	parameter TIME_T = 1000;//just for test.
    	//parameter TIME_T = 1000000;
    	
    	// counter
    	always@(posedge clk or negedge rst_n)
    		if (!rst_n)
    			cnt <= 1'b0;
    		else if (cnt == TIME-1)
    				cnt <= 1'b0;
    			else 
    				cnt <= cnt + 1'b1;
    	
    	//LED control
    	always@(posedge clk or negedge rst_n) //or negedge key_out)
    		begin
    			if (!rst_n)
    				led_control <= 2'b00;
    				//else if(!key_out)
    				 //		led_control <= 2'b00;
    
    			else if (cnt == TIME - 1)
    					led_control <= led_control +1'b1;
    				else
    					led_control <= led_control;
    		end
    	
    	//按键消抖
    	always @(posedge clk or negedge rst_n) 
    		begin
    			if(!rst_n)
    				key_cnt <= 0;
    			else if(key_cnt == 0 && key_out != key_in)
    				key_cnt <= 1;
    			else if(cnt1 == TIME_T - 1)
    				key_cnt <= 0;
    		end
    
        always @(posedge clk or negedge rst_n) 
    		begin
    			if(!rst_n)
    				cnt1 <= 0;
    			else if(key_cnt)
    				cnt1 <= cnt1 + 1'b1;
    			else
    				cnt1 <= 0;
    		end
    
        always @(posedge clk or negedge rst_n) 
    	begin
            if(!rst_n)
                key_out <= 0;
            else if(key_cnt == 0 && key_out != key_in)
                key_out <= key_in;
        end
    	
    	//
    	always@(negedge key_out or negedge rst_n)
    		begin
    			if (!rst_n)
    				cnt2 <= 1'b0;		
    			else if (cnt2 == 2'b11)
    					cnt2 <= 1'b0;
    				else  
    					cnt2 <= cnt2 + 1'b1;		
    				
    		end
    		
    		
    		
    	//切换流水灯模式
    	always@(posedge clk or negedge rst_n)
    		begin
    			if (!rst_n)
    				begin
    				led <=  4'b1111;
    				end
    			else if (cnt2==2'b00)
    				case(led_control)
    				2'b00:led <= 4'b1111;
    				2'b01:led <= 4'b1111;
    				2'b10:led <= 4'b1111;
    				2'b11:led <= 4'b1111;
    				default:led <= 4'b1111;
    				endcase
    			else if (cnt2==2'b01)
    				case(led_control)
    				2'b00:led <= 4'b0001;
    				2'b01:led <= 4'b0010;
    				2'b10:led <= 4'b0100;
    				2'b11:led <= 4'b1000;
    				default:led <= 4'b1111;
    				endcase
    			else if (cnt2 == 2'b10)
    				case(led_control)
    				2'b00:led <= 4'b1000;
    				2'b01:led <= 4'b0100;
    				2'b10:led <= 4'b0010;
    				2'b11:led <= 4'b0001;
    				default:led <= 4'b1111;
    				endcase
    			else if (cnt2 == 2'b11)
    				case (led_control)
    				2'b00:led <= 4'b1000;
    				2'b01:led <= 4'b0010;
    				2'b10:led <= 4'b0100;
    				2'b11:led <= 4'b0001;
    				default:led <= 4'b1111;
    				endcase
    			else 
    				led<=4'b1111;
    				
    		end
    endmodule
    
    

    tb

    `timescale 1ns/1ps
    module tb_led;
    reg clk;
    reg rst_n;
    reg key_in;
    wire [3:0]led;
    wire key_out;
    
    //wire key_cnt;
    //wire [20:0]cnt1;
    
    led uut(
    	.clk(clk),
    	.rst_n(rst_n),
    	.key_in(key_in),
    	.key_out(key_out),
    	
    	//.cnt1(cnt1),
    	//.key_cnt(key_cnt),
    	
    	.led(led)
    );
    
    initial  begin
    			clk = 0;
    			forever #10 clk = ~clk;
    		end
    		
    initial begin
            rst_n = 0;
            #10 rst_n = 1;
        end
    		
    //key_in
    initial begin
            // initial value
            key_in = 0;
            
            // wait reset
            repeat(3) @(negedge clk);
            
            // no bounce
            // key down
            key_in = 1;
    
            // last 60ms
            repeat(3000) @(negedge clk);
    
            // key up
            key_in = 0;
    
            // wait 50ms
            repeat(2500) @(negedge clk);
    
            // down 5ms, up 15ms
            // key down, bounce 5ms
            repeat(251) @(negedge clk) key_in = ~key_in;
    
            // last 60ms
            repeat(3000) @(negedge clk);
    
            // key up, bounce 15ms
            repeat(751) @(negedge clk) key_in = ~key_in;
    
            // wait 50ms
            repeat(2500) @(negedge clk);
    
            // down 19ms, up 19ms
            // key down, bounce 19ms
            repeat(951) @(negedge clk) key_in = ~key_in;
    
            // last 60ms
            repeat(3000) @(negedge clk);
    
            // key up, bounce 19ms
            repeat(951) @(negedge clk) key_in = ~key_in;
    
            // wait 50ms
            repeat(2500) @(negedge clk);
            
            // additional, this situation shoud not ever happen
            // down 25ms, up 25ms
            // key down, bounce 25ms
            repeat(1251) @(negedge clk) key_in = ~key_in;
    
            // last 60ms
            repeat(3000) @(negedge clk);
    
            // key up, bounce 25ms
            repeat(1251) @(negedge clk) key_in = ~key_in;
    
            // wait 50ms
            repeat(2500) @(negedge clk);
    
            // stop
            $stop;
        end
    endmodule
    
    	
    

    仿真截图:

    结果分析:以上仿真和实际时间比都是小了很多,从上可以看到,按键防抖可以有很好的效果,按下第一次按键led为0100,1000,0001,0010这样的样式亮起,再次按下按键led为0010,0001,1000,0100这样的样式亮起,这相当于右移。从以上分析知,成功实现目标。改变部分值可以成功烧入开发板中,并且四种流水灯样式亮起。

    感谢博主的RAM理解分享:https://blog.csdn.net/wordwarwordwar/article/details/82811449

  • 相关阅读:
    Python在函数中使用*和**接收元组和列表
    nvidia-smi实时刷新并高亮显示状态
    linux docker
    linux Vue+nginx+django 实现前后端分离
    linux mysql 主从复制
    linux redis
    linux mysql主从复制
    linux python虚拟环境 相关的
    linux dns
    2 linux vim sudo 文件权限
  • 原文地址:https://www.cnblogs.com/kefu/p/13445105.html
Copyright © 2020-2023  润新知