CPU 速通系列 2-2:regfile 模块

代码

Path src/cpu/regfile.sv

 1`include "defines.svh"
 2
 3module regfile (
 4
 5    input logic clk,
 6    input logic rst,
 7
 8    //写端口
 9    input logic               we,
10    input logic [`RegAddrBus] waddr,
11    input logic [    `RegBus] wdata,
12
13    //读端口 1
14    input  logic               re1,
15    input  logic [`RegAddrBus] raddr1,
16    output logic [    `RegBus] rdata1,
17
18    //读端口 2
19    input  logic               re2,
20    input  logic [`RegAddrBus] raddr2,
21    output logic [    `RegBus] rdata2
22
23);
24
25    logic [`RegBus] regs[0:`RegNum-1];
26
27    always_ff @(posedge clk) begin
28        integer i;
29        if (rst == `RstEnable) begin
30            for (i = 0; i < `RegNum; i = i + 1) begin
31                regs[i] <= '0;
32            end
33        end
34    end
35
36    always_ff @(posedge clk) begin
37        if (rst == `RstDisable) begin
38            if ((we == `WriteEnable) && (waddr != `RegNumLog2'h0)) begin
39                regs[waddr] <= wdata;
40            end
41        end
42    end
43
44    always_comb begin
45        if (rst == `RstEnable) begin
46            rdata1 = `ZeroWord;
47        end else if (raddr1 == `RegNumLog2'h0) begin
48            rdata1 = `ZeroWord;
49        end else if ((raddr1 == waddr) && (we == `WriteEnable) && (re1 == `ReadEnable)) begin
50            rdata1 = wdata;
51        end else if (re1 == `ReadEnable) begin
52            rdata1 = regs[raddr1];
53        end else begin
54            rdata1 = `ZeroWord;
55        end
56    end
57
58    always_comb begin
59        if (rst == `RstEnable) begin
60            rdata2 = `ZeroWord;
61        end else if (raddr2 == `RegNumLog2'h0) begin
62            rdata2 = `ZeroWord;
63        end else if ((raddr2 == waddr) && (we == `WriteEnable) && (re2 == `ReadEnable)) begin
64            rdata2 = wdata;
65        end else if (re2 == `ReadEnable) begin
66            rdata2 = regs[raddr2];
67        end else begin
68            rdata2 = `ZeroWord;
69        end
70    end
71
72endmodule

解读

实现了一个寄存器文件(Register File,或许叫做寄存器堆更贴切)模块,用于存储和读取数据。

设计意图

简要而言,这个模块包含了一个寄存器数组,可以通过写端口写入数据,并通过读端口读取数据


具体而言,这个寄存器文件模块具有以下功能:

  • 通过写端口(we)可以写入数据,包括写入地址(waddr)和写入数据(wdata)。

  • 通过读端口 1(re1)可以读取数据,包括读取地址(raddr1)和读取数据结果(rdata1)。

  • 通过读端口 2(re2)可以读取数据,包括读取地址(raddr2)和读取数据结果(rdata2)。

  • 在上升沿时钟(clk)触发下,根据写入使能信号(we)、写入地址(waddr)和写入数据(wdata)更新寄存器数组。

  • 在组合逻辑中,根据读取使能信号(re1/re2)和读取地址(raddr1/raddr2)生成相应的读取数据(rdata1/rdata2)。

端口

  • clk:输入时钟信号

  • rst:复位信号

  • we:写使能信号

  • waddr:写地址

  • wdata:写数据

  • re1:读使能信号 1

  • raddr1:读地址 1

  • rdata1:读数据 1

  • re2:读使能信号 2

  • raddr2:读地址 2

  • rdata2:读数据 2

实现思路

  • 使用了一个logic类型的数组regs,数组大小为 0 到RegNum-1

  • 在时钟上升沿触发的always_ff块中,当复位信号(rst)为使能状态时,遍历整个寄存器数组,并将所有寄存器的值设置为 0。

  • 在时钟上升沿触发的另一个always_ff块中,当复位信号(rst)为禁用状态时,当写使能信号(we)为使能状态且写地址(waddr)不等于 0 时,将写数据(wdata)写入到指定的寄存器中。

  • 在组合逻辑的always_comb块中,根据不同的情况设置读取数据(rdata1/rdata2)的值:

    • 如果复位信号(rst)为使能状态,则将读取数据设置为零(ZeroWord)。

    • 如果读地址(raddr1/raddr2)为 0,则将读取数据设置为零(ZeroWord)。

    • 如果读地址(raddr1/raddr2)等于写地址(waddr),且写使能信号(we)为使能状态且读使能信号(re1/re2)为使能状态,则将读取数据设置为写入数据(wdata)。

    • 如果只有读使能信号(re1/re2)为使能状态,则从寄存器数组中读取相应地址(raddr1/raddr2)处的数据作为读取数据(rdata1/rdata2)。

    • 如果以上条件都不满足,则将读取数据设置为零(ZeroWord)。

FaQ

这个 regfile 的容量有多大?

在给定的代码中,RegBusRegNum 是宏定义,决定了 regfile 的大小。

  • RegBus 表示每个寄存器中存储的数据的位宽(bit width)。该宏定义决定了每个寄存器能够存储的数据的大小。

  • RegNum 表示寄存器文件中寄存器的数量。该宏定义决定了寄存器文件中可以存储的寄存器的个数。

如果写和读同时进行,会发生冒险吗?

不会。写操作不会优先于读操作执行,而是在读操作之前执行。

在组合逻辑的 always_comb 块中,针对读端口1和读端口2的逻辑如下:

 1always_comb begin
 2    if (rst == `RstEnable) begin
 3        rdata1 = `ZeroWord;
 4    end else if (raddr1 == `RegNumLog2'h0) begin
 5        rdata1 = `ZeroWord;
 6    end else if ((raddr1 == waddr) && (we == `WriteEnable) && (re1 == `ReadEnable)) begin
 7        rdata1 = wdata;
 8    end else if (re1 == `ReadEnable) begin
 9        rdata1 = regs[raddr1];
10    end else begin
11        rdata1 = `ZeroWord;
12    end
13end
 1always_comb begin
 2    if (rst == `RstEnable) begin
 3        rdata2 = `ZeroWord;
 4    end else if (raddr2 == `RegNumLog2'h0) begin
 5        rdata2 = `ZeroWord;
 6    end else if ((raddr2 == waddr) && (we == `WriteEnable) && (re2 == `ReadEnable)) begin
 7        rdata2 = wdata;
 8    end else if (re2 == `ReadEnable) begin
 9        rdata2 = regs[raddr2];
10    end else begin
11        rdata2 = `ZeroWord;
12    end
13end

根据以上代码,读操作的逻辑优先于写操作的逻辑。首先,会检查是否需要进行复位操作(rst == RstEnable),如果是,则读取数据设置为零。接下来,会检查读地址是否为0(raddr1 == RegNumLog2'h0raddr2 == RegNumLog2’h0),如果是,则读取数据设置为零。然后,会检查是否有读写操作同时进行且读写地址相同的情况,如果是,则读取数据设置为写入数据(wdata`)。最后,如果只有读使能信号为使能状态,那么从寄存器数组中读取相应地址处的数据作为读取数据。

综上所述,根据给定的代码逻辑,读操作会在写操作之后执行,并且如果读写操作的地址相同,读操作会读取到写入的数据。

参考资料

本文与 AI 合作完成。