1.function(函数),其目的是返回一个用于表达式的值。
其标准写法如下:
function <返回值的类型或范围> (函数名);
<端口说明语句> //例:input XXX;
<变量类型说明语句> //例:reg YYY;
begin
<语句>
......
函数名 = ZZZ; //函数名就相当于输出变量
end
endfunction
2.函数的使用规则:
(1)函数的定义不能包含有任何的事件控制语句,即任何用wait,#,@来标识的语句。
(2)函数不能启动任务(task)。
(3)定义函数时至少要有一个输入参量。
(4)在函数的定义中,必须有一条赋值语句给函数中的一个内部变量赋以函数的结果值,该内部变量具有和函数名相同的名字。
(5)如果描述语句是可综合的,则必须所有分支均赋值,不允许存在不赋值的情况,只能按照组合逻辑方式描述。
3.从函数返回的值:函数的定义蕴含声明了与函数同名的、函数内部的寄存器。若在函数的声明语句中<返回值的类型或范围>为默认,则这个寄存器是一位的;否则是与函数声明语句中一致的寄存器。
函数的定义把函数返回值所赋值寄存器的名称初始化为与函数同名的内部变量。
4.函数的调用:函数的调用是通过将函数作为表达式中的操作数来实现的,其中函数名作为确认符。调用格式如下:
<函数名> (表达式);
result = factorial(n);
1.电路功能:function本身描述的组合电路,赋值给某个触发器。
2.电路划分:1.函数部分,这部分是纯组合电路实现;2.D触发器部分;整体可抽象成:out=fx(a,b,…,n),描述伪代码格式为:
module ab_circult #(parameter N = 1) (
input clk,
input rst,
input a,
input b,
...
input n,
output reg [N-1:0] out
);
function [N-1:0] fx; //函数部分
input a;
input b;
...
input n;
begin
fx = 组合函数运算(a,b,...,n);
end
endfunction
always@(posedge clk or negedge rst) //D触发器部分
begin
if(!rst)
out <= 0;
else
out <= fx(a,b,...,n);
end
endmodule
这种方式的电路图如图:
这种电路描述方式,如果不考虑时序或者最高工作频率问题,对于任意的通信算法,只要给出函数形式,就可以按照这个模式形成电路。
当函数fx()是简单的算法时,上面的描述方式可以直接实现;但fx()若是复杂函数,就需要对其进行分解成许多小的fxn()函数来实现,这种概念就是朴素的流水线电路设计。
3.电磁波的传播速度是光速,IC内部信号的传导速度也是光速,但前提是信号不发生变化。若一个信号由0变为1,实际上是对后面的整个负载电路进行充放电,而这是需要时间的。任何一个电路都可以按照RCL(阻容和电感)模型进行描述。
因此,任何函数计算本质上都是一系列RC电路,这些电路串联或并联很多的负载。当输入由0变为1时,等效对后面的负载电路进行充放电,而每级负载都会有对应的延迟,若串接延迟过多就必然导致末级相应过慢,从而无法在高时钟频率下工作。
从电路角度来看,时序优化的核心工作就是降低后级的RC负载,增强前级的驱动能力。
当函数f(x)过于复杂时,需要对其进行拆解,使单个函数负载和时延控制在一定范围内。经典的分解方法有三种:
(1)f(x) = f1(x) + f2(x)
(2)f(x) = g1(x) × g2(x)
(3)f(x) = m(y);y = n(x)
这三种拆解方法通常混合应用,来达到最佳的实现方案。
一、这类拆解是针对类似通过累加求和得出最终结果的需求而设计的。伪代码如下:
ACC_sum = 0;
loop:
if(i < N)
fi(x) = 计算fi(i);
ACC_sum = ACC_sum + fi(x);
i = i + 1;
goto loop;
else
输出最终结果 = ACC_sum;
二、这类拆解的关键点有两个:
1.设计一个计数器,用于统计当前的累加计算次数,并给出状态指示;
2.单独的子函数计算以及将单次子函数计算结果与中间缓存结果合并。
三、这类拆解的实质是:将一步完成的工作分解为N个小的步骤完成,整体完成的时间拉长了。这种方法称为折叠(fold)。
一、电路组合逻辑的伪代码:
always@(*)
begin
temp_a = gx1(A); //gx1,gx2,...gxn都是function函数
temp_b = gx2(B);
......
temp_n = gx_n(N);
end
always@(posedge clk)
out <= temp_a * temp_b * ... *temp_n; //乘法完成后进行储存
二、累乘拆解的本质是通过中间插入寄存器,将耗时较长的运算连打断,从而实现时序优化,达到在较高的工作频率下工作的目的。
插入流水线寄存器后电路优化如图:
因此,代码修改为:
always@(posedge clk) //时序电路,插入流水线寄存器
begin
temp_a = gx1(A);
temp_b = gx2(B);
......
temp_n = gx_n(N);
end
always@(posedge clk)
out <= temp_a * temp_b * ... *temp_n;
一、电路组合逻辑的伪代码:
always@(*)
begin
temp_a = nx(A); //nx和ny都是function函数
temp_b = ny(temp_a);
end
always@(posedge clk)
out <= temp_b; //储存temp_b
二、插入流水线寄存器进行优化之后的电路:
always@(posedge clk) //时序电路,插入流水线寄存器
begin
temp_a = nx(A);
temp_b = ny(temp_a);
end
always@(posedge clk)
out <= temp_b;
总结:这三种拆解方法的核心都是在保证输入和输出结果不变的前提下,通过插入寄存器或者改变输入输出的时间关系,实现时钟工作频率提高的方法。
可以发现,函数其实是一种抽象形式描述,是一种需要实例对象才能功能生效的描述。
若将函数的定义头换成always@(*),然后定义必要的变量,就可以直接将其转换成实际的组合电路。因此,将函数转换成实际电路描述,可以采用always@直接例化的的方法实现。
1.函数表示方法:
//call_in为函数的自变量
wire [width-1:0] function_result;
function [width-1:0] function_name;
input XXX;
......
begin
function_name = YYY; //给function的输出赋值
end
endfunction
assign function_result = function_name(call_in);
2.直接组合电路描述方法:
//call_in为函数的自变量
wire [width-1:0] function_result;
reg [width-1:0] function_name;
always@(call_in) //always@(*)
begin
......
function_name = YYY; //给function_name输出赋值
end
assign function_result = function_name;