一.环境
- EDA:电子设计自动化(Electronics Design Automation,EDA)是计算机为工具,设计者在EDA软件平台上,融合应用电子技术、计算机技术、信息处理及智能化技术的最新成果,进行电子产品的自动设计。EDA可以提高电路设计的效率和可操作性,相当于以前我们使用纸和笔来写文章,现在我们使用word来写文章,创作的速度加快了,还节约了墨水。要知道,以前的电路设计是要靠工人用纸和笔来完成,工程量大且效率低。本次实习使用的EDA位Inter的quartus。
- 环境安装方面,仍然使用当初实训时使用的quartus,使用modelsim仿真,以及vscode编辑器(vscode与modelsim联合检错)。其间涉及quartus安装(modelsim在此处自动安装);quartus的破解;芯片使用cyclone IV(注意需要放在同一个文件夹下)新建project时选择256引脚和8核心速度等级;vscode中添加verilog插件下载。Modelsim联合检错需要配置好环境变量然后在vscode设置中配置。
- 硬件上选择EP4CE6F17C8开发板,注意连接电源和jtag烧录器的线。以及保证驱动安装成功以便识别usb-blaster。
- 我在配置上出现了种种问题:modelsim无法运行仿真,报错搜索不到解决办法,经过询问老师,重装更换安装路径即可解决;包括我的开发板出现了上文提及的无法识别usb-blaster的情况,经检查发现因为jtag烧录器坏了,更换烧录器即可正常烧录。
二.3-8译码器
-
3-8译码器,就是把3种输入状态翻译成8种输出状态,译码器是将输入的具有特定含义的二进制代码翻译成输出信号的不同组合,实现电路控制功能的逻辑电路。译码器在数字系统中应用广泛,可用于代码的转换、终端数字的显示、数据的分配等等。
-
3-8 译码器有 3 个输入和 8 个输出,真值表如下
-
设计思路如下:
确定输入和输出:3-8译码器有3个输入线和8个输出线。输入线用来接收3位二进制数作为输入信号,输出线用来输出对应的译码结果。确定真值表:根据3个输入线的所有可能组合,生成一个真值表。真值表的表头是输入线的状态,每一行对应着输入线状态对应的输出线的状态。
确定逻辑函数:根据真值表,可以确定每个输出线所需的逻辑函数。对于每个输出线,可以通过逻辑门来实现对应的逻辑函数。常见的逻辑门包括与门、或门和非门。
确定逻辑门的连接方式:根据逻辑函数和逻辑门的特性,选择适当的逻辑门连接方式来实现每个输出线的逻辑函数。
实现电路图:将逻辑门和连接方式绘制在一个电路图中,确保各个逻辑门正确连接,符合所需的逻辑函数。
验证电路功能:通过输入不同的二进制数,观察输出线是否正确地译码出对应的信号。可以使用逻辑模拟软件或者电路实验来验证电路功能。
-
原理图如下
三.LED流水灯
-
LED灯就是发光二极管,二极管功能很多,在本实验中我们暂且将其看作是一个开关。二极管在正向电压作用下电阻很小,处于导通状态,相当于一只接通的开关;在反向电压作用下,电阻很大,处于截止状态,如同一只断开的开关。发光二极管在导通的时候发光,在没有导通的时候不发光。四个led灯共阴极接地,所以需要高电平导通led灯。
-
原理图如下:
-
代码如下:
module led (
input wire clk, //时钟信号,50MHz
input wire rst_n, //复位信号,下降沿有效
output wire [3:0] led_on //位宽3的输出信号
);
parameter MAX1S = 26'd2500_0000 ; //50MHz时钟设置
reg [25:0] cnt1s ; //计数寄存器
reg [25:0] led_r ; //led信号寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt1s <= 26'd0; //复位,重新计数
end
else if (cnt1s == MAX1S - 1'd1) begin
cnt1s <= 26'd0; //记到最大数4999_9999后复位
end
else begin
cnt1s <= cnt1s + 1'd1; //其他情况+1
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
led_r <= 4'd0001; //复位时4个led0001
end
else if (cnt1s == MAX1S - 1'd1) begin
led_r <= {~led_r[0],led_r[3:1]};
end
else begin
led_r <= led_r; //其他情况不变
end
end
assign led_on = led_r; //顺序依次是led_03,led_02,led_01,led_00
endmodule
四.状态机控制流水灯
-
有限状态机(Finite State Machine, FSM),又称有限状态自动机,简称状态机,是指在有限个状态之间按照一定规律转换的时序电路。
有限状态机应用很广,包括红绿灯,电子门锁等。这次实现状态机控制流水灯。 -
原理图如下:
-
主要代码如下(三段式状态机)
/***三段式状态机第一段,时序逻辑***/
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cstate <= IDLE;/***初始当前状态为空闲***/
end
else begin
cstate <= nstate;/***次态赋值给现态***/
end
end
/***三段式状态机第二段,组合逻辑***/
always @(*) begin
case (cstate)
IDLE :nstate = RED ;
RED :
begin
if (cnt == TIME_60s - 1'd1) begin
nstate = GREEN ;
end
else begin
nstate = RED ;
end
end
GREEN :
begin
if (cnt == TIME_80s - 1'd1) begin
nstate = YELLOW ;
end
else begin
nstate = GREEN ;
end
end
YELLOW :
begin
if (cnt == MAX -1'd1) begin
nstate = RED ;
end
else begin
nstate = YELLOW ;
end
end
default :
nstate = IDLE;
endcase
end
/***有限状态机第三段,时序逻辑***/
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
led_r <= NOTION;
end
else begin
case (cstate)
IDLE :
led_r <= NOTION ;
RED :
led_r <= STOP ;
GREEN :
led_r <= RUSH ;
YELLOW :
led_r <= READY ;
default :
led_r <= NOTION ;
endcase
end
end
五.按键控制流水灯
- 按键控制流水灯首先根据上文的状态机实现流水灯改进,四个按键分别实现四个不同的亮灯情况。后续实现每个按键控制对应的灯亮灭。
- 设计思路如下:
硬件准备:选择合适的FPGA开发板,其中需要包含适当数量的LED和按键。确保开发板与电脑的连接正常。
确定设计规格:考虑流水灯的数量以及流水灯的排列方式。确定使用的按键数量和按键功能。例如,可以使用一个按键来控制流水灯的启动和停止。
Verilog编程:使用verilog硬件描述语言来编写FPGA的逻辑设计。可以将流水灯的逻辑设计和按键的读取与控制代码整合在一起。
输入处理:编程FPGA以读取按键输入。当按键被按下时,FPGA应该能够检测到并触发相应的动作。
流水灯控制:使用计数器或状态机等技术实现流水灯的控制。控制流水灯的方式可以根据需求进行选择,例如可以从左到右或者从右到左移动。
输出控制:根据流水灯的状态,控制FPGA上的LED以显示对应的灯光状态。例如,当流水灯从左到右移动时,FPGA上的LED依次亮起。
调试和测试:在完成设计之后,进行必要的调试和测试。确保按键能够正确地控制流水灯的启动和停止,以及流水灯的控制逻辑正常工作。
-
原理图如下:
-
主要代码
module key_led (
input wire clk ,
input wire rst_n ,
input wire [3:0] key ,
output reg [3:0] led
);
parameter MAX_NUM = 24'd9_999_999;
reg [1:0] state;//保存当前状态
reg [23:0] cnt;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt <= 24'd0;
end
else if(cnt == MAX_NUM)begin
cnt <= 24'd0;
end
else begin
cnt <= cnt + 1'd1;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
state <= 2'd0;
end
else if(cnt == MAX_NUM)begin
state <= state + 2'd1;
end
else begin
state <= state;
end
end
//结合按键判断led的赋值
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
led <= 4'b0000;
end
else if(key[0] == 0)begin
case (state)
2'd0 : led <= 4'b0001;
2'd1 : led <= 4'b0010;
2'd2 : led <= 4'b0100;
2'd3 : led <= 4'b1000;
default : led <= 4'b0000;
endcase
end
else if(key[1] == 0)begin
case (state)
2'd0 : led <= 4'b1000;
2'd1 : led <= 4'b0100;
2'd2 : led <= 4'b0010;
2'd3 : led <= 4'b0001;
default : led <= 4'b0000;
endcase
end
else if(key[2] == 0)begin
case (state)
2'd0 : led <= 4'b1111;
2'd1 : led <= 4'b0000;
2'd2 : led <= 4'b1111;
2'd3 : led <= 4'b0000;
default : led <= 4'b0000;
endcase
end
else if(key[3] == 0)begin
led <= 4'b1111;
end
end
endmodule
- 附修改为每个按键控制对应灯的主要代码:
module key_led (
input wire clk,
input wire [3:0] key,
input wire rst_n,
output wire [3:0] led_out
);
localparam MAX = 25'd2500_0000;
localparam MAX_20ms = 20'd100_0000;
reg [19:0] cnt_20ms;
reg [24:0] cnt;
reg [3:0] led_r;
reg start;
reg [3:0] key_r0;
reg [3:0] key_r1;
wire nedge;//下降沿信号
reg [3:0] flag;
//20
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_20ms <= MAX_20ms;
end
else if (start) begin
if (cnt_20ms == 1'd1) 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
//flag
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
flag <= 4'd0000;
end
else if (cnt_20ms == 1'd1) begin
flag <= ~key_r1;
end
else begin
flag <= 4'd0000;
end
end
//start
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
start <= 1'd0;
end
else if (nedge) begin
start <= 1'd1;
end
else if (cnt_20ms == 1'd1) begin
start <= 1'd0;
end
else begin
start <= start;
end
end
//led
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
led_r <= 4'b0001;
end
else if (flag[0]) begin
led_r <= {led_r[3:1],~led_r[0]};
end
else if (flag[1]) begin
led_r <= {led_r[3:2],~led_r[1],led_r[0]};
end
else if (flag[2]) begin
led_r <= {led_r[3],~led_r[2],led_r[1:0]};
end
else if (flag[3]) begin
led_r <= {~led_r[3],led_r[2:0]};
end
else begin
led_r <= led_r;
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]) ;
assign led_out = led_r;
endmodule
六.PWM实现灯的渐亮渐灭
- 本次实验先是实现了单个led灯的渐灭,再实现单个灯的渐亮,再实现单个led灯持续完成渐亮渐灭,再实现全部led灯同时渐亮渐灭,最后实现流水灯渐亮渐灭。
- 设计思路如下:
硬件准备:选择合适的FPGA开发板,确保具备足够的PWM输出通道和适量的LED用于显示呼吸灯效果。确保开发板与电脑的连接正常。
确定设计规格:确定呼吸灯的亮度范围和呼吸速度。这将有助于确定PWM周期的长度和呼吸灯变化的频率。
Verilog编程:使用verilog硬件描述语言编写FPGA的逻辑设计。需要实现一个PWM模块,该模块能够生成可调节占空比的PWM信号。
输出控制:根据PWM模块生成的PWM信号,将其传递给FPGA上的LED,以控制其亮度。较高的占空比对应于较亮的灯光,较低的占空比则对应于较暗的灯光。
呼吸灯效果实现:通过改变PWM信号的占空比来实现呼吸灯效果。可以使用计数器或状态机等技术来调整PWM信号的占空比,从而实现呼吸灯的渐变效果。
输入处理:可选的,可以使用外部设备(例如按键、旋钮等)来调节呼吸灯的亮度和速度等参数。通过读取外部设备的输入并相应地修改PWM模块中的占空比值,实现动态控制和调整。
调试和测试:在完成设计之后,进行必要的调试和测试。确保PWM模块能够生成正确的PWM信号,LED能够根据PWM信号的变化呈现呼吸灯效果。
-
原理图如下:
-
主要代码如下
module pwm_led(
input clk ,
input rst_n ,
output reg [3:0] led
);
parameter TIME_US = 6'd49;
parameter TIME_MS = 10'd999;
parameter TIME_S = 10'd999;
reg [5:0] cnt_us;
reg [9:0] cnt_ms;
reg [9:0] cnt_s;
reg flag;
//us计数器
wire add_cnt_us;//us计数器开始计数的标志
wire end_cnt_us;//us计数器结束计数的标志
always @(posedge clk or negedge rst_n) begin
if (!rst_n) 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 + 1'd1;
end
end
else begin
cnt_us <= cnt_us;
end
end
assign add_cnt_us = 1'b1;
assign end_cnt_us = add_cnt_us && cnt_us == TIME_US;
//ms计数器
wire add_cnt_ms;//ms计数器开始计数的标志
wire end_cnt_ms;//ms计数器结束计数的标志
always @(posedge clk or negedge rst_n) begin
if (!rst_n) 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 + 1'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;
//s计数器
wire add_cnt_s;//s计数器开始计数的标志
wire end_cnt_s;//s计数器结束计数的标志
always @(posedge clk or negedge rst_n) begin
if (!rst_n) 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 + 1'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;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
flag <= 1'b0;
end
else if (end_cnt_s) begin
flag <= ~flag;
end
else begin
flag <= flag;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
led[1] <= 1'b0;
led[2] <= 1'b0;
led[3] <= 1'b0;
led[0] <= 1'b0;
end
else if(!flag) begin
led[0] <= (cnt_s > cnt_ms)?1'b1:1'b0;
led[1] <= (cnt_s > cnt_ms)?1'b1:1'b0;
led[2] <= (cnt_s > cnt_ms)?1'b1:1'b0;
led[3] <= (cnt_s > cnt_ms)?1'b1:1'b0;
end
else if (flag) begin
led[0] <= (cnt_s > cnt_ms)?1'b0:1'b1;
led[1] <= (cnt_s > cnt_ms)?1'b0:1'b1;
led[2] <= (cnt_s > cnt_ms)?1'b0:1'b1;
led[3] <= (cnt_s > cnt_ms)?1'b0:1'b1;
end
else begin
led <= led;
end
end
endmodule
七.静态数码管
-
静态数码管最重要的是查看手册,了解数码管亮灭原理(1为亮,0为灭还是1为灭,0为亮)也就是我们常说的共阴极还是共阳极的问题以及6位8段数码管各个引脚分别代表哪一段数码管的控制。这一次的文件需要用到顶层文件,将主体代码与时钟部分分割开来,不糅合在一起也会使项目更具备逻辑性,不仅在自己回顾的时候方便,也更容易让别人理解自己的代码,方便解释。
-
原理图如下
-
主要代码如下
module seg_led_static(
input clk,
input rst_n,
input flag,//记满0.5s后,输入的一个高电平
output reg [5:0] sel,//六位位选信号
output reg [7:0] seg //八位段选信号
);
reg [3:0] num;//保存当前数码管要显示的数字
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
sel <= 6'b111_111;
end
else begin
sel <= 6'b000_000;//打开全部6位数码管
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
num <= 4'h0;
end
else if (flag) begin
num <= num + 1'h1;
end
else begin
num <= num;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
seg <= 8'b0;
end
else begin
case (num)
4'h0: seg <= 8'b1100_0000;//匹配到后参考共阳极真值表
4'h1: seg <= 8'b1111_1001;
4'h2: seg <= 8'b1010_0100;
4'h3: seg <= 8'b1011_0000;
4'h4: seg <= 8'b1001_1001;
4'h5: seg <= 8'b1001_0010;
4'h6: seg <= 8'b1000_0010;
4'h7: seg <= 8'b1111_1000;
4'h8: seg <= 8'b1000_0000;
4'h9: seg <= 8'b1001_0000;
4'ha: seg <= 8'b1000_1000;
4'hb: seg <= 8'b1000_0011;
4'hc: seg <= 8'b1100_0110;
4'hd: seg <= 8'b1010_0001;
4'he: seg <= 8'b1000_0110;
4'hf: seg <= 8'b1000_1110;
default : seg <= 8'b1100_0000;
endcase
end
end
endmodule
4'h5: seg <= 8'b1001_0010;
4'h6: seg <= 8'b1000_0010;
4'h7: seg <= 8'b1111_1000;
4'h8: seg <= 8'b1000_0000;
4'h9: seg <= 8'b1001_0000;
4'ha: seg <= 8'b1000_1000;
4'hb: seg <= 8'b1000_0011;
4'hc: seg <= 8'b1100_0110;
4'hd: seg <= 8'b1010_0001;
4'he: seg <= 8'b1000_0110;
4'hf: seg <= 8'b1000_1110;
default : seg <= 8'b1100_0000;
endcase
end
end
endmodule