CPU理解笔记
笔者是集成电路专业的本科生,最近不幸对计组和verilog产生兴趣(然后就被现实暴打啦。)
总之被脑力风暴折磨以后,总算写了点笔记(不要靠近IC,会不幸)
(今天要讨论的是RISC架构八位CPU)
图中,包含CPU,存储器,数据和地址总线。CPU主要由算数逻辑单元(ALU, Arithmetic Logic Unit),累加器(accumulator),通用寄存器(registers),程序计数器(PC,Program Counter),指令寄存器(IR,Instruction Register),地址选择器(address multiplexer)组成。存储器这里指主 存储器,分为随机存取存储器RAM(Radom Access Memory)和只读存储器ROM(Read Only Memory)。主存 和CPU之间通过总线访问,总线有地址总线(Address Bus)和数据总线(Data Address)两种。寄存器组也可能被叫做寄存器堆
需要注意的是,我所讨论的是单周期CPU。
至于大家经常听到的“x级流水”,可以参考这个https://blog.csdn.net/weixin_50026222/article/details/125446367
(下面是具体实现)
module PC( pc_addr,//取的地址 clk,//时钟信号 rst, //一边来说,如果想直接指定某个指令 //可以加上,input jump,jump_ddr,前者判定指定是否有效,jump_addr可以是希望指向的地址啦 en//高电平信号,监管pc_addr向下移动 ); input clk, rst, en; output reg[7:0] pc_addr;//输出 always @(posedge clock or negedge rst) begin //if(jump)pc_addr<=jump_addr if (!rst) pc_addr <= 8'd0;//复位 else begin if (en) pc_addr <= pc_addr + 1;//通过en控制计数器++,比如001+1,变成了010,这就指向了一个新的指令,可以认为类似c语言的指针 else pc_addr <= pc_addr;//当然了,加几都可以,只要是指向某个指令的地址 end end /*顺便一说,1)时序电路建模时,用非阻塞赋值。 2)锁存器电路建模时,用非阻塞赋值。 3)用always块建立组合逻辑模型时,用阻塞赋值。 4)在同一个always块中建立时序和组合逻辑电路时,用非阻塞赋值。 5)在同一个always块中不要既用非阻塞赋值又用阻塞赋值。 6)不要在一个以上的always块中为同一个变量赋值。 7)用$strobe系统任务来显示用非阻塞赋值的变量值 8)在赋值时不要使用#延迟 -------避免竞争 */ endmodule //累加器 module accum( in, out, ena, clk, rst );//很不幸,你算出来了a+b,但是还不想直接给c,所以把a+b先放在这吧 input clk, rst, ena;//决定中间值是否更新的信号,可以锁存 input[7:0] in; output reg[7:0] out; always @(posedge clk or negedge rst) begin if (!rst) out <= 8'd0; else begin if (ena) out <= in; else out <= out;//如果没有信号激励,结果不会更新,中间值一直保留 end end endmodule //地址选择器 module addr_mux( addr, sel, ir_ad,//指令寄存器的输出 pc_ad);//程序计数器的指令 input[7:0] ir_ad, pc_ad; input sel;//select output[7:0] addr; assign addr = (sel) ? ir_ad : pc_ad; endmodule //算术逻辑单元 module alu( alu_out, alu_in, accum, //这个accum当然是从accum来的,前面我们说到,accum可以保管中间值,也可以根据情况拿出来 op//此op非彼op,是指令编号啦 ); input[2:0] op; input[7:0] alu_in, accum; output reg[7:0] alu_out; parameter A = 3'b000, B = 3'b001, C = 3'b010, D = 3'b011, E = 3'b100, F = 3'b101, X = 3'b110, Y = 3'b111; always @(*)(1444584) begin casez(op)//这里自由性很高,完全可以自己定义, A: alu_out = accum; B: alu_out = ~accum; C: alu_out = alu_in^accum; D: alu_out = alu_in+accum; E: alu_out = accum-alu_in; F: alu_out = alu_in&accum; X: alu_out = alu_in|accum; Y: alu_out = alu_in; default: alu_out = 8'bzzzz_zzzz;//避免异常 endcase end //顺便补充一下casex和casez。假如我们用case找最高位为1的8位二进制数,那得有2^7-1种可能.但是用casez,找1zzz_zzzz就可以了 endmodule //寄存器们 module reg_32( in, data, write, read, addr, clk ); input write, read, clk; input[7:0] in; input[7:0] addr; output[7:0] data; reg[7:0] R[31:0]; //8*32 wire[4:0] r_addr; assign r_addr = addr[4:0]; assign data = (read) ? R[r_addr] : 8 always @(posedge clk) begin if (write) R[r_addr] end endmodule //IR指令寄存器从数据总线上获取数据,根据输入控制信号,根据指令类型将特定指令和地址输出到ALU,通用寄存器 和地址选择器 module ins_reg( data, fetch, clk, rst, ins, ad1, ad2 ); input clk, rst; input[1:0] fetch; input[7:0] data; output[2:0] ins; output[4:0] ad1; output[7:0] ad2; reg[7:0] ins_p1, ins_p2; reg[2:0] state; assign ins = ins_p1[7:5]; //指令标识 assign ad1 = ins_p1[4:0]; //操作数 assign ad2 = ins_p2; always @(posedge clk or negedge rst) begin if (!rst) begin ins_p1 <= 8'd0; ins_p2 <= 8'd0; end else begin if (fetch == 2'b01) begin ins_p1 <= data; ins_p2 <= ins_p2; end else if begin ins_p1 <= ins_p1; ins_p2 <= data; end else begin ins_p1 <= ins_p1; ins_p2 <= ins_p2; end end end endmodule 、
(控制器等器件和顶部文件什么的日后更新,要考试了orz,寒假一定完整仿真完送上资源)
#IC#