为什么灯不闪烁?

为什么仿真结果是正确的,但是下载到板上却是错误的? 注意到verilog文件中有一个语句我们一直没有讲述它的具体含义。

`timescale 1ns / 1ps

这句话代表着仿真时间单位与精度,这里代表我们以1ns的时间单位仿真,仿真精度为1ps。而我们在激励文件中是这样写的

forever #(10) clk = ~clk;

这意味我们的时钟信号的半周期为10ns,频率也就是50MHz,这与我们开发板的时钟信号是一致的。这就意味着我们的LED每秒会闪烁25M次。我们希望这个灯能闪烁的慢一点,最好它周期是可控的,比如我希望这个灯的周期是1s。

如果你了解过单片机,你会发现在单片机中类似的功能是由计数器提供的。这个计数器的原理顾名思义就是累计时钟信号的次数,当到达一定次数的时候也就意味着过了多长时间了。

在FPGA中,我们也可以通过实现一个计数器来达到类似的目的。比如时钟信号是50MHz的,那我们要做三件事情

  1. 累计时钟信号经过的周期数
  2. 当累计25M次,清零
  3. 当周期数=25MHz,也就是经过了0.5s时,改变LED的状态

将代码修改为

`timescale 1ns / 1ps

module led(
    input clk,
    output logic led=0
    );

    logic [24:0] counter=0;
    //累积周期数,并清零
    always_ff@(posedge clk)begin
        if(counter==25000000)
            counter<=0;
        else
            counter<=counter+1;
    end
    
    //判断是否改变LED
    always_ff@(posedge clk)
        if(counter==25000000)
            led<=~led;

endmodule

接着我们进行仿真。将counter信号添加到波形中,进行仿真,结果如下。我们注意到在0.5s的时候计数器到了25M次,于是就清零了。同时led的状态也被改变了。而在1s的时候,counter的又记到了25M次,于是又清零了。而同时led的状态也再次被改变。

我们下载到开发板,果然一切正常了。

优化

在编写完这样一个简单的程序,一个问题是,是否有优化的空间?和编写软件一样,实现一个功能有多种方法,我们需要对多个设计参数进行考量,找到一种最合理的方法。

为了查看这个设计综合实现的结果,我们打开Project Summary,这里可以看到所有的结果。

我们发现这个设计竟然使用了9个LUT和26个FF。虽然我们总共有17600个LUT和35200个FF,我们希望占用更少的LUT和FF,那要如何实现呢。

定位优化空间

我们注意到这个设计中最大的开销就是26位宽的counter和它的比较器,如果我们能想办法缩减counter的位数,就可以减少LUT和FF使用的数量。

而如果要存储25M,我们必须要使用26位宽的寄存器。虽然\( 2^{25}\approx 35M \),但我们无法再减小位宽了,因为\( 2^{24}\approx 16M <25M \)。

但是归根到底,我们需要25M还是因为时钟信号的周期为50MHz。如果我们有一个2的整数倍频率的周期信号,也就不需要这么麻烦了。事实上,FPGA中内置的锁相环可以解决这个问题,它可以将我们的时钟信号通过某种玄学变为4.687MHz~800MHz中的任意一个频率。由于锁相环是模拟电路实现的,直接在FPGA内就有,不需要占用FF和LUT,这样就可以减少FF和LUT的占用了。

生成Clock IP

点击IP Catalog,搜索clock,并找到Clock Wizard,双击。

将输入的时钟频率改为你开发板的时钟频率,一般为50MHz。

将输出的时钟频率改为8.388608MHz,这个数字正好是\( 2^{23} \)。

点击OK,将Synthesis Options改为Global,并点击Generate。

最后将led.sv改为

module led(
    input clk,
    output led
    );
    logic clk_div,locked;

    //连接clk_wiz_0ip与led模块
    clk_wiz_0 c(
        .clk_out1(clk_div),
        .locked(locked),
        .clk_in1(clk)
    );

    //计数器,由于计数器在8388608+1时溢出了,所以就变成0了
    logic [22:0] cache=0;
    always_ff@(posedge clk_div)
        if(locked)
            cache <= cache + 1'b1;

    assign led=cache[22];
endmodule

结果

可以看到我们只用了1个LUT与23个FF。这是因为加法运算由快速进位链完成了。而由于我们不需要比较器,所以大幅减少了LUT与FF的数量。但是我们发现时序出现了一些问题,这是由于23位的加法需要6个4位的快速进位链,这延长了最长路径,加大了延迟。所以尽管我们减少了LUT和FF的数量,却加大了最大延迟,降低了时钟的速度。