目录
DE2-115学习过程中verilog语法入门
以流水灯实验代码为例。
这个流水灯实现了以下功能:
流速控制(使用拨码开关控制),亮度控制(按键控制),8个LED依次点亮。
流速控制
点亮LED很简单,定义8个位分别对应8个LED,初始为【0000 0000】然后依次点亮
【0000 0001】,【0000 0010】·······设置好切换时间。
学习中,表述不完整,always语句,阻塞式非阻塞式,asign语句,逻辑符号等,穿插着提太乱了,所以具体语句语法还需另外学习。欢迎交流
一、概述
首先,Verilog是硬件描述语言,通过描述硬件,生成电路。
Verilog - hdl 语法相对宽松,但相对来说不适合大型项目
Verilog有设计语言(30%)和测试语言(70%)。
代码经综合器解释后转化成电路,这个综合器就是 quartus , vivado等开发工具。
二、代码结构
一份完整的代码框架如下:
`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
module 模块名
【端口信号声明】
input,output,inout
【参数声明】
reg [0:1] a,
wier [] b
```````````````````````````````````````````````````````【功能描述】````````````````````````````````````````````````````````
内部信号声明
assign语句
always语句
底层模块调用
`
`
endmodule
`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
在Verilog中,代码结构通常由以下几个主要部分组成:
1. 模块定义:
每个Verilog代码文件通常以模块(module)开始和结束。模块是Verilog代码的基本构建块,可以包含输入、输出和内部信号的定义。
这里将模块命名water_led
定义了4个输入,1个输出
module water_led (
input wire sys_clk,
input wire sys_rst_n,
input wire key1,
input [1:0] speed_control, // 速度控制输入
output reg [7:0] led_out
);
2. 参数定义:
使用parameter定义常量参数,通常用于配置模块的行为,比如设置波特率、时钟频率等。
这里是根据DE2-115内部50MHZ的时钟定义的
parameter CNT_1US_MAX = 6'd49; // 1 us
parameter CNT_1MS_MAX = 10'd999; // 1 ms
// 定义不同速度下的计数最大值
parameter CNT_MAX_SLOW = 25'd49_999_999; // 1s
parameter CNT_MAX_MEDIUM = 25'd24_999_999; // 0.5s
parameter CNT_MAX_FAST = 25'd12_499_999; // 0.25s
3. 内部信号定义:
使用wire和reg定义内部信号。wire用于连接模块之间的信号,而reg用于存储值的信号。
wire key1_flag; //底层模块调用
reg cnt_flag;
reg [9:0] tim; //亮度控制
reg [24:0] cnt;
reg [5:0] cnt_1us;
reg [9:0] cnt_1ms;
reg [2:0] i; //0-7灯亮
reg [24:0] cnt_max; // 用于存储当前速度下的计数最大值
4.always语句
这是控制流水灯流速的部分代码
这样理解:总是,当信号变化时执行以下语句,speed_contrlo已经给出定义,位宽为2的输入,当输入为00时,cnt_max = CNT_MAX_SLOW,以此类推
而这个输入由开发板上两个拨码开关控制。
// 根据速度控制输入选择计数最大值
always @(*) begin
case (speed_control)
2'b00: cnt_max = CNT_MAX_SLOW;
2'b01: cnt_max = CNT_MAX_MEDIUM;
2'b10: cnt_max = CNT_MAX_FAST;
default: cnt_max = CNT_MAX_MEDIUM; // 默认中等速度
endcase
end
5. 实例化模块(底层模块调用):
在一个模块中实例化其他模块,可以使用其他模块的功能。
这里用按键消抖举例,顶层模块对底层模块调用也称端口映射,有两种方式,这里用最常见的命名法,格式如代码中,每个端口逗号隔开,末尾分号结束。 内容为:(.底层端口名(外接信号名),)
// 按键滤波模块
key_filiter key_filiter_inst (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.key_in(key1),
.key_flag(key1_flag)
);
6. 结束模块
每个模块都以endmodule结束。
仿真不过多提及,为了完整性,提一下仿真中会用到的语句:
在仿真中,可以使用initial块来初始化信号或变量的值。
这些部分共同组成了Verilog代码的结构,能够描述数字电路的行为和功能。大致对代码框架有个理解,更多的用法和语法还需继续学习。
三、功能实现
1、流速控制
首先要知道灯是怎么点亮的,暂时不关注亮度调节的部分,可以看到,当 i 变化时,亮灯的状态发生改变,因此只需要改变 i
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)//复位
led_out <= 8'b00000001;
else if (cnt_1ms <= tim)//亮度调节
led_out <= 8'b00000000;
else//亮灯
case (i)
3'd0: led_out <= 8'b00000001;
3'd1: led_out <= 8'b00000010;
3'd2: led_out <= 8'b00000100;
3'd3: led_out <= 8'b00001000;
3'd4: led_out <= 8'b00010000;
3'd5: led_out <= 8'b00100000;
3'd6: led_out <= 8'b01000000;
3'd7: led_out <= 8'b10000000;
default: led_out <= 8'b00000000;
endcase
end
那么请看代码内解释
首先,设计3种流速,那么对应至少需要一个2位寄存器或输入来控制【00】【01】【10】
这里使用输入控制
input [1:0] speed_control, // 速度控制输入
流速就是相邻两个LED间点亮的间隔,根据芯片内50MHZ的时钟定义
// 定义不同速度下的计数最大值
parameter CNT_MAX_SLOW = 25'd49_999_999; // 1s
parameter CNT_MAX_MEDIUM = 25'd24_999_999; // 0.5s
parameter CNT_MAX_FAST = 25'd12_499_999; // 0.25s
使用always语句,对应speed_control不同输入,计时发生状态改变
// 根据速度控制输入选择计数最大值
always @(*) begin
case (speed_control)
2'b00: cnt_max = CNT_MAX_SLOW;
2'b01: cnt_max = CNT_MAX_MEDIUM;
2'b10: cnt_max = CNT_MAX_FAST;
default: cnt_max = CNT_MAX_MEDIUM; // 默认中等速度
endcase
end
在接下来的代码中怎么实现呢,以 cnt_max = CNT_MAX_MEDIUM 为例
下面这段代码将根据不同的cnt_max计时,因此,cnt将计时到24_999_999为止。我们知道,当它记满时,只需一段 i+1的代码,就可以使LED依次点亮,接下来用两段代码实现
// 基于速度的计数逻辑
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt <= 25'd0;
else if (cnt == cnt_max)
cnt <= 25'd0;
else
cnt <= cnt + 25'd1;
end
计数标志位以及 i 自加1
注意观察代码,判断为当cnt == cnt_max - 1为真,cnt_flag赋值1
即cnt=24_999_998时执行cnt_flag赋值1
接着计满,i+1,此时来到24_999_999
然后亮灯(这部分属于个人理解,仅供参考)
// 计数满标志位
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_flag <= 1'b0;
else if (cnt == cnt_max - 1)
cnt_flag <= 1'b1;
else
cnt_flag <= 1'b0;
end
// 每次计数满,i自加1,改变亮灯位置
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
i <= 3'd0;
else if (cnt_flag && (i == 3'd7))
i <= 3'd0;
else if (cnt_flag)
i <= i + 3'd1;
end
通过这些代码,将实现LED流速控制
2、亮度调节
所谓亮度调节,就是点亮时间的调节,类似PWM原理,大致可以理解为相同时间内能量的变化。1s输出1W,1s输出0.5W,亮度不同。有兴趣可以去探究。
定位到亮度调节代码
用tim的值控制点亮时间,tim最大值为d999,cnt_1ms的最大值也为d999,按下一次tim+300现象会比较明显。
由条件判断可知,当cnt_1ms <= tim为真,led_out <= 8'b00000000,即LED熄灭
利用视觉暂留效果,做到LED亮度调节。
// 亮度变量调整
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
tim <= 10'd0;
else if (tim >= 10'd999)
tim <= 10'd0;
else if (key1_flag)
tim <= tim + 10'd300;
// 输出控制
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
led_out <= 8'b00000001;
else if (cnt_1ms <= tim)
led_out <= 8'b00000000;
else
case (i)
3'd0: led_out <= 8'b00000001;
3'd1: led_out <= 8'b00000010;
3'd2: led_out <= 8'b00000100;
3'd3: led_out <= 8'b00001000;
3'd4: led_out <= 8'b00010000;
3'd5: led_out <= 8'b00100000;
3'd6: led_out <= 8'b01000000;
3'd7: led_out <= 8'b10000000;
default: led_out <= 8'b00000000;
endcase
end
完整代码
module water_led (
input wire sys_clk,
input wire sys_rst_n,
input wire key1,
input [1:0] speed_control, // 速度控制输入
output reg [7:0] led_out
);
parameter CNT_1US_MAX = 6'd49; // 1 us
parameter CNT_1MS_MAX = 10'd999; // 1 ms
// 定义不同速度下的计数最大值
parameter CNT_MAX_SLOW = 25'd49_999_999; // 1s
parameter CNT_MAX_MEDIUM = 25'd24_999_999; // 0.5s
parameter CNT_MAX_FAST = 25'd12_499_999; // 0.25s
wire key1_flag;
reg cnt_flag;
reg [9:0] tim;
reg [24:0] cnt;
reg [5:0] cnt_1us;
reg [9:0] cnt_1ms;
reg [2:0] i;
reg [24:0] cnt_max; // 用于存储当前速度下的计数最大值
// 根据速度控制输入选择计数最大值
always @(*) begin
case (speed_control)
2'b00: cnt_max = CNT_MAX_SLOW;
2'b01: cnt_max = CNT_MAX_MEDIUM;
2'b10: cnt_max = CNT_MAX_FAST;
default: cnt_max = CNT_MAX_MEDIUM; // 默认中等速度
endcase
end
// 基于速度的计数逻辑
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt <= 25'd0;
else if (cnt == cnt_max)
cnt <= 25'd0;
else
cnt <= cnt + 25'd1;
end
// 计数满标志位
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_flag <= 1'b0;
else if (cnt == cnt_max - 1)
cnt_flag <= 1'b1;
else
cnt_flag <= 1'b0;
end
// 亮度变量调整
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
tim <= 10'd0;
else if (tim >= 10'd999)
tim <= 10'd0;
else if (key1_flag)
tim <= tim + 10'd300;
end
// cnt_1us
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_1us <= 6'd0;
else if (cnt_1us == CNT_1US_MAX)
cnt_1us <= 6'd0;
else
cnt_1us <= cnt_1us + 6'd1;
end
// cnt_1ms
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_1ms <= 10'd0;
else if ((cnt_1ms == CNT_1MS_MAX) && (cnt_1us == CNT_1US_MAX))
cnt_1ms <= 10'd0;
else if (cnt_1us == CNT_1US_MAX)
cnt_1ms <= cnt_1ms + 10'd1;
end
// 每次计数满,i自加1,改变亮灯位置
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
i <= 3'd0;
else if (cnt_flag && (i == 3'd7))
i <= 3'd0;
else if (cnt_flag)
i <= i + 3'd1;
end
// 输出控制
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
led_out <= 8'b00000001;
else if (cnt_1ms <= tim)
led_out <= 8'b00000000;
else
case (i)
3'd0: led_out <= 8'b00000001;
3'd1: led_out <= 8'b00000010;
3'd2: led_out <= 8'b00000100;
3'd3: led_out <= 8'b00001000;
3'd4: led_out <= 8'b00010000;
3'd5: led_out <= 8'b00100000;
3'd6: led_out <= 8'b01000000;
3'd7: led_out <= 8'b10000000;
default: led_out <= 8'b00000000;
endcase
end
// 按键滤波模块
key_filiter key_filiter_inst (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.key_in(key1),
.key_flag(key1_flag)
);
endmodule
按键消抖
//按键滤波器,按下按钮后即低电平输入,经过该模块输出一个系统时钟周期的高脉冲
module key_filiter
(
input sys_clk ,
input sys_rst_n ,
input key_in ,
output reg key_flag
);
reg [19:0] cnt_20ms;
parameter CNT_MAX=20'd999999;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
cnt_20ms<=20'd0;
else if(key_in)
cnt_20ms<=20'd0;
else if(cnt_20ms==CNT_MAX) //最大值保持
cnt_20ms<=CNT_MAX;
else
cnt_20ms<=cnt_20ms+1'd1;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
key_flag<=1'b0;
else if(cnt_20ms==CNT_MAX-20'd1)
key_flag<=1'b1;
else
key_flag<=1'b0;
endmodule