自学内容网 自学内容网

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

FPGA: 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
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块(可以理解为多线程)之间沟通的变量。

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吗?

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

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块

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也就是硬件电路的特点,不能强行把所有事情塞到一个线程里面去处理。

原文地址:https://blog.csdn.net/Andius/article/details/144375484

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!