1、三段译码器
1.1、实现目标
顾名思义,三段译码器就是有三段输入,然后有8位的输出。
比如:输入为0,那么输出就应该是8’b1111_1110;同理,如果输入为1,那么输出应该为8’b1111_1101;以此类推,输入7,输出应该为8’b0111_1111。
因此,我们在这里可以得到输入和输出的关系:
输入 | 输出 |
---|---|
0 | 8’b1111_1110 |
1 | 8’b1111_1101 |
2 | 8’b1111_1011 |
3 | 8’b1111_0111 |
4 | 8’b1110_1111 |
5 | 8’b1011_1111 |
6 | 8’b0111_1111 |
7 | 8’b0000_0000 |
1.2、创建项目:
创建项目之前,我们有必要先了解一下项目的结构:
打开Quartus之后会进入到这个界面中,点击“New Project Wizard”——创建向导,进行项目的创建
进入创建向导之后点击next
之后是选择项目的保存路径和项目的名称,项目路径和名称当中最好不要包含中文
这里的第一个选项的意思是创建一个空项目,第二个是使用项目模板。因为我们并没有项目模板,所以我们就选择第一个,创建一个空项目
这里是添加文件,目前我们还没有开始编写文件,不需要添加,直接next
我们在这里进行开发板的选择,这里使用的是EP4CE6F17C8的开发板,
上面的操作进行完成之后进入到工具的选择,我们将仿真工具选择为ModelSim-Altera
上面的操作完成之后就进入到总览界面,到这里就代表项目创建完成了,我们点击Finish就可以了
1.2、模块代码:
1.2.1、创建verilog文件
点击菜单栏中的File,然后点击选择框中的New
然后选择
我们就创建好一个Verilog文件了。
1.2.2、编写代码
module decoder(
input wire [2:0] data_in,
output reg [7:0] data_out
);
always @(*)
begin
case (data_in)
3'b000: data_out <= 8'b1111_1110;
3'b001: data_out <= 8'b1111_1101;
3'b010: data_out <= 8'b1111_1011;
3'b011: data_out <= 8'b1111_0111;
3'b100: data_out <= 8'b1110_1111;
3'b101: data_out <= 8'b1101_1111;
3'b110: data_out <= 8'b1011_1111;
3'b111: data_out <= 8'b0111_1111;
default: data_out <= 8'b0000_0000;
endcase
end
endmodule
这样,我们就实现了一个简单的三段译码器。
1.2.3、保存文件
我们创建好了一个文件,但是我们还没有将这个文件保存在电脑中。
将代码编写好之后,我们只需要按下快捷键ctrl+s,会弹出来界面让我们选择保存的路径。我们将代码保存在项目的src文件夹下。同时,文件的名字和模块的名字必须相同。仿真文件也一样。
1.3、仿真
1.3.1、编写仿真文件
同时,为了能够更加直观地看到输出,我们可以进行仿真,在仿真查看结果。
`timescale 1ns/1ns // 延时时间的单位和精度
module decoder_tb();
reg [2:0] data_in ;
wire [7:0] data_out;
initial
begin
#100; data_in = 3'b000; // #100表示延时100ns
#100; data_in = 3'b001;
#100; data_in = 3'b010;
#100; data_in = 3'b011;
#100; data_in = 3'b100;
#100; data_in = 3'b101;
#100; data_in = 3'b110;
#100; data_in = 3'b111;
end
decoder decoder_inst (
.data_in (data_in ), // 输入,wire一般用来保存已经定义过的变量
.data_out(data_out) // 输出,reg用来初始化某个变量
);
endmodule
1.3.2、保存文件
将文件编写完成之后保存到项目下的sim文件夹下
1.4、添加模块文件和仿真文件到项目中
在上面菜单栏中选择Assignments->Setting->Files,进入到添加文件的界面。然后点击这个有三个点的按钮进行文件的选择。
然后找到刚才写的两个文件的路径,选择文件,并打开,
将两个文件选择好之后点击OK就完成添加了。
1.5、添加仿真
留在Settings界面,找到Simulation,点击
将仿真参数按照图中所示进行配置,然后是添加仿真文件到仿真项目中。点击Test Benches…
创建仿真项目
这里创建完成之后就一直OK,回到编写代码的界面中,然后点击全编译按钮(记得将模块文件设置为顶层文件)
编译完成之后点击菜单栏中的Tools->Run Simulation Tool-> RTL Simulation,会自动打开ModelSim,
,然后我们重启波形,并将运行时间设置为1微秒,然后再运行波形。波形大概是这样的。
2、led流水灯
2.1、创建项目
2.2、编写模块文件
/*
模块描述:流水灯
*/
module led_2(
input wire clk ,
input wire rst_n,
output reg [3:0] led
);
// parameter MAX_1S = 26'd49_999_999;
parameter MAX_0_2S = 26'd9_999_999;
// parameter MAX_0_2S = 26'd99;
// 计时1s
reg [25:0] cnt_1s;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0) // 初始化计时器
begin
cnt_1s <= 26'd0;
end
else if (cnt_1s == MAX_0_2S) // 当到达MAX_1S个周期后就表示计时到1s了
begin
cnt_1s <= 26'd0;
end
else
begin
cnt_1s <= cnt_1s + 26'd1; // 其他时候都+1
end
end
// led控制
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
led <= 4'b0001;
end
else if (cnt_1s == MAX_0_2S) // 计数器到0.2s时,改变状态
begin
led <= {led[2:0], led[3]};
end
else // 其他时候保持不变
begin
led <= led;
end
end
endmodule
2.3、保存文件
2.4、烧录文件到开发板中
点击这个按钮
烧录完成
2.5、结果
VID_20230715_163818
3、呼吸灯
3.1、创建项目
3.2、编写模块文件
module breath_led (
input wire clk ,
input wire rst_n ,
output reg [3:0] led
);
parameter TIME_US = 6'd49;
parameter TIME_MS = 10'd999;
parameter TIME_S = 10'd999;
reg [9:0] cnt_s; // 1000
reg [9:0] cnt_ms;
reg [5:0] cnt_us; // 50
wire add_cnt_us;
wire end_cnt_us;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
cnt_us <= 6'd0;
end
else if (add_cnt_us)
begin
if (end_cnt_us)
begin
cnt_us <= 6'd0;
end
else
begin
cnt_us <= cnt_us + 6'd1;
end
end
else
begin
cnt_us <= 6'd0;
end
end
assign add_cnt_us = 1;
assign end_cnt_us = add_cnt_us && cnt_us == TIME_US;
wire add_cnt_ms;
wire end_cnt_ms;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
cnt_ms <= 10'd0;
end
else if (add_cnt_ms)
begin
if (end_cnt_ms)
begin
cnt_ms <= 10'd0;
end
else if (cnt_us == TIME_US)
begin
cnt_ms <= cnt_ms + 10'd1;
end
end
else
begin
cnt_ms <= cnt_ms;
end
end
assign add_cnt_ms = end_cnt_us;
assign end_cnt_ms = add_cnt_ms && (cnt_ms == TIME_MS);
wire add_cnt_s;
wire end_cnt_s;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
cnt_s <= 10'd0;
end
else if (add_cnt_s)
begin
if (end_cnt_s)
begin
cnt_s <= 10'd0;
end
else
begin
cnt_s <= cnt_s + 10'd1;
end
end
else
begin
cnt_s <= cnt_s;
end
end
assign add_cnt_s = end_cnt_ms;
assign end_cnt_s = add_cnt_s && (cnt_s == TIME_S);
// 控制led灯亮灭
reg flag;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
flag <= 1'b0;
end
else if (end_cnt_s)
begin
flag <= ~flag;
end
else
begin
flag <= flag;
end
end
// 根据计数器的数值来控制led灯
always @(*)
begin
if (flag)
begin
led[0] = cnt_s > cnt_ms ? 1 : 0;
led[1] = cnt_s > cnt_ms ? 1 : 0;
led[2] = cnt_s > cnt_ms ? 1 : 0;
led[3] = cnt_s > cnt_ms ? 1 : 0;
end
else
begin
led[0] = cnt_s > cnt_ms ? 0 : 1;
led[1] = cnt_s > cnt_ms ? 0 : 1;
led[2] = cnt_s > cnt_ms ? 0 : 1;
led[3] = cnt_s > cnt_ms ? 0 : 1;
end
end
endmodule
3.3、保存文件
3.4、烧录
3.5、结果
VID_20230715_174420
4、呼吸流水灯
4.0、目标
第一个灯开始闪烁之后第二个灯延时250ms亮,然后第三个灯延时第二个灯250ms亮,同理,第四个灯延时250ms第三个灯亮。
4.1、创建项目
4.2、编写模块文件
延时函数
module delay_ms (
input wire clk ,
input wire rst_n ,
input wire [9:0] delay , // 加入延时控制,单位ms
output reg flag
);
parameter TIME_US = 6'd49;
parameter TIME_MS = 10'd999;
parameter TIME_S = 10'd999;
reg [5:0] cnt_delay_us; // 延时计时器
reg [9:0] cnt_delay_ms;
reg [9:0] cnt_delay_s;
/*
延时
*/
wire add_cnt_delay_us;
wire end_cnt_delay_us;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
cnt_delay_us <= 6'd0;
end
else if (add_cnt_delay_us)
begin
if (end_cnt_delay_us)
begin
cnt_delay_us <= 6'd0;
end
else
begin
cnt_delay_us <= cnt_delay_us + 6'd1;
end
end
else
begin
cnt_delay_us <= cnt_delay_us;
end
end
assign add_cnt_delay_us = !flag; // 延时计时中
assign end_cnt_delay_us = add_cnt_delay_us && cnt_delay_us == TIME_US; // 延时终止
wire add_cnt_delay_ms;
wire end_cnt_delay_ms;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
cnt_delay_ms <= 10'd0;
end
else if (add_cnt_delay_ms)
begin
if (end_cnt_delay_ms)
begin
cnt_delay_ms <= 10'd0;
end
else
begin
cnt_delay_ms <= cnt_delay_ms + 10'd1;
end
end
else
begin
cnt_delay_ms <= cnt_delay_ms;
end
end
assign add_cnt_delay_ms = end_cnt_delay_us;
assign end_cnt_delay_ms = add_cnt_delay_ms && (cnt_delay_ms == TIME_MS);
wire add_cnt_delay_s;
wire end_cnt_delay_s;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
cnt_delay_s <= 10'd0;
end
else if (add_cnt_delay_s)
begin
if (end_cnt_delay_s)
begin
cnt_delay_s <= 10'd0;
end
else
begin
cnt_delay_s <= cnt_delay_s + 10'd1;
end
end
else
begin
cnt_delay_s <= cnt_delay_s;
end
end
assign add_cnt_delay_s = end_cnt_delay_ms;
assign end_cnt_delay_s = add_cnt_delay_s && (cnt_delay_s == delay);
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
flag <= 1'b0;
end
else if (end_cnt_delay_s)
begin
flag <= 1'b1;
end
else
begin
flag <= flag;
end
end
endmodule
控制每个led的延时
module breath_led_new (
input wire clk ,
input wire rst_n ,
input wire [9:0] delay , // 加入延时控制,单位ms
output reg led
);
/*
实现一个呼吸流水灯
*/
parameter TIME_US = 6'd49;
parameter TIME_MS = 10'd999;
parameter TIME_S = 10'd999;
reg [9:0] cnt_s; // 1000
reg [9:0] cnt_ms;
reg [5:0] cnt_us; // 50
wire flag_run; // 判断延时是否结束,如果结束,则正式开始计时
wire add_cnt_us;
wire end_cnt_us;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
cnt_us <= 6'd0;
end
else if (add_cnt_us)
begin
if (end_cnt_us)
begin
cnt_us <= 6'd0;
end
else
begin
cnt_us <= cnt_us + 6'd1;
end
end
else
begin
cnt_us <= 6'd0;
end
end
assign add_cnt_us = flag_run;
assign end_cnt_us = add_cnt_us && cnt_us == TIME_US;
wire add_cnt_ms;
wire end_cnt_ms;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
cnt_ms <= 10'd0;
end
else if (add_cnt_ms)
begin
if (end_cnt_ms)
begin
cnt_ms <= 10'd0;
end
else
begin
cnt_ms <= cnt_ms + 10'd1;
end
end
else
begin
cnt_ms <= cnt_ms;
end
end
assign add_cnt_ms = end_cnt_us;
assign end_cnt_ms = add_cnt_ms && (cnt_ms == TIME_MS);
wire add_cnt_s;
wire end_cnt_s;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
cnt_s <= 10'd0;
end
else if (add_cnt_s)
begin
if (end_cnt_s)
begin
cnt_s <= 10'd0;
end
else
begin
cnt_s <= cnt_s + 10'd1;
end
end
else
begin
cnt_s <= cnt_s;
end
end
assign add_cnt_s = end_cnt_ms;
assign end_cnt_s = add_cnt_s && (cnt_s == TIME_S);
// 控制led灯亮灭
reg flag;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
flag <= 1'b0;
end
else if (end_cnt_s)
begin
flag <= ~flag;
end
else
begin
flag <= flag;
end
end
always @(*)
begin
if (flag)
begin
led = (cnt_s) > cnt_ms ? 0 : 1;
end
else
begin
led = (cnt_s) > cnt_ms ? 1 : 0;
end
end
delay_ms delay_ms_inst (
.clk (clk ),
.rst_n (rst_n ),
.delay (delay ), // 加入延时控制,单位ms
.flag (flag_run )
);
endmodule
顶层文件
module breath_led_top (
input wire clk ,
input wire rst_n ,
output wire [3:0] led
);
wire [3:0] led_temp;
breath_led_new breath_led_new_inst0 (
.clk (clk ),
.rst_n (rst_n ),
.delay (10'd1 ), // 加入延时,控制
.led (led_temp[0] )
);
breath_led_new breath_led_new_inst1 (
.clk (clk ),
.rst_n (rst_n ),
.delay (10'd250), // 加入延时,控制
.led (led_temp[1] )
);
breath_led_new breath_led_new_inst2 (
.clk (clk ),
.rst_n (rst_n ),
.delay (10'd500), // 加入延时,控制
.led (led_temp[2] )
);
breath_led_new breath_led_new_inst3 (
.clk (clk ),
.rst_n (rst_n ),
.delay (10'd750), // 加入延时,控制
.led (led_temp[3] )
);
assign led = led_temp;
endmodule
4.3、保存文件
4.4、烧录
4.5、结果
VID_20230715_165402
5、按键消抖
5.1、为什么要进行按键消抖
按键抖动:按键抖动通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。当按下一次按键,可能在A点检测到一次低电平,在B点检测到一次高电平,在C点又检测到一次低电平。同时抖动是随机,不可测的。那么按下一次按键,抖动可能会误以为按下多次按键。
因此,为了消除按键抖动对程序的影响,我们需要对按键进行消抖处理。
5.2、思路
等信号平稳之后,再等待20ms进行采样。
/*
按键消抖模块
*/
module key_debounce (
input wire clk ,
input wire rst_n ,
input wire [3:0] key , // 输入的不稳定信号
output wire [3:0] key_out
);
localparam MAX20 = 20'd1_000_000;
reg [19:0] cnt_20ms;
reg start; //稳定信号开始
reg [3:0] key_r0;//按键抖动寄存器0
reg [3:0] key_r1;//按键抖动寄存器1
wire nedge;
reg [3:0] key_r;//开启流水
//20ms倒计时计数器设计
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_20ms <= 20'd0;
end
else if (nedge) begin
cnt_20ms <= MAX20;
end
else if (start) begin
if (cnt_20ms == 1'b1) begin
cnt_20ms <= 20'd0;
end
else begin
cnt_20ms <= cnt_20ms - 1'd1;
end
end
else begin
cnt_20ms <= cnt_20ms;
end
end
//下降沿检测
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_r0 <= 4'b1111;
key_r1 <= 4'b1111;
end
else begin
key_r0 <= key;//打一拍,同步时钟域
key_r1 <= key_r0;//打一拍,检测时钟下降沿
end
end
assign nedge = (~key_r0[0] && key_r1[0])|| (~key_r0[1] && key_r1[1])|| (~key_r0[2] && key_r1[2])||(~key_r0[3] && key_r1[3]);//检测到下降沿
//约束start信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
start <= 1'b0;
end
else if (nedge) begin
start <= 1'b1;
end
else if (cnt_20ms == 1'b1)
begin
start <= 1'b0;
end
else begin
start <= start;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
key_r <= 4'b0000;
end
else if (cnt_20ms == 1'b1) begin
key_r <= ~key_r0;
end
else begin
key_r <= key;
end
end
assign key_out = key_r; // 将处理好的key信号赋值给key_out
endmodule
6、总结
本次的实习大概就是学习了这些知识,但是这些知识对于未来的找工作肯定是不够的,学无止境!