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

posted @ 2020-08-06 11:18  习惯科夫  阅读(1198)  评论(0编辑  收藏  举报