VHDL编写多功能数字钟,spartan3 FPGA开发板硬件实现-学习笔记

    • 多功能数字钟硬件测试视频:
    • https://www.bilibili.com/video/av62501230
  • 1.数字钟标准--功能总体描述
    • 1.1 正常时间显示模式
    • 1.2 秒表模式
    • 1.3 闹铃设定模式
    • 1.4 时间设定模式
  • 2. 数字钟系统设计方案及模块组成--系统方案论证
    • 2.1 数字钟系统封装模型
    • 2.2 正常时间显示模块 timer
    • 2.3 秒表模块 stopwatch
    • 2.4 闹铃设定模块 alarm_set
    • 2.5 时间设定模块 time_set
    • 2.6 功能选择控制模块 control
    • 2.7 分频模块 clk_div
    • 2.8 按键消抖 debounce
    • 2.9 蜂鸣器显示模块 alarm_buzz
    • 2.10 数码管显示模块 LED_display
  • 3. 数字钟各功能模块程序原理和VHDL代码编写--算法和程序实现
    • 3.1 正常时间显示模块
      • 3.1.1模块端口说明:
      • 3.1.2模块程序原理:
      • 3.1.3模块程序结构:
      • 3.1.4仿真测试信号设置:
      • 3.1.5仿真结果:
    • 3.2 秒表模块
      • 3.2.1模块端口说明:
      • 3.2.2模块程序原理:
      • 3.2.3模块程序结构:
      • 3.2.4仿真测试信号设置:
      • 3.2.5仿真结果:
    • 3.3 闹铃设定模块
      • 3.3.1模块端口说明:
      • 3.3.2模块程序原理:
      • 3.3.3模块程序结构:
      • 3.3.4仿真测试信号设置:
      • 3.3.5仿真结果:
    • 3.4 时间设定模块
      • 3.4.1模块端口说明:
      • 3.4.2模块程序原理:
      • 3.4.3模块程序结构:
      • 3.4.4仿真测试信号设置:
      • 3.4.5仿真结果:
    • 3.5 功能选择控制模块
      • 3.5.1模块端口说明:
      • 3.5.2模块程序原理:
      • 3.5.3模块程序结构:
      • 3.5.4仿真测试信号设置:
      • 3.5.5仿真结果:
    • 3.6 分频模块
      • 3.6.1模块端口说明:
      • 3.6.2模块程序原理:
      • 3.6.3模块程序结构:
      • 3.6.4仿真测试信号设置:
      • 3.6.5仿真结果:
    • 3.7 按键消抖模块
      • 3.7.1模块端口说明:
      • 3.7.2模块程序原理:
      • 3.7.3模块程序结构:
      • 3.7.4仿真测试信号设置:
      • 3.7.5仿真结果:
    • 3.8 蜂鸣器显示模块
      • 3.8.1模块端口说明:
      • 3.8.2模块程序原理:
      • 3.8.3模块程序结构:
      • 3.8.4仿真测试信号设置:
      • 3.8.5仿真结果:
    • 3.9 数码管显示模块
      • 3.9.1模块端口说明:
      • 3.9.2模块程序原理:
      • 3.9.3模块程序结构:
      • 3.9.4仿真测试信号设置:
      • 3.9.5仿真结果:
  • 4. 数字钟系统实现—down-to-top
    • 4.1系统概述
      • 4.1.1系统的封装端口
      • 4.1.2顶层电路代码:
    • 4.2 系统仿真信号设置
    • 4.3 仿真结果
  • 5. 数字钟系统硬件实现--spartan3 FPGA开发板硬件实现
    • 5.1 ISE使用流程
    • 5.2 硬件测试结果
    • 5.3 出现的问题
  • 6. 电子设计自动化技术课程感想
  • 7.附schematic

多功能数字钟硬件测试视频:

https://www.bilibili.com/video/av62501230

个人理解对于一个数字设计过程是top-down-top。需要先进行功能总体描述,然后系统方案论证,然后再分模块编写代码实现各个模块的功能,接着进行整体系统功能实现,最后进行硬件实现。

1.数字钟标准–功能总体描述

以生活中常用的电子手表为模板描述。数字钟功能框图如下图所示。

图1-1 数字钟系统框图
总共设置三个键:reset 、start 、mode。
用mode 键进行功能转换。根据当前功能模式,通过信号选择将相关的信号传输给数码管。比如正常时间模式就把时间显示的“00-00-00”信号传输给数码管显示即可。start键用于设置时间时的"按一下加1"或者秒表的开启/停止转换。reset键用于时间设置时的位置调整或者作为秒表的复位键。

1.1 正常时间显示模式

正常显示时间:时-分-秒.

1.2 秒表模式

在正常时间显示下,按MODE键一次进入秒表模式.
按START键开始/停止跑秒.
跑秒停止时,按RESET键跑秒数值归0.
按MODE键进入闹铃设定模式,跑秒仍在后台运行.

1.3 闹铃设定模式

在正常时间显示下,按MODE键2次,进入闹铃设定模式
按RESET键选择校准时或者分的十位和个位,再按START键对所选择位加1
按MODE键进入时间设定模式

1.4 时间设定模式

在正常时间显示下,按MODE键三次,进入时间设定状态.
按RESET键可顺序选择校准时,分,秒,再按START键可分别对时,分,秒,加1
按MODE键进入正常显示时间模式

2. 数字钟系统设计方案及模块组成–系统方案论证

功能总体描述和系统方案论证不涉及具体程序设计内容。此两部分在整个数字设计中占据80%的时间也是最具创造力和有趣的部分。编代码和仿真测试则是次要但同样重要的。
系统设计方案关注系统的输入输出端口然后是结合各个模块的功能确定各个功能模块的输入输出端口和内部各功能模块信号的相互传输。这一步需要反复分析才能使得系统完善,输入输出的信号可能不够或者多余。进一步查漏不缺则是在编程的时候了。
初步思路如图1-1系统功能框图。更详细的思路如图2-1数字钟顶层电路。“所见即所得”,数字钟设计即给LED不同的信号,使其看起来像数字钟一样。

图2-1数字钟顶层电路

2.1 数字钟系统封装模型


图2.1-1数字钟系统封装模型
端口与生活中的电子表相同。
输入:FPGA开发板的主时钟clk_main和三个按键信号mode,reset,start。
输出:select为LED位选信号,display为LED的段选信号,buzz为闹铃。

2.2 正常时间显示模块 timer


图2.2-1 正常时间显示模块
功能:此模块实现计时功能,显示“时-分-秒”。需要注意的是需要输入时间设定的值才能实现时间可调整。
输入输出端口:输入clk1,clk200,clk1k分别为1Hz,200Hz,1000Hz时钟信号。clk1用于时钟秒个位计时信号,clk200在本模块的最终设计中未使用。clk1k信号用于位选信号select输出,display为随select同步输出的LED的段选信号,其最终输入到数码管显示模块进行译码,然后输出到硬件的数码管。timeset为时间设定值,当时间设定使能信号timeset_en有效时,将时间设定值传输给当前时间。输出Time为当前时间。

2.3 秒表模块 stopwatch


图2.3-1 秒表模块
功能:在正常时间显示下,按MODE键1次,进入秒表模式,按START键开始/停止跑秒.
跑秒停止时,按RESET键跑秒数值归0.若未按start键使秒表停止,reset键不会有效,秒表仍然在运行,切换功能后秒表仍然在后台运行。
输入输出端口:clk100为100Hz的时钟信号,为秒表百分秒的计时信号。stopwatch_en为秒表使能信号由控制模块输出到秒表模块,当使能信号有效时,reset和start键才能控制秒表。输入reset和start为功能键。输入信号clk1k和输出信号select,display意义与正常时间显示模块相同。

2.4 闹铃设定模块 alarm_set


图2.4-1 闹铃设定模块
功能:在正常时间显示下,按MODE键2次,进入闹铃设置。按RESET键选择设定时或者分的十位和个位,再按START键对所选择位加1,也可以设置秒的十位和个位,但是闹铃仅判断时和分来决定是否触发蜂鸣器。
输入输出端口:clk200为200Hz的时钟信号,其用于产生200Hz的位选信号,再将其用于闹铃正在设定位,可以实现正在设定位以人眼可见频率闪烁,而其他数码管仍然常亮,因为后者是采用1000Hz的扫描信号。alarm_en为控制模块输出的使能信号。输入reset和start为功能键。输入信号time为当前时间。输出信号alarm_set为闹铃设定值。输入信号clk1k和输出信号select,display意义与正常时间显示模块相同。

2.5 时间设定模块 time_set


图2.5-1 时间设定模块
功能:在正常时间显示下,按MODE键3次,进入时间设置。按RESET键选择设定时或分或秒的十位和个位,再按START键对所选择位加1。这与闹铃设定模块功能其实相似。
输入输出端口:输入信号timeset_en为控制模块输出的时间设置模式使能信号。输出信号time_set为时间设定值。其他输入,输出信号意义与闹铃设定模块相同。

2.6 功能选择控制模块 control


图2.6-1 功能选择控制模块
功能:根据Mode按下的次数输出使能信号以使某个功能模块开始工作,另外输出表征当前状态的信号statereg用于选择判断输出到LED的信号。
输入输出端口:mode为输入按键信号,各功能模块使能信号time_en,timeset_en,stopwatch_en,alarm_en分别用于时间显示,时间设定,秒表,闹铃设定。

2.7 分频模块 clk_div


图2.7-1 分频模块
功能:对输入主时钟信号clk_main进行分频。对于Spartan 3 开发板clk_main=33.8688MHz。
输入输出端口说明:输入信号为主时钟clk_main,输出信号为分频后的不同频率的时钟信号。

2.8 按键消抖 debounce


图2.8-1 按键消抖模块
功能:将输入的按键信号经过按键消抖输出稳定的按键信号。
输入输出端口:输入按键信号mode,reset,start;输出为消抖后的按键信号mode,reset,start

2.9 蜂鸣器显示模块 alarm_buzz


图2.9-1 蜂鸣器显示模块
功能:当前时间和闹铃设定时间的小时和分钟相同时则触发蜂鸣器,响一分钟。
输入输出端口:输入信号clk1k用于驱动蜂鸣器。输入信号time为当前时间,alarmset为闹铃设定时间。输出为驱动蜂鸣器的buzz信号即1000Hz的信号。
注:此处考虑了开发板的蜂鸣器是无源蜂鸣器,需要输入一定频率的信号才能响,只输入稳定的高电平不会响。

2.10 数码管显示模块 LED_display


图2.10-1 数码管显示模块
功能:根据当前状态选择各个功能模块的输出信号然后译码输出到数码管。
输入输出端口:输入信号statereg为当前状态编码,由控制模块输出。输入的select和display为各个功能模块输出的位选信号和未译码的段选信号。输出的select和display为译码后的位选信号和段选信号。

3. 数字钟各功能模块程序原理和VHDL代码编写–算法和程序实现

在具体程序设计过程中有些小改动,各功能模块的模块端口与最初的系统设计方案略有不同。端口命名最好体现此端口的作用。下面有些端口命名不太好,如正常时间显示模块的输入clk最好命名成clk1,这样表明这个端口用于输入1Hz的时钟信号。输出端口count_1在程序中是用于输出秒钟的个位,就适合直接命名成secl。由于此设计已经做完了,难得改了-。部分信号的功能在“1数字钟标准”和“2数字钟系统设计方案及模块组成”部分已详细描述就不再赘述。

3.1 正常时间显示模块

3.1.1模块端口说明:

输入:hourhset(3:0),hourlset(3:0),minhset(3:0),minlset(3:0),sechset(3:0),seclset(3:0)为时间设定模块输出的时间设定值,分别是小时的十位、个位,分钟的十位、个位,秒的十位、个位。它们的范围都是0~9,所以需要4比特位宽(小时的十位最大为2)。Clk端口用于输入1Hz信号,Clk200用于输入200Hz信号,clk_scan用于输入扫描时钟信号,为1000Hz。time_set_en为时间设置使能信号,当其有效是将时间设定值赋给当前时间.
输出:count_1(3:0), count_2(3:0), count_3(3:0), count_4(3:0), count_5(3:0), count_6(3:0)分别用于输出当前时间secl,sech,minl,minh,hourl,hourh。decode(3:0)用于输出数码管的未译码的段选信号,wire(7:0)用于输出数码管的位选信号。

3.1.2模块程序原理:


1) 与计数器原理相同。计时到60秒“00-00-59”,分钟+1“00-01-00”;计时到60分“00-59-59”,时钟+1“01-00-00”。计时到“23-59-59”,归零“00-00-00”
2)在计时过程中同时判断time_set_en是否有效,有效时将时间设定值赋给当前时间
3)数码管扫描显示:spartan3 FPGA开发板上的是共阴极数码管,其位选信号和段选信号都是‘1’有效

3.1.3模块程序结构:

1)计时

begincount_1<=secl;count_2<=sech;count_3<=minl;count_4<=minh;count_5<=hourl;count_6<=hourh;          process(clk,time_set_en)     begin if time_set_en='1'  then  --时间设置使能信号有效时secl<=seclset;         --将设置的时间赋给当前值sech<=sechset;         elsif clk'event and clk='1' thenif secl="1001" thensecl<="0000";if sech="0101" then      sech<="0000";   c1<='1';            --判断是否为59,若是则产生进位信号else sech<=sech+1;c1<='0';end if;else secl<=secl+1;c1<='0';                end if;     end if;     end process; process(c1,time_set_en)                                                        beginif time_set_en='1' then minl<=minlset;minh<=minhset;elsif c1'event and c1='1' then if minl="1001" thenminl<="0000";if minh="0101" then minh<="0000";   c2<='1';else minh<=minh+1;c2<='0'; end if;else minl<=minl+1;c2<='0';               end if;     end if; end process;process(c2,time_set_en)              --以分的进位作为分钟计时的使能信号beginif time_set_en='1' then hourl<=hourlset;hourh<=hourhset;     elsif c2'event and c2='1' then              if hourl="1001" thenhourl<="0000";if hourh="0101" then hourh<="0000";   c3<='1';                --判断是否为23else hourh<=hourh+1;c3<='0'; end if;else hourl<=hourl+1;c3<='0';             end if;     end if;         end process;

2)生成数码管扫描显示信号

process(count7,clk_scan)        --位选begin     if clk_scan'event and clk_scan='1' then if(count7="111")thencount7<="000";      elsecount7<=count7+'1';    end if;end if;end process; process(count7)begin                      --decode为未经过译码的段选信号case count7 is               --即原始的数字when"000"=>wire<="10000000";decode<=hourh;  --hourhwhen"001"=>wire<="01000000";decode<=hourl;   --hourlwhen"010"=>wire<="00100000";decode<="1111";    -- 单杆when"011"=>wire<="00010000";decode<=minh;  --minhwhen"100"=>wire<="00001000";decode<=minl;     --minlwhen"101"=>wire<="00000100";decode<="1111";     -- 单杆when"110"=>wire<="00000010";decode<=sech;  --sechwhen"111"=>wire<="00000001";decode<=secl;     --seclwhen others => wire<="00000000";decode<=hourh;end case;end process;end archtimer;

3.1.4仿真测试信号设置:

 process --无限循环 产生时钟信号   beginclk <='0';wait  for 500 ms;clk<='1';wait for  500 ms;  end process;process --无限循环 产生时钟信号beginclk_scan<='0';wait for 0.5 ms;clk_scan<='1';wait for 0.5 ms;      end process;process --无限循环 产生时钟信号beginclk200<='0';wait for 2.500 ms;clk200<='1';wait for 2.500 ms;      end process;processbegin time_set_en<='0';hourhset <="0000";hourlset <="0000";minhset <="0001";minlset <="0001";sechset <="0001";seclset <="0001";wait for 5000ms;time_set_en<='1'; --测试时间设置信号 是否有效   wait for 5000ms;time_set_en<='0';wait;  end process;
end TB_ARCHITECTURE;

3.1.5仿真结果:

图1时间设置信号有效,计时功能正常;图2秒到分的进位正常。

3.2 秒表模块

3.2.1模块端口说明:

输入:clk1k,clk1000分别用于输入1000Hz信号;reset和start用于输入按键信号。Stopwatch_en 秒表模块的使能信号。
输出:min_h,min_l,sec_h,sec_l,sec_100h,sec_100l分别为秒表运行时的分钟的十位和个位,秒的十位和个位;百分秒的十位和个位。stopwatch_display,stopwatch_display_select
为秒表模块输出的数码管的段选和位选信号。

3.2.2模块程序原理:

1)秒表计时与正常时间显示模块原理相同。注意百分秒计时到100再进位。
2)start键控制秒表的开始和停止
3)reset键只有在秒表停止时才有效

3.2.3模块程序结构:

 beginsec_100h<=sec100h ;     --秒表显示 分,秒,百分秒sec_100l<=sec100l ;min_h<=minh ;min_l<=minl ;sec_h<=sech ;sec_l<=secl ;process(stopwatch_en,start)    --控制秒表开始/暂停begin                            if stopwatch_en='1' then if start'event and start='1' then       start_en<=not start_en;  --用start_en的变化来记录start键的按下。end if; end if;end process;process(clk100,start_en,stopwatch_en,reset)  begin        if start_en='0' then       --秒表停止if reset='0' then          --此进程敏感信号列表有clk100,可实现时刻检测reset是否按下resettmp<='0';          --reset按下时按键值为’0‘ sec100h<=(others=>'0');--resettmp赋值为0,用于另两个进程。sec100l<=(others=>'0');--复位else resettmp<='1';end if;                elsif clk100'event and clk100='1' thenif stopwatch_en='1'    then if start_en='1' then      --开始计时             if sec100l="1001" thensec100l<="0000";if sec100h="1001" then sec100h<="0000";         c1<='1';              --产生进位else sec100h<=sec100h+1;c1<='0';end if;else sec100l<=sec100l+1;c1<='0';                end if;          end if;                               end if;  end if; end process ;process(c1,start_en,resettmp)beginif start_en='0' thenif resettmp='0' thensech<="0000"; secl<=(others=>'0');        end if;              elsif c1'event and c1='1' thenif secl="1001" thensecl<="0000";if sech="0101" then sech<="0000";   c2<='1';else sech<=sech+1;c2<='0';end if;else secl<=secl+1;c2<='0';             end if;     end if;     end process;process(c2,start_en,resettmp)begin if start_en='0' thenif resettmp='0' thenminh<=(others=>'0') ;minl<=(others=>'0') ;        end if; elsif c2'event and c2='1' thenif minl="1001" thenminl<="0000";if minh="0101" then minh<="0000";   c3<='1';else minh<=minh+1;c3<='0'; end if;else minl<=minl+1;c3<='0';             end if;     end if;                 end process;

数码管扫描显示信号的生成与3.1.3相同

3.2.4仿真测试信号设置:

 process      --无限循环进程产生时钟信号beginclk1k<='0';wait for 0.5 ms;clk1k<='1';wait for 0.5 ms;      end process;process      --无限循环进程产生时钟信号beginclk100<='0';wait for 5 ms;clk100<='1';wait for 5 ms;        end process; processbeginreset<='0';stopwatch_en<='0';wait for 40000 ms;stopwatch_en<='1';wait for 40000 ms;reset<='1';  wait for 40000 ms;wait;end process;process      --start 键周期性按下beginstart<='0';wait for  10000 ms;start<='1';wait for  10000 ms;        end process;
end TB_ARCHITECTURE;

3.2.5仿真结果:

秒表计时功能正常,且reset=0只在start_en=0即秒表停止时才将秒表值归零。

3.3 闹铃设定模块

3.3.1模块端口说明:

输入:secl,sech,minl,minh,hourl,hourh用于输入当前时间,alarm_en为闹铃使能信号。
输出:hourhset,hourlset,minhset,minlset,sechset,seclset为闹铃设定时间。
其他信号不再赘述。

3.3.2模块程序原理:


1)用计数器来标志当前选择设置的是时,分,秒的个位或十位,再检测start键结合case语句使相应位加1。注意小时最大23,秒和分最大是59。
2)闹铃正在设定位以人眼可见频率闪烁,其他位仍然看起来常亮。生成两个不同频率的扫描位选信号,将低频率的扫描位选信号赋给闹铃正在设定位即可。

3.3.3模块程序结构:

hourhset<=hourh_set;hourlset<=hourl_set;minhset<=minh_set;minlset<=minl_set;sechset<=sech_set;seclset<=secl_set;--进入闹铃设定模式--reset 选择时、分、秒process(reset)         beginif reset'event and reset='1'then    --检测reset沿变化位置选择if alarm_en='1' then if(alarm_select<7) then alarm_select<=alarm_select+1;   --用计数器来标志reset按下的次数elsealarm_select<="000";       end if; end if; end if;   end process;process(start)       begin                              --检测按键沿变化if start'event and start='1' then --若是检测电平,则可以实现长按start键连续加一.if alarm_en='1' then           --且闹铃使能信号有效case alarm_select is           when "000" => if (hourh_set<2)       --小时的十位then hourh_set<=hourh_set+1;  else hourh_set<="0000";end if;        when "001" => if (hourl_set<9)       --小时的个位then hourl_set<=hourl_set+1;else hourl_set<="0000";end if;when "010"=> null ;               --单杠,无操作when "011" => if (minh_set<5)      --分钟的十位then minh_set<=minh_set+1;else minh_set<="0000";end if;when "100" => if (minl_set<9)    --分钟的个位then minl_set<=minl_set+1;else minl_set<="0000";end if;when "101"=> null;               --单杠,无操作when "110" => if (sech_set<5)      --秒的十位then sech_set<=sech_set+1;else sech_set<="0000";end if;   when "111" => if (secl_set<9)   --秒的个位then secl_set<=secl_set+1;else secl_set<="0000";end if;when others =>hourh_set<=hourh;hourl_set<=hourl;minh_set<=minh;     sech_set<=sech;     minl_set<=minl;secl_set<=secl;end case;    end if;end if;end process;

数码管扫描显示信号的生成与3.1.3相同

3.3.4仿真测试信号设置:

  processbeginhourh <= "0001";  --12:00:00hourl <= "0010";minh <= "0000";minl <= "0000";sech <= "0000";secl <= "0000";               reset<='0';       wait for  50ms;reset<='1';        wait for 50ms;alarm_en <='1';wait for 50ms;alarm_en <='1';end process;-- 时钟信号和按键加1信号processbeginclk1k<='0';wait for 0.5 ms;clk1k<='1';wait for 0.5 ms;      end process;processbeginclk200<='0';wait for 2.5 ms;clk200<='1';wait for 2.5 ms;        end process;processbeginstart<='0';wait for  5ms;start<='1';wait for  5ms;      end process;
end TB_ARCHITECTURE;

3.3.5仿真结果:

实现了reset按一下,设定位改变;start键按一下相应位加1。

3.4 时间设定模块

3.4.1模块端口说明:

此模块直接复制的闹铃设定模块的代码,注意此处alarm_en用于输入time_set_en。

3.4.2模块程序原理:

原理与闹铃设定模块相同。

3.4.3模块程序结构:

程序结构直接复制闹铃模块。稍有改动,此处设置了一个start_reg变量记录start键按下的次数。为何设置参考“5.3 出现的问题”

process(start,hourh,hourl,minh,minl,sech,secl)      begin if start='0' then if alarm_en='1' thenstart_reg<=start_reg+1;   case alarm_select is            when "000" => if (hourh_set<2) then hourh_set<=hourh_set+1;else hourh_set<="0000";end if;       when "001" => if (hourl_set<9) then hourl_set<=hourl_set+1;else hourl_set<="0000";end if;when "010"=> null ;  when "011" => if (minh_set<5) then minh_set<=minh_set+1;else minh_set<="0000";end if;       when "100" => if (minl_set<9) then minl_set<=minl_set+1;else minl_set<="0000";end if;when "101"=> null;   when "110" => if (sech_set<5) then sech_set<=sech_set+1;else sech_set<="0000";end if;   when "111" => if (secl_set<9) then secl_set<=secl_set+1;else secl_set<="0000";end if;when others =>hourh_set<=hourh;hourl_set<=hourl;minh_set<=minh;minl_set<=minl;sech_set<=sech;secl_set<=secl;end case;  end if;end if;if start_reg="000000" thenhourh_set<=hourh;hourl_set<=hourl;minh_set<=minh;minl_set<=minl;sech_set<=sech;secl_set<=secl;end if;end process;

3.4.4仿真测试信号设置:

与闹铃模块相同。

3.4.5仿真结果:

与闹铃模块相同。
注:时间显示和时间设定模块
3.1的正常时间显示模块和3.4的时间设定模块此处封装在时间显示和时间设定模块之中。

模块端口说明:
输入:clk,clk_scan,clk200分别用于输入1,1000,200Hz信号;reset,start为时间设置时的按键信号。time_en信号在具体程序中未使用。time_set_en用于输入时间设定使能信号。
输出:count_1(3:0), count_2(3:0), count_3(3:0), count_4(3:0), count_5(3:0), count_6(3:0)分别用于输出当前时间secl,sech,minl,minh,hourl,hourh。decode(3:0) ,wire(7:0)分别是正常时间显示模块用于输出到数码管的未译码的段选信号和位选信号。time_set_display和time_set_display_select分别是时间设定模块用于输出到数码管的未译码的段选信号和位选信号。封装在同一模块中,信号传输思路更清晰。

begin                                            newtime_setuut: newtime_set --时间设置  port map(clk1k=>clk_scan,--scanclk200=>clk200 ,reset=>reset,start=>start,alarm_en=>time_set_en_tmp,hourh=>count6,hourl=>count5 ,minh=>count4 ,minl=>count3 ,sech=>count2,secl=>count1,  hourhset=>hourhset,hourlset=>hourlset,minhset=>minhset,minlset=>minlset,sechset=>sechset,seclset=>seclset,   alarm_display =>time_set_display_tmp,       -- 源数据alarm_display_select=>time_set_display_select_tmp    --位选     正在设定位以人眼可见频率闪烁 );      time_set_display        <= time_set_display_tmp ;      --  output registertime_set_display_select   <= time_set_display_select_tmp;  --   output register       time_set_en_tmp         <= time_set_en;  --input registertimeruut: timer          --计时port map(clk=>clk,clk200 =>clk200,clk_scan =>clk_scan,time_set_en=>time_set_en_tmp,count_1=>count1,count_2 =>count2 ,count_3 =>count3 ,count_4 =>count4 ,count_5 =>count5,count_6 =>count6,hourhset=>hourhset,hourlset=>hourlset,minhset=>minhset,minlset=>minlset,sechset=>sechset,seclset=>seclset,          decode =>decode,wire =>wire);count_1<=count1;count_2<=count2;count_3<=count3;count_4<=count4;count_5<=count5;count_6<=count6;

3.5 功能选择控制模块

3.5.1模块端口说明:

输入:mode为按键信号
输出:statereg为当前状态,其表示当前为何种功能模式;其他为各个功能模块的使能信号。

3.5.2模块程序原理:

采用计数器记录mode键按下的次数,然后输出对应功能模块的使能信号,同时将当前状态statereg输入到数码管显示模块中由此决定数码管显示。

3.5.3模块程序结构:

 process(mode) --mode键计数和生成当前状态编码beginif mode'event and mode='1' then if state<"11" then state<=state+1;else state<="00";end if;     end if;end process;statereg<=state; process(state) --根据当前状态输出使能信号begincase state iswhen "00" => time_en<='1';stopwatch_en<='0'; alarm_en<='0';   time_set_en<='0';    when "01" => time_en<='0';stopwatch_en<='1'; alarm_en<='0';  time_set_en<='0';    when "10" => time_en<='0';stopwatch_en<='0'; alarm_en<='1';  time_set_en<='0';    when "11" => time_en<='0';stopwatch_en<='0'; alarm_en<='0';  time_set_en<='1';        when others=> time_en<='1'; stopwatch_en<='0'; alarm_en<='0';  time_set_en<='0';      end case; end process;

3.5.4仿真测试信号设置:

processbeginmode<='0';wait for 2 ns;mode<='1';wait for 2 ns;
end process;

3.5.5仿真结果:

实现了按mode键,在四个功能模式之间轮回转换。

3.6 分频模块

3.6.1模块端口说明:

输入:clk用于输入主时钟,spartan3 开发板的晶振频率为33.8688MHz
输出:en1,en1k,en100,en200分别用于输出1,1000,100,200Hz。

3.6.2模块程序原理:

分频原理。此处采用1/N占空比的分频。

3.6.3模块程序结构:

begin if(clk'event and clk='1') thenif (count1k<33867) then   count1k<=count1k+1;     en1k<='0';else count1k<=(others=>'0');en1k<='1';   end if;   end if;end process;  process(clk)   --分频出100hz做秒表百分位计时信号begin if(clk'event and clk='1') thenif (count100<338679) then              count100<=count100+1;     en100<='0';     --1/338680 占空比else count100<=(others=>'0');  en100<='1';   end if;   end if;end process;     process(clk)    --分频出200hz做使能信号begin if(clk'event and clk='1') thenif (count200<169340) then     count200<=count200+1;en200<='0';else count200<=(others=>'0');  --1/169343 占空比en200<='1';   end if;   end if;end process;     process(clk)    --分频出1hz做使能信号begin if(clk'event and clk='1') thenif (count1<33868799) then       --1/33868800 占空比count1<=count1+1;en1<='0';       else count1<=(others=>'0');en1<='1';      end if;   end if;end process;

3.6.4仿真测试信号设置:

仿真时仅测试程序逻辑是否正确,测试33.8688MHz分频耗时太久。此处在tesetbench中测试对1000Hz主时钟的分频效果。

3.6.5仿真结果:

实现了en1为1/1000占空比,en100为1/10占空比,en200为1/5占空比.说明程序逻辑正确。只需再如 “3.6.3模块程序结构”设置数据即可。

3.7 按键消抖模块

3.7.1模块端口说明:

输入:Key_in 用于输入按键信号。Clk100hz用于
输出:Key_out用于输出消抖后的按键信号。

3.7.2模块程序原理:

抖动时间长短由按键机械特性决定,一般为5ms~10ms,按键抖动会引起信号误读。人按键速度至多10次/秒,即一次按键时间100ms,假设按键按下的有效时间是50ms,使用采样频率为100HZ,则一次按键大约可以采样5次。相邻两次采样数据相同则认为有效。按键按下时电平为‘0’。逻辑电路如下图(老师画的)

3.7.3模块程序结构:

process(clk100hz)beginif(clk100hz'event and clk100hz='1')thenD1<=key_in;  --两级延时D2<=D1;end if;end process; S<=D1 and D2;          --SR latchR<=(NOT D1) AND (NOT D2);--可使硬件中的信号更稳定DLY<=R NOR DLY_N;    DLY_N<=S NOR DLY;key_out<=DLY;

3.7.4仿真测试信号设置:

process  --生成时钟信号设置抖动信号begin clk100hz<='0';wait for 5ms;clk100hz<='1';wait for 5ms;          end process;process --设置抖动信号begin   --按键按下为‘0’key_in<='0';wait for 6ms;key_in<='1';wait for 6ms;--抖动6mskey_in<='0';wait for 6ms;key_in<='1';wait for 8ms;--抖动8mskey_in<='0';wait for 50ms;--信号稳定50mskey_in<='1';  --模拟按键松开的抖动wait for 6ms;key_in<='0';wait for 8ms;-- 抖动8mskey_in<='1';wait for 6ms;key_in<='0';wait for 6ms;--抖动6mskey_in<='1';wait;--信号稳定end process;

3.7.5仿真结果:

输出的按键信号稳定。

3.8 蜂鸣器显示模块

3.8.1模块端口说明:

输入分别为闹铃设定时间和时间显示模块输出的当前时间。输出buzz为蜂鸣器的驱动信号。

3.8.2模块程序原理:

3.8.3模块程序结构:

 --比较当前时间和闹铃设定时间,输出一分钟的蜂鸣器process(clk200,hourhset,hourlset,minhset,     --敏感信号列表包含clk200minlset,sechset,seclset,hourh,hourl,minh,minl)--,这样时刻检测闹铃时间和当前时间是否相等beginif (hourhset=hourh) and (hourlset=hourl) and (minhset=minh) and (minlset=minl) then     buzz<=clk200;     --本开发板 为无源蜂鸣器 elsebuzz<='0';end if;   end process;

3.8.4仿真测试信号设置:

 processbeginhourh <="0000";hourl <="0000";minh <="0000";minl <="0001";sech <="0000";secl <="0000";hourhset <="0000";hourlset <="0000";minhset <="0001";minlset <="0001";sechset <="0001";seclset <="0001";wait for 1000ms;secl<= "0001";minh <="0001";wait;end process;

3.8.5仿真结果:

3.9 数码管显示模块

3.9.1模块端口说明:

输入:Statereg为当前状态编码,由功能选择控制模块输出。其他分别为时间显示,时间设定,秒表,闹铃模块输出的数码管的位选信号和未译码的段选信号。

3.9.2模块程序原理:

根据statereg选择当前需要显示的信号,对其进行译码和输出。

3.9.3模块程序结构:

scan_display_select<=scan_display_select_tmp;process(statereg,time_display_select,stopwatch_display_select,alarm_display_select,time_set_display_select,time_display,stopwatch_display,alarm_display,time_set_display)begincase statereg iswhen "00" => scan_display_tmp<=time_display;     scan_display_select_tmp<=time_display_select;when "01" => scan_display_tmp<=stopwatch_display;scan_display_select_tmp<=stopwatch_display_select;when "10" => scan_display_tmp<=alarm_display;    scan_display_select_tmp<=alarm_display_select; when "11" => scan_display_tmp<=time_set_display; scan_display_select_tmp<=time_set_display_select;when others=> scan_display_tmp<=time_display;   scan_display_select_tmp<=time_display_select;end case;         end process;            --译码PROCESS (scan_display_tmp)               --段选begincase scan_display_tmp is when"0000"=> scan_display<="00111111";WHEN"0001"=> scan_display<="00000110"; WHEN"0010"=> scan_display<="01011011"; WHEN"0011"=> scan_display<="01001111"; WHEN"0100"=> scan_display<="01100110"; WHEN"0101"=> scan_display<="01101101"; WHEN"0110"=> scan_display<="01111101"; WHEN"0111"=> scan_display<="00000111"; WHEN"1000"=> scan_display<="01111111"; when"1001"=> scan_display<="01101111";when"1111"=> scan_display<="01000000"; --单杆when others=>scan_display<="00000000"; END CASE; END PROCESS;

3.9.4仿真测试信号设置:

3.9.5仿真结果:

略,这部分适合在上板测试时检查。最终上板测试数码管显示正常。

4. 数字钟系统实现—down-to-top

4.1系统概述

4.1.1系统的封装端口


端口不再赘述。

4.1.2顶层电路代码:

begin--分频sec_1hzuut: sec_1hzport map(     clk=>clk_main,  --33868800en1=>clk1,en100=>clk100,en200=>clk200,en1k=>clk1k); --三个按键消抖debouceuut_mode: debounceport map(clk100hz=>clk100 ,key_in=>mode0,key_out=>mode  );debouceuut_start: debounceport map(clk100hz=>clk100 ,key_in=>start0 ,key_out=>start   );debouceuut_reset: debounceport map(clk100hz=>clk100 ,key_in=>reset0 ,key_out=>reset   ); -- 功能转换control_modeuut: control_mode   -- use recycle states tansition  port map( mode=>mode,time_en=>time_en,   stopwatch_en=>stopwatch_en,alarm_en=>alarm_en,time_set_en=>time_set_en,statereg=>statereg);time_and_set_displayuut: time_and_set_display port map(clk =>clk1,clk200=>clk200,clk_scan=>clk1k,reset=>reset,                  -- 选择 时 分 秒 位置start=>start,                     -- 加一time_en=>time_en,time_set_en=>time_set_en,count_1=>secl,count_2=>sech,count_3=>minl,count_4=>minh,count_5=>hourl,count_6=>hourh ,                   decode=>time_display,           wire=>time_display_select,time_set_display =>time_set_display,            -- 源数据time_set_display_select=>time_set_display_select);      --位选     闹钟正在设定位为 200赫兹 频率闪烁 是对数码管的使能信号                                             --time_set_display--秒表stopwatchuut: stopwatchport map(clk1k=>clk1k,clk100=>clk100,reset=>reset,start=>start ,              --开启和停止stopwatch_en=>stopwatch_en ,        --使能sec_100h=>sec_100h, --秒表显示 分,秒,百分秒sec_100l=>sec_100l ,min_h=>min_h,min_l=>min_l ,sec_h=>sec_h ,sec_l=>sec_l,stopwatch_display=>stopwatch_display,stopwatch_display_select=>stopwatch_display_select);--闹铃显示及设置alarm_clockuut: alarm_clock port map(clk1k=>clk1k,clk200=>clk200 ,reset=>reset ,start=> start,alarm_en=>alarm_en,hourh=> hourh,hourl=>hourl,minh=>minh,minl=>minl,sech=>sech,secl=>secl,    --本闹铃仅设定到分钟,--剩余数码管用于显示闹铃是关闭还是开启
--       buzz=>buzz ,hourhset=>hourhset,hourlset=>hourlset,minhset=>minhset,minlset=>minlset,sechset=>sechset,seclset=>seclset,alarm_display=>alarm_display,    --扫描输出的,源数据,还未译码alarm_display_select=>    alarm_display_select  --闹钟正在设定位为 200赫兹 频率闪烁 这是对数码管的使能信号);   alarm_buzzuut: alarm_buzz          --闹铃 蜂鸣器显示port map ( clk200=>clk200,hourh=>hourh ,hourl=>hourl ,minh =>minh,minl=>minl ,sech=>sech ,secl=>secl ,   --本闹铃仅设定到分钟,--剩余数码管用于显示闹铃是关闭还是开启    buzz=>buzz ,hourhset=>hourhset,hourlset=>hourlset,minhset=>minhset,minlset=>minlset,sechset=>sechset,seclset=>seclset) ;--信号选择及数码管 译码显示 LED_displayuut: LED_displayport map(statereg=>statereg,time_display=>time_display,stopwatch_display=> stopwatch_display,alarm_display=>alarm_display,time_set_display=>time_set_display,time_display_select=>time_display_select,   stopwatch_display_select=>stopwatch_display_select ,alarm_display_select=> alarm_display_select,time_set_display_select=>time_set_display_select,scan_display_select=> scan_display_select,scan_display=>scan_display   --此已被译码信号可直接作用于数码管 );

4.2 系统仿真信号设置

processbegin          --此处以1000Hz为主时钟clk_main<='0';--对应分频模块也改成对1000Hz进行分频wait for 0.5 ms;clk_main<='1';wait for 0.5 ms;      end process; process    begin--测试秒表及计时mode0<='0';reset0<='0';start0<='1';wait for 10ms;mode0<='1';wait for 50ms;start0<='0';wait for 50ms;start0<='1';wait for 5000ms;--秒表计时5秒,正常时间显示也是5秒        ---测试闹铃设置  wait for 50ms;mode0<='0';wait for 50ms;mode0<='1';       --选择位置--1wait for 50ms;reset0<='0';wait for 50ms;reset0<='1';--2wait for 50ms;reset0<='0';wait for 50ms;reset0<='1';        --3wait for 50ms;reset0<='0';wait for 50ms;reset0<='1';     --4wait for 50ms;reset0<='0';wait for 50ms;reset0<='1';     --闹铃分钟低位minl设置成2--1wait for 50ms;start0<='0';wait for 50ms;start0<='1';     --2wait for 50ms;start0<='0';wait for 50ms;start0<='1';     --时间设置wait for 50ms;mode0<='0';wait for 50ms;mode0<='1';        --时间minl 设置成2       --1wait for 50ms;reset0<='0';wait for 50ms;reset0<='1';--2wait for 50ms;reset0<='0';wait for 50ms;reset0<='1';      --3wait for 50ms;reset0<='0';wait for 50ms;reset0<='1';     --4wait for 50ms;reset0<='0';wait for 50ms;reset0<='1';         --1wait for 50ms;start0<='0';wait for 50ms;start0<='1';     --2wait for 50ms;start0<='0';wait for 50ms;start0<='1'; wait for 50ms;mode0<='0';wait for 50ms;mode0<='1';

4.3 仿真结果

由于本次数字钟设计比较简单,可以肉眼验证波形但也比较容易出错。更复杂的设计则需要编代码自动检查波形以提高效率。仿真也只是初步检查设计的功能是否正常,进一步的检查则是在硬件测试的时候了。
按测试顺序,分别验证各模块信号是否正常。图1可见计时功能正常,图2秒表功能正常,图3闹铃设置时minl设置为2正常,图4时间设定仿真不正常本应设置成2,结果却是3.(这是我做完之后总结的,当时没注意,但是上板测试是正常的。可能是时间设定模块的检测start电平部分有问题,有兴趣的同学可以探究一下,改成和闹铃设定模块相同的沿检测形式,或者敏感变量调整一下)



5. 数字钟系统硬件实现–spartan3 FPGA开发板硬件实现

5.1 ISE使用流程

Windows10安装ISE出现一些问题,最终是在windows xp professional虚拟机里面烧录的。

  1. 新建项目: file–>new project并选择器件,此处选择spartan3 XCS200 PQ208 -4
  2. 添加源文件:此处是导入已有文件
  3. 设计综合:选中顶层文件,点击Synthesize-XST
  4. 添加管脚约束:点击I/O pin Planning(PlanAhead)-Post-Synthesis
  5. 设计实现:点击implementation
  6. 生成设计文件:点击generate program files。注意先右键–>Process Properties–>Startup options–>JTAG Clock 这样是生成*.bit文件可烧录到FPGA中.
  7. FPGA编程下载: 点击configure target device–>boundary scan–>initial chain–>program FPGA
    管脚约束如下:
    可在图形界面配置。

5.2 硬件测试结果

视频: https://www.bilibili.com/video/av62501230

5.3 出现的问题

问题1: 设置完闹铃后按mode键会进入时间设定模式,虽然在未操作按键进行时间设定,但还是会把时间设定模块输出的值赋给当前时间。于是在时间设定模块设置了star_reg记录start键按下的次数。这样进入时间设定模块时的初值就是当前时间,但是得快速按mode键,切换到正常时间显示模式。
问题1解决办法: 进入某个模式若使用按键触发了该模式的功能后再次按mode键则是返回正常时间显示模式,否则进入下一个模式。这样在使用了某个功能模块后再按mode键就不会再进入下一个功能模式,就避免了必然会进入时间设定模式。
对于秒表模式切换到闹铃模式不会对当前计时造成影响。所以仅需更改闹铃模式。
注意到原设计按mode键切换到闹铃模式后,再按start键就直接可以使hourh+1。
可以这样更改:在功能选择控制设置一个中间状态Stemp代表闹铃模式,然后若按reset键才会进入hourh的设置再按mode键会返回正常时间显示模式;或者若按mode键则会进入时间设定模式。
**问题2:**硬件测试时发现,程序烧录进去会进入秒表模式而不是正常时间显示模式。原因在于开发板的按键是按下为“0”,不按下为“1”,而3.5 中功能转换模块是检测到mode键为“1” 上升沿进行模式转换,所以烧录后mode键的信号由 不定态 变为 “1”,此刻出现一个上升沿,所以状态转换一次。
问题2解决办法: 3.5 功能转换模块改为检测下降沿,或者设置全局复位使系统进入初始状态即正常时间显示模式。
问题3: 没有设置全局复位键,对于硬件系统烧录程序后可能产生不稳定的信号以致系统进入某一状态,应设置全局复位信号,将代码中的相关参数赋初值,使系统进入代码中的初始状态。
问题3解决办法: 设置一个按键为复位键,将其下降沿作为全局复位信号。

6. 电子设计自动化技术课程感想

组合逻辑:利用基本与、或、非门实现各种组合逻辑功能。
时序逻辑:利用基本的D触发器和组合逻辑实现各种时序逻辑电路功能。
对于某一个功能模块的实现可理解为模型建立和模型实现两大部分:
输入输出端口说明和功能描述以及具体程序算法则是模型建立的过程。VHDL程序设计即为模型实现的过程。
对于复杂设计采用top-down-top。先top-down分解任务至最小功能模块然后down-top将小模块按初步构思的顶层电路连接起来实现系统功能。

7.附schematic

VHDL编写多功能数字钟,spartan3 FPGA开发板硬件实现-学习笔记相关推荐

  1. Xilinx FPGA开发板 Digilent Spartan-3E 学习资料整理

    很多人抱怨Xilinx FPGA的资料很难找,Digilent的板卡资料网上怎么就没有呢!针对这些问题写了如下的BLog,希望对大家有帮助. 最近几日在整理关于Xilinx FPGA和Digilent ...

  2. Xilinx FPGA开发板 Digilent Spartan-3E 学习资料

    一.FPGA相关资料贴 EDK实验 base in spartan-3e 适合mircoblaze初学者  MicroBlaze嵌入式软核是一个被Xilinx公司优化过的可以嵌入在FPGA中的RISC ...

  3. Xilinx Spartan-6 FPGA开发板硬件说明书(1)

    前 言 TL-S6Box是广州创龙基于Xilinx Spartan-6 FPGA设计的高速数据采集处理开发板,采用核心板+底板的设计方式,尺寸为18cm*13cm,它主要帮助开发者快速评估核心板的性能 ...

  4. 数字系统设计(FPGA)课程设计: 多功能数字钟

    一.目的: 实现多功能数字钟,具备下列功能: 1.数字钟:能计时,实现小时.分钟.秒的显示: 2.数字跑表:精度至0.01秒 比如显示12.97秒: 3.闹钟: 可以设定闹钟,用试验箱上的蜂鸣器作为闹 ...

  5. 基于Quartus II软件的FPGA综合实验——多功能数字钟

    有很多自制元器件,内部电路附在文章中 文章目录 前言 一.设计要求 二.设计原理 三.设计过程 1.数码管扫描模块 2.计时模块 3.闹钟模块 4.闹钟响铃模块 5.数码管显示模块 6.整点报时功能 ...

  6. FPGA实现多功能数字钟(Verilog)

    FPGA实现多功能数字钟(Verilog) 介绍 整体框架 模块介绍 按键模块 主体计数模块 调整时间(日期)和设置闹钟 秒表模块 闹钟音乐模块 数码管显示 介绍 本文设计的数字钟的功能包括:正常时钟 ...

  7. EDA实验(Quartus Ⅱ+fpga) (五)---多功能数字钟设计

    前言: 本文主要介绍了EDA原理与应用这门课程的相关实验及代码.使用的软件是Quartus Ⅱ,该实验使用fpga芯片为cycloneⅤ 5CSEMA5F31C6. (一)实验目的 (1)了解数字钟的 ...

  8. 【verilog】多功能数字钟的设计

    实验目的 掌握数字钟的工作原理. 掌握计数器级联构成更大模值计数器的方法.  能用verilog描述简单的时序逻辑电路. 实验原理 多功能数字钟应该具有的基本功能有:显示时-分-秒.整点报时.小时和分 ...

  9. eda多功能数字钟课程设计_《多功能数字钟》EDA实验报告

    <EDA课程设计> 1.摘要 实验报告 多功能数字钟 姓 名: 学 号: 联系方式: 成 绩: 在当代,随着人类社会进入到高度发达的信息化社会.信息技术的发展起着越来越大的作用,它几乎涉及 ...

最新文章

  1. Linux的scp命令
  2. openresty开发系列24--openresty中lua的引入及使用
  3. 身份证到期需更换 警方提醒市民提前办理
  4. cocos 报错dts文件未导入_cocos2dx 3.4项目 导入到 eclipse 爬过的坑
  5. 自动化测试 div sendkeys无效_【自动化测试】【JestSelenium】(04)—— Selenium WebDriver...
  6. Linux软件源apt,仓库,包的概念
  7. ISLR学习笔记(2)线性回归
  8. 【codeforces】【比赛题解】#849 CF Round #431 (Div.2)
  9. 数据结构之线性表(附代码)
  10. 收藏 | 深度学习pytorch训练代码
  11. android switch控件的大小,关于Android Action Bar 上的 Switch控件
  12. python类属性数据三维图_Python图表属性
  13. 【Objective-C】类与结构体的区别
  14. linux的ps命令
  15. 程序员实用算法 源码_程序员必须知道的十大基础实用算法综述
  16. 串口监视软件_力控监控组态软件与西门子S7200
  17. 用yolov5训练kitti数据集
  18. 关于桌面运维工程师的错题本(前言)
  19. 再见北理工:忆北京研究生的编程时光
  20. 一文详解!你真的了解商业智能BI吗?

热门文章

  1. 《黑马程序员》演讲比赛管理系统实战
  2. 物联网卡管理平台有什么功能
  3. ATUVOS Tracker支持苹果Find My功能,Find My将成为防丢器必备功能
  4. 浅谈数据挖掘——频繁模式、序列挖掘与搜索优化算法
  5. ztree 折叠 CollapseAll函数
  6. 【shell脚本】常用工具的学习grep、sed、awk、cut
  7. Python之父:GIL不是问题,是幸事
  8. ACL最全详解:原理及作用、分类及特点、配置及需求
  9. SHP转化成JSON
  10. 黄聪:wordpress后台加载ajax.googleapis.com导致打开速度很慢的解决方案