基于通用寄存器的单总线 CPU

  1. CPU 数据通路
  2. CPU 控制器

github 项目地址:https://github.com/yishiyu/mipscpu

1. CPU 数据通路

  1. 多路选择器(mux)
  2. 寄存器(reg)
  3. 通用寄存器组(RF)
  4. 算术/逻辑运算单元(ALU)
  5. 三态门(TSG)
  6. 存储器(RAM)
  7. ALU 控制器(ALUCU)
  8. 总数据通路

1.1 多路选择器(mux)

不使用单独的模块,使用 case 语句实现

1
2
3
4
5
case (control)
2'd1: out = input1;
2'd2: out = input2;
default: out = input3;
endcase

1.2 寄存器(reg)

用于暂存不同阶段的数据,其中 Wr 信号为写入信号,高电平有效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module reg (
input clk,
input Wr,
input wire [N-1:0] in,
output reg [N-1:0] out);

parameter N = 1;

always @(posedge clk) begin
if (Wr)
out <= in;
else
out <= out;
end

endmodule

1.3 通用寄存器组(RF)

寄存器 效果
W_data 数据写入端
R_data 据输出端(可以输出高阻态)
Reg 寄存器地址输入
OE 输出使能,高电平有效,低电平时输出高阻态
Wr 写入使能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module RF(
input wire clk,
input wire [31:0] W_data,
output reg [31:0] R_data,
input wire [4:0] Reg,
input wire OE,
input wire Wr
);

reg [31:0] mem [0:31]; //128个32位存储单元

//输出引脚控制
always @(*) begin
if (OE == 1'b1)
R_data == mem[Reg][31:0];
else
R_data == 32'bz;
end

//输入引脚控制
always @(posedge clk) begin
//如果写入使能有效 同时 输出使能无效
if ((Wr == 1'b1) && (OE == 1'b0))
mem[Reg] = W_data;
end
endmodule

1.4 算术/逻辑运算单元(ALU)

ALUCtrl 功能 ALUCtrl 功能
000 AND 100 小于置一
001 OR 101 NOT(A)
010 ADD 110 NOR
011 SUB 111 XOR(异或)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
module alu(
input [2:0] ALUCtrl,
input [31:0] a, b,
output reg [31:0] out,
output Zero,
output OF);

wire [31:0] sub_ab;
wire [31:0] add_ab;
wire oflow_add;
wire oflow_sub;
wire oflow;
wire slt;

//计算零标志位
assign Zero = (0 == C);

//计算加减结果
assign sub_ab = a - b;
assign add_ab = a + b;

// 计算时候溢出
assign oflow_add = (a[31] == b[31] && add_ab[31] != a[31]) ? 1 : 0;
assign oflow_sub = (a[31] == b[31] && sub_ab[31] != a[31]) ? 1 : 0;
assign oflow = (ctl == 4'b0010) ? oflow_add : oflow_sub;

// 计算小于置一运算结果
assign slt = oflow_sub ? ~(a[31]) : a[31];

//使用多路选择器输出计算结果
// 000 AND
// 001 OR
// 010 ADD
// 011 SUB
// 100 小于置一
// 101 NOT(a)
// 110 NOR
// 111 XOR(异或)
always @(*) begin
case (ALUCtrl)
3'b000: out <= a & b; /* and */
3'd001: out <= a | b; /* or */
3'b010: out <= add_ab; /* add */
3'b011: out <= sub_ab; /* sub */
3'b100: out <= {{31{1'b0}}, slt}; /* slt */
3'b101: out <= ~ a; /* not */
3'b110: out <= ~(a | b); /* nor */
3'b111: out <= a ^ b; /* xor */
default: out <= 0;
endcase
end

endmodule

1.5 三态门(TSG)

Ctrl 引脚高电平有效,低电平时输出高阻态

1
2
3
4
5
6
7
8
9
module(
input Ctrl,
input wire [N-1:0] in,
output reg [N-1:0] out);

parameter N = 1;

out = Ctrl ? in : N'bz;
endmodule

后面发现了这个模块有很多的 error,最终发现问题在于使用参数表示位宽和按照 c 语言的习惯将 1 当作 True.
遂将模块重写,最大支持参数为 32,实测当 N 小于 32 时不会报错(甚至连 warning 都没)

1
2
3
4
5
6
7
8
9
module tsg(
input wire Ctrl,
input wire [N-1:0] in,
output wire [N-1:0] out);

parameter N = 1;
assign out = (Ctrl==1'b1)? in : 32'hz;

endmodule

1.6 存储器(RAM)

RAM 总体可以看作一个组合电路,其数据的输入输出由 MAR 和 MDR 进行缓存(对实际存储器进行了理想的简化)
RAM 的访问可以在一个时钟周期内实现,故而使用同步方式进行控制

引脚 功能
R 读取使能端(其实不需要)
W 写入使能端,高电平有效
Addr 地址输入引脚
R_data 读取数据线
W_data 写入数据线
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module ram(
input wire [6:0] Addr,
input wire W, R,
input wire [31:0] W_data,
output wire [31:0] R_data);

reg [31:0] mem [0:127]; //128个32位存储单元

//写入数据
always @(*) begin
if (W) begin
mem[addr] <= W_data;
end
end

//读取数据,支持同时写入和读取
assign R_data = W ? W_data : mem[Addr][31:0];

endmodule

1.7 ALU 控制器(ALUCU)

由下面指令控制流程可知,ALUCU 根据 ALUOp 和 funct 输入输出 ALU 控制信号 ALUCtrl,真值表如下

ALUOp 功能
00 ALU 执行加操作,用来在修改 PC 寄存器计算地址等
10 ALU 执行 funct 指定的运算操作
01 输出(A==0)的值
11 未定义的操作

8. 总数据通路

数据通路和支持的指令如图所示

2. CPU 控制器

  1. 指令控制流程
  2. 引脚功能
  3. MCU 设计

2.1 指令控制流程

主控制器,负责控制整个数据通路部件的控制,其中引脚定义见数据通路

  1. 取指令阶段

    1. 第一个时钟周期:送指令地址
      MAR <- (PC);PCOeh,PCOel,MARWr
      A <- (PC);AWr
    2. 第二个时钟周期:取指令
      IR <- M[MAR];MemRd,MemOe,IRWr
    3. 第三个时钟周期:送 PC 修正量
      B <- 4;ExtSel=10,ImmOeh,ImmOel,BWr
    4. 第四个时钟周期:修正 PC
      PC <- (PC)+4;ALUOp=00,ALUCtrl==010,ALUOe,PCWr
    5. 第五个时钟周期:指令译码和读寄存器
      A <- (RF[IR[25:21]]);RegSel=00,RegOe,Awr
  2. 指令执行阶段

    1. 取数指令(LW)
      1. 第六个时钟周期:计算存储器地址
        B <- (SigExt[IR[15:0]]);ExtSel=00,ImmOeh,ImmOel,BWr
      2. 第七个时钟周期:送存储器地址
        MAR <- (A)+(B);ALUOp=00,ALUCtrl=010,ALUOe,MARWr
      3. 第八个时钟周期:读存储器
        MDR <- M[MAR];MemRd,MDRSrc,MDRWr
      4. 第九个时钟周期:写寄存器
        RF[IR[20:16]] <- (MDR);MDROe,RegSel=01,RegWr
    2. 存数指令(SW)
      1. 第六个时钟周期:计算存储器地址
        B <- (SigExt[IR[15:0]]);ExtSel=00,ImmOeh,ImmOel,BWr
      2. 第七个时钟周期:送存储器地址
        MAR <- (A)+(B);ALUOp=00,ALUCtrl=100,ALUOe,MARWr
      3. 第八个时钟周期:读寄存器
        MDR <- (RF[IR[20:16]]);RegSel=01,RegOe,MDRSrc=0,MDRWr
      4. 第九个时钟周期:写存储器
        M[MAR] <- (MDR);MemWr
    3. 算术/逻辑运算指令(R 型指令)
      1. 第六个时钟周期:读寄存器
        B <- (RF[IR[20:16]]);RegSel=01,RegOe,BWr
      2. 第七个时钟周期:运算并写寄存器
        RF[IR[15:11]] <- (A)OP(B);ALUOP=10,ALUCtrl=xxx,ALUOe,RegSel=10,RegWr
    4. 分支指令(BEQ)
      1. 第六个时钟周期:程序转移
        A <- (RF[IR[20:16]]);RegSel=10,RegOe,BWr
        if Z==0 then 指令周期结束,ALUOp=01,ALUCtrl=101
      2. 第七个时钟周期:送 PC
        A <- (PC);PCOeh,PCOel,AWr
      3. 第八个时钟周期:送 PC 修正量
        B <- (SigExt (IR[15:0]<<2));ExtSel=01,ImmOeh,ImmOel,BWr
      4. 第九个时钟周期:计算转移地址
        PC <- (A)+(B);ALUOp=00,ALUCtrl=010,ALUOe,PCWr
    5. 跳转指令(J)
      1. 第六个时钟周期:转移
        PC <- (PC[31:28])||(IR[25:0]<<2);PCOeh,ExtSel=11,ImmOel,PCWr

2.2 引脚功能

  1. 输出引脚
输出引脚 作用 输出引脚 作用
CLK 时钟信号 ALUOe ALU 输出使能
PCOeh PC 高 4 位输出总线 RegOe 通用寄存器输出使能
PCOel PC 低 28 位输出总线 RegWr 通用寄存器写入使能
PCWr PC 写入使能 RegSel 通用寄存器写入源选择
IRWr IR 写入使能 MARWr MAR 写入使能
ImmOeh 立即数高 4 位输出总线 MemRd 存储器读使能
ImmOel 立即数低 28 位输出总线 MemWr 存储器写使能
ExtSel 符号扩展方式选择 MDRSrc MDR 写入源选择
ALUOp ALU 操作 MDROe MDR 输出使能
ALUCtrl ALU 操作控制 MDRWr MDR 写入使能
AWr A 暂存器写入使能 MemOe 存储器输出使能
BWr B 暂存器写入使能
  1. ExtSel 功能表
ExtSel 符号扩展方式 解释
00 SigExt(IR[15:0]) 将输入的 16 位立即数符号扩展为 32 位
01 SigExt(IR[15:0])<<2 在 00 的基础上左移两位
10 立即数 4 直接输出立即数 4
11 IR[25:0]<<2 将 26 位地址值左移 2 位

其中输出立即数 4 是为了普通的连续执行指令的时候作为 PC 的修正值输出源

2.3 MCU 设计

参考资料:《计算机组成与设计》 王换招、陈妍、赵青苹 编著版