1、CPU简介
CPU的本质是在整个电路系统中为核心电路单元,主要的作用是对数据进行逻辑和数学运算的电路单元,运算时通过人机交互控制。
2、工作示意图
CU:控制单元,分析指令,给出控制信号
IR:指令寄存器,分析指令,给出控制信号
PC:程序计数器,存放下一条指令地址,有自动加1功能
ACC:累加器,用于存放操作数/操作结果
MQ:乘商寄存器
x:用于存放操作数
ALU:实现逻辑运算
完成一条完整的指令需要三个步骤: 1、取指令(PC)
2、分析指令(IR)
3、执行指令(CU)
3、本程序设计一种基础架构的RISC CPU
1、顶层架构
1 //当wr=1的时候data上数据写有效
2 //当rd=1的时候data上数据读有效
3 module my_risc_cpu(clk, rst_n, data, addr, wr, rd);
4
5 input clk, rst_n; //系统时钟、复位
6 inout [7:0] data; //指令/数据(数据总线)
7 output [12:0] addr; //地址(地址总线)
8 output wr; //写有效使能->控制总线
9 output rd; //读有效使能->控制总线
10
11
12 wire load_ir;
13 wire clk_inc;
14 wire load_pc;
15 wire load_acc;
16 wire addr_sel;
17 wire data_link;
18 wire alu_en;
19 wire [7:0] alu_out; //计算组件计算的结果
20 wire [7:0] acc; //将计算组件计算的结果存入acc
21 wire [15:0] ir;
22 wire [12:0] pc_addr;
23
24
25 //时序控制组件
26 tm_gc tm_gc_ins(
27 .clk(clk),
28 .rst_n(rst_n),
29 .ir(ir[15:13]),
30 .load_ir(load_ir),
31 .clk_inc(clk_inc),
32 .load_pc(load_pc),
33 .load_acc(load_acc),
34 .addr_sel(addr_sel),
35 .data_link(data_link),
36 .wr(wr),
37 .rd(rd),
38 .alu_en(alu_en)
39 );
40
41 //计算组件
42 alu alu_ins(
43 .clk(clk),
44 .rst_n(rst_n),
45 .data(data),
46 .acc(acc),
47 .alu_en(alu_en),
48 .ir(ir[15:13]),
49 .alu_out(alu_out)
50 );
51
52 //累加器
53 acc_m acc_m_ins(
54 .clk(clk),
55 .rst_n(rst_n),
56 .alu_out(alu_out),
57 .load_acc(load_acc),
58 .acc(acc)
59 );
60
61 //指令寄存
62 ir_register ir_register_ins(
63 .clk(clk),
64 .rst_n(rst_n),
65 .data(data),
66 .load_ir(load_ir),
67 .ir(ir)
68 );
69
70 //指令地址发生器
71 pc_addr_gen pc_addr_gen_ins(
72 .clk_inc(clk_inc),
73 .rst_n(rst_n),
74 .load_pc(load_pc),
75 .pc_addr(pc_addr),
76 .ir(ir[12:0])
77 );
78
79 //数据选择
80 data_sel_m data_sel_m_ins(
81 .data_sel(data_link),
82 .acc(acc),
83 .data(data)
84 );
85
86 //地址路由
87 addr_sel_m addr_sel_m_ins(
88 .ir(ir[12:0]),
89 .pc_addr(pc_addr),
90 .addr_sel(addr_sel),
91 .addr(addr)
92 );
93
94 endmodule
2、时序控制组件顶层
1 /*********************时序控制组件********************************/
2 module tm_gc(clk, rst_n, ir, load_ir, clk_inc, load_pc, load_acc, addr_sel, data_link, wr, rd, alu_en);
3
4 input clk, rst_n;
5
6 input [15:13] ir;
7
8 output load_ir;
9 output clk_inc;
10 output load_pc;
11 output load_acc;
12 output addr_sel;
13 output data_link;
14 output wr;
15 output rd;
16 output alu_en;
17
18 wire clk_ir;
19
20 tm_gen tm_gen_ins(
21 .clk(clk),
22 .rst_n(rst_n),
23 .alu_en(alu_en),
24 .clk_ir(clk_ir)
25 );
26
27 tm_ctl tm_ctl_ins(
28 .clk(clk),
29 .rst_n(rst_n),
30 .clk_ir(clk_ir),
31 .ir(ir),
32 .load_ir(load_ir),
33 .clk_inc(clk_inc),
34 .load_pc(load_pc),
35 .load_acc(load_acc),
36 .addr_sel(addr_sel),
37 .data_link(data_link),
38 .wr(wr),
39 .rd(rd)
40 );
41
42 endmodule
2.1生成指令周期(8个机器周期)
1 //本模块主要产生指令周期的时钟信号clk_ir
2 module tm_gen(clk, rst_n, alu_en, clk_ir);
3
4 input clk, rst_n;
5 output reg alu_en;
6 output reg clk_ir;
7
8 reg [2:0] cnt; //本设计的指令周期为8个机器周期(8个clk),8051单片机也为8个机器周期
9
10 always @ (posedge clk, negedge rst_n)
11 begin
12 if(~rst_n)
13 cnt <= 3'b0;
14 else
15 if(cnt == 3'd7)
16 cnt <= 3'd0;
17 else
18 cnt <= cnt + 1'b1;
19 end
20
21 always @ (posedge clk, negedge rst_n)
22 begin
23 if(~rst_n)
24 clk_ir <= 1'b0;
25 else
26 if(cnt == 3'd1)//2-5,四个时钟周期,尽量避免0作为开始
27 clk_ir <= 1'b1;
28 else
29 if(cnt == 3'd5)
30 clk_ir <= 1'b0;
31 end
32
33 //在clk_ir低电平的倒数第二个周期将alu_en拉高
34 always @ (posedge clk, negedge rst_n)
35 begin
36 if(~rst_n)
37 alu_en <= 1'b0;
38 else
39 if(cnt == 4'd7)
40 alu_en <= 1'b1;
41 else
42 alu_en <= 1'b0;
43 end
44
45 endmodule
2.2实现时序控制
1 module tm_ctl(clk, rst_n, clk_ir, ir, load_ir, clk_inc, load_pc, load_acc, addr_sel, data_link, wr, rd);
2
3 input clk, rst_n;
4
5 input clk_ir;
6 input [15:13] ir;
7
8 output reg load_ir;
9 output reg clk_inc;
10 output reg load_pc;
11 output reg load_acc;
12 output reg addr_sel;
13 output reg data_link;
14 output reg wr;
15 output reg rd;
16
17 parameter HLT = 3'b000,
18 RD = 3'b001,
19 WR = 3'b010,
20 SFT = 3'b011,
21 XOR = 3'b100,
22 SUB = 3'b101,
23 SKZ = 3'b110,
24 NJMP = 3'b111;
25
26
27 parameter FETCH1 = 3'd0,
28 FETCH2 = 3'd1,
29 FETCH3 = 3'd2,
30 EXZIT1 = 3'd3,
31 EXZIT2 = 3'd4,
32 EXZIT3 = 3'd5,
33 EXZIT4 = 3'd6,
34 EXZIT5 = 3'd7;
35
36 reg [2:0] state;
37
38 always @ (posedge clk, negedge rst_n)
39 begin
40 if(~rst_n)
41 state <= FETCH1;
42 else
43 case(state)
44 FETCH1 : if(clk_ir) state <= FETCH2;
45 FETCH2 : state <= FETCH3;
46 FETCH3 : state <= EXZIT1;
47 EXZIT1 : state <= EXZIT2;
48 EXZIT2 : state <= EXZIT3;
49 EXZIT3 : state <= EXZIT4;
50 EXZIT4 : state <= EXZIT5;
51 EXZIT5 : state <= FETCH1;
52 default : state <= FETCH1;
53 endcase
54 end
55
56 //此控制程序过程:在FETCH1状态拉高load_ir和rd两拍,在FETCH3拉低;读取一个完整的地址上的指令(因为数据是8位宽);
57 //在FETCH1状态拉高load_ir和rd两拍,在FETCH3拉低;选择指令的地址pc_addr,在FETCH1拉高clk_inc一个周期让addr更新一次+1;
58 //在EXZIT1拉高clk_inc信号使得pc_addr指向下一个新的地址(用于进行下一次运算取pc_addr上对应的操作码和数据的地址);
59 //在EXZIT2时拉高rd信号读取数据地址上的数据,且此时addr_sel为低读取的是IR的地址,赋值给了addr;读取出数据
60 //在EXZIT3时拉高alu_en信号,用IR的高三位(操作码)选择对应的操作运算
61 //在EXZIT4时拉高load_acc信号,将alu计算的结果存入累加器中
62 always @ (posedge clk, negedge rst_n)
63 begin
64 if(~rst_n)
65 begin
66 load_ir <= 1'b0;
67 clk_inc <= 1'b0;
68 load_pc <= 1'b0;
69 load_acc <= 1'b0;
70 addr_sel <= 1'b0;
71 data_link <= 1'b0;
72 wr <= 1'b0;
73 rd <= 1'b0;
74 end
75 else
76 case(state)
77 FETCH1 : begin
78 load_ir <= 1'b1;
79 clk_inc <= 1'b0;
80 load_pc <= 1'b0;
81 load_acc <= 1'b0;
82 addr_sel <= 1'b1;
83 data_link <= 1'b0;
84 wr <= 1'b0;
85 rd <= 1'b1;
86 end
87 FETCH2 : begin
88 load_ir <= 1'b1;
89 clk_inc <= 1'b1; //一次
90 load_pc <= 1'b0;
91 load_acc <= 1'b0;
92 addr_sel <= 1'b1;
93 data_link <= 1'b0;
94 wr <= 1'b0;
95 rd <= 1'b1;
96 end
97 FETCH3 : begin
98 load_ir <= 1'b0;
99 clk_inc <= 1'b0;
100 load_pc <= 1'b0;
101 load_acc <= 1'b0;
102 addr_sel <= 1'b0;
103 data_link <= 1'b0;
104 wr <= 1'b0;
105 rd <= 1'b0;
106 end
107 EXZIT1 : begin
108 load_ir <= 1'b0;
109 clk_inc <= 1'b1; //指向下一条指令首地址或(当执行跳过指令时:表示跳过下一条指令的高8位地址)
110 load_pc <= 1'b0;
111 load_acc <= 1'b0;
112 addr_sel <= 1'b0;
113 data_link <= 1'b0;
114 wr <= 1'b0;
115 rd <= 1'b0;
116 end
117 EXZIT2 : begin //在这个状态就要用ir操作码,前面状态都是相同的取操作码和进行操作数据的地址(可以读/写)
118 if((ir == RD ) || (ir == XOR) || (ir == SFT) || (ir == SUB))
119 begin
120 load_ir <= 1'b0;
121 clk_inc <= 1'b0;
122 load_pc <= 1'b0;
123 load_acc <= 1'b0;
124 addr_sel <= 1'b0;
125 data_link <= 1'b0;
126 wr <= 1'b0;
127 rd <= 1'b1;
128 end
129 else
130 if(ir == NJMP)
131 begin
132 load_ir <= 1'b0;
133 clk_inc <= 1'b0;
134 load_pc <= 1'b1; /******可以不需要这个可以给0;但要保证数据的准确性所以在这给了1*****/
135 load_acc <= 1'b0;
136 addr_sel <= 1'b0;
137 data_link <= 1'b0;
138 wr <= 1'b0;
139 rd <= 1'b0;
140 end
141 else
142 if(ir == WR) //拉高data_link
143 begin
144 load_ir <= 1'b0;
145 clk_inc <= 1'b0;
146 load_pc <= 1'b0;
147 load_acc <= 1'b0;
148 addr_sel <= 1'b0;
149 data_link <= 1'b1;
150 wr <= 1'b0;
151 rd <= 1'b0;
152 end
153 else
154 begin
155 load_ir <= 1'b0;
156 clk_inc <= 1'b0;
157 load_pc <= 1'b0;
158 load_acc <= 1'b0;
159 addr_sel <= 1'b0;
160 data_link <= 1'b0;
161 wr <= 1'b0;
162 rd <= 1'b0;
163 end
164 end
165 EXZIT3 : begin
166 if(ir == SKZ)//拉高clk_inc,表示跳过下一条指令的低8位地址
167 begin
168 load_ir <= 1'b0;
169 clk_inc <= 1'b1;
170 load_pc <= 1'b0;
171 load_acc <= 1'b0;
172 addr_sel <= 1'b0;
173 data_link <= 1'b0;
174 wr <= 1'b0;
175 rd <= 1'b0;
176 end
177 else
178 if(ir == NJMP)
179 begin
180 load_ir <= 1'b0;
181 clk_inc <= 1'b1; /****当clk_inc为高*******/
182 load_pc <= 1'b1; /******load_pc也为高时,才跳转到指令操作地址所指向的存储空间中执行新的指令*****/
183 load_acc <= 1'b0;
184 addr_sel <= 1'b0;
185 data_link <= 1'b0;
186 wr <= 1'b0;
187 rd <= 1'b0;
188 end
189 else
190 if(ir == WR) //拉高data_link和wr
191 begin
192 load_ir <= 1'b0;
193 clk_inc <= 1'b0;
194 load_pc <= 1'b0;
195 load_acc <= 1'b0;
196 addr_sel <= 1'b0;
197 data_link <= 1'b1;
198 wr <= 1'b1;
199 rd <= 1'b0;
200 end
201 else
202 begin
203 load_ir <= 1'b0;
204 clk_inc <= 1'b0;
205 load_pc <= 1'b0;
206 load_acc <= 1'b0;
207 addr_sel <= 1'b0;
208 data_link <= 1'b0;
209 wr <= 1'b0;
210 rd <= 1'b0;
211 end
212 end
213 EXZIT4 : begin //在这个状态也要用ir操作码
214 if((ir == RD ) || (ir == XOR) || (ir == SFT) || (ir == SUB))begin
215 load_ir <= 1'b0;
216 clk_inc <= 1'b0;
217 load_pc <= 1'b0;
218 load_acc <= 1'b1;
219 addr_sel <= 1'b0;
220 data_link <= 1'b0;
221 wr <= 1'b0;
222 rd <= 1'b0;
223 end
224 else
225 begin
226 load_ir <= 1'b0;
227 clk_inc <= 1'b0;
228 load_pc <= 1'b0;
229 load_acc <= 1'b0;
230 addr_sel <= 1'b0;
231 data_link <= 1'b0;
232 wr <= 1'b0;
233 rd <= 1'b0;
234 end
235 end
236 EXZIT5 : begin
237 if(ir == SKZ) //拉高clk_inc,指向下一条指令首地址
238 begin
239 load_ir <= 1'b0;
240 clk_inc <= 1'b1;
241 load_pc <= 1'b0;
242 load_acc <= 1'b0;
243 addr_sel <= 1'b0;
244 data_link <= 1'b0;
245 wr <= 1'b0;
246 rd <= 1'b0;
247 end
248 else
249 begin
250 load_ir <= 1'b0;
251 clk_inc <= 1'b0;
252 load_pc <= 1'b0;
253 load_acc <= 1'b0;
254 addr_sel <= 1'b0;
255 data_link <= 1'b0;
256 wr <= 1'b0;
257 rd <= 1'b0;
258 end
259 end
260 default : begin
261 load_ir <= 1'b0;
262 clk_inc <= 1'b0;
263 load_pc <= 1'b0;
264 load_acc <= 1'b0;
265 addr_sel <= 1'b0;
266 data_link <= 1'b0;
267 wr <= 1'b0;
268 rd <= 1'b0;
269 end
270 endcase
271 end
272
273 endmodule
3、计算组件
1 ///********************计算组件*************************/
2 ////主要完成数学或者逻辑运算,是CPU的核心组件
3 module alu(clk, rst_n, data, acc, alu_en, ir, alu_out);
4
5 input clk, rst_n; //系统时钟、复位
6 //控制信号
7 input alu_en; //当alu_en=1计算
8 input [15:13] ir; //操作码ir[15:13]
9
10 input [7:0] data; //外部存储器读取的数通过数据总线表示
11 input [7:0] acc; //运算时候所有的指令需要累加器参与运算
12
13 //输出运算结果
14 output reg [7:0] alu_out; //计算输出
15
16 parameter HLT = 3'b000,
17 RD = 3'b001,
18 WR = 3'b010,
19 SFT = 3'b011,
20 XOR = 3'b100,
21 SUB = 3'b101,
22 SKZ = 3'b110,
23 NJMP = 3'b111;
24
25 reg [7:0] data_r;
26 always @ (posedge clk, negedge rst_n)
27 begin
28 if(~rst_n)
29 data_r <= 8'd0;
30 else
31 data_r <= data;
32 end
33
34 always @ (posedge clk, negedge rst_n)
35 begin
36 if(~rst_n)
37 alu_out <= 8'd0;
38 else
39 if(alu_en)
40 case(ir)
41 HLT : alu_out <= acc; //HLT(空指令):该指令不做任何操作
42 RD : alu_out <= data_r; //RD:将操作地址空间的数据读出,保存在累加器中
43 WR : alu_out <= acc; //WR:将累加器的数据写入操作地址空间中
44 SFT : alu_out <= {data_r[6:0], data_r[7]}; //SFT:将操作地址空间的数据读出(循环)左移1位,然后计算结果保存在累加器中
45 XOR : alu_out <= data_r ^ acc; //XOR:将操作地址空间的数据读出和累加器相异或,然后计算结果保存在累加器中
46 SUB : alu_out <= data_r - acc; //SUB:将操作地址空间的数据读出和累加器相减,然后计算结果保存在累加器中
47 SKZ : alu_out <= acc; //SKZ:跳过下一条指令执行下下一条指令
48 NJMP : alu_out <= acc; //NJMP:跳转到该指令所指向的地址空间处读取新的指令
49 default : alu_out <= alu_out;
50 endcase
51 end
52
53 endmodule
4、累加器
1 /*****************累加器***********************/
2 //主要用于保存ALU的计算结果
3 module acc_m(clk, rst_n, alu_out, load_acc, acc);
4
5 input clk, rst_n; //系统时钟、复位
6
7 //控制信号
8 input load_acc; //当load_acc=1的时候将alu的计算结果保存到累加器中
9
10 input [7:0] alu_out; //计算组件计算的结果
11 output reg [7:0] acc; //将计算组件计算的结果存入acc
12
13 always @ (posedge clk, negedge rst_n)
14 begin
15 if(~rst_n)
16 acc <= 8'd0;
17 else
18 if(load_acc)
19 acc <= alu_out;
20 end
21
22 endmodule
5、指令寄存
1 /*******************指令集寄存器*******************/
2 //寄存当前用于分析和执行的指令
3 module ir_register(clk, rst_n, data, load_ir, ir);
4
5 input clk, rst_n; //系统时钟、复位(CPU核心工作时钟)
6 //控制信号
7 input load_ir; //当load_ir=1的时候从数据总线加载指令
8 //数据总线
9 input [7:0] data; //CPU数据总线默认8bit
10 //输出指令
11 output reg [15:0] ir; //指令:默认16bit
12
13 //Note:由于数据总线8位的,指令是16位,因此需要加载两次才能完成一次指令的加载,在这里先加载指令高8位,再加载指令低8位
14 always @ (posedge clk, negedge rst_n)
15 begin
16 if(~rst_n)
17 ir <= 16'd0;
18 else
19 if(load_ir)
20 ir <= {ir[7:0], data[7:0]};
21 end
22
23 endmodule
6、指令地址发生器
1 /******************指令地址发生器******************/
2 //主要用于产生指令地址(指令放在存储器中)
3 module pc_addr_gen(clk_inc, rst_n, load_pc, pc_addr, ir);
4
5 input rst_n; //系统复位
6 //控制信号
7 input clk_inc; //用于控制指令地址变化,该信号上升沿地址自加
8 input load_pc; //当该信号为高电平的时候跳转指令地址所指向的存储空间读取指令;当load_pc=1,加载新的指令地址
9 input [12:0] ir; //指令操作地址
10 //输出指令地址
11 output reg [12:0] pc_addr;
12
13 always @ (posedge clk_inc, negedge rst_n)
14 begin
15 if(~rst_n)
16 pc_addr <= 13'd0; //复位初始地址。文件地址初始为0,我们如果改初始地址,则将会跳过一些指令(不执行一些指令)
17 else
18 if(load_pc)
19 pc_addr <= ir[12:0];
20 else
21 pc_addr <= pc_addr + 1'b1; //若没有跳转,则指令地址+1
22 end
23
24 endmodule
7、数据选择
1 module data_sel_m(data_sel, acc, data);
2
3 input data_sel;
4 input [7:0] acc;
5 inout [7:0] data;
6
7 assign data = (data_sel) ? acc : 8'hzz; //进行读操作时,data为高阻状态,读取数据
8
9 endmodule
8、地址路由
1 module addr_sel_m(ir, pc_addr, addr_sel, addr);
2
3 input [12:0] ir;
4 input [12:0] pc_addr;
5 input addr_sel;
6 output reg [12:0] addr;
7
8 always @ (*)
9 begin
10 if(addr_sel)
11 addr = pc_addr;
12 else
13 addr = ir;
14 end
15
16 endmodule