FPGA之流水线思想(含代码)
本文主要涉及以下几个问题:
1:什么是流水线
2:流水线使用场景
3:一个例子来说明流水线
1:CASE1: 例如工厂要对一个产品进行组装,分为A,B,C三步,以往是一个人负责这三步。这三步分别用时T1,T2,T3。且一个人完成所有步骤后才能发出通知:我完成这个产品了,其他部门只有接收到这个通知后才能继续生产零部件供应下一次的生成。有一天领导觉得这样似乎效率低了,于是想出一个办法:每个人只负责这三个步骤中的一个,且以每步最长完成时间为通知标志,以往发出完成标志的时间是T1+T2+T3,假如T2的时间最长。那么改进后的时间就为T2了。生成效率大大提高。
CASE2:术业有专攻,这个工厂的工人不是对产品组装的每一步都熟练掌握,他只会其中的一步,这样会A的到A部门,会B的到B部门,会C的到C部门。这样就解决了这个问题。
2:回到FPGA中,有时候组合逻辑过长势必会造成很大的延时,而系统的时钟频率的限制往往由这些关键路径延时来决定的,如果时钟频率过快,那一个clock的边沿到来时,组合逻辑的信号还没有传到端口或者传到后建立时间没有满足会产生亚稳态。这时把一个组合逻辑拆成几块,每一块插入一个寄存器用来暂时存储当前数据。这样可以提升工作频率。还有就是数据位宽和需要的计算位宽不一致的问题,如存储器位宽只有8位,我们想要进行16位的运算这时候可以借助流水线的思想去做。流水线用一个此词来概括就是“拆分”
3:以8位加法为例,对比一下不用流水线和用流水线的区别。
RTL设计
module add8_2(
input wire sclk,
input wire rst_n,
input wire cin,
input wire [7:0] cina,
input wire [7:0] cinb,
output wire [7:0] sum,
output wire cout,
output wire [7:0] sum_normal
);
reg cout_reg;
reg [7:0] sum_reg;
reg cout1; //插入的寄存器
reg [3:0] sum1 ; //插入的寄存器
reg [3:0] cina_reg;
reg [3:0] cinb_reg;//插入的寄存器
//流水线
always @(posedge sclk or negedge rst_n) //第一级流水
if(rst_n == 1'b0)begin
cout1 <= 1'b0;
sum1 <= 4'd0;
end
else begin
{cout1 , sum1} <= cina[3:0] + cinb [3:0] + cin ;
end
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)begin
cina_reg <= 4'd0;
cinb_reg <= 4'd0;
end
else begin
cina_reg <= cina[7:4];
cinb_reg <= cinb[7:4];
end
always @(posedge sclk or rst_n ) //第二级流水
if(rst_n == 1'b0)begin
cout_reg <=1'b0;
sum_reg <=8'd0;
end
else
{cout_reg,sum_reg[7:0]} <= {{1'b0,cina_reg[3:0]}+{1'b0,cinb_reg[3:0]+cout1,sum1[3:0]}};
assign cout = cout_reg;
assign sum = sum_reg;
assign sum_normal = cina+cinb+cin;//无流水线,纯组合逻辑
endmodule
仿真测试代码:
`timescale 1ns/1ns
module tb_add8_2;
reg sclk;
reg rst_n;
reg cin;
reg [7:0] cin_a;
reg [7:0] cin_b;
initial begin
sclk=0;
rst_n=0;
cin=1;
cin_a=2;
cin_b=3;
#20
rst_n=1;
end
initial begin
#30
data_ina();
end
initial begin
#30;
data_inb();
end
always #10 sclk = ~sclk;
task data_ina();
integer i;
begin
for(i=0;i<256;i=i+1)
begin
@(posedge sclk)
cin_a<=i;
end
end
endtask
task data_inb();
integer j;
begin
for(j=1;j<254;j=j+1)
begin
@(posedge sclk)
cin_b<=j;
cin<=cin_b[0];
end
end
endtask
add8_2 add8_2_inst(
.sclk (sclk),
.rst_n (rst_n),
.cin (cin),
.cina (cin_a),
.cinb (cin_b),
.sum (),
.sum_normal (),
.cout ()
);
endmodule
先在quartus下看看RTL级的结构,这里没有加流水线的的未考虑计算溢出,但不影响分析。可以看到把最下面没有加流水线的分成两步。
如下红色部分是流水线部分
如下红色部分是未加入流水线部分
如下图是逻辑仿真图,可以看到加入一级流水线第一个数据的输出要比未加入流水线的要延迟两个时钟周期,但是之后就没有再增加延迟了,单看计算使用流水线不但没有减少计算时间反而增加了。但是提高了整个系统的频率,其他行为可以提升操作速度,不用再等速度慢的操作。加入流水线后消耗了寄存器资源,拿面积换取速度。