前言

提到函数与任务,很多已从业的逻辑设计人员甚至都会对此陌生,听过是听过,但是很少用过。

与大多数编程语言一样,我们应该尝试使尽可能多的Verilog代码可重用。这使我们能够减少将来项目的开发时间,因为我们可以更轻松地将代码从一种设计移植到另一种设计。

人是懒惰的,觉得麻烦且使用场合局限,就不去重视,甚至不予接触,我宁愿使用其他语法代替。这样的话,就失去了这些语法宝贵的用途,Verilog中的函数与任务有时候使用起来会给工作带来很多便利,且有的时候这是一种竞争力,作为工作的一份子,难道你接触的都是自己的代码吗?不可能吧,甚至可以说,你接触的大多数都是别人的代码,你能保证别人不使用函数和任务吗?所以,这一关始终是难以绕过的。

使用Verilog中的函数和任务,可以编写出很多精炼的代码,让代码可读性提高。例如仿真中,某个功能模块我们需要重复利用,那么就可以使用函数或者任务的一种,让其成为我们仿真平台的一部分,逼格提升了不说,效率也提高了不少。

本文首发:https://www.ebaina.com/articles/140000010072

举个例子,此时你可以不用懂函数以及任务的语法,就看它们带来的方便即可:
定义一个求和函数:

function [7:0] sum;input [7:0] a, b;beginsum = a + b;end
endfunction

仿真时调用这个函数:

reg [7:0] result;
reg [7:0] a, b;initial begina = 4;b = 5;#10 result = sum (a, b);
end

有人会觉得好傻,我直接加不就可以了。
当然,不会为此抬杠,这只是一个例子,如果是其他复杂的复用结构,函数会是一个利器。

任务也是如此:

 task sum;input  [7:0] a, b;output [7:0] c;beginc = a + b;endendtask
 initial beginreg [7:0] x, y , z;sum (x, y, z);          end

这里只是说明函数和任务可以干什么?下面探讨如何在Verilog中使用任务和函数。这些被统称为子程序,它们使我们能够编写可重用的Verilog代码。在后续的进阶中,你可能会阅读到很多进阶的设计,用到了函数以及任务,你会越来越熟悉它们。

函数与任务

功能和任务之间有两个主要区别。

  • 当我们编写一个verilog函数时,它将执行计算并返回一个值。

  • 相反,verilog任务执行许多时序语句,但不返回值。相反,任务可以有无限数量的输出。

  • 除此之外,verilog函数可立即执行,并且不能包含耗时的构造,例如延迟,posege宏或wait语句

  • 一个Verilog的任务,在另一方面,可以包含耗时结构。

接下来,我们将深入讨论这两种构造。这包括提供一些示例,说明我们如何在Verilog中编写和调用函数和任务。

Verilog 函数

在Verilog中,函数是一个子程序,它接受一个或多个输入值,执行一些计算并返回输出值。

我们使用函数来实现一小部分代码,这些代码要在设计中的多个地方使用。

通过使用函数而不是在多个位置重复相同的代码,我们使我们的代码更具可维护性。

我们在verilog模块中编写函数代码,以调用该函数。

下面的代码段显示了Verilog中函数的一般语法。

// First function declaration style - inline argumentsfunction <return_type> <name> (input <arguments>);// Declaration of local variablesbegin// function codeendendfunction : <name>
// Second function declaration style - arguments in bodyfunction <return_type> <name>;(input <arguments>);// Declaration of local variablesbegin// function codeendendfunction

我们必须给每个函数起一个名字,如上例中的字段所示。

我们可以在函数声明中内联声明输入,也可以将其声明为函数主体的一部分。我们用来声明输入参数的方法对函数的性能没有影响。

但是,当我们使用内联声明时,如果需要,我们也可以省略begin和end关键字。

在上面的示例中,我们使用字段声明函数的输入。

我们使用<return_type>字段来声明函数返回哪种Verilog数据类型。如果我们排除了函数声明的这一部分,则该函数将默认返回一个1位值。

当我们返回一个值时,我们通过为函数名称分配一个值来实现。下面的代码片段显示了我们如何简单地将输入返回给函数。

function integer easy_example (input integer a);easy_example = a;endfunction : easy_example

在Verilog中使用函数的规则

尽管函数通常很简单,但是编写Verilog函数时必须遵循一些基本规则。

  • 函数最重要的规则之一是它们不能包含任何耗时的构造,例如延迟,posege宏或wait语句。

当我们想编写一个消耗时间的子程序时,我们应该改用verilog任务。

结果,我们也无法从函数中调用任务。相反,我们可以从函数体内调用另一个函数。

当函数立即执行时,我们只能在Verilog函数中使用阻塞分配。

当我们在verilog中编写函数时,我们可以声明和使用局部变量。这意味着我们可以在函数中声明无法在声明其的函数之外访问的变量。

除此之外,我们还可以访问Verilog函数中的所有全局变量。

例如,如果我们在模块块中声明一个函数,则该函数中可以访问和修改该模块中声明的所有变量。

下面总结在Verilog中使用函数的规则。

在Verilog中使用函数的规则

  • Verilog函数可以具有一个或多个输入参数
  • 函数只能返回一个值
  • 函数不能使用耗时的构造,例如posege,wait或delays(#)
  • 我们不能从函数中调用任务
  • 我们可以从一个函数中调用其他函数
  • 非阻塞分配不能在函数中使用
  • 局部变量可以在函数内部声明和使用
  • 我们可以从verilog函数内部访问和修改全局变量
  • 如果不指定返回类型,则该函数将返回单个位

Verilog函数示例

为了更好地演示如何使用Verilog函数,让我们考虑一个基本示例。

对于此示例,我们将编写一个函数,该函数接受2个输入参数并返回它们的总和。

我们使用verilog整数类型作为输入参数和返回类型。

我们还必须使用verilog加法运算符来计算输入的总和。

下面的代码段显示了此示例函数在verilog中的实现。

正如我们之前讨论的,可以使用两种方法来声明verilog函数,这两种方法都显示在下面的代码中。

// Using inline declaration of the inputsfunction integer addition (input integer in_a, in_b);// Return the sum of the two inputsaddition = in_a + in_b;endfunction : addition
// Declaring the inputs in the function bodyfunction integer addition;input integer in_a;input integer in_b;begin// Return the sum of the two inputsaddition = in_a + in_b;endendfunction : addition

在Verilog中调用函数

当我们想在Verilog设计的另一部分中使用函数时,必须调用它。我们用于执行此操作的方法类似于其他编程语言。

当我们调用一个函数时,我们以与声明它们相同的顺序将参数传递给该函数。这称为位置关联,这意味着我们声明参数的顺序非常重要。

下面的代码段显示了如何使用位置关联来调用附加示例函数。

在下面的示例中,in_a将映射到a参数,而in_b将映射到b。

// Calling a verilog function using positional associationfunc_out = addition(a, b);

Verilog中的自动功能

我们还可以使用verilog automatic关键字将函数声明为reentrant。

但是,自动关键字是在verilog 2001标准中引入的,这意味着在使用verilog 1995标准时我们无法编写可重入函数。

当我们将一个函数声明为可重入函数时,该函数内的变量和参数将动态分配。相反,普通函数对内部变量和参数使用静态分配。

当我们编写一个普通函数时,用于执行该函数处理的所有内存仅分配一次。这个过程在计算机科学中被称为静态内存分配。

因此,我们的仿真软件必须完全执行该功能,然后才能再次使用该功能。

这也意味着该函数使用的内存永远不会被释放。结果,存储在该存储器中的所有值将在调用该函数之间保持其值。

相反,每当调用函数时,使用自动关键字的函数都会分配内存。函数完成后,便会释放内存。

在计算机科学中,此过程称为自动或动态内存分配。

结果,我们的仿真软件可以执行自动功能的多个实例。

我们可以使用自动关键字在verilog中编写递归函数。这意味着我们可以创建调用自身以执行计算的函数。

例如,递归函数的一个常见用例是计算给定数字的阶乘。

下面的代码段显示了如何使用自动关键字在verilog中编写递归函数。

function automatic integer factorial (input integer a);beginif (a > 1) beginfactorial = a * factorial(a - 1);endelse beginfactorial = 1;endendendfunction : factorial

Verilog任务

就像函数一样,我们使用任务来实现一小段代码,这些代码可以在整个设计中重复使用。

在Verilog中,任务可以具有任意数量的输入,也可以生成任意数量的输出。这与只能返回单个值的函数相反。

与函数不同,我们还可以在任务中使用耗时的构造,例如等待,姿势或延迟(#)。

结果,我们可以在verilog任务中同时使用阻塞分配和非阻塞分配。

这些功能意味着任务最适合用来实现简单的代码,这些代码在我们的设计中重复了几次。一个很好的例子是在已知接口(例如SPI或I2C)上驱动引脚。

我们在verilog模块中编写任务代码,该代码将用于调用任务。

我们还可以创建全局任务,这些任务由给定文件中的所有模块共享。为此,我们只需在文件中的模块声明之外编写任务的代码即可。

下面的代码段显示了Verilog中任务的一般语法。

与函数一样,有两种方法可以声明任务,但是两种方法的性能相同。

// Task syntax using inline IOtask <name> (<io_list>);// Code which implements the taskendtask
// Task syntax with IO declared in the task bodytask <name>;<io_list>begin// Code which implements the taskendendtask

我们必须给每个任务起一个名字,如上面的字段所示。

当我们编写在任务主体中声明输入和输出的任务时,还必须使用begin和end关键字。

但是,当我们将内联声明用于输入和输出时,可以省略begin和end关键字。

当我们在verilog中编写任务时,我们可以声明和使用局部变量。这意味着我们可以在任务中创建不能在声明它的任务之外访问的变量。

除此之外,我们还可以访问Verilog任务中的所有全局变量。

与verilog函数不同,我们可以从一个任务中调用另一个任务。我们还可以从任务中调用函数。

Verilog任务示例

让我们考虑一个简单的示例,以更好地演示如何编写Verilog任务。

对于此示例,我们将编写一个可用于生成脉冲的基本任务。当我们在设计中调用任务时,可以指定脉冲的长度。

为此,我们需要一个输入(用于确定脉冲多长时间)和一个用于生成脉冲的输出。

下面的verilog代码使用两种不同的任务样式显示了此示例的实现。

// Task implementation using inline declaration of IOtask pulse_generate(input time pulse_length, output pulse);pulse = 1'b1#pulse_timepulse = 1'b0;endtask
// Task implementation with IO declared in bodytask pulse_generate;input time pulse_length;output pulse;beginpulse = 1'b1;#pulse_timepulse = 1'b0;endendtask

尽管此示例非常简单,但是我们可以在此处看到如何在任务中使用Verilog延迟运算符(#)。如果尝试在函数中编写此代码,则在尝试对其进行编译时将导致错误。

从这个例子中我们还可以看出,返回值的方式与使用函数的方式不同。

相反,我们必须声明在任务声明中使用的所有输出。

在verilog中编写任务时,我们可以包含并驱动任意数量的输出。

在Verilog中调用任务

与函数一样,当我们要在Verilog设计的另一部分中使用任务时,必须调用它。

我们用于执行此操作的方法类似于用于调用函数的方法。

但是,在verilog中调用任务和函数之间存在一个重要区别。

当我们在Verilog中调用任务时,不能像使用函数一样将其用作表达式的一部分。

相反,我们应该将任务调用视为在设计中包含代码块的简便方法。

与函数一样,当我们调用任务时,我们使用位置关联将参数传递给任务。

这只是意味着我们以编写任务代码时所声明的顺序将参数传递给任务。

下面的代码片段显示了我们将如何使用位置关联来调用先前考虑过的pulse_generate任务。

在这种情况下,pulse_length输入被映射到pulse_time变量,而脉冲输出被映射到pulse_out变量。

// Calling a task using positional associationgenerate_pulse(pulse_time, pulse_out);

Verilog中的自动任务

我们还可以将自动关键字与verilog任务一起使用,以使其可重入。同样,此关键字是在verilog 2001标准中引入的,这意味着它不能与verilog 1995兼容代码一起使用。

如前所述,使用关键字auto意味着我们的仿真工具使用了动态内存分配。

与函数一样,任务默认情况下使用静态内存分配,这意味着仿真软件只能运行任务的一个实例。

相反,无论何时调用任务,使用自动关键字的任务都会分配内存。任务完成后,然后释放内存。

让我们考虑一个基本示例,以显示自动任务的使用以及它们与正常任务的区别。

对于此示例,我们将使用一个简单的任务,该任务将局部变量的值增加给定的数量。

然后,我们可以在仿真工具中多次运行此命令,以查看局部变量在使用自动任务和正常任务时的行为。

下面的代码显示了我们如何编写静态任务来实现此示例。

// Task which performs the incrementtask increment(input integer incr);integer i = 1;i = i + incr;$display("Result of increment = %0d", i);endtask
// Run the task three timesinitial beginincrement(1);increment(2);increment(3);end

在icarus verilog仿真工具中运行此代码将得到以下输出:

Result of increment = 2Result of increment = 4Result of increment = 7

从中可以看出,局部变量i的值是静态的,并存储在单个存储位置中。

结果,i的值是持久的,并且在任务调用之间保持其值。

当我们调用任务时,我们将增加已经存储在给定存储位置中的值。

下面的代码段显示了相同的任务,除了这次我们使用了自动关键字。

// Automatic task which performs the incrementtask automatic increment(input integer incr);integer i = 1;i = i + incr;$display("Result of increment = %0d", i);endtask
// Run the task three timesinitial beginincrement(1);increment(2);increment(3);end

在icarus verilog仿真工具中运行此代码将得到以下输出:

Result of increment = 2Result of increment = 3Result of increment = 4

由此,我们现在可以看到局部变量i是动态的,并且在调用任务时会如何创建它。创建它之后,然后为其分配值1。

任务完成运行后,将释放动态分配的内存,并且本地变量不再存在。

FPGA的设计艺术(14)使用函数和任务提升逻辑的可重用性相关推荐

  1. FPGA的设计艺术(17)如何搭建一个简易的逻辑测试平台?

    前言 提到FPGA逻辑的仿真,一般指的是行为仿真或者功能仿真,还有人会称为前仿,不包含时间延迟信息,只验证逻辑功能.对于小模块的仿真,需要写一个测试文件,英文是testbench,即测试平台.在tes ...

  2. FPGA的设计艺术(11)FPGA的构建过程

    前言 本文讨论FPGA的构建过程,由于FPGA的过程太多了,恐怕会有歧义,这个过程,不是开发过程,不是开发流程,而是实实在在的FPGA编译的过程,使用编译恐怕不是太合适,但是大家都叫习惯了,也知道FP ...

  3. FPGA的设计艺术(8)最佳的FPGA开发实践之严格遵循过程

    文章目录 前言 如何花费更少的时间去调试? 为什么使用过程? 需要多少过程? 最小的过程 明确需求 数字设计方案 逻辑设计 功能仿真 板上验证 版本控制 编码指南:简短的技术组合,可最大程度地减少错误 ...

  4. FPGA的设计艺术(6)STA实战之SmartTime时序约束及分析示例(I)

    前言 FPGA进行时序分析通常使用厂家的编译工具,进行时序分析,但是万变不离其宗,时序分析的知识通常都是通用的,原理都是一致的.下面根据SmartTime的资料来看下时序分析的实际操作是如何的,这在其 ...

  5. FPGA的设计艺术(5)STA实战之时钟偏斜对建立保持时间的影响以及时序报告分析

    前言 本文首发:FPGA的设计艺术(5)STA实战之时钟偏斜对建立保持时间的影响以及时序报告分析. STA回顾 70年代的时序是通过Spice仿真执行的.80年代的时序包括在Verilog仿真中,以确 ...

  6. FPGA的设计艺术(4)STA实战之不同时序路径的建立保持时间计算

    前言 本文首发:FPGA的设计艺术(4)STA实战之不同时序路径的建立保持时间计算 STA定义 STA定义为:时序验证,可确保各种电路时序是否满足各种时序要求. ASIC / FPGA设计流程中最重要 ...

  7. FPGA的设计艺术(3)静态时序分析

    前言 本文首发:FPGA的设计艺术(3)静态时序分析,我的易百纳技术社区专栏. 同行邀请消息:FPGA/IC Technology Exchange 什么是静态时序分析(STA)? 静态时序分析介绍 ...

  8. FPGA的设计艺术(2)FPGA开发流程

    前言 注:本文首发易百纳技术社区,文章链接:FPGA的设计艺术(2)FPGA开发流程 本文介绍整个FPGA设计流程以及设计FPGA所需的各个步骤-从一开始到可以将设计下载到FPGA的阶段.但是在此之前 ...

  9. FPGA的设计艺术(7)STA实战之SmartTime时序约束及分析示例(II)

    前言 本文续FPGA的设计艺术(6)STA实战之SmartTime时序约束及分析示例(I),分析了时钟的不确定性,多周期路径,以及门控时钟的STA分析方法.可以使用各大厂家的时序分析工具,大多数都自带 ...

最新文章

  1. treeselect 如何选中多个_word使用技巧之-如何让你工作效率翻倍提升
  2. Android如何获得当前应用显示的Activity
  3. Spring import配置文件使用占位符
  4. shell 脚本和 bash 脚本的关系
  5. amazeUI 复择框问题解决
  6. b+tree索引在MyIsam和InnoDB的不同实现方式
  7. html 必填设置,html如何设置必填项
  8. matplotlib中x轴y轴字号或字体修改
  9. pagefile.sys占用空间过大问题
  10. DBCP连接池配置优化分析
  11. 提问的智慧/ 如何优雅的提问
  12. 多视图信息瓶颈表征学习
  13. ClassCastException: XXX are in unnamed module of loader ‘app‘异常分析
  14. WIN10打开网络共享文件夹提示0x80004005怎么解决?(转载)
  15. 【2019多校第一场补题 / HDU6578】2019多校第一场A题1001Blank——dp
  16. 用编译器写的mov ax,[0]指令,执行的实际上是mov ax,0
  17. Win10怎么设置默认输入法为美式英文键盘
  18. Vivado下集成逻辑分析仪ILA入门续
  19. python爬虫下载影视网站的电影
  20. 4.HTML设计超链接的网页

热门文章

  1. jBPM4.4 no jBPM DB schema:
  2. ok6410 u-boot-2012.04.01移植六完善MLC NAND支持
  3. Web Application Stress Tool(WAS)性能测试
  4. Exchange 2007 配置POP3
  5. python 局域网通讯_python3 实现tcp/udp局域网通信
  6. 计算机教育的发展,计算机教育发展方向研究
  7. linux文件删除指定内容,Linux bash删除文件中含“指定内容”的行功能示例
  8. swoole单台并发php,php swoole 并发多少?
  9. idea 文件流读取web-inf下的文件_C#初学者教程系列20:Stream流读写
  10. 基于场效应管2N3819制作非接触电压检测 - 购买到假货了