CPU 速通系列 1-1:PC 模块

背景知识

在 Verilog 中,always是最常见的过程类型,而always_ffalways_latch是 Verilog 的扩展语法。它们的区别如下:

  • alwaysalways块表示一个普通的过程,会在仿真开始时运行一次,然后根据敏感列表中的信号进行触发执行。常用的敏感列表包括时钟信号、寄存器输入等。例如,always @(posedge clk)表示在每个时钟上升沿触发时执行。

  • always_ffalways_ff块表示一个触发器(flip-flop),在每个时钟的上升沿触发执行。它是对always @(posedge clk)的替代语法。在时钟边沿时执行的代码应该放在always_ff块中。这种语法更加严格,有助于减少 RTL 到门级综合(post synthesis)时的不匹配问题。

  • always_latchalways_latch块用于表示锁存器(latch)。它类似于always @*,但是提供了更严格的触发条件,并可以明确区分需要和不需要锁存器的设计意图。

以下是使用这些语法的示例:

1always_ff @(posedge clk) begin
2  a <= b;
3end

上述代码表示在每个时钟的上升沿触发时,将信号b赋值给信号a

1always_latch begin
2  if (enable) begin
3     a_latch <= something;
4  end
5  //没有 else 语句,所以 a_latch 的值
6  //在某些情况下可能未定义,因此它会保持其值
7end

上述代码表示在满足条件 enable 时,将 something 的值赋值给锁存器a_latch。由于没有else语句,所以a_latch的值在其他情况下保持不变。

always_comb用于表示组合逻辑,它是always @*的替代语法,用于表达不需要锁存器的组合逻辑。通过使用always_comb,我们可以在设计中明确区分需要和不需要锁存器的部分。

这些扩展语法的使用可以提供更严格的触发条件,减少 RTL 到门级综合时的不匹配问题,但也可能会对仿真行为产生一些影响。

代码

Path src/cpu/pc_reg.sv

 1`include "defines.svh"
 2
 3module pc_reg (
 4
 5    input logic clk,
 6    input logic rst,
 7
 8    //来自控制模块的信息
 9    input logic [5:0] stall,
10
11    //来自译码阶段的信息
12    input logic           branch_flag_i,
13    input logic [`RegBus] branch_target_address_i,
14
15    output logic [`InstAddrBus] pc,
16    output logic                ce
17
18);
19
20    always_ff @(posedge clk) begin
21        if (ce == `ChipDisable) begin
22            pc <= 32'h00000000;
23        end else if (stall[0] == `NoStop) begin
24            if (branch_flag_i == `Branch) begin
25                pc <= branch_target_address_i;
26            end else begin
27                pc <= pc + 4'h4;
28            end
29        end
30    end
31
32    always_ff @(posedge clk) begin
33        if (rst == `RstEnable) begin
34            ce <= `ChipDisable;
35        end else begin
36            ce <= `ChipEnable;
37        end
38    end
39
40endmodule

解读

实现了一个程序计数器寄存器(pc_reg)模块,用于存储和更新指令的地址。

设计意图

简要而言,该模块的设计目的是根据控制模块和译码阶段的信息来更新程序计数器的值,并且在复位时禁用计数器。

端口

  • clk:输入时钟信号

  • rst:输入复位信号

  • stall:来自控制模块的信息,用于停顿指令执行的信号

  • branch_flag_i:来自译码阶段的信息,用于判断是否发生分支

  • branch_target_address_i:来自译码阶段的信息,分支目标地址

  • pc:输出的程序计数器地址

  • ce:输出的计数器使能信号

实现思路

该模块使用两个时序逻辑块(always_ff)来实现功能。

第一个时序逻辑块中,使用时钟上升沿触发。首先判断计数器使能信号(ce)是否为禁用状态(ChipDisable),如果是,则将程序计数器(pc)的值设置为初始值(32’h00000000)。如果计数器使能信号不为禁用状态,并且停顿信号(stall[0])为不停顿状态(NoStop),则判断分支标志(branch_flag_i)是否为分支状态(Branch)。如果是分支状态,则将程序计数器的值设置为分支目标地址(branch_target_address_i)。如果不是分支状态,则将程序计数器的值增加一个偏移量(4’h4)。

第二个时序逻辑块中,同样使用时钟上升沿触发。首先判断复位信号(rst)是否为使能状态(RstEnable),如果是,则将计数器使能信号(ce)设置为禁用状态(ChipDisable)。如果复位信号不是使能状态,则将计数器使能信号设置为使能状态(ChipEnable)。

FaQ

两个时序逻辑块是否存在先后执行顺序?

并行执行,因为它们都是由相同的时钟边沿触发。

参考资料

本文与 AI 合作完成。