FPGA之道(30)编写自己的vhdl库文件

举报
李锐博恩 发表于 2021/07/15 03:01:00 2021/07/15
4.3k+ 0 0
【摘要】 文章目录 前言编写自己的vhdl库文件Work库记录数据类型子程序介绍函数过程 子程序使用总结程序包自定义程序包范例 前言 本文节选自《FPGA之道》来一起学习下高阶Verilog内容,编写自己的VHDL库文件。 编写自己的vhdl库文件 Work库 在VHDL基本程序框架中,我们介绍过work库,在那里,我们说“这个库一般不用显式声明,因为F...

前言

本文节选自《FPGA之道》来一起学习下高阶Verilog内容,编写自己的VHDL库文件。

编写自己的vhdl库文件

Work库

在VHDL基本程序框架中,我们介绍过work库,在那里,我们说“这个库一般不用显式声明,因为FPGA开发工具一般会自动帮我们完成这个工作”。这也是为什么一个FPGA工程下的一个VHDL文件中可以例化另外一个VHDL文件中描述的元件而不需要在代码的最开始加上声明:
Library work;
不过,有“一般”就会有“特殊”。我们都知道,库文件为我们的代码开发带来了非常大的便利,否则我们必须自己定义数据结构、定义相关的基本函数、过程等。如果VHDL中没有库,就好比C语言中没有int类型,没有print函数一样,会让人觉得两眼一摸黑,即使实现一个非常简单的功能也要摆出万里长征的架势还不一定能完成。那么,由于我们在编写代码时碰到的情况总是多种多样的,尤其是有一些情况并不是普遍情况,而几个标准库中定义的都是一些最通用、最基础的内容,不见得能很好的满足我们的编程需求。尤其是当我们的工程结构比较大、比较复杂、包含了成百上千个VHDL文件时,如果能够定义一些可供工程中所有文件共同调用的常数、数据类型、函数或进程的话,那该有多方便啊!要知道,一个实体中定义的常量、数据类型和子程序等对于其它实体是不可见的,于是,VHDL提供了自定义库的方法来解决我们的困扰。自定义库,其实主要指的就是自定义work库中的程序包,而要使用work库中的程序包时,就是本段开头所说的“特殊”情况。 如果哪个VHDL文件要使用自定义的程序包,一定要在代码最开始的库声明中加上:
use work.<package_name>.all;

记录数据类型

一般来说,VHDL的几个标准库中给我们提供的常用数据类型已经完全满足我们使用了,所以我们几乎不需要也没有必要定义新的基本数据类型。但是有一种情况比较特殊,那就是在我们的元件中,总是有几个数据是被同时传递的,这时候可以用记录数据类型将它们封装起来,从而简化代码并提高可读性。
例如,处理图像时,非常多的元件都需要知道图像的长、宽、颜色类型、颜色深度等等信息,按照通常的做法,我们可能需要在几十个VHDL的entity中都加上如下若干行定义:

width : in std_logic_vector(15 downto 0); 
height : in std_logic_vector(15 downto 0); 
colorMode : in std_logic; 
colorDepth : in std_logic_vector(3 downto 0);
	……

  
 

VHDL是一个比较严谨的语言,严谨到让人觉得繁琐,所以上述定义语句还需要在使用它们的父元件的architecture的声明部分声明一次,还需要在父元件的architecture的语句部分实例化一次!怎么样,是不是有点崩溃?这个时候记录类型就体现出它的优势,我们只需要定义一个类似名为pictureParaType的记录类型,然后在entity的定义中仅添加一行定义:

pictureParameter : in pictureParaType;

  
 

即可!注意,记录类型为VHDL语法带来了更高的抽象能力,仅适合高级用户使用,入门级用户请在高级用户的陪同下谨慎学习和使用,否则造成软、硬件编程思路混淆导致精神崩溃本书概不负责!

子程序介绍

VHDL语言中也可以定义子程序,它们是过程(Procedure)和函数(Function)。分别介绍如下。

函数

函数即function,只能用来计算数值,而不能改变与函数形参相关的对象的值。所以,函数的参数类型只能是方向为in的信号或者常量。而函数的计算结果通过return语句返回,这点与C语言非常类似。Function的一般语法如下:

Function <function_name> (signal <parameter> : in <paramter_type>; <other parameters>) Return <result_type> IS variable result : <data_type>; <other variable declare>
Begin <statements> -- 串行语句 Return result;
End <function_name>;

  
 

关于function的语法,有以下几点说明:
1、参数列表中只能是signal,不能使variable。这点很好理解,因为function调用语句是并行的,除此以外,signal的方向只能是in。
2、函数返回值<result_type>的类型如果是逻辑向量类型的话,请直接写明std_logic_vector即可,如果加上范围约束的话会报错。
3、函数体内只能定义variable,不能定义signal,这跟process一样。
4、函数支持嵌套,即函数里面可以调用别的函数。因为function内部其实也是顺序语句,所以函数 a调用了函数b,跟在a内展开b的代码结果是一样的。虽然这违背了function调用语句是并行的概念,但是编译器一般是能够通过的。
5、函数中可以调用过程,但是注意,函数中调用的过程的输出参数必须是variable类型的,因为函数体中没有可供过程输出所用的signal。
6、函数不支持递归。首先,函数的参数列表必须是signal类型的,所以在递归调用自己时,必须传递signal类型的参数;第三,函数体内部只能定义variable类型的中间变量,所以如果需要signal类型的参数,只能从参数列表来;最后,参数列表中的signal方向都是in,即不能再function内部进行修改;最后,如果同样的参数和内容被递归调用,那么该怎么设置递归结束条件呢?所以,这样只能是一个死循环,编译器无法通过,即使通过了,仿真器也会卡死。
7、函数调用总体来说是并行语句。但是有时候也可以出现在串行语句中,例如另一个函数体内或另一个过程体内。

过程

过程即procedure,它的参数可以是in,out或者inout方式,但是不能为buffer方式。所以过程没有返回值,但可以通过改变参数来向调用方返回相关信息。Procedure的一般语法如下:

Procedure <procedure_name> (signal/variable <signal/variable _name> : <direction> <type>; <other signal or variable>) IS <variable declare>
Begin
<statements>; -- 串行语句
End <procedure_name>;

  
 

关于procedure的语法有以下几点说明:
1、参数列表可以是signal也可以是variable;
2、过程语句体内只能定义variable;
3、过程支持嵌套,即过程里面可以调用别的过程。
4、过程里面可以调用函数,相当于把函数里面的语句平铺到过程语句中。虽然这与函数调用语句是并行的不相符,不过编译器一般是可以通过的。
5、过程支持递归调用。即过程可以调用它自己,不过此时过程的输入类型参数必须为variable,否则也会由于无法修改而导致无法设置循环结束条件。
6、过程调用是串行语句,不能单独出现在architecture下。

子程序使用总结

子程序可以出现在architecture的声明与定义部分,也可以出现在库的程序包中。它们常用于多层次的电路综合设计,这点与元件例化的功能从本质上来说是一样的,只不过调用时更加方便、简练,尤其是对定义在库中的子程序来说。但是子程序的抽象级别比较高,不方便写出一些硬件方面的优化,也不方便很好的去掌控硬件,而且概念接近于软件,它更像是为整个VHDL程序体系打基础所用,为编译器、仿真器而服务,而不像为了具体设计硬件代码而出现。因此,一般情况下,即使再复杂的系统,都是通过元件例化来实现层次化电路综合设计的。所以,对于初级用户,建议不要使用子程序,而对于中、高级用户,请在修炼前做好充分准备,否则很容易走火入魔呦!

程序包

程序包即库中的package,它就是VHDL为了使一组常量、数据类型、子程序甚至元件等内容能够被多个实体可见而提供的一种结构。也许大家会有疑问,自己编写的元件不是会自动放在work库中的供别的VHDL代码调用么,为什么还要放在package中呢?首先,我们是可以显式的把元件添加到work库中某一个package的,虽然感觉有些画蛇添足,但这不错;其次,有些元件并不是我们在代码中自己编写的元件,而是一些在其他程序中预定义的元件,例如每个FPGA厂商都有自己的原语库,如果要使用这些原语,必须声明这些库中的程序包。例如编程时需要用到xilinx的原语,必须声明:

library UNISIM;
use UNISIM.VComponents.all;

  
 

那么,这个VComponents程序包中必然包含了元件的声明。
Package的语法如下:

library <library_name>;
use <library_name>.<packege_name>.<function_name>;

package <Package_Name> is
-- new type declare use record statement  
-- Declare constants
-- Declare functions and procedure
-- Declare component 
end <Package_Name>;

package body <Package_Name> is
-- funtion
-- Procedure 
end <Package_Name>;

  
 

可见,程序包主要分为包头和包体两部分。
包头主要用于常量、数据类型、子程序甚至元件的声明,而包体主要用于子程序的具体实现。
子程序的声明和实现类似C语言中函数的声明和定义。
Function的声明语法如下:
Function <function_name> (signal : in <paramter_type>; ) Return <result_type>;
Procedure的声明语法如下:
Procedure <procedure_name> (signal/variable <signal/variable _name> : ; );

自定义程序包范例

关于自定义库文件,我们通过介绍一个自定义程序包的设计和使用范例,来给大家加深一下印象,并且大家可以参考范例复习一下自定义库中的各种语法。范例代码如下:
– 自定义程序包 testPack.vhd文件

library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.STD_LOGIC_ARITH.ALL;

package testPack is
-- Define new data type type person is
	record
		id : std_logic_vector(7 downto 0);
		sex : std_logic;
		salary	  : std_logic_vector(15 downto 0);
	end record;
-- Declare constants
  constant average_salary : std_logic_vector(15 downto 0) := conv_std_logic_vector(3500, 16); 
-- Declare functions and procedure
  function income_level_func (signal salary : in std_logic_vector(15 downto 0)) return std_logic;
  procedure income_level_pro (signal salary : in std_logic_vector(15 downto 0); signal result : out std_logic);
end testPack;

package body testPack is
  function income_level_func(signal salary : in std_logic_vector(15 downto 0)) return std_logic is
	variable result : std_logic;
  begin
	if (salary >= conv_std_logic_vector(3500, 16)) then result := '1';
	else result := '0';
	end if;
	return result; end income_level_func; procedure income_level_pro (signal salary : in std_logic_vector(15 downto 0); signal result : out std_logic) is begin
	if (salary >= conv_std_logic_vector(3500, 16)) then
		result <= '1';
	else
		result <= '0';
	end if;
  end income_level_pro; 
end testPack;

-- Desire.vhd文件
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use work.testPack.all;

entity Desire is
port (
	husband : in person;
	wife : in person;	
	husband_salary_level : out std_logic;
	wife_salary_level : out std_logic;
	family_mode : out std_logic;
	family_salary_level : out std_logic
);
end Desire;

architecture Behavioral of Desire is
signal family_income : std_logic_vector(15 downto 0);
signal family_salary : std_logic_vector(15 downto 0);
begin
husband_salary_level <= income_level_func(husband.salary);

process(wife)
begin
	income_level_pro(
		salary => wife.salary,
		result => wife_salary_level
	);
end process;

family_income <= husband.salary + wife.salary;
family_salary <= '0' & family_income(15 downto 1);
family_salary_level <= '1' when family_salary >= average_salary else '0';
family_mode <= wife.sex xor husband.sex;
end Behavioral;

  
 
上述例子完成了两个功能:

  
 

一、判断家庭收入在我国2011年年中调整工资收税起征点到3500后的收入水平的功能。包括丈夫的收入水平、妻子的收入水平以及家庭平均水平。通过逻辑值’1’表示收入高于社会平均水平,’0’表示低于社会平均水平。
二、判断家庭组成状况的功能。family_mode为’1’表示一男一女组成的正常家庭,为’0’表示同性恋家庭。

文章来源: reborn.blog.csdn.net,作者:李锐博恩,版权归原作者所有,如需转载,请联系作者。

原文链接:reborn.blog.csdn.net/article/details/104289681

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。