第一篇:基于VHDL编程FPGA的地铁自动售票机
地铁自动售票机
一、设计要求
1、功能描述
用于模仿地铁售票自动售票,完成地铁售票的核心控制功能。
2、功能要求
售票机有两个进币孔,一个是输入硬币,识别的范围是一元硬币;一个是纸币,识别的范围是一元、两元、五元、十元、二十元。乘客可以连续多次投入钱币。乘客 一次只能选择一个出站口,购买车票时,乘客先选出站口,有六个出站口可供选择,再选择所需的票数,然后投币,投入的钱币达到或者超过所需金额时,售票机自 动出票,并找零。本次交易结束后,等待下一次交易。在选择出站口、所需票数以及在投币期间,乘客可以按取消键取消操作,钱币自动退出。
二、实验分析
1、买票时,乘客按下开始键,售票机进入站台选择程序,乘客选择出站口后,可以按取消键重新选择,否则售票机自动进入票数选择程序,同样这时可以按下取消键重新开始选择出站口以及票数。
2、当选择好出站口以及所需票数时,乘客可以投硬币或者用纸币,当所投的钱币总额大于或者等于票价时,售票机自动出票以及找零。期间,可以按下取消键重新开始选择,并退出所有的钱币。
3、乘客若还没选择出站口或者票数,就投币或者使用纸币,售票机会自动退出所有的钱币。
4、有六个站台可供乘客选择,每个乘客最多可以买3张票,六个站台编号为1到6,票价从2元依次递增到7。
三、系统流程图
四、程序源代码 LIBRARY IEEE;USE IEEE.std_logic_1164.ALL;USE IEEE.std_logic_arith.ALL;USE IEEE.std_logic_unsigned.ALL;ENTITY metrosell IS PORT(clk:in std_logic;startselect:in std_logic;sure:in std_logic;save your forward step(s)coin1y:in std_logic;pmoney1y:in std_logic;pmoney2y:in std_logic;pmoney5y:in std_logic;pmoney10y:in std_logic;money pmoney20y:in std_logic;money cancel:in std_logic;number:in std_logic_vector(3 downto 0);the tickets platform:in std_logic_vector(3 downto 0);want to reach moneystorage:out std_logic;acceptmo:out std_logic;stamp:out std_logic;--set the clock signal--start to select the platform--this button is to--1 yuan coin
--1 yuan paper money--2 yuan paper money--5 yuan paper money--10 yuan paper--20 yuan paper--cancel the forward step(s)--choose the number of--choose the platform you--to store the money--accept the money--stamp outgate charge:out std_logic_vector(3 downto 0);--the mount of charge,up to 15 yuan chargegate:out std_logic--charge outgate);END metrosell;ARCHITECTURE sell OF metrosell IS type state_type is(initial_type,selectp_type,selectnum_type,insert_type,stamp_type,charge_type);--define six types signal state:state_type;--define a shared state BEGIN main:process(clk,state,startselect,platform,number,coin1y,pmoney1y,pmoney2y,pmoney5y,pmoney10y,pmoney20y,cancel,sure)variable univalence :integer range 0 to 7;--the univalence of the ticket variable total_money :integer range 0 to 21;--the price of the ticket(s)variable selectp_alr:std_logic;--the flag of select platform type variable selectnum_alr:std_logic;--the flag of select number type variable stamp_alr:std_logic;--the flag of the stamp gate variable charge_alr:std_logic;--the flag of the charge gate variable money_reg:integer range 0 to 21;--the mount of money put in variable coin1y_f:std_logic;--the flag of one yuan coin variable pmoney1y_f:std_logic;--the flag of one yuan paper money variable pmoney2y_f:std_logic;--the flag of two yuan paper money variable pmoney10y_f:std_logic;--the flag of ten yuan paper money variable pmoney20y_f:std_logic;--the flag of twelve yuan paper money variable pmoney5y_f:std_logic;--the flag of five yuan paper money variable charge_reg:integer range 0 to 15;
begin if(rising_edge(clk))then case state is when initial_type => variables univalence:=0;selectp_alr:='0';selectnum_alr:='0';stamp_alr:='0';charge_alr:='0';money_reg:=0;total_money:=0;coin1y_f:='0';pmoney1y_f:='0';pmoney2y_f:='0';pmoney5y_f:='0';
--the register of charge--initialize some pmoney10y_f:='0';pmoney20y_f:='0';moneystorage<='0';stamp<='0';charge_reg:=0;charge<=“0000”;acceptmo<='0';chargegate<='0';if(startselect='1')then state<=selectp_type;end if;when selectp_type => if(selectp_alr='0'and cancel='0')then--choose the platform if(platform=“0001”)then univalence:=2;selectp_alr:='1';elsif(platform=“0010”)then univalence:=3;selectp_alr:='1';elsif(platform=“0011”)then univalence:=4;selectp_alr:='1';elsif(platform=“0100”)then univalence:=5;selectp_alr:='1';elsif(platform=“0101”)then univalence:=6;selectp_alr:='1';elsif(platform=“0110”)then univalence:=7;selectp_alr:='1';elsif(platform=“0000”)then univalence:=0;selectp_alr:='0';else null;end if;elsif(selectp_alr='1'and cancel='1')then state<=initial_type;elsif(selectp_alr='1'and sure='1')then state<=selectnum_type;end if;when selectnum_type =>--you can buy at most 3 tickets if(selectnum_alr='0'and cancel='0')then--choose the number of tickets if(number=“0001”)then if(univalence=2)then total_money:=2;selectnum_alr:='1';elsif(univalence=3)then total_money:=3;selectnum_alr:='1';elsif(univalence=4)then total_money:=4;selectnum_alr:='1';elsif(univalence=5)then total_money:=5;selectnum_alr:='1';elsif(univalence=6)then total_money:=6;selectnum_alr:='1';elsif(univalence=7)then total_money:=7;selectnum_alr:='1';elsif(univalence=0)then total_money:=0;selectnum_alr:='0';else null;end if;end if;
if(number=“0010”)then if(univalence=2)then total_money:=4;selectnum_alr:='1';elsif(univalence=3)then total_money:=6;selectnum_alr:='1';elsif(univalence=4)then total_money:=8;selectnum_alr:='1';elsif(univalence=5)then total_money:=10;selectnum_alr:='1';elsif(univalence=6)then total_money:=12;selectnum_alr:='1';elsif(univalence=7)then total_money:=14;selectnum_alr:='1';elsif(univalence=0)then total_money:=0;selectnum_alr:='0';else null;end if;end if;if(number=“0011”)then if(univalence=2)then total_money:=6;selectnum_alr:='1';elsif(univalence=3)then total_money:=9;selectnum_alr:='1';elsif(univalence=4)then total_money:=12;selectnum_alr:='1';elsif(univalence=5)then total_money:=15;selectnum_alr:='1';elsif(univalence=6)then total_money:=18;selectnum_alr:='1';elsif(univalence=7)then total_money:=21;selectnum_alr:='1';elsif(univalence=0)then total_money:=0;selectnum_alr:='0';else null;end if;end if;elsif(selectnum_alr='1'and cancel='1')then state<=initial_type;elsif(selectnum_alr='1'and sure='1')then state<=insert_type;end if;when insert_type => moneystorage<='1';if(money_reg
when 2 => charge<=“0010”;when 3 => charge<=“0011”;when 4 => charge<=“0100”;when 5 => charge<=“0101”;when 6 => charge<=“0110”;when 7 => charge<=“0111”;when 8 => charge<=“1000”;
end case;end if;
when 9 => charge<=“1001”;when 10 => charge<=“1010”;when 11 => charge<=“1011”;when 12 => charge<=“1100”;when 13 => charge<=“1101”;when 14 => charge<=“1110”;when 15 => charge<=“1111”;when others => charge<=“0000”;end case;if(charge_reg>0 and charge_alr='0')then chargegate<='1';charge_alr:='1';elsif(charge_reg=0 and charge_alr='0')then chargegate<='0';charge_alr:='1';else state<=initial_type;end if;end process main;END sell;
五、波形仿真
1、乘客按下开始按钮,进入选站台模式,选择二号站台,按下确定键,再选择票数为2张,按下确定键,售票机钱箱关闭,投入一张两元和五元纸币(对顺序没有要求),此时钱币总额大于票价,出两张票并找零一元。之后系统进入初始化状态。具体仿真如图 1 仿真1
图 1 仿真1
2、测试cancel键,当乘客按正确的操作完成选站台时,按下取消键,再重新选择,如图 2 cancel仿真,仿真波形如下。
图 2 cancel仿真
3、还是测试cancel键,当乘客选择好票数时,按下cancel键,然后重新选择两张单价为七块钱的六号站台票,投入一张20元和5元,找零六元。仿真波形如图 3 cancel仿真2
图 3 cancel仿真2
4、乘客选择五号站台,两张票,然后先后投入一元纸币,两元纸币,一元纸币,五元纸币,然后按下取消键,售票机自动放出所有的钱币。仿真如图 4 cancel仿真3。
图 4 cancel仿真3
六、心得体会
在我的设计中,有一个moneystorage信号量用于控制储存钱币箱的开与关,这个设计主要考虑到当乘客要求退币时,最好不是从售票机中取出投入的钱数,然后退还,设置了这个开关,就可以在按下取消键时,直接从储存钱币箱中退出钱币。
还有,乘客选择的站台以及票数,在售票机内部会自动将这两个信号传给出票系统,从而自动出票,以上写的程序只是让系统知道怎样收钱以及找零。
这次实验总体上来说比六人抢答器简单,但是因为这个售票机完全是自己写的,所以也不是想象中的那么简单。这也让我看出,要完全自己去做一件东西不是简单的,特别是要考虑很全面,还是要发一些时间的。
第二篇:EDA课程设计—自动售票机
燕 山 大 学
EDA课程设计报告书
题目:
自动售票机
姓名: 班级: 学号:
成绩:
(注:此文件应以同学学号为文件名)
一、设计题目及要求 1.设计题目:自动售票机 2.设计要求:
⑴、每次投一枚硬币,但可以连续投入数枚硬币。硬币种类两种:1元和5角,各用一个按键表示。
⑵、设定票价为2.5元,每次售一张票。购票时先投入硬币,当投入的硬币总金额达到或超过票的面值时,用LED发出指示,这时可以按取票键取出票。
⑶、如果所投硬币超过票的面值则会有LED提示找零钱,取完票以后按找零键则可以取出零钱。
⑷、用两位数码管显示已投币金额,若刚好投币2.5元,取票后金额归零;若投币超过2.5元,取票后显示找零金额,按下找零键后金额再归零。
1总体设计的文字描述,即由哪几个部分构
二、设计过程及内容(包括○
2主要模块比较详尽的文字描成的,各个部分的功能及如何实现方法;○述,并配以必要的图片加以说明,但图片数量无需太多)1.总体结构如下:
总体设计思路:此自动售票系统总共有5个主要模块,分别是:累加模块,比较器模块,找零模块,数字转换模块,显示器模块。⑴、累加模块实现金额的累加功能。
实现方法:该模块设置3个输入口(包括5角、1元、复位),8个输出口(B1—B8)。该模块将在给五角或一元高电平的同时实现金额的累加,复位则会将会对其进行清零。该模块由一片8位的加法器,2片4位寄存器及简单门电路组成,利用8位加法器将输入的金额(5、10)进行二进制相加(00000101、00001010),通过寄存器后返回到加法器实现累加功能。复位键则与寄存器复位清零短CLRN相连,实现复位的功能。
⑵、比较器模块实现与票价进行比较的功能。
实现方法:该模块设置了8个输入口(A0—A7)1个取票口,4个输出口。该模块 将累加的钱币与2.5元的票价比较,如果累加金额高于票价则黄灯亮,小于票价则红灯亮,等于票价则绿灯亮。给取票输入端高电平则会出票。该模块由一片八位比较器及门电路组成,输入的信号与二进制的票价相比较(00011001)。⑶、找零模块实现大于票价找零钱的功能。
实现方法:该模块设置了2个输入口(zhaoling,H),8个输出口(E1—E8)。该模块将在输入金额大于票价及出票之后的时候给予高电平,使在显示器中显示5。该模块由两个四位寄存器及少量门电路组成。给zhaoling输入口高电平,使寄存器工作,之后输出所找的零钱(二进制输出),通过数码管显示出来。⑷、数字转换模块实现TTL 二进制—BCD代码转换的功能。
实现方法:该模块设置了8个输入口(S1—S8),和8个输出口(C1—C8)。该模块由3个TTL 二进制—BCD码转换器及门电路组成。将需要数码管显示的数字二进制代码输入将输出相应的BCD码。即用4位二进制数来表示1位十进制数中的0~9这10个数码。
⑸、显示器模块实现将在数码管上显示数字的功能。
实现方法:该模块设置了8个输入口(A1—A7)输入相应的BCD码,7个输出口(Y1—Y7)输出相应的使数码管亮的代码及另外3个输出口(str1,str2,str3)控制相应的数码管亮。该模块由1个四位二进制计数器和双四选一数据选择器和1个七段译码器及相应门电路组成。将BCD码输入进去,通过计数器控制双位四选一数据选择器的输入端(00或01)。s0—s3通过00控制IC0的输出,此时str2 str1 str0通过000来控制第一个数码管亮,s4—s7通过01控制IC1口的输出,此时str2 str1 str0通过001来控制第二个数码管亮,将选择的输输入到七段译码器中将进行译码使数码管显示相应数字。2.各个模块电路图及仿真模型 总体电路图:
超过2.5元即3元时结果如下:
当正好为2.5元时,结果如下:
⑴、累加模块
累加模块仿真结果如下: ⑵、比较器模块
比较器模块仿真模型:
⑶、找零模块
找零模块仿真结果:
⑷、数字转换模块
数字转换模块仿真结果:
⑸、显示器模块
显示器模块仿真结果:
三、设计结论(包括设计过程中出现的问题;对EDA课程设计感想、意 见和建议)
经过一周多的课程设计,我受益匪浅,学到了团队合作,提高信息检索能力的重要性。在这次设计中遇到了很多实际性的问题,在实际设计中才发现,书本上理论性的东西与在实际运用中的还是有一定的出入的,所以有些问题不但要深入地理解,而且要不断地更正以前的错误思维。一切问题必须要靠自己一点一滴的解决,而在解决的过程当中你会发现自己在飞速的提升。在实验过程中,根据任务书的要求,查找资料,设计了电路方案,在差额计算模块、投币模块、选票模块,有几种预想方案,和同组人员仔细分析后确定了一套简单实用的方案。
在课程设计过程中,其中最具有代表性的错误就是累加模块。当信号输入时,输入信号与寄存器储存信号相加时出现严重的延时问题,后来在老师以及同组同学的努力下,加入了延时器,解决了这个问题。还有在试验箱进行仿真时,发现显示数字很不准确,在查阅资料和老师的帮助下,意识到是输入抖动问题,我们在输入端口加入了防抖动电路,很好的解决了这个问题。
在进行硬件方面测试的过程中,也遇到了一些问题,在进行数据累加的过程中,数据很不稳定,我们在摁键处加入了防抖动电路,便解决了这一问题。对设计的建议:
⑴课程设计是理论与实际相结合的应用,对我们的学习帮助很大,让我们更好的掌握所学知识,希望以后能更多的开展这样的活动,让我们有更多的机会运用所学的知识。
⑵实际电路中,有些模块在模拟软件中无法进行模拟仿真,这让我们对自己设计的电路的可用性有些疑惑,希望学校能给我们提供能让我们进行实际仿真的实验室,让我们能更加完善自己的电路。
同时在整个EDA的设计中,老师给予了无私的,耐心的教导,在此感谢老师的教诲,愿老师工作顺利,身体健康。
第三篇:VHDL 编程的一些心得体会
VHDL 编程的一些心得体会(转)
VHDL 是由美国国防部为描述电子电路所开发的一种语言,其全称为(Very High Speed Integrated Circuit)Hardware Description Language。与另外一门硬件描述语言 Verilog HDL 相比,VHDL 更善于描述高层的一些设计,包括系统级(算法、数据通路、控制)和行为级(寄存器传输级),而且 VHDL 具有设计重用、大型设计能力、可读性强、易于编译等优点逐渐受到硬件设计者的青睐。但是,VHDL 是一门语法相当严格的语言,易学性差,特别是对于刚开始接触 VHDL 的设计者而言,经常会因某些小细节处理不当导致综合无法通过。为此本文就其中一些比较典型的问题展开探讨,希望对初学者有所帮助,提高学习进度。
一.关于端口
VHDL 共定义了 5 种类型的端口,分别是 In, Out,Inout, Buffer及 Linkage,实际设计时只会用到前四种。In 和 Out 端口的使用相对简单。这里,我们主要讲述关于 buffer和inout 使用时的注意事项。
与 Out 端口比,Buffer 端口具有回读功能,也即内部反馈,但在设计时最好不要使用 buffer,因为 buffer类型的端口不能连接到其他类型的端口上,无法把包含该类型端口的设计作为子模块元件例化,不利于大型设计和程序的可读性。若设计时需要实现某个输出的回读功能,可以通过增加中间信号作为缓冲,由该信号完成回读功能。
双向端口 Inout 是四种端口类型中最为特殊的一种,最难以学习和掌握,为此专门提供一个简单程序进行阐述,部分程序如下:
...„
①DataB<=Din when CE=’1’ and Rd=’0’ else
②(others=>’Z’);
③ Dout<=DataB when CE=’1’ and Rd=’1’ else
④(others=>’1’);
„ „
程序中 DataB 为双向端口,编程时应注意的是,当 DataB 作为输出且空闲时,必须将其设为高阻态挂起,即有类似第②行的语句,否则实现后会造成端口死锁。而当 DataB 作为有效输入时,DataB 输出必须处于高阻态,对于该例子中即,当 CE=’1’ and Rd=’1’时,二.信号和变量
常数、信号和变量是 VHDL 中最主要的对象,分别代表一定的物理意义。常数对应于数字电路中的电源或地;信号对应某条硬件连线;变量通常指临时数据的局部存储。信号和变量功能相近,用法上却有很大不同。
表 1信号与变量主要区别
信
号变量
赋值延迟至少有△延时无,立即变化
相关信息有,可以形成波形无,只有当前值进程敏
感是否全局性具有全局性,可存在于多个进程中只能在某个进程或子程序中有效相互赋值关系信号不能给变量赋值变量可以给信号赋值
对于变量赋值操作无延迟,初学者认为这个特性对 VHDL 设计非常有利,但这只是理论上的。基于以下几点原因,我们建议,编程时还是应以信号为主,尽量减少变量的使用。
(1)变量赋值无延时是针对进程运行而言的,只是一个理想值,对于变量的操作往往被综合成为组合逻辑的形式,而硬件上的组合逻辑必然存在输入到输出延时。当进程内关于变量的操作越多,其组合逻辑就会变得越大越复杂。假设在一个进程内,有关于变量的 3 个级连操作,其输出延时分别为 5ns,6ns,7ns,则其最快的时钟只能达到 18ns。相反,采用信号编程,在时钟控制下,往往综合成触发器的形式,特别是对于 FPGA 芯片而言,具有丰富的触发器结构,易形成流水作业,其时钟频率只受控于延时最大的那一级,而不会与变量一样层层累积。假设某个设计为 3 级流水作业,其每一级延时分别为 10ns,11ns,12ns,则其最快时钟可达 12ns。因此,采用信号反而更能提高设计的速度。
(2)由于变量不具备信息的相关性,只有当前值,因此也无法在仿真时观察其波形和状态改变情况,无法对设计的运行情况有效验证,而测试验证工作量往往会占到整个设计 70%~80%的工作量,采用信号则不会存在这类问题。
(3)变量有效范围只能局限在单个进程或子程序中,要想将其值带出与其余进程、子模块之间相互作用,必须借助信号,这在一定程度上会造成代码不够简洁,可读性下降等缺点。
当然,变量也具有其特殊的优点,特别是用来描述一些复杂的算法,如图像处理,多维数组变换等。
三.位(矢量)与逻辑(矢量)
bit 或其矢量形式 bit_vector只有’0’和’1’两种状态,数字电路中也只有’0’和’1’两种逻辑,因此会给初学者一个误区,认为采用位(矢量)则足够设计之用,而不必像std_logic那样出现’X’,’U’,’W’各种状态,增加编程难度。但实际情况却并非如此,以一个最简单 D型触发器设计为例
„ „
① process(clk)
② begin
③ if clk’event and clk=’1’ then
④ Q<=D;
⑤ end if;
⑥ end process;
„ „
实际中 clk 对数据端 D的输入有一定的时间限制,即在 clk 上升沿附近(建立时间和保持时间之内),D必须保持稳定,否则 Q输出会出现亚稳态,如下图所示。
当 clk 和 D时序关系不满足时,由于 bit 只有’0’或’1’,系统只能随机的从’0’和’1’中给 Q 输出,这样的结果显然是不可信的;而采用 std_logic 类型,则时序仿真时会输出为一个’X’,提醒用户建立保持时间存在问题,应重新安排 D和 clk 之间时序关系。
此外,对于双向总线设计(前面已提及)、FPGA/CPLD上电配置等问题,如果没有’Z’,’X’等状态,根本无法进行设计和有效验证。
四.关于进程
进程(Process)是 VHDL 中最为重要的部分,大部分设计都会用到 Process 结构,因此掌握Process 的使用显得尤为重要。以下是初学和使用 Process 经常会出错的例子。
1.多余时钟的引入
在设计时往往会遇到这种情况,需要对外部某个输入信号进行判断,当其出现上跳或下跳沿时,执行相应的操作,而该信号不像正常时钟那样具有固定占空比和周期,而是很随机,需要程序设计判断其上跳沿出现与否。这时,很容易写出如下程序:
①process(Ctl_a)--Ctl_a即为该输入信号
② begin
③ if Ctl_a’event and Ctl_a=’1’ then
④„„;--执行相应操作
⑤ end if;
⑥ end process;
由于出现第③行这类语句,综合工具自动默认 Ctl_a 为时钟,某些 FPGA 更会强行将该输入约束到时钟引脚上。而设计者的初衷只是想将其作为下位机的状态输入以进行判断。上面的程序容易造成多时钟现象,增加设计的难度。解决的办法可以如下,将 Ctl_a 增加一级状态
Ctl_areg 寄存,通过对 Ctl_a 和Ctl_areg 状态判断上跳与否,改正程序如下:① process(clk)
② begin
③ if clk’event and clk=’1’ then
④ Ctl_areg<=Ctl_a;--产生相邻状态
⑤ if Ctl_areg=’0’ and Ctl_a=’1’ then--上跳判断
⑥„„;--执行相应操作
⑦ end if;
⑧ end if;
⑨ end process;
程序中第④行用以产生两个相邻状态,第⑤行对前后状态进行判断是否有上跳现
象发生。其中,需注意的是 clk 的时钟频率应明显快于 Ctl_a信号的变化频率,以保证正确采样。
2.输出多驱动
误用 Process经常会引起输出多驱动源的发生,即在两个以上的进程内对同一信号赋值操作。
以下程序就出现了这类情况:
⑴ Proc_a: process(clk)
⑵ begin
⑶ if clk’event and clk=’1’ then
⑷ Dout<=Din_A;
⑸ end if
⑹ end process;;
⑺
⑻ Proc_b:process(sel_en)
⑼ begin
⑽ if sel_en=’1’ then
⑾ Dout<=Din_B;
⑿ end if;
⒀ end process;
进程 Proc_a 和 Proc_b 中都出现了对 Dout 的赋值语句,设计者原本的想法是,只要合理控制好 clk 和 sel_en 输入,使其不发生冲突,即 clk上升沿时 sel_en 不为’1’;sel_en 为’1’时,不出现 clk 的上升沿,这样 Proc_a,Proc_b 两个进程就不会发生冲突。但综合时,综合工具会将所有可能情况全部罗列进去,包括第⑶行和第⑽行同时成立的情况,此时对于 Dout就有 Din_A和 Din_B 两个输入驱动,Dout 不知接收哪一个,因此该程序无法综合,改正的方法是只要将两个进程合并成一个即可。
由于进程在 VHDL 中的重要性,对此专门做了一个总结如下:
(1)一个进程中不允许出现两个时钟沿触发,(Xilinx 公司 CoolRunner 系列 CPLD 支持单个时双钟的双触发沿除外)
(2)对同一信号赋值的语句应出现在单个进程内,不要在时钟沿之后加上 else 语句,如 if clk’event and clk=’1’ then-else„的结构,现有综合工具支持不了这种特殊的触发器结构
(3)当出现多层 IF语句嵌套时,最好采用 CASE 语句替代,一是减少多层嵌套带来的延时,二来可以增强程序的可读性
(4)顺序语句如 IF语句、CASE 语句、LOOP 语句、变量赋值语句等必须出现在进程、函数或子程序内部,而不能单独出现在进程之外
(5)进程内部是顺序执行的,进程之间是并行运行的;VHDL 中的所有并行语句都可以理解为特殊的进程,只是不以 Process结构出现,其输入信号和判断信号就是隐含的敏感表
五.关于 VHDL 学习中的几点说明
与软件语言相比,VHDL 最重要的特点就在于它的并行运行特性,当设计好的电路上电后,器件内部所有信号将同时并发工作,而不会以软件方式按照
程序顺序执行,即使在进程内部也是趋向并行工作的。例如以下程序:① process(clk)
② begin
③ if clk’event and clk=’1’ then
④ <=;
⑤ <=;
⑥ end if;;
⑦ end process;
综合的结果两个独立的 D 型触发器,虽然进程内部应按顺序执行,但是硬件实现后,只要采样到时钟上升沿,和 状态会同时翻转,而不会先执行的变化,然后才会去执行的转变。因此,VHDL 学习过程中,应加强硬件概念的理解,没有硬件概念或是硬件概念不强,在设计时,往往会将 VHDL 设计以软件编程的方式来处理,而得出一些不可思议的结果。
作为一门硬件描述语言,VHDL 几乎可以用来描述现有的大型系统数字电路、算法以及其它设计。但是,限于目前综合工具的水平,VHDL 中的许多语法还不能支持,例如:
dout<=din after 5 ns;
综合时就无法达到如此精度,因此这条语句主要用来编写测试激励,而很少出现在设计实体中。类似的情况还有很多,目前 VHDL 设计使用的也只是整个标准中的一部分,这也正是VHDL 的“可综合子集”性质,它一定程度上限制了 VHDL 的广泛应用,但是随着综合技术的发展,这种情况会逐渐得以改善,VHDL 也将在各个领域中发挥出愈来愈重要的作用
第四篇:FPGA编程经验
整个verilog中是以module为编写基本单元的,module不宜过大,目标是实现一些基本功能即可,module的层次不宜太深,一般3-5层即可,给module划分层次原则:实现最基本功能的为底层module,然后中层是调用这些基本module,实现大的功能,最高层是系统级模块,统筹各大块之间端口连接,时序关系等。
在module内部编写中,最基本块是initial,always,以及assign块(此外还有一些UDP原语,在行为级暂且不谈),其他语句都要包含在这些块里面。这其中,initial块是不可综合语句,可以用来编写testbench,这里面的内容在程序运行时只执行一次;assign语句是在不用寄存器的情况下直接编写组合逻辑;always块是最常用的块,其语法格式是always @(*);其中括号里称为敏感列表,即对于组合逻辑而言,必须是所实现逻辑的所有输入变量,意思是当组合逻辑的每一个变量发生变化,结果立刻发生变化(这与实际情况一致,对于任何组合逻辑,输入变化,输出立刻变化)。对于时序逻辑,常为
always@(posedge/negedge clk),指在时钟上升沿/下降沿到来时,输出才根据那一时刻的输入来决定输出结果。
编程思想:
这一部分是我的心得体会,一般讲verilog的书肯定不会讲这个,因为这部分感觉的东西比较多,完全靠理解应用,没什么固定模式,呵呵,玄了点。不废话了,开始切入正题。Verilog归根到底还是编程,同时它是对电路的编程,所以就可以利用这两个特点,充分利用高级语言编程(例如C)的思想和数字电路的知识,就会很大程度上帮助你。首先牢记,编写verilog依据的是时间轴,根据时间顺序确定各种信号何时进入你的电路,可以在编写时先把几个主流信号(即贯穿于整个系统的信号,比如数据流信号)用always写出来,这些信号就是你的基准,其他控制信号根据所处的位置在介入这些主流信号,分别用 always
模块写入。这样,这种时间轴顺序跟C的编程思想就一致,在编写这种顺序性信号时,带着C的思想,基本就容易的多。同时,verilog有个很重要的电路特点,就是在每一时刻,同时会有多个电路有信号(即在运行),这样就必须从传统的顺序语言中跳出来(跳出C的思想),然后进入数字电路的思想,即你的编程要时刻跟实际电路模型联系到一起,比如A<=B,C<=A,即可以想象成两个D触发器,其中C的输入就是A的输出,这样当时钟沿到来时,两个D触发器同时运行,B的值就给了A,而A的输出就给了C,不考虑电路延时,就可以认为二者在时间轴的同一时刻运行成功,A在此时刻的值为B,C 在此时刻的值就是A前一时刻的值。这里只是举了简单的例子,但是时间轴的思路、高级语言编程思想和数字电路模型化思想以及这几个思想的转换对于编程来说帮助很大,帮你在编程时头脑时刻冷静,即使有很多信号,也会让你从全局把握整个电路,避免头脑一团糟。
关于复用:
复用对于硬件设计来说,尤为重要,在实现功能的前提下,电路比的就是主频和资源,而资源的减少思路就是复用。比如多次调用一个子函数A,如果直接写,在结构上就是这个子函数电路块A的复制,电路完成后就会看到你调用了几次A函数,你的结构中就有几个一模一样的A电路,当你的A电路本身资源很大,而且你调用的次数很多时,就很少有FPGA能装的下了。这当然跟我们的思想不符,我们只是想让整个电路中只有一块电路A,只不过把A电路在不同时刻用了几次而已,这时就要用到电路复用的思想。电路复用并不神秘,简单来讲,就是把控制端放在A电路两边,用计数器之类的东西,控制在时间轴的不同时刻把值写入A和从A输出端读取值。补充一点,对于电路的同步,计数器是一个很好的东西,因为本身计数器资源并不很大,用它来对于相差多个周期的信号进行同步,非常实用。但是,当信号相差的时间过于大,计数器的计数规模
就变的不可接受,这时就要用到状态机划分状态的方法进行同步了。有了同步,复用就变得简单的多。
关于task:
这是前段时间论坛里讨论比较热的东西。语法上说,task是比always低个等级,即task必须在always里面调用,task本身可以调用 task,但不能调用module(module的调用是与always,initial,assign语句并列的,所以在这些语句中均不能直接调用 module,只能采用给module端口送值的方法达到调用的目的)。Task有什么用呢,个人觉得,用task来封装大的逻辑语句不错,使代码显得简单明了,这个对于testbench尤为有用,但在实际电路中用处不大,因为顺序调用task对于电路来说就是电路块的复制,顺序多次调用就是多次复制电路,资源会成倍增加,不能达到电路复用的目的,同时用task封装的纯逻辑代码会使得电路的周期变大,主频降低,不利于为了提高主频而采用的大逻辑切分的方法!
第五篇:LabVIEW FPGA编程小结
LabVIEW FPGA编程小结
NI PXI-7813R为FPGA卡,板卡上引出4个端口,每个端口有40路引脚,共160路DIO,使用LabVIEW FPGA模块进行编程控制。当FPGA程序复杂度变大或是使用的DIO端口数增多时,可能面临的主要问题包括FPGA空间不够用以及实际循环时间过长等。之前编写的 FPGA程序示意图如下,采集循环与输出循环独立,均采用控件形式与RT程序通讯,两块板卡均使用了近120路DIO口。基于7813R板卡编程实践及涉及到的几个瓶颈问题,简要做了一下总结:
1)使用FIFO还是使用控件?
FPGA与RT通讯时,常用的方法是使用读写FIFO或是使用输入输出控件。这两者的特点是:
a)两者在速度上无明显差别。这是建立在不使用For循环的基础上的,但实际中FIFO通常都要配合For循环来使用,For循环相当于串行操作,当同类端口较多时,使用for将导致循环时间变长,故运行速度上FIFO并没有多少优势。b)FIFO使用合理时不丢数,而控件不能保证。
通过配置FPGA与RT中读写FIFO的超时以及FIFO大小,读写方式等手段,通常可保证FIFO传递数据不丢数(可能要经过多次尝试);而使用控件则可能会有丢数的情况。当不严格要求每次while循环都不丢数时,可考虑使用控件,例如对DO输出的配置,用户可能很久才会去配置一次,而且配置后不会要求马上生效,稍微晚几个循环周期(us级)再使配置生效也不会有很大影响,这种时候使用读取控件值是合理的。c)FIFO可使用的数据类型有限,而控件几乎无限制。
就7813R而言,FIFO只能传递指定的几种类型的数据,而使用控件时,可使用包括簇数组在内的自定义控件。
所以,还是根据实际需要来选择吧,虽然这句话跟没说一样~ 2)用不用For循环?
端口较多时,很容易就遇到连续几个都是要求采集脉宽的,而另外连续几个要求采集电平即可。这时候很自然想到使用For循环对多个端口一起进行操作,例如下图:
如上面所说,使用FOR循环相当于使端口操作(上图中所说的操作是指将采集到的布尔转成U32数值)变为串行;另外,要使用For,通常就得配合数组操作,例如上图中创建数组等,这样就更加导致循环时间变长。当发现循环时间不满足使用要求时,这种处理方法可能就不能使用了,每一路单独处理就省掉了创建数组及For,节省了循环时间,但这样又使重复代码变多,工作量加大。
3)怎么使用子VI?
输出脉冲时,脉冲的产生可以封装成一个子VI,供多路端口进行使用;采集脉宽时,脉宽采集可以封装成一个子VI供多路端口进行调用。然而,默认情况下,子VI的执行是串行的(因为并没有设置VI属性为“可重入执行”),子VI在同一时间内只能被一路端口所占用,可能导致的结果是循环时间变长,精度降低。如果设置子VI为可重入执行,又可能导致FPGA空间占用率过高,编译无法通过。我想到的一种折中的办法是:使用几个程序框图一样的子VI(功能完全一样,将子VI多另存为几个所生成)来代替原先一个子VI,替换之后,相当于减少了串行运行的子VI数量,循环用时减少明显。下图中使用6个子VI A,如果用2个B与2个C替换其中的4个A,循环时间可能减少为原来的1/3。
While循环方式1:子VI A子VI A子VI A子VI A子VI A子VI AWhile循环方式2:子VI A子VI A子VI B子VI B子VI C子VI C
4)精打细算
这一点可能只会在FPGA空间不够用时才会被重视,下图是逻辑片不够用时导致的FPGA编译失败错误:
因此,当资源有限时,尽量使用能满足使用要求的最小长度的数据类型来实现,能使用U16满足要求的坚决不用U32!另一方面,暂时还没有发现数据类型变长时对FPGA循环运行时间产生明显影响。
5)循环延时考虑
脉冲输出及采集均需要根据实际循环时间来计算,若参与运算的值不是实际的循环时间,输出或采集的结果自然不会准确。例如可配置脉冲或电平输出的端口,若输出电平,其算法简单,所需时间较少,而配置为脉冲输出时,算法复杂。配置为单路脉冲输出可能不会有明显的影响循环时间,但当多路脉冲一起输出时,可能影响到循环时间增加1~2us甚至更长。故实际配置循环时间时,需按照可能的最复杂算法进行运行测试,并依此来设置循环时间,以保证循环时间的确定性已经算法运算的正确性。
总而言之,设计时还是应该根据实际需要,综合考虑数据完整性、FPGA板卡资源大小、循环时间等因素,已达到满意效果。