在阅读 verilog HDL 代码时,经常看到 function 函数功能,之前对其不太了解,正好趁着这个例子来看一下 verilog 中的函数功能——function。
该函数的主要功能是判断输入的字符是否为数字(包含 0~9,A~F,a~f),如果是,就输出数字;如果不是,就将最 MSB 置位。
源码及注释为:
//*************************************************************************** // Tasks and Functions //*************************************************************************** // This function takes the lower 7 bits of a character and converts them // to a hex digit. It returns 5 bits - the upper bit is set if the character // is not a valid hex digit (i.e. is not 0-9,a-f, A-F), and the remaining // 4 bits are the digit function [4:0] to_val; input [6:0] char; begin if ((char >= 7'h30) && (char <= 7'h39)) // 0-9 begin to_val[4] = 1'b0; to_val[3:0] = char[3:0]; end else if (((char >= 7'h41) && (char <= 7'h46)) || // A-F ((char >= 7'h61) && (char <= 7'h66)) ) // a-f begin to_val[4] = 1'b0; to_val[3:0] = char[3:0] + 4'h9; // gives 10 - 15 end else begin to_val = 5'b1_0000; end end endfunction
功能还算比较易懂;
关于这个 function,心中是有几个疑惑的;
- 这段代码在电路上是如何实现的?是像描述那样,完全的组合电路吗?
- 电路的复杂程度,需要考虑单周期内的电路延迟吗?
- 能否综合?
通过阅读《通信 IC 设计》找到了上述答案。
function 写法
function 的标准写法如下:
function <返回值的类型或范围>(函数名); <端口说明语句> // input XXX <变量类型说明语句> // reg YYY ...... begin <语句> ...... 函数名 = ZZZ; // 函数名就相当于输出变量; end endfunction
语法
函数的语法为:
- 定义函数时至少要有一个输入参量;可以按照 ANSI 和 module 形式直接定义输入端口。例如:
function[63:0] alu (input[63:0] a, b, input [7:0] opcode);
- 在函数的定义中必须有一条赋值语句给函数名具备相同名字的变量赋值;
- 在函数的定义中不能有任何的时间控制语句,即任何用
#
,@
或wait
来标识的语句。 - 函数不能启动任务。
- 如果描述语句是可综合的,则必须所有分支均赋值,不予存在不赋值的情况,只能按照组合逻辑方式描述。
function 与触发器电路结合
function 本身表述的是组合电路,但可以赋值给某个触发器;
module func_test( input rst_n, input clk, input [6:0] func_i, output reg [4:0] func_o ); //*************************************************************************** // Tasks and Functions //*************************************************************************** // This function takes the lower 7 bits of a character and converts them // to a hex digit. It returns 5 bits - the upper bit is set if the character // is not a valid hex digit (i.e. is not 0-9,a-f, A-F), and the remaining // 4 bits are the digit function [4:0] to_val; input [6:0] char; begin if ((char >= 7'h30) && (char <= 7'h39)) // 0-9 begin to_val[4] = 1'b0; to_val[3:0] = char[3:0]; end else if (((char >= 7'h41) && (char <= 7'h46)) || // A-F ((char >= 7'h61) && (char <= 7'h66)) ) // a-f begin to_val[4] = 1'b0; to_val[3:0] = char[3:0] + 4'h9; // gives 10 - 15 end else begin to_val = 5'b1_0000; end end endfunction wire [4:0] func_wire; assign func_wire = to_val(func_i); always @ (posedge clk or negedge rst_n ) begin if ( !rst_n ) begin func_o <= 5'b0; end else begin func_o <= func_wire; end end endmodule
电路中,function 对应的功能为判断输入字符是否为数字;而 always 描述数据的存储过程。
于是电路被清晰地划分为两部分:
- 计算函数部分,此部分为纯组合电路实现;
- D 触发存储部分。
如果只是简单的上述组合逻辑,工作频率不高,是完全可以使用该语法的。
但考虑到函数的复杂程度,比如许多超越函数,就需要进行分解;
时序模型
先前的问题 2 中,就是关于这方面的思考,既然只有组合逻辑,就肯定要涉及到时序的问题;
这一部分内容,在《通信 IC 设计》中有详细的介绍。
因此,如果较为复杂的函数功能,就会出现时序无法满足的情况;
此时,可以通过如下的方法进行解决;
基于算法视角的时序优化
函数 f(x)过大,就会需要进行函数拆解,从而使单个函数负载和掩饰控制在一定的范围内。
- 累加拆解(第一种拆解)
- 累乘拆解(第二种拆解)
- 函数嵌套模式拆解(第三种拆解)
函数与通用描述的转换方法
函数其本身性质为组合逻辑,那么也一定可以与通用的描述进行转换;
函数其实是一种抽象形式描述,是一种需要实例对象才能功能生效的描述。如果将函数的定义替换为always @ ( * )
,然后定义必要的变量,就可以直接将其转化为实际的组合电路。
因此要讲函数转化为实际电路描述,可以采用always@
直接例化的方法实现。
函数小结
站在 Verilog 综合器的角度,描述的任何一个组合电路块,或者组合 always 语句,以及所有赋值给 D 触发器之前的逻辑,都将抽象为多值函数:即多个输入与输出的组合。
前面的各种拆解方法,其目的都是为了优化时序,并便于让综合软件更加快速、更灵活地实现 HDL 到电路网表的转换。
原理图及仿真
看一下 Schematic,能明显看出是一堆组合逻辑的搭配:
vivado 工程仿真:
而且可以看出function
本身耗时取决于函数功能的复杂程度;
扫码关注尚为网微信公众号
原创文章,作者:sunev,如若转载,请注明出处:https://www.sunev.cn/embedded/1288.html