【Verilog 教程】 6.1Verilog函数

news/2024/11/29 4:50:04/

关键词:函数,大小端转换,数码管译码
在 Verilog 中,可以利用任务(关键字为 task)或函数(关键字为 function),将重复性的行为级设计进行提取,并在多个地方调用,来避免重复代码的多次编写,使代码更加的简洁、易懂。

函数
函数只能在模块中定义,位置任意,并在模块的任何地方引用,作用范围也局限于此模块。函数主要有以下几个特点:

1)不含有任何延迟、时序或时序控制逻辑
2)至少有一个输入变量
3)只有一个返回值,且没有输出
4)不含有非阻塞赋值语句
5)函数可以调用其他函数,但是不能调用任务
Verilog 函数声明格式如下:

function [range-1:0]     function_id ;
input_declaration ;other_declaration ;
procedural_statement ;
endfunction

函数在声明时,会隐式的声明一个宽度为 range、 名字为 function_id 的寄存器变量,函数的返回值通过这个变量进行传递。当该寄存器变量没有指定位宽时,默认位宽为 1。

函数通过指明函数名与输入变量进行调用。函数结束时,返回值被传递到调用处。

函数调用格式如下:

function_id(input1, input2, …);
module endian_rvs#(parameter N = 4)(input             en,     //enable controlinput [N-1:0]     a ,output [N-1:0]    b);reg [N-1:0]          b_temp ;always @(*) beginif (en) beginb_temp =  data_rvs(a);endelse beginb_temp = 0 ;endendassign b = b_temp ;//function entityfunction [N-1:0]     data_rvs ;input     [N-1:0] data_in ;parameter         MASK = 32'h3 ; integer           k ;beginfor(k=0; k<N; k=k+1) begindata_rvs[N-k-1]  = data_in[k] ;  endendendfunctionendmodule

函数里的参数也可以改写,例如:

defparam data_rvs.MASK = 32'd7 ;

但是仿真时发现,此种写法编译可以通过,仿真结果中,函数里的参数 MASK 实际并没有改写成功,仍然为 32’h3。这可能和编译器有关,有兴趣的学者可以用其他 Verilog 编译器进行下实验。

函数在声明时,也可以在函数名后面加一个括号,将 input 声明包起来。

例如上述大小端声明函数可以表示为:

function [N-1:0]     data_rvs(
input     [N-1:0] data_in ......;

常数函数
常数函数是指在仿真开始之前,在编译期间就计算出结果为常数的函数。常数函数不允许访问全局变量或者调用系统函数,但是可以调用另一个常数函数。

这种函数能够用来引用复杂的值,因此可用来代替常量。

例如下面一个常量函数,可以来计算模块中地址总线的宽度:

parameter    MEM_DEPTH = 256 ;
reg  [logb2(MEM_DEPTH)-1: 0] addr ; //可得addr的宽度为8bitfunction integer     logb2;input integer     depth ;//256为9bit,我们最终数据应该是8,所以需depth=2时提前停止循环for(logb2=0; depth>1; logb2=logb2+1) begin depth = depth >> 1 ;end
endfunction

automatic 函数
在 Verilog 中,一般函数的局部变量是静态的,即函数的每次调用,函数的局部变量都会使用同一个存储空间。若某个函数在两个不同的地方同时并发的调用,那么两个函数调用行为同时对同一块地址进行操作,会导致不确定的函数结果。

Verilog 用关键字 automatic 来对函数进行说明,此类函数在调用时是可以自动分配新的内存空间的,也可以理解为是可递归的。因此,automatic 函数中声明的局部变量不能通过层次命名进行访问,但是 automatic 函数本身可以通过层次名进行调用。

下面用 automatic 函数,实现阶乘计算:

wire [31:0]          results3 = factorial(4);
function automatic   integer         factorial ;input integer     data ;integer           i ;beginfactorial = (data>=2)? data * factorial(data-1) : 1 ;end
endfunction // factorial

下面是加关键字 automatic 和不加关键字 automatic 的仿真结果。

由图可知,信号 results3 得到了我们想要的结果,即 4 的阶乘。

而信号 results_noauto 值为 1,不是可预知的正常结果,这里不再做无用分析。
在这里插入图片描述
数码管译码
上述中涉及的相关函数知识似乎并没有体现出函数的优越性。下面设计一个 4 位 10 进制的数码管译码器,来说明函数可以简化代码的优点。

下图是一个数码管的实物图,可以用来显示 4 位十进制的数字。在比赛计分、时间计时等方面有着相当广泛的应用。
在这里插入图片描述
每位数码显示端有 8 个光亮控制端(如图中 a-g 所示),可以用来控制显示数字 0-9 。

而数码管有 4 个片选(如图中 1-4),用来控制此时哪一位数码显示端应该选通,即应该发光。倘若在很短的时间内,依次对 4 个数码显示端进行片选发光,同时在不同片选下给予不同的光亮控制(各对应 4 位十进制数字),那么在肉眼不能分辨的情况下,就达到了同时显示 4 位十进制数字的效果。
在这里插入图片描述
下面,我们用信号 abcdefg 来控制光亮控制端,用信号 csn 来控制片选,4 位 10 进制的数字个十百千位分别用 4 个 4bit 信号 single_digit, ten_digit, hundred_digit, kilo_digit 来表示,则一个数码管的显示设计可以描述如下:

module digital_tube(input             clk ,input             rstn ,input             en ,input [3:0]       single_digit ,input [3:0]       ten_digit ,input [3:0]       hundred_digit ,input [3:0]       kilo_digit ,output reg [3:0]  csn , //chip select, low-availableoutput reg [6:0]  abcdefg        //light control);reg [1:0]            scan_r ;  //scan_ctrlalways @ (posedge clk or negedge rstn) beginif(!rstn)begincsn            <= 4'b1111;abcdefg        <= 'd0;scan_r         <= 3'd0;endelse if (en) begincase(scan_r)2'd0:beginscan_r    <= 3'd1;csn       <= 4'b0111;     //select single digitabcdefg   <= dt_translate(single_digit);end2'd1:beginscan_r    <= 3'd2;csn       <= 4'b1011;     //select ten digitabcdefg   <= dt_translate(ten_digit);end2'd2:beginscan_r    <= 3'd3;csn       <= 4'b1101;     //select hundred digitabcdefg   <= dt_translate(hundred_digit);end2'd3:beginscan_r    <= 3'd0;csn       <= 4'b1110;     //select kilo digitabcdefg   <= dt_translate(kilo_digit);endendcaseendend/*------------ translate function -------*/function [6:0] dt_translate;input [3:0]   data;begincase(data)4'd0: dt_translate = 7'b1111110;     //number 0 -> 0x7e4'd1: dt_translate = 7'b0110000;     //number 1 -> 0x304'd2: dt_translate = 7'b1101101;     //number 2 -> 0x6d4'd3: dt_translate = 7'b1111001;     //number 3 -> 0x794'd4: dt_translate = 7'b0110011;     //number 4 -> 0x334'd5: dt_translate = 7'b1011011;     //number 5 -> 0x5b4'd6: dt_translate = 7'b1011111;     //number 6 -> 0x5f4'd7: dt_translate = 7'b1110000;     //number 7 -> 0x704'd8: dt_translate = 7'b1111111;     //number 8 -> 0x7f4'd9: dt_translate = 7'b1111011;     //number 9 -> 0x7bendcaseendendfunctionendmodule

仿真结果如下。

由图可知,片选、译码等信号,均符合设计。实际中,4 位数字应当在一定的时间内保持不变,而片选信号不停的循环扫描,数码管才能给肉眼呈现一种静态显示的效果。
在这里插入图片描述
小结
如果译码器设计没有使用函数 dt_translate,则在每个 case 选项里对信号 abcdefg 进行赋值时,还需要对 single_digit,ten_digit, hundred_digit, kilo_digit 进行判断。这些判断语句又会重复 4 次。虽然最后综合出的实际硬件电路可能是一样的,但显然使用函数后的代码更加的简洁、易读。


http://www.ppmy.cn/news/1129291.html

相关文章

【红日靶场】vulnstack2-完整渗透过程

文章目录 一、网络拓扑二、描述配置环境渗透开始信息收集开始攻击干掉杀软cs上线权限提升 内网探测横向移动再次横向rdp连接痕迹清除 总结&#xff1a; 一、网络拓扑 网络配置&#xff1a; 二、描述 红队实战系列&#xff0c;主要以真实企业环境为实例搭建一系列靶场&#x…

SpringMVC 学习(八)整合SSM

10. 整合 SSM (1) 新建数据库 CREATE DATABASE SSM;USE SSM;DROP TABLE IF EXISTS BOOKS;CREATE TABLE BOOKS (BOOK_ID INT(10) NOT NULL AUTO_INCREMENT COMMENT 书ID,BOOK_NAME VARCHAR(100) NOT NULL COMMENT 书名,BOOK_COUNTS INT(11) NOT NULL COMMENT 数量,DETAIL VARCH…

Python的单选框、复选框、下拉框、消息提示框、文件对话框

优秀教程&#xff1a;https://zhuanlan.zhihu.com/p/569960987?utm_id0 单行文本框 - Entry textvariable&#xff1a;单行文本框变量&#xff0c;String类型&#xff0c;可以使用变量的set和get可以获取或设置值show&#xff1a;单行文本框内文字显示方式&#xff0c;如密码…

docker系列(9) - docker-compose

文章目录 9. compose编排9.1 介绍9.2 安装9.3 compose常用命令9.4 编排微服务9.4.1 准备docker-compose.yml9.4.2 启动服务9.4.3 测试验证 9. compose编排 9.1 介绍 多服务部署工具&#xff0c;通过一个docker-compose.yml模板文件&#xff0c;定义一组关联的应用容器为一个项…

13.(开发工具篇github)如何在GitHub上上传本地项目

一:创建GitHub账户并安装Git 二:创建一个新的仓库(repository) 三、拉取代码 git clone https://github.com/ainier-max/myboot.git git clone git@github.com:ainier-max/myboot.git四、拷贝代码到拉取后的工程 五、上传代码 (1)添加所有文件到暂存

java 根据身份证号码判断性别

在Java中&#xff0c;您可以根据身份证号码的规则来判断性别。中国的身份证号码通常采用的是以下规则&#xff1a; 第17位数字代表性别&#xff0c;奇数表示男性&#xff0c;偶数表示女性。 通常&#xff0c;男性的出生日期的第15、16位数字是01&#xff0c;女性是02。 请注意&…

深入props --React进阶指南笔记

一次render的过程&#xff1a; 调用React.createElement形成新的element过程&#xff0c;新的element上就会有新的props属性&#xff08;即重新渲染视图的关键&#xff09;。 来看一个demo&#xff1a; /* children 组件 */ function ChidrenComponent(){return <div> I…

centos给用户分配docker权限

原理 docker守护进程启动的时候&#xff0c;会默认赋予名为docker的用户组读写Unix socket的权限&#xff0c;因此只要创建docker用户组&#xff0c;并将当前用户加入到docker用户组中&#xff0c;那么当前用户就有权限访问Unix socket了&#xff0c;进而也就可以执行docker相…