[Verilog]第一个流水灯项目的一些疑问和思考

FPGA: verilog,有关第一个流水灯项目

示例程序写的还挺复杂的,但是静下心来分析一下,其实还挺好理解的,这里就来简单分析一下这个代码程序

原始代码

verilog 复制代码
module led_flash(
    input wire clk, // 系统时钟,传入时序信号
    input wire rst_n   ,// 系统重置信号,低电平有效
    output reg [1:0] led    // 输出 led 信号,2位寄存器输出
);

reg [27:0] cnt; // 0.25s 计数器,28位寄存器
wire add_cnd;   // 计数器递增条件信号
wire end_cnt;   // 计数器到达最大值信号
assign add_cnt = 1;                    // 计数器递增条件始终为真
assign end_cnt = add_cnt && cnt== 10_000_000 - 1;          // 当计数器达到 10_000_000 - 1 时,end_cnt 为真

// cnt
// cnt 计数器模块
always@(posedge clk or negedge rst_n) begin

    if(rst_n==1'b0) begin   // 当复位信号 rst_n 为低电平时
        cnt <= 'd0;         // 将计数器 cnt 清零
    end
    else if(add_cnt) begin  // 当计数器递增条件 add_cnt 为真时
    if(end_cnt)             // 如果计数器到达最大值 end_cnt 为真
        cnt <= 'd0;         // 计数器 cnt 清零
    else                    // 否则
        cnt <= cnt + 1'b1;  // 计数器 cnt 加 1
        end
 end
 

 
 always @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin            // 当复位信号 rst_n 为低电平时
        led <= 2'b10;// 将 led 输出设置为 2'b10
        end
    else if(end_cnt)begin       // 当计数器到达最大值 end_cnt 为真时
        led <= {led[0], led[1]};// 将 led 输出的两位反转
    end
    else begin                      // 否则
        led <= led; // 保持 led 输出不变
    end
end
endmodule
 
 
 

怎么理解

这段代码其实看上去比较抽象,这个add_cnt是什么,这个end_cnt又是什么?这个cnt为什么要定义为一个28位的寄存器?

首先我们要明确一点,就是这里的所有代码实际上并没有经过特意设计,只是随便写出来的,所以不用纠结太多细节上的问题,但是为什么这里的代码会这么写,还请我娓娓道来。

简单的来说,在这段代码中,硬件电路部分、always块1和always块2都是并行电路,换句话说所有的verilog代码中always块都应该是并行的,中间的寄存器就像我们在多线程对齐同步时所用的中间变量,好处是所有的always块在单个时间片内执行的时候,都会取用寄存器中的原有值(前提是你使用非阻塞赋值,当然了,在这种环境下跨always块的行为你也应该使用非阻塞赋值)。这样就不需要考虑竞争的问题了。

  1. 定义module
VERILOG 复制代码
module led_flash(
    input wire clk, // 系统时钟,传入时序信号
    input wire rst_n   ,// 系统重置信号,低电平有效
    output reg [1:0] led    // 输出 led 信号,2位寄存器输出
);

我们首先定义了一个模块,名为led_flash,输入参数有clk和rst_n,输出参数是led。也就是说从外部硬件电路上会从外输入一个clk信号和rst_n信号,然后会输出一个led寄存器内容。

  1. 定义计时器以及累加

在这里定义了一些用于做多个always块(可以理解为多线程)之间沟通的变量。

verilog 复制代码
reg [27:0] cnt; // 0.25s 计数器,28位寄存器
wire add_cnd;   // 计数器递增条件信号
wire end_cnt;   // 计数器到达最大值信号
assign add_cnt = 1;                    // 计数器递增条件始终为真
assign end_cnt = add_cnt && cnt== 10_000_000 - 1;          // 当计数器达到 10_000_000 - 1 时,end_cnt 为真

这里有几个问题:

1. 为什么要定义一个28位的寄存器?定义一个整数类型的数字在这里行不通吗?

这个数字应该是纯粹人为定义的,这里其实取到26位计时器也可以。我们在xdc中定义的时钟是20ns一个tick,而我们在代码中规定时间一个来回是10_000_000 - 1,

也就是20ns * 10000000 = 0.2s,也就是说,我们只需要保证这个寄存器cnt不会在取到10_000_000 - 1这个值的时候溢出就行。

我们知道2^24 = 16777216,也就是0.2s内cnt可以取到这个值。也就是说极端一点reg [27:0] cnt;甚至可以换成[23:0],当然了,

你也不差这点内存。

2. 为什么要专门定义一个add_cnt和end_cnt?难道不能放一个寄存器在那里,然后让这个寄存器一直自增,然后判断是否等于10_000_000 - 1吗?

当然是可以这么做的,但是这个教程里并没有这么实现,我这里也可以试着写一写

verilog 复制代码
module led_flash(
    input wire clk,        // 系统时钟,传入时序信号
    input wire rst_n,      // 系统重置信号,低电平有效
    output reg [1:0] led   // 输出 led 信号,2位寄存器输出
);

reg [27:0] cnt; // 0.25s 计数器,28位寄存器

// led 闪烁模块
always @(posedge clk or negedge rst_n) begin
    if (rst_n == 1'b0) begin      // 当复位信号 rst_n 为低电平时
        led <= 2'b10;          // 将 led 输出设置为 2'b10
        cnt <= 'd0;              // 将计数器 cnt 清零
    end else begin                // 否则
        if (cnt == 10_000_000 - 1) begin  // 当计数器达到最大值时
            led <= {led[0], led[1]};    // 将 led 输出的两位反转
            cnt <= 'd0;              // 计数器 cnt 清零
        end else begin                // 否则
            led <= led;             // 保持 led 输出不变
            cnt <= cnt + 1'b1;      // 计数器 cnt 加 1
        end
    end
end

endmodule

这一段的效果和我们先前给到的是一样的,同样是在流水线中进行的处理,但是需要注意的时序和tick概念对于硬件很重要

就像游戏中一样,所有的事件都并行地发生在一个tick内,我们不希望某个时间处理时间过长导致某个时间片其余事件等待。

所有的always块共享一个时间片这是verilog中重要的概念,也是模拟电路中重要的概念。

我们当然希望整个硬件系统中的东西全部都是并行的,这样效率才高。

  1. 两个always块
verilog 复制代码
always@(posedge clk or negedge rst_n) begin

    if(rst_n==1'b0) begin   // 当复位信号 rst_n 为低电平时
        cnt <= 'd0;         // 将计数器 cnt 清零
    end
    else if(add_cnt) begin  // 当计数器递增条件 add_cnt 为真时
    if(end_cnt)             // 如果计数器到达最大值 end_cnt 为真
        cnt <= 'd0;         // 计数器 cnt 清零
    else                    // 否则
        cnt <= cnt + 1'b1;  // 计数器 cnt 加 1
        end
 end
 

 
 always @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin            // 当复位信号 rst_n 为低电平时
        led <= 2'b10;// 将 led 输出设置为 2'b10
        end
    else if(end_cnt)begin       // 当计数器到达最大值 end_cnt 为真时
        led <= {led[0], led[1]};// 将 led 输出的两位反转
    end
    else begin                      // 否则
        led <= led; // 保持 led 输出不变
    end
end
endmodule

我们先前分析了,两个块相当于是独立的线程,在各自线程中做自己的事情。其中一个线程维护了一个cnt寄存器,另一个线程维护通过这个cnt寄存器的结果维护了一个led寄存器。

// 否则

led <= led; // 保持 led 输出不变

end

end

endmodule

我们先前分析了,两个块相当于是独立的线程,在各自线程中做自己的事情。其中一个线程维护了一个cnt寄存器,另一个线程维护通过这个cnt寄存器的结果维护了一个led寄存器。
并不是非要写这么复杂的,其实可以简单一点写,但是我们要尊重verilog也就是硬件电路的特点,不能强行把所有事情塞到一个线程里面去处理。
相关推荐
技术小白爱FPGA5 小时前
Xilinx IDDR和 ODDR原语使用和仿真
fpga开发
技术小白爱FPGA6 小时前
Xilinx LVDS 接口中的时钟对齐模块的RTL编写
fpga开发
Anin蓝天(北京太速科技-陈)6 小时前
204-基于Xilinx Virtex-6 XC6VLX240T 和TI DSP TMS320C6678的信号处理板
嵌入式硬件·fpga开发·信号处理
ThreeYear_s19 小时前
基于FPGA的智能电子密码指纹锁(开源全免)
fpga开发·开源
我爱C编程1 天前
【硬件测试】基于FPGA的4FSK调制解调通信系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR
fpga开发·信道模块·snr·4fsk调制解调
正在努力的小河1 天前
FPGA实战篇(按键控制LDE实验)
fpga开发
whik11941 天前
Microsemi Libero SoC免费许可证申请指南(Microchip官网2024最新方法)
fpga开发
∑狸猫不是猫2 天前
数字图像处理(17):RGB与HSL互转
图像处理·计算机视觉·fpga开发
@晓凡2 天前
移植NIOS10.1工程,NIOS10.1路径修改
fpga开发·eclipse·nios ii