1. 程式人生 > 實用技巧 >Verilog入門例項2——雙埠RAM,單按鍵控制多樣式流水燈

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