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