VHDL语言的一些东西

话说有快一个月没有写点什么了…
这段时间里也发生了各种事…
其中一个是EDA课程的大作业(用VHDL设计洗衣机芯片)…deadline是在7月9日(周五)…
而期末考试是在7月5日,7月7日,7月9日…EDA大作业心血来潮赶着在7月7日的时候就做好交了…不过也庆幸没有往后拖…
因为后来得了胃炎…7月9日的考试时真是煎熬啊…胃很难受,又饿着肚子又反胃…

EDA这门课结束了…VHDL语言的东西也不知道以后还会不会接触…写点什么纪念下吧…

VHDL是一种硬件描述语言…比如用来编写各种芯片…
课上用的环境是maxplus II…不过貌似落伍了?…
编写硬件真是和编写软件差很多…
基本上,VHDL中每条语句之间都是并发的,软件则是顺序执行的(当然多核多线程下可能有真正意义上的并发)…
因为软件每条指令背后都有个cpu时钟…指令之间有着先后顺序…
而VHDL则是用最基本的门电路构建一个从输入到输出的逻辑关系…不计信息传播速度的话,输出只在输入改变的时候改变…

那么…先说下VHDL的几个够用的语法吧…

首先,和C++中#include语句类似的是library

LIBRARY IEEE;

话说到现在也没太搞明白library后面跟的东西是从什么文件夹里找的…
vhdl中use是类似C++中的using的东西

USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;

就像using namespace std;一样平常…
恩…对应C++的namespace的话应该是package了吧…
不过…我没有用过namespace,也没有用过package…不用管这些也挺够用的…

然后是元件(实体)的定义…一段vhdl程序就是用来做成一个元件的…就像C++中一个函数(感觉比起类来说更接近函数)
这个实体的定义是放在entity语句中的,其中主要是port语句定义端口名字和类型,就像C++中定义函数参数一样

ENTITY counter IS
	PORT(clk,en,reset:IN STD_LOGIC;
		target:IN STD_LOGIC_VECTOR(4 DOWNTO 0);
		q:OUT STD_LOGIC);
	--counter:clk来一个上升沿则内部计数器加一,直到内部计数器与target值相同时q端从0变成1
	--en:1-enable,0-disable
	--reset:0-nothing 1-reset to 0
END ENTITY counter;

这样定义了一个叫做counter的元件(实体)
其中clk,en,reset是输入端,并且都是1bit大小(STD_LOGIC是1个bit),也就是这三个端口分别占用一个引脚…
target也是输入端,但是有5bit大小,也就是它用了5个引脚,要指定是哪个引脚可以用下标,就像C++的数组一样
q是输出端,1bit大小
下面是注释…在vhdl中用–表示注释…

到这里,就像是C++中声明了一个函数,但是函数内容还没有
下面要用architecture来写这个元件内部结构了,相当于C++的函数体

ARCHITECTURE behav OF counter IS
--这里可以放内部信号的定义
--引用外部元件的定义也写在这里
BEGIN
--这里放代码(赋值语句)
END ARCHITECTURE behav;

内部信号类似于C++中函数体中的局部变量,作用域是整个元件(实体)内部
然后代码部分…是的…从根本上来说只有赋值语句…
连接电路的时候除了连连线之外还能做什么呢…
vhdl只是做这种连线的工作…

赋值语句是用<=这个符号的,右边是由引脚,信号量,常量等的逻辑运算组合
当然,声明为IN的引脚只能作为右值,声明为OUT的引脚只能作为左值
声明为BUFF的引脚既可以作为右值也能作为左值,类似内部反馈一样的东西...有点像C++中的引用...

ARCHITECTURE behav OF counter IS
SIGNAL cnt:STD_LOGIC_VECTOR(4 DOWNTO 0);
SIGNAL m_clk:STD_LOGIC;
SIGNAL m_reset:STD_LOGIC;
BEGIN
	m_clk  <=en AND clk;
	m_reset<=en AND reset;
	PROCESS(m_clk,m_reset) IS
	BEGIN
		IF(m_reset='1')THEN
			cnt<="00001";
			q<='0';
		ELSIF(m_clk'EVENT AND m_clk='1')THEN
			IF(cnt=target)THEN
				q<='1';
				cnt<="00001";
			ELSE
				q<='0';
				cnt<=cnt+1;
			END IF;
		END IF;
	END PROCESS;
END ARCHITECTURE behav;

在定义了几个信号量,写了2句普通的赋值语句之后出现了process这个东西…
在一个architecture中可以写多个process,它们都是并发的…
process这东西…基本上是专门为触发器准备的…
虽然在process中可以不用触发器…但那基本上只剩下”让程序结构清晰”这一条特点了…

一开始的时候我也很奇怪,居然会有if语句…
但是试着把各种东西放进if条件中去后就会发现…这其实只是假象…
就像51单片机的汇编代码…指令加上后面的参数一起都只是助记符而已…别以为有个mov A,@Ri的指令,就会有mov Rn,@Ri的指令…
vhdl中的if也一样…看到判断边沿可以放在if里面就以为一个process里面可以判断多个边沿或者判断边沿之后写个else什么的…怎么可能嘛…
这是硬件…当你发现写出来的代码不能编译通过时就想想,如果自己手动连线,得怎么连才能实现代码所描述的情况呢…?
就像把一个与门写成AND的形式,加法器写成加号的形式一样,这里只是把一个上升沿触发器写成了如下的形式而已…

IF(信号'EVENT AND 信号='1')THEN
...
END IF;

而带有使能端的上升沿触发器写成了这样的形式:

IF(简单条件)THEN
...
ELSEIF(信号'EVENT AND 信号='1')THEN
...
END IF;

不知是不是上课的时候老师说过…process中代码是顺序执行的…
但我感觉这样说不恰当…应该说是在某一时刻上,把所有无法进入的分支语句去除后剩下的所有赋值语句之间,是并发的…
这是试了很多种写法得出的结论,比如在前一个判断语句中给某个信号赋值,然后在后面某个判断语句中判断这个信号的值,实际结果是,后面的判断语句中判断到的值是在未经前一个判断语句中赋值的…
又比如b<=c;a<=b;是和a<=b;b<=c;一样的...结果都是a得到原来b的值,b得到c的值
不过貌似没有试过用variable代替signal会发生什么情况?

至此,这个叫做counter的元件就写完了...
把代码人工翻译成示意图的话大致会是这个样子...
counter元件示意图

就像C++中函数内可以调用其他函数一样,vhdl中元件内也可以放其他元件,只要连上放进去的元件的引脚就行了
像上图中所有小方框都算是一个元件,寄存器内部可以是一组触发器做成的,[+1]也只是一些逻辑电路做成的加一的加法器…
在这里,比如门,加法器,触发器,都用逻辑运算符,加号,触发器的if语句对应了,就像…C++中的重载运算符…
但是如果要加入其他元件或者自己制作的元件,就要用port map来显式设置了

要使用某种元件的话,先要在architecture与它的begin之间,也就是定义内部信号量的地方,使用component语句来声明这个外部元件

LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY buzzer IS
	PORT(clk,done:IN STD_LOGIC;
		buzz:OUT STD_LOGIC);
	--buzzer:done出现上升沿时开始buzz输出1,持续30个clk周期,其他时候buzz输出0
	--buzz:0-no sound, 1-buzz
END ENTITY buzzer;
ARCHITECTURE behav OF buzzer IS
COMPONENT counter
	PORT(clk,en,reset:IN STD_LOGIC;
		target:IN STD_LOGIC_VECTOR(4 DOWNTO 0);
		q:OUT STD_LOGIC);
END COMPONENT counter;
SIGNAL cnt_30:STD_LOGIC_VECTOR(4 DOWNTO 0);
SIGNAL wait_en,wait_reset,wait_done:STD_LOGIC;
SIGNAL count_start,count_end,counting:STD_LOGIC;
BEGIN
	cnt_30<="11110";
	wait_en<='1';
	wait_reset<=NOT counting;
	wait_30:counter PORT MAP(clk,wait_en,wait_reset,cnt_30,wait_done);

	counting<=count_start XOR count_end;
	buzz<=counting;

	st:PROCESS(done) IS
	BEGIN
		IF(done'EVENT AND done='1')THEN
			count_start<=NOT count_end;
		END IF;
	END PROCESS st;

	ed:PROCESS(wait_done) IS
	BEGIN
		IF(wait_done'EVENT AND wait_done='1')THEN
			count_end<=count_start;
		END IF;
	END PROCESS ed;

END ARCHITECTURE behav;

在component中也要用port来声明该元件的引脚,直接从该元件的entity中的port声明里面复制下来就行了
然后在需要用这种元件的时候,就用port map来建一个该元件的副本,给个名字,把它的引脚与你的信号量或者引脚连接(映射)起来
前面代码中,buzzer元件内部就设置了一个counter元件的副本,名字叫做wait_30,并且将后面那些信号量或者引脚一一连到wait_30对应的引脚上

声明一次之后,当前元件的代码中可以多次用port map使用这种元件,像C++中函数内调用其他函数...
只不过这里每次都必须给不同的名字,因此使用多少次这种元件,它就会有多少个副本...
和软件中调用函数对比一下的话会很有趣呢...
软件中调用多次函数不会增加空间复杂度,但是会增加时间复杂度...
而硬件中使用多次元件的话不会增加时间复杂度,但是会增加空间复杂度...

那么就到这里吧...基本上用这些就能...嗯...至少应付这门课是没有问题了...

Tags: ,

评论暂缺

rssComments RSS   transmitTrackBack Identifier URI

No comments. Be the first.

add留下评论