带暂停的四级流水线实现8bit加法器
1. 流水线简介
流水线的设计实际上是把规模较大、层次较多的组合逻辑电路分为几级,在每一级插入寄存器组并暂存中间数据。
K级流水线就是从组合逻辑的输入到输出恰好有K个寄存器组(分为K级,每一级都有一个寄存器组),上一级的输出是下一级的输入而又无反馈的电路。
2. 普通三级流水线
无阻塞流水线其实就是依次串接起来的多组触发器。
module no_stall_pipeline (
input wire clk,
//
input wire [7:0] din,
output wire [7:0] dout
);
reg [7:0] pipe1_data;
reg [7:0] pipe2_data;
reg [7:0] pipe3_data;
always @ (posedge clk)
pipe1_data <= din;
always @ (posedge clk)
pipe2_data <= pipe1_data;
always @ (posedge clk)
pipe3_data <= pipe2_data;
assign dout = pipe3_data;
endmodule
3. 带阻塞信号的三级流水
简单的流水线相当于是对输入进行打拍,但实际上很多时候流水线都会被阻塞,比如在实现CPU多周期指令的时候,需要暂停流水。这就意味着一旦后面的流水线被阻塞,前面的流水线也立刻被阻塞。因为此时后面的流水线不通,不能从输入读入新数据,所以前面的流水线必须把原有的数据保持在本级流水线(即被阻塞)。若前一级还在往后送数据,那就会导致数据丢失。
为了使流水线能够应对阻塞,需要设法把数据保持在一级流水级,可以仿照带使能的触发器的时序行为特征。只要触发器的使能信号无效,不管输入端的数据发生什么变化,它的内部存储的数据就保持不变。
可以为每一级流水线设置监管,从前后级接收状态并向前后级发送状态,根据前后级的情况决定下一时刻是否向下传递数据。
对某一级流水线而言,它会向后一级发送一个“下一时刻我有数据传递给你”的请求,也会向前一级发送一个“下一个时刻我可以接收数据”的反馈,然后它也会收到从后一级送来的“是否可以接收数据“的反馈,也会收到从前一级送来的”是否有数据传递过来“的请求。如果某一级流水线当前时刻有数据并且想在下一时刻传递给后一级流水线,但是后一级说它不能接收,那么该级流水线在下一时刻就要保持当前时刻的数据,就产生了阻塞。
- pipeX_valid:当前级是否存在有效的数据,高有效。在需要清空流水线的时候不需要把数据域的值置为无效,只需要将valid拉低,代表数据无效,可以节约逻辑资源。
- pipeX_allowin:第X级传给第X-1级的状态,是否可以接收上一级的数据。
- pipeX_ready_go:描述第X级的状态,1表示第X级的处理任务已经完成,可以传给X+1级。在实现多周期任务的时候,在最终结果没有得到的时候,可以将这个信号拉低表示暂停住了。
- pipeX_to_pipeX+1_valid:从第X级传递给第X+1级,1表示下一时刻第X级有数据传递给第X+1级。
下面的例子将ready_go信号持续拉高,表示没有阻塞暂停,回头需要阻塞的时候就控制这个信号。
设计的逻辑如下,out_allowin,validin始终为1,也就相当于只是对数据进行打两拍。
- 当前级能否进入下一级:始终可以
- 当前级能否接受:当前级值无效或当前级可以进入下一级,并且下一级可以接收
- 本级能否转移:本级值有效,并且可以传入下一级
module stallable_pipeline
#(
parameter width = 8
)
(
input wire clk,
input wire rst,
input wire validin,
input wire [width-1 : 0] datain,
input wire out_allow,
output wire validout,
output wire [width-1 : 0] dataout
);
// pipeX_valid:当前级是否存在有效的数据
reg pipe1_valid;
reg [width-1 : 0] pipe1_data;
reg pipe2_valid;
reg [width-1 : 0] pipe2_data;
reg pipe3_valid;
reg [width-1 : 0] pipe3_data;
/* ************************* pipe1 ************************* */
// pipe1是否能接收上一级的数据,被刷新
wire pipe1_allowin;
// pipe1是否可以用于传递给下一级,刷新下一级
wire pipe1_ready_go;
// pipe1是否可以进入pipe2
wire pipe1_to_pipe2_valid;
// 设为1,表示没有暂停
assign pipe1_ready_go = 1'b1;
// pipe1的值无效,或要传给下一轮,那么就pipe1可以接受新数据
assign pipe1_allowin = !pipe1_valid || pipe1_ready_go && pipe2_allowin;
// pipe1有效,并且pipe1可以进行传递,那么pipe1_to_pipe2_valid拉高
assign pipe1_to_pipe2_valid = pipe1_valid && pipe1_ready_go;
always @ (posedge clk)
begin
// 清空流水线,pipe1_valid置0,表示pipe1的数据无效
if (rst == 1'b1)
pipe1_valid <= 1'b0;
// pipe1刷新新值
// 如果输入端有输入,代表pipe1_valid的数据有效
// 如果没有输入,代表数据无效
else if (pipe1_allowin)
pipe1_valid <= validin;
// 如果输入的值有效,并且pipe1可以接收,那么就从输入端读入
if (validin && pipe1_allowin)
pipe1_data <= datain;
end
/* ************************* pipe2 ************************* */
// 是否可以接收
wire pipe2_allowin;
// 是否可以用于向下传递
wire pipe2_ready_go;
// pipe2是否可以进入pipe3
wire pipe2_to_pipe3_valid;
// 设为1,表示没有暂停
assign pipe2_ready_go = 1'b1;
assign pipe2_allowin = !pipe2_valid || pipe2_ready_go && pipe3_allowin;
assign pipe2_to_pipe3_valid = pipe2_valid && pipe2_ready_go;
always @ (posedge clk)
begin
if (rst == 1'b1)
pipe2_valid <= 1'b0;
else