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