目录
- 1. 前端设计时是否要考虑功耗? 需要
- 2. 常用逻辑资源消耗
- 3. 使用Grey码而非独热码
- 4. 降低if层数——真值表法 + 画波形/仿真
- 5. 时序违规——加入触发器 + 画波形图/仿真
- 6. inout信号处理——输入为高阻、输出赋值
- 7. `generate for`循环与 一维数组
- 8. module递归 - 参数化动态规划
- 9. parameter、localparam:默认integer、 一次赋值、算式不综合、只可使用压缩数组
- 10. Vivado 变位宽FIFO:先高位 后低位 空为可读数据不够 & Vivado XPM_FIFO:先低位 后高位 空为可读数据不够 & Vivado 变位宽BRAM:先低位 后高位
- 11. `-:`、`+:`与`$clog2()`
- 12. 多级组合逻辑 加装触发器
本文是RTL算法设计流程中常用技巧总结
1. 前端设计时是否要考虑功耗? 需要
扪心自问,作为IC前端设计人员,有没有必要在设计RTL时考虑资源?
因为综合过程有专门的后端人员去实现。
例如严格按照下面的代码去综合的话,应该是需要一个MUX和一个触发器,即2与1或1非1触发器
always@(posedge clk) begin
if(cond1) begin
if(cond2)
a <= 1'b1;
end
end
但优化代码之后,只需1与1或1触发器就OK了
always@(posedge clk) begin
a <= (cond1 && cond2) || a;
end
优化代码可以降低直接综合的资源消耗,但是会降低代码可读性。
例如状态机设计出的RTL经常会出现多个if嵌套,是否需要优化?
应该是有必要的,可以直接用Vivado -> Flow Navigator -> RTL ANALYSIS -> Schemetic看到真正综合出来的电路。上述两个相同逻辑的代码综合的电路就不相同
2. 常用逻辑资源消耗
RTL代码中常见的逻辑需要消耗什么基本资源呢?基本资源,即与或非门和触发器
注意此处的门电路个数是指最少的门电路个数,例如 Y = A ⊕ B ⊕ C = A ′ B C ′ + A ′ B ′ C + A B ′ C ′ + A B C Y=A⊕B⊕C=A'BC'+A'B'C+AB'C'+ABC Y=A⊕B⊕C=A′BC′+A′B′C+AB′C′+ABC,前者需要4与2或4非、后者需要8与3或6非,选择前者。
下表中a[m-1:0]和b[m-1:0]为m位寄存器变量,m’dx为m位常量,zero(m’dx)为常量m’dx中1’d0的个数。
含义 | 逻辑 | 与门 | 或门 | 非门 | 触发器 | Description |
---|---|---|---|---|---|---|
单个MUX | if(...) or else if(...) | 2 | 1 | 1 | 0 | |
m位无符号数加法器(m>=2) | a[m-1:0] + b[m-1:0] | 8m-5 | 4m-3 | 6m-4 | 0 | 注意最终的结果一个(m+1)位数据s[m:0]。根据真值表写逻辑表达式,若和为s、进位为c。当i=0时,s[0] = a[0]⊕b[0];当(m-1)≤i≤1时,s[i]=a[i]⊕b[i]⊕c[i];当i=m时,s[m]=c[m]。则s需要(4m-2)个与门、(2m-1)个或门、(4m-2)个非门。当i=0时,c[0] = 0;当i=1时,c[1] = a[0]b[0];当m≤i≤2时,c[i]=a[i-1]b[i-1]+c[i-1](a[i-1]⊕b[i-1]),则c需要(4m-3)个与门、(2m-2)个或门、(2m-2)个非门。因此一共有(8m-5)个与门、(4m-3)个或门、(6m-4)个非门 |
a[m-1:0] +m'dx | 4m-4 | 2m-zero(m'dx)-1 | 4m-(2zero(m'dx)+2) | 0 | 注意最终的结果一个(m+1)位数据s[m:0]。参考变量相加,若和为s、进位为c。当i=0时,s[0] = (b[0])?a'[0]:a[0];当(m-1)≤i≤1时,s[i]=(b[0])?(a[i]⊕c[i])'b[i]:(a[i]⊕c[i])b[i];当i=m时,s[m]=c[m]。则s需要(3m-3)个与门、(m-1)个或门、(3m-(zero(m'dx)+2))个非门。当i=0时,c[0] = 0;当i=1时,c[1] = (b[0])?a[0]:0;当m≤i≤2时,c[i]=(b[i-1])?(a[i-1]+c[i-1]a'[i-1]):(c[i-1]a[i-1]),则c需要(m-1)个与门、(m-zero(m'dx))个或门、(m-zero(m'dx))个非门。因此一共有(4m-4)个与门、(2m-zero(m'dx)-1)个或门、(4m-(2zero(m'dx)+2))个非门 | |
m位信号按位与或非 | ||||||
&a[m-1:0] | m-1 | 0 | 0 | 0 | 等价m个叶子节点且度1为0的二叉树,节点总数为2m-1,再去掉叶子节点就为m-1 | |
|a[m-1:0] | 0 | m-1 | 0 | 0 | 等价m个叶子节点且度1为0的二叉树,节点总数为2m-1,再去掉叶子节点就为m-1 | |
~a[m-1:0] | 0 | 0 | m | 0 | 注意最终的结果还是一个m位数据,每一位取反 | |
a[m-1:0] & m'dx | 0 | 0 | 0 | 0 | 注意最终的结果还是一个m位数据res[m-1:0]。任取(m-1)≤i≤0,若m'dx在位i为1,则res[i] = a[i];若m'dx在位i为0,则res[i] = 0。即无需任何门电路 | |
a[m-1:0] | m'dx | 0 | 0 | 0 | 0 | 注意最终的结果还是一个m位数据res[m-1:0]。任取(m-1)≤i≤0,若m'dx在位i为1,则res[i] = 1;若m'dx在位i为0,则res[i] = a[i]。即无需任何门电路 | |
m位无符号数比较器 | ||||||
a[m-1:0] == b[m-1:0] | 3m-1 | m | 2m | 0 | 先是a和b每一位相同或,需要2m个与门、m个或门、2m个非门。之后的与门判等结构等价m个叶子节点且度1为0的二叉树,n2节点总数为m-1。因此一共有与门3m-1个与门、m个或门、2m个非门 | |
a[m-1:0] == m'dx | m-1 | 0 | zero(m'dx) | 0 | 对于所有满足(m-1)≤i≤0且m'dx在i位为0的i,a[i]先经过一个非门。之后等价m个叶子节点且度1为0的二叉树,节点总数为2m-1,再去掉叶子节点就为m-1 | |
a[m-1:0] > b[m-1:0] | 1.5[(m^2)-m]+1 | 0.5[(m^2)-m] | (m^2) | 0 | 思路是真值表:a[m-1]为1而b[m-1]为0,结果为1;a[m-1]和b[m-1]相等,且a[m-1]为1而b[m-1]为0,结果为1;a[m-1]和b[m-1]相等,且a[m-2]和b[m-2]相等,且a[m-3]为1而b[m-3]为0,结果为1,以此类推......逻辑表达式为Y=(a[m-1]· ~b[m-1])+(a[m-1]⊙b[m-1])(a[m-2]· ~b[m-2])+(a[m-1]⊙b[m-1])(a[m-2]⊙b[m-2])(a[m-3]· ~b[m-3])+...+(a[m-1]⊙b[m-1])(a[m-2]⊙b[m-2])...(a[1]⊙b[1])(a[0]· ~b[0]) | |
a[m-1:0] > {x{1'd0},y{1'd1},(m-x-y){1'd0}} | x+y | m-y | x | 0 | 此处只讨论m'dx中所有的1'd1之间不含1'd0的情况,其他情况较复杂。逻辑表达式为Y=a[m-1]+a[m-2]+...+a[m-x+1]+(a'[m-1]a'[m-2]...a'[m-x+1]a[m-x+1]...a[m-(x+y)+1])(a[m-(x+y)]+a[m-(x+y)-1]+...+a[0])。 | |
m位信号左移寄存器 | a[m-1:0] << 1 | 0 | 0 | 0 | m |
2.1. 含有m个叶子节点且不含度为1节点 的二叉树节点数目为2m-1
对于多bit信号相乘或者作比较时,需要1个bit1个bit地比较,最终得到一个结果,需要计算这种电路耗费多少资源。
涉及到的结构本质就是二叉树
先明确几个概念。
● 叶子节点: 二叉树中每个圆圈代表1个节点,节点之间用边连接。图中绿色的节点没有孩子,称之为叶子节点
● 度: 表示某个节点的孩子个数。图中绿色节点的度为0
那么耗费的门电路资源如何计算呢?将叶子节点看作是输入,其余的每个节点就可看作门电路资源,并且此处考虑门电路资源都是两个输入的,因此二叉树中不存在度1的节点
总而言之, 我们要解决的问题是:已知二叉树含有m个叶子节点,且不存在度1的节点,问度2的节点数目是多少?
用到一个性质:
● 性质:对任何一棵二叉树, 如果其叶结点数为n0,度为1的结点数为 n1,度为2的结点数为 n2,则n0=n2+1
所以若叶子节点有m个,即n0=m,而n1=0,整个二叉树含有的节点数目为n0+n1+n2=m+0+m-1=2m-1
证明:任意一颗二叉树的节点总数为(n0+n1+n2),由于除了根外其余节点均连有一个上边,该二叉树的边总数就为(n0+n1+n2-1)。又因为度0节点无孩子没有下边,度1节点有1个孩子有1个下边,度2节点有2个孩子有2个下边,因此二叉树的边总数又为(n1+2n2)。因此有(n0+n1+n2-1)=(n1+2n2)推得(n0=n2+1)。证闭
二叉树的五大性质及证明
3. 使用Grey码而非独热码
两个状态机,看看综合出来是个啥电路
always@(*) begin
case(cur_state)
2'b00: nxt_state = 2'b01;
2'b01: nxt_state = 2'b11;
2'b11: nxt_state = 2'b10;
2'b10: nxt_state = 2'b00;
default: nxt_state = 2'b00;
endcase
end
//综合:nxt_state[0] = !cur_state[1]
//综合:nxt_state[1] = cur_state[0]
//---------------------------------------------------------
always@(*) begin
case(cur_state)
4'b0001: nxt_state = 4'b0010;
4'b0010: nxt_state = 4'b0100;
4'b0100: nxt_state = 4'b1000;
4'b1000: nxt_state = 4'b0001;
default: nxt_state = 4'b0001;
endcase
end
//综合:nxt_state[0] = cur_state[3] && (!cur_state[2]) && (!cur_state[1]) && (!cur_state[0])
//综合:其余的也不同看了会非常复杂,原因在于default: nxt_state == 4'b0001这句话导致其余情况必然要输出4'b0001
4. 降低if层数——真值表法 + 画波形/仿真
经常遇到这样的情况,always块内嵌套多层if,是否可以优化呢?
always@(posedge clk) begin
if(!rstn)
a <= 1'b0;
else if(cond1) begin
if(cond2)
a <= res1;
else
a <= res2;
end
else
a <= res3;
end
//----------------------------------------------
always@(posedge clk) begin
if(!rstn)
a <= 1'b0;
else if(cond1 && cond2)
a <= res1;
else if(cond1 && !cond2)
a <= res2;
else
a <= res3;
end
综合出来的电路分别是
由于一个MUX需要 2与门、1或门、1非门
若a为1bit,那么第一个电路需要4与门、2或门、2非门、1触发器。第二个电路需要6与门、2或门、3非门、1触发器。
同时可以得出一个结论一个if对应一个MUX
上述过程经过了Vivado -> Flow Navigator -> RTL ANALYSIS -> Schemetic的验证
4.1. 多层if等价——case(1'b1)
如果if层数过多,则可以是使用case(1'b1)
等价,会优先执行第一个满足条件语句的代码
例如
case(1'b1)
res_frac_ext[23]: begin res_frac <= res_frac_ext[22:0]; res_exp <= res_exp; end
res_frac_ext[22]: begin res_frac <= {res_frac_ext[21:0],1'b0}; res_exp <= res_exp - 1; end
res_frac_ext[21]: begin res_frac <= {res_frac_ext[20:0],2'b0}; res_exp <= res_exp - 2; end
res_frac_ext[20]: begin res_frac <= {res_frac_ext[19:0],3'b0}; res_exp <= res_exp - 3; end
res_frac_ext[19]: begin res_frac <= {res_frac_ext[18:0],4'b0}; res_exp <= res_exp - 4; end
res_frac_ext[18]: begin res_frac <= {res_frac_ext[17:0],5'b0}; res_exp <= res_exp - 5; end
res_frac_ext[17]: begin res_frac <= {res_frac_ext[16:0],6'b0}; res_exp <= res_exp - 6; end
res_frac_ext[16]: begin res_frac <= {res_frac_ext[15:0],7'b0}; res_exp <= res_exp - 7; end
res_frac_ext[15]: begin res_frac <= {res_frac_ext[14:0],8'b0}; res_exp <= res_exp - 8; end
res_frac_ext[14]: begin res_frac <= {res_frac_ext[13:0],9'b0}; res_exp <= res_exp - 9; end
res_frac_ext[13]: begin res_frac <= {res_frac_ext[12:0],10'b0}; res_exp <= res_exp - 10; end
res_frac_ext[12]: begin res_frac <= {res_frac_ext[11:0],11'b0}; res_exp <= res_exp - 11; end
res_frac_ext[11]: begin res_frac <= {res_frac_ext[10:0],12'b0}; res_exp <= res_exp - 12; end
res_frac_ext[10]: begin res_frac <= {res_frac_ext[9:0] ,13'b0}; res_exp <= res_exp - 13; end
res_frac_ext[9]: begin res_frac <= {res_frac_ext[8:0] ,14'b0}; res_exp <= res_exp - 14; end
res_frac_ext[8]: begin res_frac <= {res_frac_ext[7:0] ,15'b0}; res_exp <= res_exp - 15; end
res_frac_ext[7]: begin res_frac <= {res_frac_ext[6:0] ,16'b0}; res_exp <= res_exp - 16; end
res_frac_ext[6]: begin res_frac <= {res_frac_ext[5:0] ,17'b0}; res_exp <= res_exp - 17; end
res_frac_ext[5]: begin res_frac <= {res_frac_ext[4:0] ,18'b0}; res_exp <= res_exp - 18; end
res_frac_ext[4]: begin res_frac <= {res_frac_ext[3:0] ,19'b0}; res_exp <= res_exp - 19; end
res_frac_ext[3]: begin res_frac <= {res_frac_ext[2:0] ,20'b0}; res_exp <= res_exp - 20; end
res_frac_ext[2]: begin res_frac <= {res_frac_ext[1:0] ,21'b0}; res_exp <= res_exp - 21; end
res_frac_ext[1]: begin res_frac <= {res_frac_ext[0:0] ,22'b0}; res_exp <= res_exp - 22; end
res_frac_ext[0]: begin res_frac <= 23'd0; res_exp <= res_exp - 23; end
default: begin res_frac <= 23'd0; res_exp <= 8'd0; end
endcase
4.2. 参数化 多层if/case等价——for
循环
如果组合逻辑中有太多if,或者case语句中的分支太多,如何实现parameter组合逻辑呢?可以用for循环实现。
for循环本质是生成语句,循环变量只能为reg或integer类型,for语句只可存在于always块内部
无论是if还是case以下三种写法等价,也就是说将else语句写到最前面不会综合出LATCH,已经过vivado验证
//----------------------------------------------------------------------------------------------------
// 输出nums最后那个1的下标: if写法(可综合、无LATCH)
//----------------------------------------------------------------------------------------------------
always@(*) begin
if(nums[0])
idx = 0 + sel[0] + sel[1];
else if(nums[1])
idx = 1 + sel[1] + sel[2];
else if(nums[2])
idx = 2 + sel[3] + sel[3];
else if(nums[3])
idx = 3 + sel[4] + sel[4];
else if(nums[4])
idx = 4 + sel[5] + sel[5];
else if(nums[5])
idx = 5 + sel[6] + sel[6];
else if(nums[6])
idx = 6 + sel[7] + sel[7];
else if(nums[7])
idx = 7 + sel[8] + sel[8];
else
idx = 0;
end
//----------------------------------------------------------------------------------------------------
// 输出nums最后那个1的下标: case写法(可综合、无LATCH)
//----------------------------------------------------------------------------------------------------
always@(*) begin
case(1'b1)
nums[0]: idx = 0 + sel[0] + sel[1];
nums[1]: idx = 1 + sel[1] + sel[2];
nums[2]: idx = 2 + sel[2] + sel[3];
nums[3]: idx = 3 + sel[3] + sel[4];
nums[4]: idx = 4 + sel[4] + sel[5];
nums[5]: idx = 5 + sel[5] + sel[6];
nums[6]: idx = 6 + sel[6] + sel[7];
nums[7]: idx = 7 + sel[7] + sel[8];
default: idx = 0;
endcase
end
//----------------------------------------------------------------------------------------------------
// 输出nums最后那个1的下标: 参数化if写法(可综合、无LATCH)
//----------------------------------------------------------------------------------------------------
integer i;
always@(*) begin
idx = 0;
for(i=0;i<=7;i=i+1) begin
if(nums[i])
idx = i + sel[i] + sel[i+1];
end
end
其实integer i;
等价于reg signed [31:0] i;
,所以可以使用reg型变量进行单bit片选,但多bit片选则报错。
但是移位符号不会报错,如下两个相同结果的程序,均可编译通过,但是综合结果差别很大。
● 第一个程序,使用for循环与移位实现
module vivado_test_top(
input [3:0] nums,
input [1:0] sel,
output reg [3:0] nums_high,
output reg [3:0] nums_low
);
integer i;
always@(*) begin
nums_high = 4'd0;
nums_low = 4'd0;
if(sel == 0) begin
nums_high = nums[3:0];
nums_low = 4'd0;
end
for(i=1;i<=3;i=i+1) begin
if(sel == i) begin
nums_high = nums >> i;
nums_low = (nums << (4-i)) >> (4-i);
end
end
end
endmodule
综合结果:
● 第二个程序,使用if-else实现
module vivado_test_top(
input [3:0] nums,
input [1:0] sel,
output reg [3:0] nums_high,
output reg [3:0] nums_low
);
integer i;
always@(*) begin
if(sel == 0) begin
nums_high = nums[3:0];
nums_low = 4'd0;
end
else if(sel == 1) begin
nums_high = nums[3:1];
nums_low = nums[0];
end
else if(sel == 2) begin
nums_high = nums[3:2];
nums_low = nums[1:0];
end
else if(sel == 3) begin
nums_high = nums[3];
nums_low = nums[2:0];
end
else begin
nums_high = 4'd0;
nums_low = 4'd0;
end
end
endmodule
综合结果:
综上所述得出结论:
不涉及位宽选通 推荐使用for循环
涉及位宽选通 推荐不使用for循环
How to parameterized a case statement with don’t cares? - stackoverflow
How to define parameterized multiplexer using Systemverilog - stackoverflow
5. 时序违规——加入触发器 + 画波形图/仿真
在合适的地方加入触发器,然后画波形图/仿真。如果出现未达预期的时序,再在其他地方修正时序
6. inout信号处理——输入为高阻、输出赋值
对于inout
信号,常使用assign
修饰,当该信号作为输出时,赋予其应该输出的值、当该信号作为输入时,赋予其高阻态。
module spi(
output sck,
output csn,
inout [3:0] sdio
);
...
// spi_dir指示sdio方向
// spi_dir为高表示sdio为输出信号、赋予其spi_dout
// spi_dir为低表示sdio为输入信号、赋予其高阻
assign sdio = (spi_dir)? spi_dout : 4'dz;
// spi_dir为低表示sdio为输入信号、将输入值赋给spi_din
assign spi_din = (spi_dir)? 4'd0 : sdio;
endmodule
7. generate for
循环与 一维数组
generate for
和for
的功能是一样的,都是生成语句,但用法有区别
generate for本质是生成语句,循环变量只能为genvar类型,generate for语句只可存在于 always块/assign语句 外部
verilog-2001允许使用多维数组,用法核心思想是只可对数组内单个元素进行操作,例如
module vivado_test_top(
input [31:0] nums_i[5:0] //错误,端口禁止声明数组
);
//----------------------------------------------------
// 使用generate for循环初始化
//----------------------------------------------------
wire [31:0] mem[15:0];
genvar i;
generate
for(i=0;i<=15;i=i+1) begin //正确
assign mem[i] = i;
end
endgenerate
//----------------------------------------------------
// 访问
//----------------------------------------------------
wire [63:0] a1;
wire [31:0] a2[15:0];
wire [31:0] a3[2:0];
assign a1 = mem[1:0]; //错误,不可part-select数组
assign a1 = {mem[1], mem[0]}; //正确
assign a3 = {mem[2], mem[1], mem[0]}; //错误,不可直接使用数组本身
assign {a3[2], a3[1], a3[0]} = {mem[2], mem[1], mem[0]}; //正确
assign a2 = mem; //错误,不可直接使用数组本身
assign a2[0] = mem[0]; //正确,只可直接使用数组元素
assign a2[1] = mem[1];
//----------------------------------------------------
// 传递
//----------------------------------------------------
wire [31:0] res;
assignment U(
.a (mem ), //错误,不可直接使用数组本身
.b (mem[3:0] ), //错误,不可part-select数组
.mem_0 (mem[0] ), //正确,只可直接使用数组元素
.mem_1 (mem[1] ),
.mem_2 (mem[2] ),
.mem_3 (mem[3] ),
.c (res )
);
endmodule
8. module递归 - 参数化动态规划
Verilog允许module递归,vivado可以正确综合。但注意要用generate if...else...
作修正,否则会无限递归下去
可以使用递归module实现参数化动态规划。同一拍算得 s 1 , . . . , s n s1,...,sn s1,...,sn,要计算 f ( s 1 , . . . , s n ) = g ( s 1 , f ( s 2 , . . . , s n ) ) f(s1,...,sn) = g(s1,f(s2,...,sn)) f(s1,...,sn)=g(s1,f(s2,...,sn)),如果 n n n为参数,就无法显式写明 f ( s 1 , . . . , s n ) f(s1,...,sn) f(s1,...,sn),可以使用module递归列写。
例如计算 f ( s 1 , . . . , s n ) = s 1 + s 2 + . . . + s n f(s1,...,sn) = s1+s2+...+sn f(s1,...,sn)=s1+s2+...+sn,其中n被参数化,多少个数相加由用户配置parameter确定。RTL中就可以写成 f ( s 1 , . . . , s n ) = s 1 + f ( s 2 , . . . , s n ) f(s1,...,sn) = s1+f(s2,...,sn) f(s1,...,sn)=s1+f(s2,...,sn)进行递归实现。例程如下
module acc#(
parameter n = 4,
parameter a_width = 16,
parameter s_width = 16,
)(
input [n*a_width-1:0] a,
output [s_width-1:0] sum
);
generate if(n>1) begin
wire [s_width-1:0] sum_sub;
acc#(
.n ( n-1 ),
.a_width ( a_width ),
.s_width ( s_width )
)SUB_ACC(
.a ( a[(n-1)*a_width-1:0] ),
.sum ( sum_sub )
);
assign sum = a[n*a_width -: a_width] + sum_sub;
end
else begin
assign sum = a;
end
endgenerate
endmodule
9. parameter、localparam:默认integer、 一次赋值、算式不综合、只可使用压缩数组
不指定位宽则默认为Integer类型。只可在初始化时赋予值,之后不可再更改,并且综合时计算式不会成综合成实际电路
localparam NUM_MUL_U = (adata_width > bdata_width)? adata_width:bdata_width;
localparam node_width_u = adata_width + bdata_width;
localparam NUM_MUL = (data_type == 1)? (NUM_MUL_U-1) : NUM_MUL_U;
localparam node_width = (data_type == 1)? (node_width_u-2) : node_width_u;
localparam CNT = NUM*10+92/3200; // 不会被综合
localparam NUMS[5:0] = {1,2,3,4,5,6}; //错误,不可声明数组
localparam [8*CNT-1:0] NUMS = {8'd1,8'd2,8'd3,8'd4,8'd5}; // 正确,压缩数组
10. Vivado 变位宽FIFO:先高位 后低位 空为可读数据不够 & Vivado XPM_FIFO:先低位 后高位 空为可读数据不够 & Vivado 变位宽BRAM:先低位 后高位
● vivado FIFO IP:
1)写位宽<读位宽
先入高位,后入低位;空标志拉高表示FIFO数据中不够读位宽,不代表没数!!!
如依次写入十六进制数0x00、0x11、0x22、0x33;空标志在0x33写入成功后才拉低,读出数据为0x00112233
2)写位宽>读位宽:
先出高位,再出低位;
如写入一个十六进制数0xAABBCCDD;依次读出数据为0xAA, 0xBB, 0xCC, 0xDD。
● vivado XPM_FIFO:
1)写位宽<读位宽
先入低位,后入高位;空标志拉高表示FIFO数据中不够读位宽,不代表没数!!!
如依次写入十六进制数0x00、0x11、0x22、0x33;空标志在0x33写入成功后才拉低,读出数据为0x33221100
2)写位宽>读位宽:
先出低位,再出高位;
如写入一个十六进制数0xAABBCCDD;依次读出数据为0xDD, 0xCC, 0xBB, 0xAA。
● BRAM:
1)写位宽<读位宽:
先入存低位,后入存高位;
如依次写入十六进制数0x00、0x11、0x22、0x33;读出数据为0x33221100。
2)写位宽>读位宽:
先出低位,再出高位;
如写入一个十六进制数0xAABBCCDD;读4次,依次读出数据为0xDD、0xCC、0xBB、0xAA。
11. -:
、+:
与$clog2()
来自Verilog2001标准
reg [31:0] big_vect;
reg [0:31] little_vect;
localparam [7:0] CS;
big_vect[ 0 +: 8] // == big_vect[ 7 : 0]
big_vect[15 -: 8] // == big_vect[15 : 8]
little_vect[ 0 +: 8] // == little_vect[0 : 7]
little_vect[15 -: 8] // == little_vect[8 :15]
CS[7:-4] // == CS[7:4]
系统函数 $clog2(x)
是将x取以2为底的对数并且向上取整。即
$clog2(8)=3
$clog2(9)=4
$clog2(10)=4
12. 多级组合逻辑 加装触发器
有NUM_LEVEL级相同运算(组合逻辑),要加入触发器形成ppl_stgs级流水,这些触发器加在哪里会使流水的建立时间最大呢?例如有13个数相加,要构成4级流水,这4个触发器加在哪个加法器之后?
首先肯定不能把ppl_stgs个触发器全加在运算末尾,这样建立时间是最小的。正确思路是将触发器均匀的加在各级组合逻辑中。
RTL如下:
# 每隔inr_ff_level个层加装一层FF
localparam integer inr_ff_level = (NUM_LEVEL > ppl_stgs)? $ceil(real'(NUM_LEVEL)/real'(ppl_stgs)) : 1;
# 第level_num层运算加FF的条件
if(level_num%inr_ff_level == 0)
# 具有num_level层的二叉树根节点还需额外加多少个FF
localparam integer NUM_ROOT_DELAY = ppl_stgs - (NUM_LEVEL/inr_ff_level);
还是13个数相加、4级流水,inr_ff_level 取4,即每4层加装一层FF。则第4层、第8层、第12层加入FF,最后检测到FF加的不够就加在第13层。即:1 2 3 4| 5 6 7 8| 9 10 11 12| 13|