2024年寒假练-基于Lattice MXO2的小脚丫FPGA核心板设计具有启动,停止,递增和清除功能的秒表
该项目使用了WebIDE环境,Verilog语言,基于Lattice MXO2的小脚丫FPGA核心板,实现了一个具有启动,停止,递增和清除功能的秒表的设计,它的主要功能为:0.0s到9.9s的一个秒表。可通过按键控制其实现正向计时,暂停计时,计时递增一次,清零功能,并通过板载数码管显示出来。。
标签
FPGA
数字逻辑
开发板
胡道扬
更新2024-04-01
北京理工大学
342

一,  内容介绍

1.  项目需求

·  通过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过四个按键来控制秒表的功能,并在数码管上显示数值。

·  在WebIDE环境下进行Verilog代码编程,综合,仿真,生成JDE代码并下载到FPGA中进行验证。

·  通过GPT等大模型工具对用到的各功能模块进行验证和修改。


2.  完成的功能

2.1     开始按键输入

开始输入使数码管秒表开始以10Hz时钟速率递增(即每0.1秒计数一次)。

2.2     停止按键输入

停止输入使计数器停止递增,但使数码管显示当前计数值。

2.3     增量按键输入

在暂停状态时,每次按下增量按键且无论按住增量按键多长时间,显示值都会增加一次。递增完成后回到暂停状态。

2.4     清除按键输入

清除按键强制计数器值为零。

 

3.  实现思路及各模块分析

分析题目要求可得,数码管初始化显示0.0s,按下启动按键后开始正向计时,计时到9.9s后自动回到0.0s重新计时;按下暂停按键后,停止计时,数码管保持当前显示不变;在暂停状态时,按下递增按键,数码管最低为递增一并显示出来,递增完成后系统回到暂停状态。将所要完成工作分为如下几个小模块: 10Hz分频器模块,按键消抖模块,数码管显示模块。、

程序流程如图所示:

image.png

下面对这些模块功能和代码分别进行说明。


3.1     分频器模块

FPGA分频器是一种用于将输入时钟频率降低为较低频率的电路,常见的有偶数分频和奇数分频。查阅项目使用开发板手册可得,开发板原始输入时钟频率为12MHz。要得到10Hz时钟信号,简单来说则要设计一个计数器,对输入12MHz时钟信号脉冲上升沿计数,每计数1200000,使得输出信号跳变一次,即可得到10Hz时钟信号。参考开发板示例资源及GPT可得模块代码和注释如下:

module divide (clk,rst_n,clkout);

 

        input clk,rst_n;                       //输入信号,其中clk连接到FPGA的C1脚,频率为12MHz

        output clkout;                          //输出信号,可以连接到LED观察分频的时钟

 

        //parameter是verilog里常数语句

   parameter   WIDTH = 24;             //计数器的位数,计数的最大值为 2**WIDTH-1

   parameter   N  = 12_000_000;             //分频系数,请确保 N < 2**WIDTH-1,否则计数会溢出

 

   reg   [WIDTH-1:0] cnt_p,cnt_n;     //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器

   reg         clk_p,clk_n;     //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟

 

   //上升沿触发时计数器的控制

   always @ (posedge clk or negedge rst_n )         //posedge和negedge是verilog表示信号上升沿和下降沿

                                                         //当clk上升沿来临或者rst_n变低的时候执行一次always里的语句

      begin

         if(!rst_n)

            cnt_p<=0;

         else if (cnt_p==(N-1))

            cnt_p<=0;

         else cnt_p<=cnt_p+1;             //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器

      end

 

         //上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50%

         always @ (posedge clk or negedge rst_n)

      begin

         if(!rst_n)

            clk_p<=0;

         else if (cnt_p<(N>>1))          //N>>1表示右移一位,相当于除以2去掉余数

            clk_p<=0;

         else

            clk_p<=1;               //得到的分频时钟正周期比负周期多一个clk时钟

      end

 

        //下降沿触发时计数器的控制          

   always @ (negedge clk or negedge rst_n)

      begin

         if(!rst_n)

            cnt_n<=0;

         else if (cnt_n==(N-1))

            cnt_n<=0;

         else cnt_n<=cnt_n+1;

      end

 

        //下降沿触发的分频时钟输出,和clk_p相差半个时钟

   always @ (negedge clk)

      begin

         if(!rst_n)

            clk_n<=0;

         else if (cnt_n<(N>>1)) 

            clk_n<=0;

         else

            clk_n<=1;                //得到的分频时钟正周期比负周期多一个clk时钟

      end

 

        assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p;      //条件判断表达式

                                                                    //当N=1时,直接输出clk

                                                                    //当N为偶数也就是N的最低位为0,N(0)=0,输出clk_p

                                                                    //当N为奇数也就是N最低位为1,N(0)=1,输出clk_p&clk_n。正周期多所以是相与

endmodule

代码使用两个计数器cnt_p和cnt_n来记录计数器的状态,分别用于上升沿触发和下降沿触发。同时,定义了两个寄存器变量clk_p和clk_n来存储分频时钟的输出状态。在上升沿触发时,计数器cnt_p会一直计数,当计数到输入N-1(N为输入分频系数)时清零,同时根据N的奇偶性输出不同的分频时钟状态;下降沿触发时同理。本项目要得到10Hz频率时钟,输入分频系数N应为1200000,为偶数,输出上升沿触发时的计数器输出状态。


3.2     按键消抖模块

按键消抖通常是指在FPGA开发板上,对按键输入信号进行处理,以消除按键操作中由于各种因素导致的输入信号不稳定的现象,从而确保输入信号的准确性和稳定性。在本项目使用开发板中,要进行的是按键机械消抖。当按键被按下时,由于机械结构的原因,可能会产生连续的抖动,导致输入信号出现噪声,影响准确性,因此需要对其进行消抖处理。具体代码参考已给示例并修改如下:

module debounce (clk,rst,key,key_pulse);

 

        parameter       N  =  1;                      //要消除的按键的数量

 

   input             clk;

        input             rst;

        input [N-1:0]   key;                        //输入的按键              

   output  [N-1:0]   key_pulse;                  //按键动作产生的脉冲 

 

        reg     [N-1:0]   key_rst_pre;                //定义一个寄存器型变量存储上一个触发时的按键值

        reg     [N-1:0]   key_rst;                    //定义一个寄存器变量储存储当前时刻触发的按键值

 

        wire    [N-1:0]   key_edge;                   //检测到按键由高到低变化是产生一个高脉冲

 

        //利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中

        always @(posedge clk  or  negedge rst)

          begin

             if (!rst) begin

                 key_rst <= {N{1'b1}};                //初始化时给key_rst赋值全为1,{}中表示N个1

                 key_rst_pre <= {N{1'b1}};

             end

             else begin

                 key_rst <= key;                     //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre

                 key_rst_pre <= key_rst;             //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值

             end   

           end

 

        assign  key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平

 

        reg [17:0]     cnt;                       //产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器    

 

        //产生20ms延时,当检测到key_edge有效是计数器清零开始计数

        always @(posedge clk or negedge rst)

           begin

             if(!rst)

                cnt <= 18'h0;

             else if(key_edge)

                cnt <= 18'h0;

             else

                cnt <= cnt + 1'h1;

             end 

 

        reg     [N-1:0]   key_sec_pre;                //延时后检测电平寄存器变量

        reg     [N-1:0]   key_sec;                   

 

 

        //延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效

        always @(posedge clk  or  negedge rst)

          begin

             if (!rst)

                 key_sec <= {N{1'b1}};               

             else if (cnt==18'h3ffff)

                 key_sec <= key; 

          end

       always @(posedge clk  or  negedge rst)

          begin

             if (!rst)

                 key_sec_pre <= {N{1'b1}};

             else                  

                 key_sec_pre <= key_sec;            

         end     

       assign  key_pulse = key_sec_pre & (~key_sec);    

 

endmodule

代码定义一个整型参数N,表示要消除抖动的按键数量。还使用了两个寄存器变量key_rst和key_rst_pre来记录按键的状态。key_edge变量来检测按键的下降沿,cnt变量来产生延时,key_sec和key_sec_pre变量来记录延时后按键的状态。当检测到按键下降沿时,会触发一个脉冲信号,并将该按键的状态存储在key_rst中。延时计数器cnt从0开始计数,计数器溢出时,产生一个高脉冲信号,该信号用于检测按键的触发。在延时计数器cnt计数过程中,按键的状态被存储在key_sec中。当延时计数器cnt计数结束时,按键的状态被存储在key_sec_pre变量中。最后,通过比较key_sec和key_sec_pre的值,可检测到按键的有效触发。如果按键状态变低,则产生一个高脉冲信号,此时认为按键有效按下,否则认为按键无效。这样,通过延时计数器cnt的计数和检测按键的有效触发,可以实现按键消抖的功能。


33     数码管显示模块

数码管显示模块是整个系统的主体部分,这一模块根据不同的按键信息,控制数码管分别实现秒表计数,停止计数,递增计数和清除功能。具体代码如下:


module counter

(

   clk            ,    //时钟

   rst            ,    //复位

   go          ,    //启动按键

   stop            ,    //暂停按键

   add            ,  //递增按键

   seg_led_1      ,    //数码管1

   seg_led_2      ,    //数码管2

);

 

   input    clk,rst;

   input go;

    input   stop;

   input add;

 

   output   [8:0] seg_led_1,seg_led_2; //9位,七段数码管,最后一位是位选信号,在小脚丫上控制一个数码管需要9个信号

 

 

   wire     clk10hz;        //10Hz时钟

   wire     go_pulse;   //按键消抖后信号

   reg         go_flag;    //按键标志位

   wire        stop_pulse;

   reg         stop_flag;  

   wire     add_pulse;

   reg         add_flag;

 

   reg         [6:0]   seg    [9:0];  //二维寄存器数组,每个寄存器七位,包含十个元素,存储数字0~9

   reg         [3:0] cnt_ge;      //个位

   reg         [3:0] cnt_shi;     //十位

 

   initial                       //在过程块中只能给reg型变量赋值,Verilog中有两种过程块always和initial

   begin                         //initial和always不同,其中语句只执行一次

      seg[0] = 7'h3f;      //  0

      seg[1] = 7'h06;      //  1

      seg[2] = 7'h5b;      //  2

      seg[3] = 7'h4f;      //  3

      seg[4] = 7'h66;      //  4

      seg[5] = 7'h6d;      //  5

      seg[6] = 7'h7d;      //  6

      seg[7] = 7'h07;      //  7

      seg[8] = 7'h7f;      //  8

      seg[9] = 7'h6f;      //  9

      go_flag =0;

      stop_flag =0;

        add_flag =0;

   end

 

 

 

   // 启动/暂停/递增按键进行消抖

   debounce  U2 (

            .clk(clk),

            .rst(rst),

            .key(go),

            .key_pulse(go_pulse)

            );

 

   debounce  U3(

            .clk(clk),

            .rst(rst),

            .key(stop),

            .key_pulse(stop_pulse)

               );

 

   debounce  U4(

            .clk(clk),

            .rst(rst),

            .key(add),

            .key_pulse(add_pulse)

            );

   // 用于分出一个10Hz的频率

   divide #(.WIDTH(32),.N(1200000)) U1 (   //10Hz则N取1200000

         .clk(clk),

         .rst_n(rst),     

         .clkout(clk10hz)

         );

    //按键动作标志信号产生

   always @ (posedge clk10hz or posedge go_pulse or posedge stop_pulse or posedge add_pulse or negedge rst)begin //检测有按键按下     按键检测部分得改,参考之前做的,也可用GPT生成消抖模块

      if(add_flag==1)begin

         add_flag<=0;

      end

 

      if(!rst==1)begin       //复位清零

         go_flag <= 0;     

         stop_flag <= 0;

         add_flag <= 0;

      end

      else if(go_pulse)begin  //启动标志

         go_flag <= 1;

         stop_flag <= 0;

         add_flag <= 0;

      end

      else if(stop_pulse) begin      //停止标志     

         go_flag <= 0;

         stop_flag <= 1;

         add_flag <= 0;

      end 

      else if(add_pulse)begin        //递增一次标志

         add_flag <=1;

      end

   end

 

 

    //9.9s正向计时

   always @(posedge clk10hz or negedge rst) begin    

      if (!rst) begin      //复位到初始值0.0

         cnt_ge <= 4'd0;

         cnt_shi <= 4'd0;            

      end

 

      else if(go_flag == 1)begin  

            if (cnt_ge == 9 && cnt_shi == 9) begin // 当个位和十位都为9时,重置计数器

               cnt_ge <= 4'd0;

               cnt_shi <= 4'd0;

            end

            else if (cnt_ge == 9) begin // 当个位为9十位不为9时,个位归0,十位加1

               cnt_ge <= 4'd0;

               cnt_shi <= cnt_shi + 1;

            end

            else begin

               cnt_ge <= cnt_ge + 1; // 若计数器没有达到阈值,则个位加1

            end     

      end 

      else if(stop_flag==1&&add_flag==1)begin  //按下add键

            if (cnt_ge == 9 && cnt_shi == 9) begin // 当个位和十位都为9时,重置计数器

               cnt_ge <= 4'd0;

               cnt_shi <= 4'd0;

            end

            else if (cnt_ge == 9) begin // 当个位为9十位不为9时,个位归0,十位加1

               cnt_ge <= 4'd0;

               cnt_shi <= cnt_shi + 1;

            end

            else begin

               cnt_ge <= cnt_ge + 1; // 若计数器没有达到阈值,则个位加1

            end  

      end

      else begin      //按下stop,保持不变

 

      end

   end

     

   assign seg_led_2[8:0] = {2'b00,seg[cnt_ge]};

 

   assign seg_led_1[8:0] = {2'b01,seg[cnt_shi]};//数码管1显示小数点

 

 

endmodule

下面给出实验流程,结果,遇到的问题和解决方案


4.  实验流程

本实验采用WebIDE环境进行Verilog代码编程实现。首先在WebIDE网页端创建项目,新建文件,写好代码。

image.png

点击逻辑综合,结果显示成功

image.png

根据所要实现功能和板卡引脚信息进行管脚分配

image.png

image.png

最后经FPGA映射,下载JED文件到本地,将下载好的文件传入板卡即可。

image.png


5.  FPGA资源利用说明

image.png


image.png

利用GPT帮忙总结可得,在设计中,用到总寄存器数为113个,占所有寄存器数(4635个)的2%,PFU寄存器数113个,占所有PFU寄存器(4320个)的3%。用到SLICEs总数100个,占所有SLICEs(2160个)的5%。LUT4s占用200个,其中用作逻辑LUTs80个,用作ripple逻辑的LUT4s120个。此外,设计中峰值内存使用为173MB。总体看来,设计中FPGA的资源占用少,符合预期。大部分SLICEs和LUT4s被用作逻辑处理,其中一部分用于ripple逻辑和作为Carry的SLICEs。PIO站点的使用比例较高,达到26%。


6.  实验结果

板卡上电后显示0.0s,为初始状态,待按键按下实现对应功能。

image.png


当按下启动按键时,开始计数功能,数码管从0.0s计时到9.9s后翻转到0.0s并继续计时。

image.png

image.png

image.png

当按下暂停按键时,计时器停止计时,数码管维持当前计数值并保持显示。

image.png

在暂停状态时按下递增按键,计数器从小数位递增一次,并通过数码管显示表示出来,且递增完成后回到暂停状态。

image.png


image.png

image.png


当按下清零按键时,计数器清零,数码显示0.0s,回到初始状态。

image.png

由结果可得,已基本完成实验要求。



7.  遇到的主要问题及解决方案

7.1 模块代码编写

最开始对按键机械消抖原理理解不清晰,编写消抖模块代码时存在困难。因此我根据要求,采用GPT大模型设计模块并注释代码,同时参考示例学习,最终解决问题。

7.2 计数器进位逻辑错误

写完代码后发现实际功能并不符合预期,自己仔细检查代码也没有发现问题。因此我利用GPT工具,对代码进行检查并修改,最终找出逻辑错误并纠正。

image.png

image.png




8.  感想

这次项目对我而言是学习FPGA的一个很好机会。项目采用WEBIDE网页环境实现,不用下载额外软件,感觉十分简便。此外,在此次寒假在家练活动中,我第一次将GPT模型用于代码实现和纠错,自我感受是效率确实提高了很多。代码中的一些逻辑错误自己可能得花很大精力才能纠正,让GPT帮忙的话就十分高效。后续计划进一步学习相关知识,希望能更深一步参与更复杂的FPGA项目,锻炼自己能力。


软硬件
元器件
LCMXO2-4000HC-4MG132C
逻辑单元数:4320 逻辑阵列块数:540
LPC11U35FHI33
32位ARM Cortex-M0微控制器;高达128 kB闪存;高达12 kB SRAM和4 kB EEPROM;USB设备;USART接口
TPMIC5504-3.3YM5
TPMIC5504-3.3YM5, 300MA, 15UA, HIGT PSRR VOLTAGE REAULATOR,SOT-23-5,package
电路图
附件下载
代码及JED文件.zip
附件
团队介绍
一人团队,胡道扬,北京理工大学本科在读学生。
团队成员
胡道扬
评论
0 / 100
查看更多
猜你喜欢
2024年寒假练-基于Lattice MXO2的小脚丫FPGA核心板设计具有启动、停止、递增和清除功能的秒表该项目使用了Lattice MXO2的小脚丫FPGA核心板,实现了秒表的设计,它的主要功能为:启动、停止、递增和清除功能。
水波不兴
236
2024年寒假练 - 基于Lattice MXO2的小脚丫FPGA核心板设计具有启动、停止、递增和清除功能的秒表该项目使用了Lattice MXO2的小脚丫FPGA核心板,实现了秒表的设计,它的主要功能为:启动、停止、递增和复位。
Esther
238
2024年寒假练 -基于Lattice MXO2的小脚丫FPGA核心板 -具有启动、停止、递增和清除功能的秒表该项目使用了Lattice MXO2的小脚丫FPGA核心板,实现了具有启动、停止、递增和清除功能的秒表的设计,它的主要功能为:通过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。 秒表使用四个按钮输入:开始、停止、增量和清除(重置)。 开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次); 停止输入使计数器停止递增,但使数码管显示当前计数器值; 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间; 复位/清除输入强制计数器值为零。。
zzkuner
290
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号