if_stage 模块负责在每个时钟周期中取指令,计算下一条指令的地址,处理分支目标的逻辑,并将获取的指令数据传递给 ID 阶段,以支持下一阶段的指令解码和执行。
assign fs_allowin = !fs_valid || (fs_ready_go && ds_allowin);
!fs_valid:这一部分表示如果 IF 阶段当前不是有效状态(fs_valid 为假),那么 fs_allowin 会为真。这通常在刚启动 CPU 或发生重置时,IF 阶段需要接收新的指令,因此允许接收来自 ID 阶段的取指令请求。
(fs_ready_go && ds_allowin):这一部分表示如果 IF 阶段已经准备好(fs_ready_go 为真),并且来自 ID 阶段的 ds_allowin 信号为真,那么 fs_allowin 也为真。这意味着 IF 阶段已准备好接收新的指令,且 ID 阶段要求传递指令。
ID 阶段模块执行指令解码、数据通路控制、数据相关性检测以及流水线控制等任务,为指令的正确执行和顺利传递到下一个阶段提供必要的支持。同时,该模块还涉及分支预测、寄存器堆访问、目的寄存器和数据冒险等方面的操作。
执行阶段模块执行指令的算术逻辑操作,包括 ALU 计算、数据内存访问等,同时也涉及到数据通路的控制和流水线阶段控制。它为指令的正确执行和数据的准备传递到存储阶段提供必要的支持。此外,也有目的寄存器、数据相关性、立即数和操作数选择等方面的操作。
内存阶段模块主要负责处理数据存储访问操作,包括从数据存储中读取数据,并准备将数据传递给下一个阶段。它还与目的寄存器相关,根据执行阶段的计算结果和数据存储的结果,选择传递的数据。流水线阶段控制确保数据在合适的时间被传递到下一个阶段。
写回阶段模块负责控制数据写回到寄存器文件的操作。这包括确定何时执行写回操作,以及将何种数据写回到寄存器文件。控制流程和数据传递在时钟的控制下执行,确保正确的数据被写回。
always @(posedge clk) begin
if (reset) begin
fs_valid <= 1'b0;
end
else if (fs_allowin) begin
fs_valid <= to_fs_valid;
end
if (reset) begin
fs_pc <= 32'hbfbffffc; //trick: to make nextpc be 0xbfc00000 during reset
end
else if (to_fs_valid && fs_allowin) begin
fs_pc <= nextpc;
end
end
always @(posedge clk ) begin
if (reset) begin
ds_valid <= 1'b0;
end
else if (ds_allowin) begin
ds_valid <= fs_to_ds_valid;
end
end
always @(posedge clk) begin
if (fs_to_ds_valid && ds_allowin) begin
fs_to_ds_bus_r <= fs_to_ds_bus;
end
end
always @(posedge clk) begin
if (reset) begin
es_valid <= 1'b0;
end
else if (es_allowin) begin
es_valid <= ds_to_es_valid;
end
if (ds_to_es_valid && es_allowin) begin
ds_to_es_bus_r <= ds_to_es_bus;
end
always @(posedge clk) begin
if (reset) begin
ms_valid <= 1'b0;
end
else if (ms_allowin) begin
ms_valid <= es_to_ms_valid;
end
if (es_to_ms_valid && ms_allowin) begin
es_to_ms_bus_r = es_to_ms_bus;
end
end
always @(posedge clk) begin
if (reset) begin
ws_valid <= 1'b0;
end
else if (ws_allowin) begin
ws_valid <= ms_to_ws_valid;
end
if (ms_to_ws_valid && ws_allowin) begin
ms_to_ws_bus_r <= ms_to_ws_bus;
end
end
产生由寄存器写后读数据相关所引发的流水线冲突的场景是:产生结果的指令尚未讲结果写回到寄存器堆中,而需要这个结果的指令已经在译码阶段了,此刻他从通用寄存器堆中独处的数值是旧值而非新值。
一种直观的解决思路是:让需要结果的指令在译码流水级一直等待,直到产生结果的指令将结果写回到通用寄存器堆,才可以进入到下一级的执行流水线。
贴一张不是特别合适的图,但应该能够表达含义了。
在考虑前递路径设计时,ADDU指令代表了那些在执行级就能产生结果的指令。但是LW指令不属于这一类指令,它直到访存级才能生成结果。不过分析之后会发现,LW指令完全可以复用那些为了前递ADDU类指令结果而在访存级和写回级增加的前递路径。因为前递路径知识把结果送出器,有寄存器号和数值这两个信息就够了,与产生结果的指令的功能没有关系。
有两种可行的设计方案: