Verilog编写FPGA程序实验小结

1. 小结内容:

① 时序电路之确切 1s 信号的产生

② 多过程仿真测试文件的编写(含对仿真时间长短的处理)

③ 数码管动态显示(随意让某位显示某值)

③ 以 Hz 为单位的可随意调节 1、0 占空比的通用时钟信号发生器 IP 核

2. 时序电路之确切 1s 信号的产生

  旧版产生 1s 信号的思路是将 100MHz 信号进行分频,产生 3Hz 信号,将新产生的 3Hz 信号的 3 个周期作为 1Hz 信号来使用,非常接近 1s。具体实现为:100MHz 时钟不能精确做到分频实现 3Hz,只能到 2.98Hz。2.98Hz 的信号可以由 100MHz 的时钟信号 clk 通过 24 分频来产生,以产生的 2.98Hz 信号的三个时钟周期来表示 1s,精确值为 1.0067s,非常接近于真实时间的 1s,但还是有差距;

  更新后的代码可以产生确切的 1s 信号,主要思路是将 100MHz 信号按周期数进行计数,当计数到 50000000 个 100MHz 时钟的上升沿后,将输出 1 Hz 信号置 1,又当计数到 100000000 个上升沿后,输出信号及计数变量清零。

  程序实现如下(verilog 语言,nginx 不支持解析 Verilog 的代码块,暂且用 python 样式替代):

  其中 clk为 100MHz 时钟信号,clk_1Hz为产生的相邻上升沿间隔 1.0067s 的输出信号,当然实际值还要看 clk 到底是不是精确的 100MHz 信号,计时 1 分钟一般会稍慢一些。clk_1s为产生的相邻上升沿间隔 1s 的输出信号。

3. 多过程仿真测试文件的编写

  多功能的程序文件需要搭配不同拨码开关为 1 或 0 的状态来展现不同的程序效果。如何使得设计文件的多功能内容直接用仿真文件来检验呢?

  要想解决这一问题,只要解决两个小问题即可:

① 如何缩短仿真时间?

  在含有时序逻辑电路的仿真测试文件编写过程中,经常会出现以为单位的计时信号,那么做仿真时,如何加快仿真速度,用一小段仿真时间得到长时间的仿真效果呢?

解答:

  如果直接把仿真时间总时长设置为好几秒,不仅非常占用电脑资源,而且还很慢。那就需要小小地修改一下设计源文件中的时钟信号频率了。如果设计源文件是按 100MHz 信号 26 分频来计时,那就可以把这个 26 换成 2,仿真时可以快很多,能够达到 us 量级。

② 不同时间展示不同效果的延时值如何设置?

  多功能程序通过仿真想要看到的结果图肯定要包含多个按键值在不同时间段为不同值时对应的程序所表现出的状态。如何设置仿真时不同时间段显示出程序的不同状态呢?

解答:

  仿真测试文件中的initial语句可以实现这一功能,让我们以十字路口交通灯控制器之状态机设计为例来说明如何操作。先看仿真测试文件(重点在 initial 语句中延时与改变状态的使用)及其结果图:

`timescale 1ns / 1ps

// 要想缩短仿真时间,请将设计源文件 traffic.v 中第 21 行和第 29 行的 24 改为 2

module sim1;
reg clk;// 模拟输入 100 MHz 时钟信号
reg reset = 1;// 复位信号,为 1 恢复到 S0 状态,为 0 显示正常状态
reg stby = 0;// 检修信号,为 1 东西、南北方向交通灯均为黄灯,为 0 倒计时 3 秒后回复原来的状态
wire [5:0] lights;// 东西红、黄、绿灯和南北红、黄、绿灯
wire [7:0] sm_wei;// 数码管的位码
wire [7:0] sm_duan1;// 右侧 4 个数码管段码
wire [7:0] sm_duan2;// 左侧 4 个数码管段码

traffic uut (clk, reset, stby, lights, sm_wei, sm_duan1, sm_duan2);

initial begin
    # 2000;// 前 2us 为计数变量清零状态,程序表现为暂停
    reset = 0;
    clk = 1;
    # 7000;// 2~9us为正常状态,lights从S0变为S1再变为S2接着变为S3,所有状态均存在,进行了一个周期多一点
    stby = 1;
    # 2000;// 9~11us为检修状态,程序表现为暂停且东西、南北方向均显示黄灯
    stby = 0;
    # 3000;// 11~14us为检修结束的状态,程序倒计时3秒后,重新正常进入S0状态
    // 14us以后为复位reset及检修stby同时进行的状态,程序表现为优先级更高的复位状态,即以S0状态暂停显示。
    stby = 1;
    reset = 1;
end
// 模拟输入时钟,周期为 2ns
always
    # 1 clk = ~clk;
endmodule

交通灯控制器多过程仿真结果

状态东西方向信号灯南北方向信号灯lights时间
S0绿001100持续 40s
S1010100持续 5s
S2绿100001持续 40s
S3100010持续 5s
S4010010跳出后再持续 3s

  其中 clk 为 EGO1 实验板自带的 100MHz 时钟信号;reset 为复位信号,为 1 时会使状态恢复到初始态 S0,为 0 时,程序可以从 S0 开始正常计数并进行状态转换;stby 为检修信号,当其为 1 时,东西、南北方向交通灯均为黄灯,直到变为 0 后倒计时 3 秒再恢复到初始状态 S0;lights中的 6 位从高到低依次表示东西、南北方向的红、黄、绿信号灯,通过查看 lights 代表的信号灯变化来观察不同功能及同功能下的状态转换。

  前 2us 设置复位信号 reset 为 1 ,程序中的 lights 表现在 S0 状态,且保持不变;2~9us 为正常状态,lights 从 S0 变为 S1 再变为 S2 接着变为 S3,所有状态均存在,进行了一个周期多一点;9~11us 为检修状态,程序表现为暂停且东西、南北方向均显示黄灯;11~14us 为检修结束的状态,程序倒计时3秒后,重新正常进入 S0 状态;14us 以后为复位 reset 及检修 stby 同时进行的状态,程序表现为优先级更高的复位状态,即以 S0 状态暂停显示。

  从仿真结果来看,达到了设计的目的,完成了各个扩展功能的实现。且实现了各个功能的仿真展示

点击下载十字路口交通灯控制器状态机设计完整扩展的实现程序压缩包下载程序包

4. 数码管动态显示——随意让 EGO1 实验板中的 8 位数码管的任意一位显示某值

  8 位数码管从 DK1~DK8 依次由 qianwanbaiwanshiwanwanqianbaishige 控制。只要修改其变量的数值,即可修改动态显示出的数字。

  直接上设计源文件,自定义其中的复位状态(第30行)、数码管各位初始值(47~54行)和自定义逻辑控制(59~61行)即可,计时周期为准确的 1s。

`timescale 1ns / 1ps
// 8 位数码管分别按位动态显示,需自定义逻辑控制语句 by EzXxY
module smg_display(clk, clr, sm_wei, sm_duan1, sm_duan2);
input clk;// 100 MHz P17
input clr;// 暂停计时信号
output [7:0] sm_wei;// 8 位位码
output [7:0] sm_duan1;// 右侧 4 个数码管的 8 位段码
output [7:0] sm_duan2;// 左侧 4 个数码管的 8 位段码
wire clkout;// 产生的 2.98Hz 时钟信号

// 分频
integer clk_cnt;
reg clk_400Hz;
always @ (posedge clk)
    if (clk_cnt==32'd100000)
    begin
        clk_cnt <= 1'b0;
        clk_400Hz <= ~clk_400Hz;
    end
    else
        clk_cnt <= clk_cnt + 1'b1;

reg [26:0] k = 0;// 计数变量
reg clk_1Hz;// 得到的准确 1 Hz 时钟信号
// 实现分频,最终得到的计数频率为 1 Hz
always @ (posedge clk or posedge clr)
begin
    if (clr==1)
        k = 0;
//        此处添加要复位到的状态
    else
    begin
        k = k + 1;
        if (k==50000000)
            clk_1Hz = 1;
        if (k==100000000)
        begin
            clk_1Hz = 0;
            k = 0;
        end
    end
end


// 位控制 + 数码管各位初始值
reg [7:0] wei_ctrl = 4'b00000001;
reg [3:0] ge = 4;// 数码管最低位(DK8)
reg [3:0] shi = 1;// 数码管次低位(DK7)
reg [3:0] bai = 3;// DK6
reg [3:0] qian = 1;// DK5
reg [3:0] wan = 10;// DK4
reg [3:0] shiwan = 0;// DK3
reg [3:0] baiwan = 2;// DK2
reg [3:0] qianwan = 5;// 数码管最高位(DK1)

// 计时时钟开始计数
always @ (posedge clk_1Hz)
begin
    ////////////////////////////////////////////////////////
    //////////////// 自己设计的各位逻辑控制 ////////////////
    /////////////////////////////////////////////////////
end

always @ (posedge clk_400Hz)
    wei_ctrl <= {wei_ctrl[6:0], wei_ctrl[7]};
    
// 段控制
reg [7:0] duan_ctrl;
always @ (wei_ctrl)
    case (wei_ctrl)
        8'b00000001:duan_ctrl = ge;
        8'b00000010:duan_ctrl = shi;
        8'b00000100:duan_ctrl = bai;
        8'b00001000:duan_ctrl = qian;
        8'b00010000:duan_ctrl = wan;
        8'b00100000:duan_ctrl = shiwan;
        8'b01000000:duan_ctrl = baiwan;
        8'b10000000:duan_ctrl = qianwan;
    endcase

// 解码模块
reg [7:0] duan;
always @ (duan_ctrl)
    case (duan_ctrl)
        8'd0:duan = 8'b11111100;// 显示0
        8'd1:duan = 8'b01100000;// 显示1
        8'd2:duan = 8'b11011010;// 显示2
        8'd3:duan = 8'b11110010;// 显示3
        8'd4:duan = 8'b01100110;// 显示4
        8'd5:duan = 8'b10110110;// 显示5
        8'd6:duan = 8'b10111110;// 显示6
        8'd7:duan = 8'b11100000;// 显示7
        8'd8:duan = 8'b11111110;// 显示8
        8'd9:duan = 8'b11110110;// 显示9
        8'd10:duan = 8'b00000010;// 显示小横线"-"
    endcase

assign sm_wei = wei_ctrl;
assign sm_duan1 = duan;
assign sm_duan2 = duan;
endmodule

约束文件:

set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan1[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan1[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan1[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan1[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan1[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan1[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan1[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan1[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan2[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan2[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan2[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan2[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan2[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan2[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan2[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_duan2[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_wei[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_wei[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_wei[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_wei[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_wei[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_wei[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_wei[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sm_wei[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clr]
set_property PACKAGE_PIN P17 [get_ports clk]
set_property PACKAGE_PIN R1 [get_ports clr]
set_property PACKAGE_PIN G2 [get_ports {sm_wei[7]}]
set_property PACKAGE_PIN C2 [get_ports {sm_wei[6]}]
set_property PACKAGE_PIN C1 [get_ports {sm_wei[5]}]
set_property PACKAGE_PIN H1 [get_ports {sm_wei[4]}]
set_property PACKAGE_PIN G1 [get_ports {sm_wei[3]}]
set_property PACKAGE_PIN F1 [get_ports {sm_wei[2]}]
set_property PACKAGE_PIN E1 [get_ports {sm_wei[1]}]
set_property PACKAGE_PIN G6 [get_ports {sm_wei[0]}]
set_property PACKAGE_PIN D4 [get_ports {sm_duan1[7]}]
set_property PACKAGE_PIN E3 [get_ports {sm_duan1[6]}]
set_property PACKAGE_PIN D3 [get_ports {sm_duan1[5]}]
set_property PACKAGE_PIN F4 [get_ports {sm_duan1[4]}]
set_property PACKAGE_PIN F3 [get_ports {sm_duan1[3]}]
set_property PACKAGE_PIN E2 [get_ports {sm_duan1[2]}]
set_property PACKAGE_PIN D2 [get_ports {sm_duan1[1]}]
set_property PACKAGE_PIN H2 [get_ports {sm_duan1[0]}]
set_property PACKAGE_PIN B4 [get_ports {sm_duan2[7]}]
set_property PACKAGE_PIN A4 [get_ports {sm_duan2[6]}]
set_property PACKAGE_PIN A3 [get_ports {sm_duan2[5]}]
set_property PACKAGE_PIN B1 [get_ports {sm_duan2[4]}]
set_property PACKAGE_PIN A1 [get_ports {sm_duan2[3]}]
set_property PACKAGE_PIN B3 [get_ports {sm_duan2[2]}]
set_property PACKAGE_PIN B2 [get_ports {sm_duan2[1]}]
set_property PACKAGE_PIN D5 [get_ports {sm_duan2[0]}]

可视化形式如下:

可视化约束文件

  应用到自己的项目中时,不要忘记修改第 30 行的复位状态、47~54 行的数码管各位初始值和 59~61 行的自定义逻辑控制。

该项目压缩包下载链接下载程序包

5. 设计的以 Hz 为单位的可随意调节 1、0 占空比的通用时钟信号发生器 IP 核

  要求实验板具有 100MHz 时钟信号,且要设计的最长计时周期数不超过 3521251667300637226 s 即可。

  如要生成 1、0 占空比为 8:7 的 50Hz 时钟信号,则双击导入的 IP 核后,分行依次输入:50、8、7即可,如下图所示:

4-1. 双击 IP 核
1. 双击 IP 核

4-2. 自定义时钟周期 Hz 数和 1、0 占空比 m、n
自定义时钟周期 Hz 数和 1、0 占空比 m、n

4-3. 使用示例

请输入图片描述

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Create Date: 2022/06/27 12:34:56 by 电子 193 LZ -- EzXxY
//////////////////////////////////////////////////////////////////////////////////

module usemyip_test(clkin, clr, clkout);
input clkin;// 板子自带的 100 MHz 时钟
input clr;// 复位信号为 1 时,计数变量清零,表现为程序暂停
output clkout;// 最后得到的时钟输出

wire clk50Hz;// IP 核生成的 50Hz 占空比为 8:7 的信号
assign clkout = clk50Hz;// 用 assign 语句得到输出 clkout 与 IP 核产生时钟的对应关系,可多次调用 IP 核,使用带条件的 assign 语句控制等
// 调用通用进制计数器 IP 核,输出 50Hz 占空比为 8:7 的信号 
clk_50Hz_8_7 uut1(
    .clkin(clkin),
    .clr(clr),
    .clkout(clk50Hz)
    );
   
endmodule

本 IP 核下载链接下载 IP 核

打赏
文章目录