FPGA的大作业我选择了交通灯控制系统的设计,此课程只有2个学分,因此只需要在相应软件仿真出结果即可。以下是我的设计报告,当时写的匆忙,没有对代码进行优化改进,但仿真结果是正确的,可以给大家提供一下思路。
一、任务分析
分析设计要求可以看出,本系统设计数码管动态显示、译码器、计数器分频方面的知识。
1.输入和输出
输入
clk:时钟输入(50MHz)。
special:特殊情况发生输入。
输出
l1、l2:数码管位选。
display_1、display_2:数码管段选。
out_1、out_2:红绿灯选择。
2.多进程
reg进程:分频计数
红绿灯的闪烁和数码管显示数字的变化都需要计时,计时单位为s(秒)时,设计最简单。因此,我将50MHz频率的clk驱动分频,这部分运用了计数器知识。
从题目中看出,红绿灯50s为一个循环,我使用信号counter来记录时间,时钟每过1秒钟,counter就加1,counter的值不能超过50。
com进程:红绿灯显示
通过counter的值就可以判断出东西方和南北方的灯为什么颜色。我使用自定义枚举类型来表示红绿灯的三种状态。
在判断红绿灯状态的同时,还应该计算出数码管应该显示的数字为多少。
smg进程:数码管显示
- 将com进程中计算出的数码管数字在数码管上显示出来。计算数码管十位上的显示数字时用10取模,计算数码管个位上的显示数字时用10取余。
- 由于数码管位选仅有两个,我使用一个变量的0和1分别表示数码管的十位显示和个位显示。
- 数码管的段选运用了译码器相关知识。
3.特殊情况
当bit类型信号special为’1’时,进入特殊情况处理。
在com进程中,只有当special为’0’时,counter才会加1。目的是在特殊情况时,数码管的数字不会发生变化。
我额外定义了一个bit类型信号change,时钟每过一秒钟,它就会取反。使用它的目的是在特殊情况时,实现数码管闪烁。
4.注意
需要格外注意的是,reg、com、smg三个进程的敏感信号。
reg:process(clk,special)
com:process(counter,special)
红绿灯显示在每隔1秒钟判断或special特殊情况到来时判断即可
reg:process(clk,change,special)
数码管为动态扫描,只有速度够快,才能使人眼无法察觉数码管的交替显示,因此需要clk时钟信号作为敏感信号。
二、quartus ii 仿真
1.VHDL语言描述
library ieee; use ieee.std_logic_1164.all; entity fisrt is --两组数码管(段选+位选),两组灯 port(clk:in std_logic; special:in bit; --数码管段选 l1,l2:out std_logic_vector(1 downto 0); --数码管位选 display_1,display_2:out std_logic_vector(6 downto 0); --红绿灯 out_1,out_2:out std_logic_vector(2 downto 0)); end fisrt; architecture behav of fisrt is type FSM is (s0,s1,s2); signal LED_1,LED_2:FSM;--自定义枚举类型 signal counter:integer range 0 to 49;--共50s signal smg_1,smg_2:integer range 0 to 24;--数码管显示数字 signal change:bit;--每一秒钟用于变化的量 begin --进程reg用于分频 reg:process(counter,special) variable clk1:integer range 0 to 9999;--50MHz分频为1Hz variable clk2:integer range 0 to 4999; begin if clk'event and clk='1' then if clk1=9999 then clk1:=0; if clk2=4999 then clk2:=0; if special='0' then change<=not change; if counter=49 then counter<=0; else counter<=counter+1; end if; end if; else clk2:=clk2+1; end if; else clk1:=clk1+1; end if; end if; end process reg; --进程com用于红绿灯显示 com:process(clk,counter,special) begin if special='1' then out_1<="100"; out_2<="100"; else if counter<20 then LED_1<=s0;smg_1<=20-counter; LED_2<=s0;smg_2<=25-counter; elsif counter<25 then LED_1<=s1;smg_1<=25-counter; LED_2<=s0;smg_2<=25-counter; elsif counter<45 then LED_1<=s2;smg_1<=50-counter; LED_2<=s1;smg_2<=45-counter; else LED_1<=s2;smg_1<=50-counter; LED_2<=s2;smg_2<=50-counter; end if; case LED_1 is when s0=>out_1<="010";--绿灯亮 when s1=>out_1<="001";--黄灯亮 when s2=>out_1<="100";--红灯亮 end case; case LED_2 is when s0=>out_2<="100";--红灯亮 when s1=>out_2<="010";--绿灯亮 when s2=>out_2<="001";--黄灯亮 end case; end if; end process com; --进程smg用于数码管显示 smg:process(clk,change,special) variable a,b:bit; variable c1,c2:integer range 0 to 10; begin if clk'event and clk='1' then a:=not a; b:=not b; case a is when '0'=> l1<="01";c1:=smg_1 rem 10;--取个位数 when '1'=> l1<="10";c1:=smg_1 mod 10;--取十位数 end case; case b is when '0'=> l2<="01";c2:=smg_2 rem 10; when '1'=> l2<="10";c2:=smg_2 mod 10; end case; if special='1' and change='1' then --特殊情况时,数码管1s闪烁一次 c1:=10;c2:=10; end if; case c1 is when 0 =>display_1<="0111111"; when 1 =>display_1<="0000110"; when 2 =>display_1<="1011011"; when 3 =>display_1<="1001111"; when 4 =>display_1<="1100110"; when 5 =>display_1<="1101101"; when 6 =>display_1<="1111101"; when 7 =>display_1<="0000111"; when 8 =>display_1<="1111111"; when 9 =>display_1<="1100011"; when others=>display_1<="0000000"; end case; case c2 is when 0 =>display_2<="0111111"; when 1 =>display_2<="0000110"; when 2 =>display_2<="1011011"; when 3 =>display_2<="1001111"; when 4 =>display_2<="1100110"; when 5 =>display_2<="1101101"; when 6 =>display_2<="1111101"; when 7 =>display_2<="0000111"; when 8 =>display_2<="1111111"; when 9 =>display_2<="1100011"; when others=>display_2<="0000000"; end case; end if; end process smg; end behav;
2.仿真结果
- RTL Viewer
- 时序仿真
为了方便仿真,我将clk1和clk2去除,将clk设置为1Hz频率的时钟信号,下图为修改后代码。这样仿真的缺点就是,数码管无法显示出完整数字,在每过一秒钟,交替显示十位数码管和个位数码管。
仿真结果如下:
三、总结
1.收获
设计交通信号灯控制系统,我熟悉了计数器、译码器、动态数码管显示的知识,加深对它们的工作原理的理解,更加熟练的使用case、if等语句。同时,也进一步熟悉EDA软件——quartusii,提高代码构思能力。
2.问题与解决
编译过程中出现很多语法错误,例如process进程忘记加begin,定义的整型类型忘记加range,变量赋值语句的符号为“ := ”,if语句没有加then,忘记写“end if”…
这些语法错误在编译后的error中查找并改正。
构思VHDL代码结构时,最大的困难就是数码管显示数字和特殊情况处理。
我将数码管显示数字的计算和判断红绿灯代码放到了一起,利用counter信号50s一循环,红绿灯50s一循环,在根据红、绿、黄灯的持续时间,就可以将数码管数字计算出来。
特殊情况是最难处理的地方。它不仅要求在此时红灯亮,还需要让数码管闪烁。我将数码管设置为1s闪烁,具体实现方法是,在com进程让change信号每过1s取反,在smg进程中,当special=‘1’ 并且change='1’时,数码管不显示数字。
3.改进与优化
数码管显示数字部分可以看出,这个程序在显示数字小于10时,两位数码管显示为"0X",也就是说十位上的数码管有数据显示,这是不符合现实的,因此,可以更改为以下代码。
--smg进程部分 case a is when '0'=> l1<="01";c1:=smg_1 rem 10;--取个位数 when '1'=> l1<="10";c1:=smg_1 mod 10;--取十位数 if c1=0 then c1:=10;--此时数码管不显示 end if; end case; case b is when '0'=> l2<="01";c2:=smg_2 rem 10; when '1'=> l2<="10";c2:=smg_2 mod 10; if c2=0 then c2:=10;--此时数码管不显示 end if; end case;
通过理解题目内容可以看出,东西方和南北方的数码管显示和灯颜色的选择是相似的,可以设计子程序,通过函数调用或过程调用优化代码。但因本人能力有限,没有写出具体实现方法。