FPGA零基础学习:LED流水灯设计

2021-03-23 09:51:54 浏览数 (1)

FPGA零基础学习:LED流水灯设计

本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。

系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。后续会陆续更新 Xilinx 的 Vivado、ISE 及相关操作软件的开发的相关内容,学习FPGA设计方法及设计思想的同时,实操结合各类操作软件,会让你在技术学习道路上无比的顺畅,告别技术学习小BUG卡破脑壳,告别目前忽悠性的培训诱导,真正的去学习去实战应用,这种快乐试试你就会懂的。话不多说,上货。

LED流水灯设计

作者:郝旭帅 校对:陆辉

在学习软件设计时,第一个例程总是“hello world!”,那么学习硬件时,也会有硬件的“hello world”--流水灯。本篇硬件基于叁芯智能科技的 SANXIN-B01开发板,如有想要入手,可以在淘宝店下单。

在FPGA开发板上有四个LED,我们要做的流水灯,顾名思义就是要LED像流水一样的点亮熄灭。直白点说就是,点亮第一个一段时间,然后熄灭第一个的同时,点亮第二个·····。在此,我们设置每一个LED点亮的时间为1秒钟。

硬件介绍

在我们的开发板上有四个LED,设计逻辑为:FPGA输出高电平时,LED点亮;FPGA输出低电平时,LED熄灭。

架构设计和信号说明

本设计的模块名称为ledrun。

建立工程、新建文件等步骤在专辑前篇中已经明确罗列,以后将不再叙述。具体可查看,FPGA零基础学习:Intel FPGA 开发流程篇。

设计代码

代码语言:javascript复制
module ledrun (

  input      wire          clk,// 50MHz
  input      wire      rst_n, // low valid
  
  output    reg    [3:0]    led   // high valid
);

  parameter    T_1s    =    50_000_000; 
  
  reg          [25:0]  cnt;
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      cnt <= 26'd0;
    else
      if (cnt < T_1s - 1'b1)
        cnt <= cnt   1'b1;
      else
        cnt <= 26'd0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      led <= 4'b0001;
    else
      if (cnt == T_1s - 1'b1)
        led <= {led[2:0], led[3]};
      else
        led <= led;
  end
  
endmodule

parameter可以定义一个参数(默认是32位)。在写代码时,对于某些数字,设计者经常利用定义参数的方式进行编写,方便修改,也方便阅读。

在硬件电路中,使用计数器当做计时器,每记录一个数字等于过去一个时钟周期。由于本设计中采用的clk为50MHz,所以经过50_000_000(在verilog中,如果是描述数字,中间的下划线只起到分隔的作用,不影响数值的大小)个周期正好为1秒钟。由于计数器是从0开始计数,所以计数器只需要记录到50_000_000-1即可。

为了能够记录到50_000_000-1这么大的数字,所以定义了一个26位的计数器cnt(参考附录1:设计中位宽的概念和计算位宽的小技巧)。

在verilog中,“{}”( 大括号)的第一个特殊作用为位拼接。{a,b}相当于将a和b拼接为一个整体,并且是高位为a,低位为b。

当led输出为4’b0001时,第一个led点亮;经过1秒钟,输出4’b0010时,第二个led点亮;经过1秒钟,输出4’b0100时,第三个led点亮;经过1秒钟,输出4’b1000时,第四个led点亮;经过1秒钟,输出4’b0001时,第一个led点亮······按照上述的过程周而复始,就形成了流水灯。

不难发现,led的输出,一直为3个0,1个1。并且1的位置每1秒钟移动一次,从头到尾,然后又到头。这种现象可以利用移位的思想进行实现。即:led[3]<=led[2]; led[2]<=led[1]; led[1]<=led[0]; led[0]<=led[3];如果将被赋值的组成一个整体,那就是led,赋值的组成一个整体就是{led[2:0], led[3]}。

仿真代码

代码语言:javascript复制
`timescale 1ns/1ps     //定义时间单位和精度

module ledrun_tb;

  reg               clk;
  reg               rst_n;
  
  wire    [3:0]     led;
  
  ledrun ledrun_inst(

      .clk        (clk),// 50MHz
      .rst_n      (rst_n), // low valid
      
      .led        (led)   // high valid
    );
  
  initial clk = 1'b0;      // 50MHz
  always # 10 clk = ~clk;
  
  initial begin
    rst_n = 1'b0;
    # 201            // reset 
    rst_n = 1'b1;
    # 2000;
    $stop;
  end
  
endmodule

$stop是一个系统任务,功能为将modelsim的仿真停止。

设置好testbench后,运行分析综合后,打开RTL仿真。

波形分析

在modelsim中,打开sim窗口,选择ledrun_tb下的ledrun_inst。

打开objects,将cnt选中。

objects窗口中显示在sim窗口中选中模块中所有的信号。

右击,将其添加入波形窗口。

返回到wave窗口中,cnt信号已经添加到wave窗口中。由于新添加进来,没有数据(no data)。

点击restart。

restart按钮为重新运行波形,点击后,软件会询问是否保持各种属性,点击ok即可。

wave窗口中所有的波形都处于no data 状态。点击run –all按钮,开始运行波形。

运行后,会自动停止。停止在tb文件中的$stop处。

返回wave窗口,各个信号都会有波形。

设置cnt的信号进制为无符号的十进制:右击cnt信号,选择radix中的unsigned。

把光标放到复位结束时,选择放大波形。

放大按钮的左侧第一个按钮为全局缩放,功能为将所有运行波形,显示到目前的窗口里;左侧第二个为缩小。最左边和最右边的按钮暂时用不到,这里不再介绍。

可以看到,在复位结束后,cnt信号每一个时钟周期都会增加1。

由于我们设计的流水灯是每1秒钟流动一个,在上述的仿真中,led数值是不会变化的。如果仿真几秒钟的话,仿真的时间会比较长。在此不建议仿真几秒钟的时长,有可能会导致电脑卡住。

仿真时,可以将T_1s的值,改成一个较小值。例如:5。然后在此编译仿真。

在quartus的编译器中,修改完后。进行综合分析,保证没有任何语法错误。在之前打开的modelsim中,打开library窗口,找到最上面的work,打开其前面的“ ”。

选中刚才修改过的文件,右击,选择recompile。此时,modelsim会重新编译此文件。

回到wave窗口中,点击restart,run-all。运行波形。

能够清楚的看到,led在进行移位,并且都是5个周期移动一次。

仿真通过后,关闭modelsim。回到quartus中,将参数修改成为50_000_000,综合分析后,分配管脚。布局布线,生成配置文件,进行下板测试。

开发板上的四个LED开始做流水状点亮。

0 人点赞