Verilog入門例項2——雙埠RAM,單按鍵控制多樣式流水燈
verilog入門例項2——雙埠RAM,單按鍵控制多樣式流水燈
一. 雙埠RAM
-
設計一個位寬8bit,地址深度為128,可以同時讀寫的雙埠RAM
-
模組名字:ram_dual
-
功能說明:當外部給出寫使能時,寫地址和寫資料有效,將資料存放在對應地址中。當外部給出讀使能時,通過讀地址讀取資料。讀寫可同時進行。
-
輸入埠:rst, clk_r, clk_w, addr_r[7:0], addr_w[7:0], data_w[7:0], rd_en, wr_en(_r表示讀,_w表示寫,_en使能)
-
輸出埠:data_rd[7:0](為讀取資料)
-
測試激勵要求:向第一個地址寫入資料0x01,第十個地址寫入資料0x0a,第二十個地址寫入資料0x20,第一百個地址寫入資料0x64。然後依次讀取這四個地址的資料。
-
使用define語句給以上地址起個名字,將命名單獨放到define.v檔案,激勵中地址用別名替代。
-
首先了解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
模擬截圖
設計一個流水燈控制器
要求:
- 有多種變化方式
- 設定復位鍵
- 有按鍵控制流水燈的變化方式
- 具有按鍵消抖功能
- 下載到FGPA上觀察實際結果是否符合設計
- 報告提交模擬結果並說明設計的正確性
設計一個流水燈控制器
要求:
- 有多種變化方式
- 設定復位鍵
- 有按鍵控制流水燈的變化方式
- 具有按鍵消抖功能
- 下載到FGPA上觀察實際結果是否符合設計
- 報告提交模擬結果並說明設計的正確性
思路:分兩塊部分,一個是按鍵消抖,一個是單個按鍵控制流水燈變化。按鍵消抖:由於按鍵的抖動不會超過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