1.实验目的
本实验旨在让学生通过设计一个简单的单周期 CPU 电路,深入理解 RISC-V 指令集的子集功能实现,掌握数字电路设计与实现的基本流程,包括指令解析、部件组合、电路设计以及功能仿真等环节,同时培养verilog HDL编程能力和对课程知识的理解。
2.实验内容及操作环境
-
指令实现
1.该CPU需要完成的指令有以下9个(附加beq),每条指令长度都为32bits。
add,sub,or,slt,addi,ori,slti,sw,lw,beq(附加)
2.该CPU的部件资源包括:1)X0-X31共32通用寄存器;2)特殊寄存器PC(program counter)和指令暂存寄存器IR(instruction Register)。以上寄存器都是32bits字长。
3.存储器包括256bytes的memory(地址为0~255,采用little endian方式存储数据或者指令)。其中地址0~127存放程序指令(最多32条指令),地址128~255存放数据。也可以使用两块128byte的独立存储器。
4.最后的波形仿真应当采用功能仿真,且所有存储器件中的数据都应当被显示。
指令功能:
-
算术逻辑运算指令(add、sub、or、slt、addi、ori、slti):根据指令规定对寄存器进行相应运算,并将结果存回目的寄存器。
-
存数指令(sw):将指定寄存器的值存入内存特定地址。
-
取数指令(lw):从内存特定地址读取数据存入指定寄存器。
-
条件跳转指令 (beq):改变程序的执行顺序,实现分支结构。
-
-
操作环境 使用vivado2019.2设计实验电路;使用Verilog HDL语言进行代码编写及仿真验证;操作系统为Windows 11。
3.实验设计
主要代码cpu.v:
`timescale 1ns / 1ps module CPU( input clk, input reset ); //信号线 reg [31:0] PC;//指令暂存器IR,指令地址PC wire [31:0] IR; wire [4:0] rs1, rs2, rd;//命令解析片段 wire ALUASrc,MemToReg,MemWr,RegWr,Zero; wire [1:0]ALUctr,ALUBSrc,ExtOp; //数据线 wire alu_src1, alu_src2; wire [31:0] alu_out;//ALU输入输出寄存器 wire [31:0] reg_data1, reg_data2;//寄存器堆读出 wire [31:0] mem_out;//内存读出,数据线都用wire,和模块内output类型无关 assign rs2 = IR[24:20]; assign rs1 = IR[19:15]; assign rd = IR[11:7]; assign funct3 =IR[14:12]; assign funct7=IR[31:25]; assign opc=IR[6:0]; //最后做接线 reg [31:0]MUX_out0; reg [31:0]ext_out; always@(*)begin case(ALUBSrc) 2'b00:MUX_out0=reg_data2; 2'b01:MUX_out0=4; 2'b10:MUX_out0=ext_out;//imm扩展器待实现 endcase end //MUX_reg or PC reg [31:0]MUX_out1;//接入ALU always@(*)begin if(ALUASrc) MUX_out1=PC; else MUX_out1=reg_data1; end //MUX_MemToReg or ALUtoReg reg [31:0]MUX_out2; always@(*)begin if(MemToReg) MUX_out2=mem_out; else MUX_out2=alu_out; end //MUX_PC_jump reg [31:0]adder_out; always@(*) begin if(Branch&&Zero) adder_out=PC+ext_out; else adder_out=PC+4; end // PC always @(posedge clk or posedge reset) begin if (reset) PC=0;//异步复位 else PC=adder_out;//每次读4bytes end ALU Alu( .opt(ALUctr),//选择算法 .ASrc(MUX_out1),//src1可能是立即数,也可能是寄存器 .BSrc(MUX_out0), .res(alu_out), .Zero(Zero) ); // Control Unit ControlUnit CU( .instr(IR), .ExtOp(ExtOp), .ALUASrc(ALUASrc), .ALUBSrc(ALUBSrc), .ALUctr(ALUctr), .MemToReg(MemToReg), .RegWr(RegWr), .MemWr(MemWr), .Branch(Branch) ); // Register File 寄存器堆,传入调用编号,获得数据 RegisterFile RF( .read1(rs1),//读取的寄存器地址 .read2(rs2), .write_data(MUX_out2),//待写入数据 .write_addr(rd),//写地址 .write_en(RegWr),//使能,写 .clk(clk), .read_data1(reg_data1),//读取的数据 .read_data2(reg_data2) ); // Memory P_Memory PM( .clk(clk), .addr(PC[6:0]),//根据PC获取指令 .rd(IR)//读到指令通过IR输出 ); //读写 Memory DM( .clk(clk), .addr(alu_out[6:0]),//获取数据 .we(MemWr), .wd(reg_data2),//写入数据 .rd(mem_out) ); //imm扩展器 always@(*)begin case(ExtOp) 2'b00:ext_out={{20{IR[31]}},IR[31:20]};//I型 2'b01:ext_out={{20{IR[31]}}, IR[31:25], IR[11:7]};//s型 2'b10:ext_out={{20{IR[31]}}, IR[7], IR[30:25], IR[11:8], 1'b0}; // B型偏移量 endcase end endmodule
这个代码是一个简单的CPU设计,主要实现了CPU的基本功能模块,包括指令获取、指令解码、ALU运算、数据存储、内存访问等。整体结构为:指令从内存中读取,经过控制单元控制各种操作,ALU计算结果与数据内存操作相结合,最后将结果写回寄存器或输出。
1. 模块说明:
-
PC(程序计数器):
-
用于存储当前正在执行的指令的地址。
-
每执行完一条指令,
PC
会加 4(即指向下一条指令)。 -
支持异步复位。
-
-
IR(指令寄存器):
-
存储从内存中读取的指令。
-
-
寄存器堆(
RegisterFile
):-
该模块用于存储CPU的寄存器数据,支持两个寄存器读操作(rs1 和 rs2)和一个寄存器写操作。
-
-
ALU(算术逻辑单元):
-
实现基础的算术和逻辑操作。
-
根据控制信号
ALUctr
来选择操作。 -
其中,
Zero
信号表示计算结果是否为零。
-
-
Memory(内存):
-
P_Memory
用于从内存中读取指令,Memory
用于数据访问(读写)。
-
-
控制单元(
ControlUnit
):-
根据指令
IR
生成相应的控制信号,包括 ALU 控制信号、寄存器写使能信号、内存读写使能信号等。
-
-
MUX(多路选择器):
-
多个 MUX 用于选择输入信号的路径。例如:决定 ALU 输入的数据来源,选择寄存器文件的输出或内存的输出等。
-
-
扩展器(Imm_Ext):
-
用于生成立即数的扩展。支持多种操作模式,如 I 型、S 型、B 型等。
-
2. 各模块及实现:
-
指令提取 (IF) 阶段:
-
每个时钟周期,
PC
递增(PC = PC + 4
),并从P_Memory
中读取指令存入IR
。 -
通过
IR
获取操作码opcode
、功能码funct3
、funct7
等信息。
-
-
指令解码 (ID) 阶段:
-
从
IR
中解析出寄存器地址(rs1
,rs2
,rd
),并通过寄存器堆RegisterFile
读取寄存器数据(reg_data1
和reg_data2
)。 -
控制信号由
ControlUnit
生成,包括 ALU 操作类型、是否使用立即数、是否进行内存操作、是否写回寄存器等。
-
-
执行 (EX) 阶段:
-
在
ALU
中执行运算,使用 ALU 控制信号(ALUctr
)和输入(MUX_out1
,MUX_out2
)计算。 -
ALU
根据输入数据选择是否计算寄存器值或立即数。 -
如果是条件跳转,ALU 会计算跳转地址,跳转时
PC
会设置为新的地址。
-
-
访存 (MEM) 阶段:
-
根据
MemWr
信号决定是否写入内存。 -
从内存
Memory
读取数据,写入mem_out
。
-
-
写回 (WB) 阶段:
-
根据
MemToReg
信号选择写回的数据来源,可以是ALU
的输出或内存的输出。
-
CPU设计
(一)电路图示例
(二)CPU 功能实现
-
指令提取(IF)阶段
-
每个时钟周期,PC 递增(PC = PC + 4),指向下一条指令地址。
-
从 P_Memory 中读取当前 PC 指向的指令,并将其存入 IR。
-
-
指令解码(ID)阶段
-
从 IR 中解析出操作码(opcode)、功能码(funct3、funct7)以及寄存器地址(rs1、rs2、rd)。
-
根据寄存器地址从 RegisterFile 中读取两个源操作数(reg_data1 和 reg_data2)。
-
控制单元根据指令生成控制信号,如确定 ALU 操作类型(ALUctr)、是否使用立即数(ALUBSrc)、是否进行内存操作(MemWr、MemToReg)以及是否写回寄存器(RegWr)等。
-
-
执行(EX)阶段
-
ALU 根据控制信号 ALUctr 对输入数据进行运算,输入数据来源由多路选择器(MUX)根据 ALUASrc 和 ALUBSrc 选择,可以是寄存器值或立即数。
-
对于条件跳转指令(beq),ALU 计算两个操作数是否相等(Zero 信号),如果相等且满足跳转条件(Branch 信号为真),则计算跳转地址(PC + ext_out),并更新 PC 的值。
-
-
访存(MEM)阶段
-
根据 MemWr 信号判断是否进行内存写操作,如果为真,则将数据(reg_data2)写入内存(Memory)中指定地址(alu_out [6:0])。
-
从内存中读取数据,存入 mem_out,准备后续写回阶段使用。
-
-
写回(WB)阶段
-
根据 MemToReg 信号选择写回的数据来源,如果为真,则将内存读取的数据(mem_out)写回寄存器堆;否则将 ALU 的运算结果(alu_out)写回寄存器堆。
-
(三)性能改进点
-
优化电路结构
-
减少不必要的逻辑层次和延迟路径,例如优化多路选择器(MUX)的设计,减少信号传输的延迟。
-
合理安排部件之间的连接,使数据传输更加高效,避免信号冲突和竞争。
-
-
采用流水线技术
-
将 CPU 的指令执行过程分为多个阶段,如取指(IF)、译码(ID)、执行(EX)、访存(MEM)、写回(WB),每个阶段由专门的硬件电路处理,不同指令的不同阶段可以并行执行,提高 CPU 的吞吐量。
-
在流水线设计中,需要解决数据相关、控制相关等问题,如采用数据前推、分支预测等技术,减少流水线的停顿,提高执行效率。
-
4.仿真结果
仿真代码test.v: 可以在仿真过程中观察到 PC
、IR
和寄存器 X1
到 X8
的值,并且能够正常控制时钟和复位信号。
`timescale 1ns / 1ps module test; reg CLK; reg reset; CPU demo( .clk(CLK), .reset(reset) ); initial begin CLK = 0;//初始化 reset = 1; #10 reset = 0;//复位 #100 $stop; end always #5 CLK = ~CLK;// 生成时钟信号 initial begin $monitor("At time %t, PC=%h,IR=%h,X1=%d,X2=%d,X3=%d,X4=%d,X5=%d,X6=%d,X7=%d,X8=%d", $time, demo.PC,demo.IR,demo.RF.registers[1],demo.RF.registers[2],demo.RF.registers[3],demo.RF.registers[4],demo.RF.registers[5],demo.RF.registers[6],demo.RF.registers[7],demo.RF.registers[8]); end endmodule
打印输出结果如下:
5.遇到问题及解决方法
-
理解和准确实现CPU不同指令功能具有一定难度。例如,对于不同类型指令中立即数的处理方式(如 I 型指令中的符号扩展)以及各指令对寄存器和内存的操作细节需要深入理解。
方法:仔细研读 RISC-V 指令集文档,结合实验提供的指令规范和示例,逐一对每条指令进行分析,明确其操作码、操作数的含义及功能实现方式。对于立即数处理,按照指令集规定编写代码实现符号扩展或零扩展等操作进行。
-
如何合理连接寄存器、ALU、存储器等部件,以及设计控制器来生成正确的控制信号以协调各部件工作较难处理。
方法:参考附录中提供的单周期 CPU 简介及相关电路原理图,理解各部件之间的数据流向和控制关系。根据指令功能需求,设计各部件之间的连接线路,并依据指令与控制信号的逻辑关系,不断优化和调试电路结构,确保各部件协同工作正确执行指令。
-
在功能仿真过程中,可能会遇到仿真结果与预期不符的情况,如指令执行结果错误、数据存储或读取异常等,且难以快速定位问题所在。
方法:仔细检查输出结果,分析程序执行流程,找出错误原因并进行修正。
6.实验感想
通过本次单周期 CPU 电路设计实验,我对硬件的底层实现有了更深入的理解。从最初对 RISC-V 指令集的迷茫,到逐步理解并实现各条指令功能,再到设计出完整的 CPU 电路,这个过程充满挑战但也收获颇丰。在实验过程中,我深刻体会到了理论与实践相结合的重要性,但真正动手设计电路时,才发现实际情况远比想象中复杂。这不仅需要扎实的理论基础,更需要具备解决实际问题的能力,如在遇到指令实现错误或电路设计问题时,通过不断调试和优化代码来解决问题,极大地锻炼了我的耐心和细心。此外,实验还让我认识到团队协作和资源利用的重要性。在遇到困难时,与同学讨论交流、参考相关资料以及借鉴附录中的示例,都为我提供了新的思路和方法。同时,实验报告的撰写过程也促使我对整个实验进行全面总结和深入思考,从对实验过程的描述到对 CPU 性能的分析,再到对实验中遇到问题的反思,这一系列的工作让我对计算机硬件设计有了更系统的认识。
总的来说,本次实验不仅提升了我的专业技能,还培养了我的创新思维。同时,我也意识到自己在硬件设计方面还有很多不足,如电路优化能力、对复杂硬件系统的理解能力等,未来还需要不断学习和实践来提高自己的能力水平。