流水线 cpu 的简易实现

github项目地址

一、整体思路

1. 流水线 cpu

流水线 cpu 中一个指令执行的五个周期如上图所示。
为了解决流水线同步问题,设计 cpu 中级间寄存器由上升沿触发,周期内器件由下降沿触发。

上图为书上的流水线 cpu 基本数据通路,本次实验主要在此基础上进行改进,且添加冲突处理模块,在实验过程中,我也发现了该图中很多不足的地方。

2.mips 指令集

mips 指令分为 R 型,I 型和 J 型,其中 R 型有三个操作数,I 型携带立即数,J 型为跳转指令。
MIPS 指令

3. 冲突管理

冲突管理是流水线 cpu 不得不面对的一个问题,解决方法主要为插入气泡来阻塞指令运行,已经增加新的数据回路使前指令的结果尽快回送到寄存器中。本次实验主要采取的是气泡插入法,结构较为简单但是降低了效率。

cpu 的冲突主要分为三种,结构冲突,数据冲突和控制冲突。结构冲突通过流水线 cpu 的结构设计可以避免,数据冲突主要是相距较近的指令所需数据地址相同产生的时间冲突,控制冲突主要针对跳转指令,本次实验中的冲突检测模块维护了一个队列用来检测冲突,从而解决数据冲突,对于控制冲突,由于数据通路问题无法较早回送,只能插入气泡。

二、组成模块

1. 存储器

存储器分为指令存储器(IM)和数据存储器(DM)两部分。
指令存储器通过 PC 的输出
verilog 代码实现

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
module DataMemory(
input Mem_rd,Mem_wr,
input [31:0] Addr,wr_data,
output reg [31:0]rd_data
);
reg [31:0]data[31:0];
integer i;
initial begin//给存储器赋初值
rd_data = 32'bz;
for(i = 0;i< 32;i++)begin
data[i] = i;
end
end
always@(*)begin
if(Mem_rd)begin
rd_data = data[Addr];
end else if(Mem_wr)begin
data[Addr] = wr_data;
end
end
endmodule

module InstructionMemory(
input [31:0]Addr,
output reg [31:0]Instr
);
reg[7:0] instr[127:0];
initial begin
$readmemh("txt/instruction.txt", instr);
end
always @(*) begin
Instr = {instr[Addr],instr[Addr+1],instr[Addr+2],instr[Addr+3]};//指令存储器按字节编址
end
endmodule

2. 级间寄存器

级间寄存器是流水线 cpu 最为关键的部分,承担着在各不同周期之间传递指令和标志位的作用。
级间寄存器由同步信号驱动,控制各自周期内部计算的开始并分割指令的各周期。
在冲突发生时,FI/ID 级间寄存器阻塞并将所有标志位置零,防止指令向下运行。
verilog 代码实现

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

//FI,ID级间寄存器
// 各级间寄存器将指令在下一周期使用的信号位伴随指令传入下一周期
// 从而减轻控制器负担
// 控制器只需在每次取指令后计算出与该指令相关的所有周期的信号位即可
module RegFI_ID(
input clk,
input [31:0]NPC1in,
input [31:0]IRin,
input suspend,
input reg_wr_in,ALU_src_in,Jump_in,Branch_in,Mem_rd_in,Mem_wr_in,MemtoReg_in,RegDst_in,
output reg [31:0]NPC1out,
output reg [31:0]IRout,
output reg reg_wr,ALU_src,Jump,Branch,Mem_rd,Mem_wr,MemtoReg,RegDst
);
always@(posedge clk) begin
if(!suspend)begin
NPC1out <= NPC1in;
IRout <= IRin;
ALU_src <= ALU_src_in;
Jump <= Jump_in;
Branch <= Branch_in;
Mem_rd <= Mem_rd_in;
Mem_wr <= Mem_wr_in;
reg_wr <= reg_wr_in;
MemtoReg <= MemtoReg_in;
RegDst <= RegDst_in;
end else begin
ALU_src <= 1'b0;
Jump <= 1'b0;
Branch <= 1'b0;
Mem_rd <= 1'b0;
Mem_wr <= 1'b0;
reg_wr <= 1'b0;
MemtoReg <= 1'b0;
RegDst <= 1'b0;
end
end
endmodule

//ID,EX级间寄存器
module RegID_EX(
input clk,
input [31:0]NPC1in,
input [31:0]Rsin,Rtin,
input [31:0]Instruction32,
input [31:0]IRin,
input reg_wr_in,ALU_src_in,Jump_in,Branch_in,Mem_rd_in,Mem_wr_in,MemtoReg_in,RegDst_in,
output reg[31:0]NPC1out,
output reg[31:0]IRout,
output reg[31:0]Rt,Rs,
output reg[31:0]Imm32,
output reg reg_wr,ALU_src,Jump,Branch,Mem_rd,Mem_wr,MemtoReg,RegDst
);
always @(posedge clk) begin
Rs <= Rsin;
Rt <= Rtin;
Imm32 <= Instruction32;
NPC1out <= NPC1in;
IRout <= IRin;
ALU_src <= ALU_src_in;
Jump <= Jump_in;
Branch <= Branch_in;
Mem_rd <= Mem_rd_in;
Mem_wr <= Mem_wr_in;
reg_wr <= reg_wr_in;
MemtoReg <= MemtoReg_in;
RegDst <= RegDst_in;
end
endmodule

//EX,MA级间寄存器
module RegMA_WB(
input clk,
input [31:0]ALU,MEM,IRin,
input MemtoReg_in,RegDst_in,reg_wr_in,
output reg [31:0]ALUout,
output reg [31:0]MEMout,
output reg [31:0]IRout,
output reg MemtoReg,RegDst,reg_wr
);
always @(posedge clk) begin
ALUout <= ALU;
MEMout <= MEM;
IRout <= IRin;
reg_wr <= reg_wr_in;
MemtoReg <= MemtoReg_in;
RegDst <= RegDst_in;

end
endmodule

//MA,WB级间寄存器
module RegEX_MA(
input clk,
input [31:0]NPC3_in,NPC2_in,
input flag_in,reg_wr_in,Jump_in,Branch_in,Mem_rd_in,Mem_wr_in,MemtoReg_in,RegDst_in,
input [31:0]ALUout_in,Rt_in,IR_in,
output reg flag,reg_wr,Jump,Branch,Mem_rd,Mem_wr,MemtoReg,RegDst,
output reg [31:0]NPC3,NPC2,
output reg [31:0]ALUout,Rt,IR
);
always @(posedge clk) begin
NPC3 <= NPC3_in;
NPC2 <= NPC2_in;
ALUout <= ALUout_in;
Rt <= Rt_in;
IR <= IR_in;
Jump <= Jump_in;
Branch <= Branch_in;
Mem_rd <= Mem_rd_in;
Mem_wr <= Mem_wr_in;
reg_wr <= reg_wr_in;
MemtoReg <= MemtoReg_in;
RegDst <= RegDst_in;
flag<=flag_in;
end

endmodule

3. 控制模块

控制模块通过识别指令输出对应的个标志位的值给级间寄存器。
由于流水线 cpu 的标志位较少,且可以借助级间寄存器传递相应指令的标志位,
可以直接使用组合逻辑电路实现。
verilog 代码实现

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
`define LW 6'b100011
`define SW 6'b101011
`define ADD 6'b100000
`define SUB 6'b100010
`define ADDU 6'b100001
`define AND 6'b100100
`define OR 6'b100101
`define SLT 6'b101010
`define BEQ 6'b000100
`define J 6'b000010
`define R 6'b000000
`define HALT 6'b111111
module ControlUnit(
input [5:0] OP_code,
output Reg_wr,
output ALU_src,
output Jump,
output Branch,
output MemtoReg,
output RegDst,
output Mem_rd,
output Mem_wr
);

assign Reg_wr=(OP_code== `R||OP_code==` LW)?1:0;
assign Jump=(OP_code==`J)?1:0;
assign RegDst=(OP_code==`R)?1:0;
assign Branch=(OP_code==`BEQ)?1:0;
assign MemtoReg=(OP_code==`LW)?1:0;
assign Mem_rd=(OP_code==`LW)?1:0;
assign Mem_wr=(OP_code==`SW)?1:0;
assign ALU_src=(OP_code== `LW||OP_code==` SW)?1:0;
endmodule

4. 运算器及运算控制器

运算器(ALU)即指令执行的主要模块,工作在 EX 周期。
在流水线 cpu 中,通过添加加法器,分离部分运算器功能的方法可以解决部分冲突问题
verilog 代码实现

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
54
55
56
57
58
59
60
61
module ALU(
input [2:0]alu_op,
input [31:0]A,B,
output flag,
output reg [31:0]ALU_out
);
always @(*) begin
case(alu_op)
3'b000:ALU_out=A+B;
3'b001:ALU_out=A-B;
3'b010:ALU_out=A*B;
3'b011:ALU_out=A<B?1:0;
3'b100:ALU_out=A&B;
3'b101:ALU_out=A|B;
3'b110:ALU_out=~A;
3'b111:ALU_out=A^B;
default:ALU_out=0;
endcase

end
assign flag=(ALU_out==0)?1'b1:1'b0;
endmodule
`define LW 6'b100011
`define SW 6'b101011
`define ADD 6'b100000
`define SUB 6'b100010
`define ADDU 6'b100001
`define AND 6'b100100
`define OR 6'b100101
`define SLT 6'b101010
`define BEQ 6'b000100
`define J 6'b000010
`define R 6'b000000
`define HALT 6'b111111

module AluControl(
input [5:0]OP_code,//func
input [5:0]ALU_op,//识别码R型指令为6位0
output reg [2:0]ALUctrl
);
always @(*) begin
if(ALU_op==6'b0)begin
case(OP_code)
`ADD:ALUctrl<=3'b000;
`SUB:ALUctrl<=3'b001;
`ADDU:ALUctrl<=3'b000;
`AND:ALUctrl<=3'b100;
`OR:ALUctrl<=3'b101;
`SLT:ALUctrl<=3'b011;
default:ALUctrl<=3'b111;
endcase
end else begin
if(ALU_op==`BEQ)begin
ALUctrl <= 3'b001;
end else if(ALU_op== `LW||ALU_op==` SW)begin
ALUctrl<=3'b000;
end
end

end
endmodule

5. 程序计数器

程序计数器(PC)记录下一步指令的地址,主要在 FI 周期工作。
PC 通过加法器实现自增功能,但在冲突发生时,关闭自增。
verilog 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//PC
module ProgramCounter(
input clk,
input [31:0]addrin,
input insist,
output reg [31:0]addrout
);
initial begin
addrout = 32'b0;
end
always @(posedge clk) begin
if(!insist)
addrout = addrin;
end
endmodule

6. 冲突检测模块

流水线 cpu 主要需要解决数据冒险和控制冒险的问题,主要工作于 FI 周期。
本次实验采用后推产生气泡的方法解决冒险,在冲突检测模块中建立队列存储已经执行指令影响的寄存器;
取出新指令时,在队列中对比,如发现冲突,则产生气泡,后退冲突影响指令。
产生气泡时阻塞 PC 自增并使 FI/ID 级间寄存器中各数据位归零,使得指令无法运行。
由于级间寄存器上升沿触发,冲突检测模块应由下降沿触发。
verilog 代码实现

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
`define LW 6'b100011
`define SW 6'b101011
`define ADD 6'b100000
`define SUB 6'b100010
`define ADDU 6'b100001
`define AND 6'b100100
`define OR 6'b100101
`define SLT 6'b101010
`define BEQ 6'b000100
`define J 6'b000010
`define R 6'b000000
`define HALT 6'b111111

module HazardDetection(
input clk,
input [5:0]OP_code,
input [4:0]rd,
input [4:0]rs,rt,
output reg [1:0]suspend
);
reg[2:0]quene[4:0];
integer i;
reg flag;
initial begin
for(i = 0;i<3;i++)begin
quene[i]=5'bz;
suspend = 2'b0;
end
flag = 0;
end
always @(negedge clk) begin
if(OP_code==`R)begin
for(i = 0;i<3;i++)begin
if(rs==quene[i]||rt==quene[i])begin
suspend=2'b11;
repeat(3-i)begin
#20;
end
suspend = 2'b0;
flag = 1;
end
end
if(!flag)begin
quene[2] = quene[1];
quene[1] = quene[0];
quene[0] = rd;
end else begin
quene[0] = rd;
quene[1] = 5'bz;
quene[2] = 5'bz;
flag = 0;
end
end else if(OP_code==`LW)begin
quene[2] = quene[1];
quene[1] = quene[0];
quene[0] = rt;
end else if(OP_code==`SW)begin
for(i = 0;i<3;i++)begin
if(rs==quene[i])begin
suspend=2'b11;
repeat(3-i)begin
#20;
end
suspend=0;
flag = 1;
end
end
if(!flag)begin
quene[2] = quene[1];
quene[1] = quene[0];
quene[0] = 5'bz;
end else begin
quene[0] = 5'bz;
quene[1] = 5'bz;
quene[2] = 5'bz;
flag = 0;
end
end else if(OP_code==`J)begin
quene[0] <=3'bz;
quene[1] <= 3'bz;
quene[2] <= 3'bz;
#11 suspend = 2'b11;
#58 suspend <= 2'b01;
#2 suspend<=2'b00;

end else if(OP_code==`BEQ) begin
for(i = 0;i<3;i++)begin
if(rs==quene[i]||rt==quene[i])begin
suspend=2'b11;
repeat(3-i)begin
#20;
end
suspend = 2'b0;
flag = 1;
end
end
quene[0] <=3'bz;
quene[1] <= 3'bz;
quene[2] <= 3'bz;
#11 suspend = 2'b11;
#58 suspend <= 2'b01;
#2 suspend<=2'b00;
end else begin
quene[2] = quene[1];
quene[1] = quene[0];
quene[0] = 5'bz;
end

end

endmodule // HarzardDectection

7. 寄存器组模块

寄存器组(RF)是 cpu 中的主要存储模块,工作在 ID 和 WB 周期。
在 ID 周期中,寄存器组取出以指令中操作数为地址的值,并输入下一层。
在 WB 周期中,RF 接受回送地址和回送数据,实现将结果存入目标地址。
verilog 代码实现

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
//REG_F
module Register_File(
input clk,
input reg_wr,
input [31:0]wr_data,
input [4:0]wr_addr,
input [4:0]rs_addr,//IR[25:20]
input [4:0]rt_addr,//IR[20:16]
output reg [31:0]Rs_data,
output reg [31:0]Rt_data
);
reg [31:0]data[31:0];
initial begin
$readmemh("txt/RF.txt", data);//通过文件直接初始化
end
// initial begin
// for(i = 0;i< 32;i++)begin
// data[i] = i;
// end
// end
always@(negedge clk)begin
if(reg_wr)begin
data[wr_addr]<=wr_data;
end
else begin
Rs_data <= data[rs_addr];
Rt_data <= data[rt_addr];
end
end
endmodule

8. 其他逻辑模块

其他逻辑模块主要包含加法器,16/32 位扩展模块,移位模块,多路选择模块。
加法器主要用于 PC 自增和分担运算器功能;
16/32 扩展模块用于处理指令中的立即数;
移位模块用于处理 beq 跳转指令,工作在 EX 周期;
多路选择器是标志位的主要作用对象,工作于 FI, MA, WB 模块。
verilog 代码实现

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
54
55
56
57
58
//加法器
module Add(
input clk,
input [31:0]a,
input [31:0]b,
output reg [31:0]c
);
always @(negedge clk) begin
c = a+b;
end
endmodule

module ShiftUnit(
input [31:0]a,
output [31:0]b
);
assign b = a<<2;
endmodule

module SignalExtension(
input [15:0]IR16,
output reg[31:0] Imm32
);
always@(*)begin
Imm32 = {16'b0,IR16};
end
endmodule
module MUX_2(
input [N-1:0]in1,
input [N-1:0]in2,
input control,
output reg [N-1:0] out
);
parameter N = 1;//决定二路选择器的数据位数,由外部指定
always @(*) begin
case(control)
0:out = in1;
1:out = in2;
endcase
end
endmodule // MUX_2

//四路选择器
module MUX_4(
input j1,
input j2,
input in1,
input in4,
input [31:0]npc3,
input [31:0]npc2,
output reg [31:0]out
);
always @(*) begin
if(j1&&(~j2))out<=npc2;
else if(j2&&(~j1))out<=npc3;
end
endmodule

9.cpu 顶层模块

顶层模块中实例化各模块,计算 PC_src, 产生时钟信号,并初始化 PC,使得 cpu 从第一条指令开始运行。
verilog 代码实现

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
`define LW 6'b100011
`define SW 6'b101011
`define ADD 6'b100000
`define SUB 6'b100010
`define ADDU 6'b100001
`define AND 6'b100100
`define OR 6'b100101
`define SLT 6'b101010
`define BEQ 6'b000100
`define J 6'b000010
`define HALT 6'b111111

`include "mux.v"
`include "ControlUnit.v"
`include "AluControl.v"
`include "ALU.v"
`include "Register.v"
`include "SigExt.v"
`include "PC.v"
`include "Add.v"
`include "Memory.v"
`include "SHL.v"
`include "HazardDetection.v"

module cpu();
//信号位_MA,WB,EX,ID,或级间寄存器内容_MA,WB,EX,ID表示级间寄存器在相应周期内的对应值的输出
reg PC_src;
wire ALU_ZF;
wire Jump,flag,Branch,RegDst;
wire Reg_wr,ALU_src,MemtoReg,Mem_wr,Mem_rd;
wire [31:0]mux_rf_data;
wire [4:0]mux_rf_addr;
wire reg_wr_EX,reg_wr_MA,reg_wr_WB;
wire Jump_MA,Jump_EX,Jump_ID;
wire Branch_ID,Branch_EX,Branch_MA;
wire ALU_src_ID,ALU_src_EX,Reg_wr_ID;
wire Mem_rd_EX,Mem_rd_ID,Mem_rd_MA,Mem_wr_ID,Mem_wr_EX,Mem_wr_MA;
wire MemtoReg_ID,MemtoReg_EX,MemtoReg_MA,MemtoReg_WB,RegDst_ID,RegDst_EX,RegDst_MA,RegDst_WB;
wire [31:0]Rs_EX,Rt_EX,Rt_MA;
wire [1:0]suspend;
wire [31:0]Rs,Rt;//直接从RF中输出
wire [31:0]Instr,Imm32;
wire [31:0]IR_ID,IR_EX,IR_MA,IR_WB;
wire [31:0]NPC1_FI,NPC1_EX,NPC1_ID;
wire [31:0]NPC3;
wire [31:0]NPC2;
wire [31:0]a_out,b_out;//移位输出
wire [31:0]SigExt_out;
wire [31:0]mux_IF_out;
wire [31:0]mux4_out,mux_EX_out;
wire [31:0]ALUOut_EX,ALUOut_MA,ALUOut_WB;
wire [31:0]PC_out;
wire [31:0]MEMOut_MA,MEMOut_WB;
wire [2:0]ALUOp;//由alu控制器产生的用于控制alu运算的信号
wire [31:0]Add1_out,Add2_out;
reg clk;

//FI
ProgramCounter PC(clk,mux_IF_out,suspend[1],PC_out);
Add Add1(.a(32'h4),.b(PC_out),.c(Add1_out),.clk(clk));
MUX_2 #(.N(32)) mux_FI(.control(PC_src),.in1(Add1_out),.in2(mux4_out),.out(mux_IF_out));
InstructionMemory IM(PC_out,Instr);

//ID
RegFI_ID regFI(
.clk(clk),
.NPC1in(Add1_out),
.suspend(suspend[0]),
.IRin(Instr),
.IRout(IR_ID),
.NPC1out(NPC1_ID),
.reg_wr_in(reg_wr),
.ALU_src_in(ALU_src),
.Jump_in(Jump),
.Branch_in(Branch),
.Mem_rd_in(Mem_rd),
.Mem_wr_in(Mem_wr),
.MemtoReg_in(MemtoReg),
.RegDst_in(RegDst),
.reg_wr(Reg_wr_ID),
.ALU_src(ALU_src_ID),
.Jump(Jump_ID),
.Branch(Branch_ID),
.Mem_rd(Mem_rd_ID),
.Mem_wr(Mem_wr_ID),
.MemtoReg(MemtoReg_ID),
.RegDst(RegDst_ID)
);
SignalExtension sig(IR_ID[15:0],SigExt_out);
Register_File RF(
.clk(clk),
.reg_wr(reg_wr),
.wr_addr(mux_rf_addr),
.wr_data(mux_rf_data),
.rs_addr(IR_ID[25:21]),
.rt_addr(IR_ID[20:16]),
.Rs_data(Rs),
.Rt_data(Rt)
);

//EX
RegID_EX regIE(clk,NPC1_ID,Rs,Rt,SigExt_out,IR_ID,
Reg_wr_ID,ALU_src_ID,Jump_ID,Branch_ID,Mem_rd_ID,Mem_wr_ID,
MemtoReg_ID,RegDst_ID,NPC1_EX,IR_EX,Rt_EX,Rs_EX,Imm32,reg_wr_EX,
ALU_src_EX,Jump_EX,Branch_EX,Mem_rd_EX,Mem_wr_EX,MemtoReg_EX,RegDst_EX);
ShiftUnit shl2a({6'b0,IR_EX[25:0]},a_out);
ShiftUnit shl2b(Imm32,b_out);
Add Add2(.a(NPC1_EX),.b(b_out),.c(Add2_out),.clk(clk));
ALU alu(.alu_op(ALUOp),.A(Rs_EX),.B(mux_EX_out),.ALU_out(ALUOut_EX),.flag(ALU_ZF));
AluControl AluC(IR_EX[5:0],IR_EX[31:26],ALUOp);
MUX_2 #(.N(32)) mux_EX(.control(ALU_src_EX),.in1(Rt_EX),.in2(Imm32),.out(mux_EX_out));

//MA
RegEX_MA regEM(
.clk(clk),
.NPC3_in({NPC1_EX[31:28],a_out[27:0]}),
.NPC2_in(Add2_out),
.NPC3(NPC3),
.NPC2(NPC2),
.ALUout_in(ALUOut_EX),
.ALUout(ALUOut_MA),
.Rt_in(Rt_EX),
.IR_in(IR_EX),
.Rt(Rt_MA),
.IR(IR_MA),
.flag_in(ALU_ZF),
.flag(flag),
.reg_wr_in(reg_wr_EX),
.reg_wr(reg_wr_MA),
.Jump_in(Jump_EX),
.Jump(Jump_MA),
.Branch_in(Branch_EX),
.Branch(Branch_MA),
.Mem_rd_in(Mem_rd_EX),
.Mem_rd(Mem_rd_MA),
.Mem_wr_in(Mem_wr_EX),
.Mem_wr(Mem_wr_MA),
.MemtoReg_in(MemtoReg_EX),
.MemtoReg(MemtoReg_MA),
.RegDst_in(RegDst_EX),
.RegDst(RegDst_MA)
);

MUX_4 m0(
.j1(flag&&Branch_MA),
.j2(Jump_MA),
.in1(1'b0),
.in4(1'b0),
.npc3(NPC3),
.npc2(NPC2),
.out(mux4_out)
);

DataMemory DM(Mem_rd_MA,Mem_wr_MA,ALUOut_MA,Rt_MA,MEMOut_MA);

//WB
RegMA_WB regMW(
.clk(clk),
.ALU(ALUOut_MA),
.MEM(MEMOut_MA),
.IRin(IR_MA),
.ALUout(ALUOut_WB),
.MEMout(MEMOut_WB),
.IRout(IR_WB),
.MemtoReg_in(MemtoReg_MA),
.MemtoReg(MemtoReg_WB),
.RegDst_in(RegDst_MA),
.RegDst(RegDst_WB),
.reg_wr_in(reg_wr_MA),
.reg_wr(reg_wr_WB)
);
MUX_2 #(.N(32)) mux_WB1 (.control(MemtoReg_WB),.in1(ALUOut_WB),.in2(MEMOut_WB),.out(mux_rf_data));
MUX_2 #(.N(5)) mux_WB2(.control(RegDst_WB),.in1(IR_WB[20:16]),.in2(IR_WB[15:11]),.out(mux_rf_addr));
//控啊器和冲突检测

ControlUnit CU(Instr[31:26],reg_wr,ALU_src,Jump,Branch,MemtoReg,RegDst,Mem_rd,Mem_wr);
HazardDetection HD(
.clk(clk),
.OP_code(Instr[31:26]),
.rs(Instr[25:21]),
.rt(Instr[20:16]),
.rd(Instr[15:11]),
.suspend(suspend)
);
always @(*) begin
PC_src <= (flag&&Branch_MA)||Jump_MA;//控制pc的改变

end
initial begin
PC_src = 0;
clk = 0;
repeat(100) begin//产生时钟信号
#10
clk = ~clk;
end
end
// initial begin
// #2000 $finish;
// end
endmodule

三、测试方法和工具

1. 汇编器

汇编器是将汇编语言转换成机器语言指令的工具,通过 python 中 ply 模块的 lex 模块和 yacc 模块可以较为轻松的编写汇编器。

需要 python3,pip 环境
安装依赖

1
pip3 install ply

代码

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# coding:utf-8
# ---------
# 简易mips指令汇编器 词法分析
# author:bibibi
# ---------

import ply.lex as lex

CODE_dict = {
'LW': '100011',
'SW': '101011',
'ADD': '000000',
'SUB': '000000',
'OR': '000000',
'AND': '000000',
'SLT': '000000',
'BEQ': '000100',
'J': '000010'
}

CAL_dict = {
'ADD': '100000',
'SUB': '100010',
'OR': '100101',
'AND': '100100',
'SLT': '101010',
}

tokens = (
'OPCODE', # 指令码
'REG', # 寄存器
'IMME', # 立即数
'LPAREN', # 左括号
'RPAREN' # 右括号
)

t_LPAREN = r'\('
t_RPAREN = r'\)'

# 操作码
def t_OPCODE(t):
r'(LW)|(SW)|(ADD)|(SUB)|(OR)|(AND)|(SLT)|(BEQ)|(J)'
# 如果是数字逻辑运算型指令,则需要加上对应的操作码
if t.value in CAL_dict:
t.value=[CODE_dict[t.value],CAL_dict[t.value]]
else:
t.value = CODE_dict[t.value]
return t

# 寄存器
def t_REG(t):
r'\$\d+'
# 寄存器号码直接转化为对应的二进制表示
t.value = "%05d"%int(bin(int(t.value[1:]))[2:])
# t.value = bin(int(t.value[1:]))[2:]
return t

def t_IMME(t):
r'\d+'
# 由于数字可能是I型指令的16位,也可能是J型指令的26位
# 保存原数字的值,在语法分析种再进行转换
# t.value = "%05d"%int(bin(int(t.value))[2:])
# t.value = bin(int(t.value))[2:]
# t.value = (t.value[0]=='-')?(-1*int(t.value)):int(t.value);
t.value = int(t.value)
return t

def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)

t_ignore = ', \t'

def t_error(t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)

lexer = lex.lex()

import ply.yacc as yacc

# 存储器访问指令,即I型指令
# 将立即数转换为16位二进制数

def p_operation_mem(p):
'operation : OPCODE REG IMME LPAREN REG RPAREN'
p[0] = p[1] + p[5] + p[2] + "%016d" % int(bin(int(p[3]))[2:])
p[0] = "%08x"%(int(p[0],base=2)) + '\n'

# 算术逻辑运算,即R型指令
# 需要根据在指令的最后加上操作码

def p_operation_cal(p):
'operation : OPCODE REG REG REG'
p[0] = p[1][0] + p[3] + p[4] + p[2] + '00000' + p[1][1]
p[0] = "%08x"%(int(p[0],base=2)) + '\n'

# BEQ指令,I型指令
# 将立即数转换为16位二进制数

def p_operation_beq(p):
'operation : OPCODE REG REG IMME'
p[0] = p[1] + p[2] + p[3] + "%016d" % int(bin(int(p[4]))[2:])
p[0] = "%08x"%(int(p[0],base=2)) + '\n'

# J指令,J型指令
# 将立即数转换为26位二进制数

def p_operation_j(p):
'operation : OPCODE IMME'
p[0] = p[1] + "%026d" % int(bin(int(p[2]))[2:])
p[0] = "%08x"%(int(p[0],base=2)) + '\n'

# 若干条指令合并成一段连续的代码

def p_program(p):
'operation : operation operation'
p[0] = p[1] + p[2]

def p_error(p):
print("Syntax error in input!")

parser = yacc.yacc()

def compile(data):
result = parser.parse(data)
return result

if __name__ == "__main__":
with open("./txt/data.txt",'r') as file:
data = file.read()

result = compile(data)

with open("./data.out",'w') as file:
file.write(result)

print(result)

2. 存储器初始化

指令存储器和寄存器组可以通过 verilog 自带函数读取.txt 文件初始化。
数据存储器在代码中自行初始化。
PC 初始化为 0.
RF 初始化如下:
11111111
22222222
33333333
44444444
55555555
66666666
77777777
88888888
99999999
aaaaaaaa
bbbbbbbb
cccccccc
dddddddd
eeeeeeee
ffffffff
12345678
87654321
01234567
76543210
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000

四、测试结果

1. 测试指令

1
2
3
4
5
6
7
8
9
10
ADD $1,$2,$3
J 4
SUB $2,$3,$4
AND $1,$2,$3
SLT $1,$2,$3
LW $1,10($2)
BEQ $1,$2,2
SW $1,10($2)
ADDU $1,$2,$3
HLT

转换为十六进制机器指令

1
2
3
4
5
6
7
8
9
10
00 43 08 20
08 00 00 04
00 64 10 22
00 43 08 24
00 43 08 2a
8c 41 00 0a
10 22 00 02
ac 41 00 0a
00 43 08 20
fc 00 00 00

2.vivado 模拟

3. 测试模块

1
2
3
4
5
6
7
8
9
module test();
cpu cpu1();
initial begin
$dumpfile("cpu.vcd");
$dumpvars(0,cpu1);
#1000;
end
endmodule // test

4. 波形效果

五、改进方案

本次实验中为了解决数据冲突和控制冲突,主要采用了插入气泡的后退法,但是尽管后退法的结构简单,不会引入较为复杂的连线和结构,但浪费了较多的 cpu 周期,削弱了
流水线 cpu 相较于其他 cpu 效率更高的优点。为了保证指令执行的效率,可以使用专用的数据通路提前回送数据从而取代气泡的插入。同时,对于控制冒险,可以将 J 指令在 FI
周期内完成,BEQ 指令在 FI 和 ID 周期内完成,可以实现 J 指令不插入气泡,BEQ 指令只插入一个气泡,但要实现这一结构,本次实验会对级间寄存器结构有较大的改动,因此没有实现。