简介本资源是西南交通大学信息科学与技术学院《计算机组成原理》课程设计的完整实验文档面向高校计算机类专业大二学生及数字电路初学者聚焦CPU微架构实现与指令周期实践。文档以Verilog HDL为核心完整呈现一个具备取指、译码、执行三阶段的有限状态机CPU模块涵盖指令寄存器IR、程序计数器PC、内存地址/数据寄存器MAR/MDR、4个通用寄存器R0–R3及溢出检测逻辑并支持MOV、ADD、SUB、AND、OR、移位、跳转等16条指令操作。资源为单个64KB的.docx文件内容含详细模块说明、状态转移逻辑、关键代码段含时序控制、复位处理与多路选择实现结构清晰、注释充分可直接用于课程报告撰写与实验复现。目前已有1571人学习下载是理解硬布线控制器、寄存器堆协同与指令流水思想的优质教学参考材料。1. 这不是“写个CPU”作业而是一份能跑通、能调试、能答辩的计组原理课程设计交付物你手上的这份《计算机组成原理实验课程设计》文档表面看是西南交通大学某届大二下学期的课设材料但实际它解决的是一个非常具体、非常痛的问题学生第一次用 Verilog 实现 CPU 控制器时90% 的人卡在状态机跳转逻辑混乱、寄存器更新时序错乱、溢出判断不闭环这三座大山里最后交上去的代码连仿真波形都对不上指令周期图。这不是理论推演题是必须在 FPGA 开发板比如 DE2-115 或类似平台上烧录、用拨码开关输指令、用 LED 观察寄存器值、用逻辑分析仪抓信号的真实工程闭环。它面向的不是“懂了就行”的课堂理解者而是需要在两周内完成设计仿真板级验证答辩 PPT 的真实执行者。整套资源包含可综合的 Verilog 源码含完整注释、顶层原理图框架支持 Quartus II 或 Vivado 导入、配套 MIF 内存初始化文件以及隐含在代码结构里的教学级设计契约——比如所有寄存器更新严格绑定posedge clock所有异步清零只作用于reset信号所有状态转移明确标注入口/出口条件。它不教你“什么是状态机”它直接给你一个能进实验室、能接硬件、能被导师指着波形图问“为什么 state2 时 MAR 没更新”的可交付方案。2. 总体架构设计四层寄存器体系与五阶段指令流水的精简映射这个 CPU 模块不是教科书里抽象的框图而是一个为 FPGA 资源和教学验证深度优化的物理实现。它没有追求 x86 那样的复杂流水线或多级缓存而是把经典冯·诺依曼结构压缩成可触摸的五个核心层级并强制每一层的职责边界清晰到能在答辩时被一句话说清。这种设计不是妥协而是精准匹配课程实验目标让学生亲手看到“取指→译码→执行→访存→写回”如何在时钟边沿上一帧帧展开。2.1 四层寄存器体系从数据通路到控制信号的垂直分层整个 CPU 的数据通路由四类寄存器构成它们不是平铺罗列而是按功能形成强依赖链顶层控制寄存器层PC程序计数器、IR指令寄存器、state状态寄存器。它们是整个控制器的“大脑”决定下一步做什么。PC永远指向当前待取指令地址IR在state0时锁存从内存读入的完整 16 位指令state是有限状态机的核心其值直接驱动case(state)分支。中间寻址寄存器层MAR内存地址寄存器、MDR内存数据寄存器。它们是 CPU 与外部存储器交互的“咽喉”。MAR的值12 位直接连接到地址总线决定访问哪块内存MDR则在Write_Read1b1写时将数据送入内存在Write_Read1b0读时暂存从内存读出的数据。底层通用寄存器层R0~R34 个 8 位通用寄存器。这是运算的“工作台”所有 ALU 操作ADD/SUB/AND/OR/XOR和数据移动MOVE/SWAP都发生在这里。注意代码中R_0~R_3是输出端口其值由内部R0~R3在posedge clock时刻同步更新这是避免组合逻辑毛刺的关键。临时运算寄存器层A操作数寄存器、Temp9 位临时寄存器、OP4 位操作码寄存器。A在state0时根据IR[9:8]选择源寄存器R0/R1/R2/R3的值为后续运算提供操作数Temp是 9 位宽专门用于带符号加减法的溢出检测高位Temp[8]与符号位Temp[7]异或OP从IR[15:12]提取是state1时case(OP)的唯一判据。提示这种分层不是为了炫技而是为了调试。当你在 ModelSim 里观察波形时如果R0值异常你可以立刻锁定问题在state1的case(OP)分支里而不是在一堆混杂的always块里大海捞针。2.2 五阶段指令周期从状态机编码到时序约束的硬性落地该设计将经典的五阶段指令周期IF/ID/EX/MEM/WB映射为一个 4 状态0/1/2/3/4的有限状态机每个状态严格对应一个硬件动作且状态跳转有明确的时钟节拍和条件。这不是软件里的if-else而是硬件电路里电平变化的精确序列State 0IF取指令PC自增 →MAR加载PC值 → 启动内存读Write_Read0→IR锁存M_data_in高 8 位8b00000000低 8 位因输入仅 8 位→OP提取IR[15:12]→A根据IR[9:8]选择源寄存器 → 跳转至state1。关键点此状态结束时IR已完整OP已就绪A已加载为译码做足准备。State 1ID EX译码与执行根据OP值执行具体指令。OP0NOP直接回state0OP1立即数将IR[11:8]直接写入R0OP2MOVE将A值写入目标寄存器由IR[11:10]指定OP3/4ADD/SUB调用Temp进行 9 位运算并检测溢出overflow信号在此生成OP5~9逻辑与移位直接操作寄存器。所有运算结果均在本状态内完成并写回目标寄存器无跨状态延迟。State 2MEM访存准备专为跳转JMP/JZ、读READ、写WRITE指令服务。此时PC已自增IR中的低 8 位IR[7:0]被当作内存地址或数据的一部分。例如OP11JMPIR[7:0]被读入MAR被设置为IR全值OP12JZ先判断R00再决定MAR是加载IR还是保持PC。此状态不执行 ALU 运算只做地址计算与总线准备。State 3MEM WB访存执行与写回Write_Read信号在此状态被最终确定。若为READOP13MAR被设为PC准备读取下一个字节若为WRITEOP14Write_Read置为1b1MAR设为PC准备写入。state4是READ指令的专属写回状态它将M_data_in的值直接写入R0完成“从内存读数据到寄存器”的闭环。2.3 指令集架构ISA设计16 位指令字与 4 位操作码的务实取舍该 CPU 定义了一个精简但功能完备的 16 位指令集其格式高度统一极大降低了译码复杂度。每条指令固定 16 位高位 4 位IR[15:12]为操作码OP低位 12 位为操作数字段具体分配如下OP 值指令名功能说明关键字段使用0NOP空操作PC1无1LDI立即数加载R0 ← IR[11:8]IR[11:8]为立即数2MOVE寄存器传送Rd ← AA由IR[9:8]选源Rd由IR[11:10]选目标IR[9:8]: 源,IR[11:10]: 目标3ADD加法Rd ← Rd AIR[11:10]: 目标寄存器4SUB减法Rd ← Rd - AIR[11:10]: 目标寄存器5AND逻辑与Rd ← Rd AIR[11:10]: 目标寄存器6OR逻辑或Rd ← Rd | AIR[11:10]: 目标寄存器7XOR逻辑异或Rd ← Rd ^ AIR[11:10]: 目标寄存器8SHR逻辑右移Rd ← {1b0, Rd[7:1]}IR[11:10]: 目标寄存器9SHL逻辑左移Rd ← {Rd[6:0], 1b0}IR[11:10]: 目标寄存器10SWAP寄存器交换Rd ← R0R0 ← AIR[11:8]: 目标寄存器索引,IR[11:10]:A写入目标11JMP无条件跳转PC ← IR[11:0]IR[11:0]: 目标地址12JZ零跳转if R00 then PC ← IR[11:0]IR[11:0]: 目标地址13READ从内存读R0 ← M[PC]PC为地址M_data_in为数据14WRITE向内存写M[PC] ← R0PC为地址R0为数据15STOP停机指令无注意READ和WRITE指令的设计是教学亮点。它们不占用额外的操作数字段而是复用PC作为地址R0作为数据载体。这使得内存读写操作变得极其直观学生在板子上用拨码开关输入READ指令后只需观察R0的 LED 是否显示内存单元的值就能立刻验证功能。3. 关键模块实现从 Verilog 代码到可综合 RTL 的逐行解析这份 Verilog 代码不是“能编译就行”的玩具而是经过 Quartus II 综合器检验、能在 Cyclone IV FPGA 上稳定运行的 RTL 级描述。它的每一行都服务于一个明确的硬件意图下面我将带你逐段拆解最核心、最容易出错的几块逻辑告诉你为什么这样写以及不这样写的后果。3.1 异步复位与同步寄存器更新时序收敛的生命线// 异步复位逻辑reset 有效时所有关键寄存器立即清零 always (reset or state) begin if (!reset) begin // reset 为低电平有效 M_address 12h0; M_data_out 8h0; end else begin M_address MAR; // 正常工作时M_address 输出 MAR 值 M_data_out MDR; // M_data_out 输出 MDR 值 end end // 同步寄存器更新所有通用寄存器在时钟上升沿更新 always (posedge clock) begin if (!reset) begin // 复位时清零 R0 8h0; R1 8h0; R2 8h0; R3 8h0; PC 12h0; IR 16h0; MAR 12h0; MDR 8h0; A 8h0; OP 4h0; state 4h0; overflow 1b0; Write_Read 1b0; end else begin R_0 R0; // 输出端口同步更新 R_1 R1; R_2 R2; R_3 R3; end end这段代码定义了整个 CPU 的“心跳”规则。第一段always (reset or state)是一个电平敏感的组合逻辑块它确保M_address和M_data_out这两个输出端口的值永远等于当前MAR和MDR的值无论state如何变化。这是为了让外部内存模块能实时看到 CPU 的地址和数据请求。第二段always (posedge clock)是边沿敏感的时序逻辑块它规定了所有寄存器的更新时机必须在clock的上升沿且reset无效时才进行。这里用了非阻塞赋值这是 Verilog 综合的铁律——它告诉综合器“这些赋值是并发发生的不要按代码顺序排队”。如果你在这里误用了阻塞赋值综合器会生成错误的组合逻辑导致R0的新值在同一个时钟周期内就被R1的计算所引用造成不可预测的时序错误。reset的处理也至关重要它是异步的always (reset or ...)意味着只要reset信号一拉低所有寄存器立刻归零不等下一个时钟。这是硬件系统启动可靠性的基石。3.2 指令周期主状态机case 语句里的时序艺术always (posedge clock) begin if (!reset) begin // 复位 PC 12h0; IR 16h0; MAR 12h0; MDR 8h0; R0 8h0; R1 8h0; R2 8h0; R3 8h0; state 4h0; OP 4h0; end else begin case(state) // State 0: 取指令 (IF) 4h0: begin Write_Read 1b0; // 发出读内存请求 PC PC 1; // PC 指向下一指令 MAR PC; // MAR 加载 PC 地址 // IR 的高 8 位来自 M_data_in低 8 位清零 IR {M_data_in, 8h00}; OP IR[15:12]; // 提取操作码 // 根据 IR[9:8] 选择源寄存器加载到 A case(IR[9:8]) 2b00: A R0; 2b01: A R1; 2b10: A R2; 2b11: A R3; endcase state 4h1; // 进入译码/执行状态 end // State 1: 译码与执行 (ID/EX) 4h1: begin Write_Read 1b0; // 继续保持读请求为后续访存准备 case(OP) 4h0: begin // NOP state 4h0; // 直接回到取指 end 4h1: begin // LDI: R0 - IR[11:8] R0 {4h0, IR[11:8]}; state 4h0; end 4h2: begin // MOVE: Rd - A case(IR[11:10]) 2b00: R0 A; 2b01: R1 A; 2b10: R2 A; 2b11: R3 A; endcase state 4h0; end 4h3: begin // ADD: Rd - Rd A case(IR[11:10]) 2b00: begin Temp {R0[7], R0} {A[7], A}; // 符号扩展为9位 R0 Temp[7:0]; end 2b01: begin Temp {R1[7], R1} {A[7], A}; R1 Temp[7:0]; end // ... R2, R3 同理 endcase // 溢出检测最高位与符号位异或 overflow (Temp[8] ^ Temp[7]); state 4h0; end // ... 其他 OP 分支SUB, AND, OR...省略结构相同 4hF: begin // STOP state 4h1; // 停在 state1等待复位 end endcase end // State 2: 访存准备 (MEM prep) 4h2: begin Write_Read 1b0; // 准备阶段仍为读 MDR R0; // 将 R0 数据放入 MDR为写做准备 PC PC 1; // PC 继续推进 case(OP) 4hB: begin // JMP: MAR - IR[11:0] IR {IR[15:8], M_data_in}; // 读入低8位补全IR MAR IR[11:0]; end 4hC: begin // JZ: if R00 then MAR - IR[11:0] IR {IR[15:8], M_data_in}; if (R0 8h0) begin MAR IR[11:0]; end else begin MAR PC; // 否则继续顺序执行 end end 4hD: begin // READ: MAR - PC (准备读取PC地址处的数据) MAR PC; end 4hE: begin // WRITE: MAR - PC (准备向PC地址处写入) MAR PC; end endcase state 4h3; // 进入访存执行状态 end // State 3: 访存执行 (MEM exec) 4h3: begin case(OP) 4hB: begin // JMP: PC - MAR PC MAR; state 4h0; // 跳转后从新地址取指 end 4hC: begin // JZ: PC - MAR (if R00) if (R0 8h0) begin PC MAR; end state 4h0; end 4hD: begin // READ: Write_Read0, MAR已设好等待下个周期读入 // 本周期只发地址数据在 state4 读入 state 4h4; end 4hE: begin // WRITE: Write_Read1, MAR已设好本周期写入 Write_Read 1b1; state 4h0; // 写完即返回 end endcase end // State 4: READ 指令专用写回 (WB for READ) 4h4: begin Write_Read 1b0; // 确保是读 if (OP 4hD) begin // 确认是 READ 指令 R0 M_data_in; // 将读到的数据写入 R0 end state 4h0; // 完成回到取指 end endcase end end这是整个 CPU 的“中枢神经”。它的精妙之处在于每一个case分支都严格遵循“先驱动总线再更新寄存器最后跳转状态”的三步法则。以State 0为例第一行Write_Read 1b0就是向内存发出“我要读”的信号第二行PC PC 1更新了程序计数器为下一条指令做准备第三行MAR PC将新的PC值送到地址总线上第四行IR {M_data_in, 8h00}才是真正锁存指令。这个顺序不能颠倒否则会出现“地址还没发出去就读到了错误数据”的经典时序错误。另一个关键点是State 2和State 3的分工State 2只负责计算地址MAR和准备数据MDRState 3才真正发出读/写命令Write_Read并更新PC。这种将“准备”和“执行”分离的设计完美模拟了真实 CPU 的流水线思想也让状态机的逻辑变得无比清晰。READ指令之所以需要State 4是因为内存读取有延时M_data_in的值在State 3结束时才稳定所以必须在下一个时钟周期State 4才能安全地将其写入R0。3.3 溢出检测与标志位生成带符号运算的硬件真相// 在 ADD/SUB 指令的执行分支中State 1 Temp {Rd[7], Rd} {A[7], A}; // 对 Rd 和 A 进行符号扩展得到9位 Rd Temp[7:0]; // 截取低8位作为结果 overflow (Temp[8] ^ Temp[7]); // 溢出标志最高位与符号位异或这是整个设计中最具“计组原理”灵魂的一段代码。它揭示了硬件层面溢出的本质不是简单的“结果大于255”而是符号位的翻转。Temp被声明为 9 位reg [8:0] Temp就是为了容纳 8 位带符号数相加可能产生的第 9 位进位。{Rd[7], Rd}是将 8 位寄存器Rd的符号位Rd[7]复制到第 9 位实现符号扩展确保加法在 9 位空间内正确进行。Temp[8]是最高位进位位Temp[7]是结果的符号位。当两者不同时^为异或意味着符号位被进位“翻转”了这就是溢出Overflow的硬件定义。这个overflow信号被直接连接到顶层输出端口你可以在开发板的 LED 上直观地看到它亮起这比任何教科书上的公式都更有说服力。SUB指令同理只是将加法换成了减法Temp {Rd[7], Rd} - {A[7], A}。4. 常见问题排查那些让答辩前夜崩溃的“玄学”Bug在带过十几届学生的计组实验后我总结出这套 CPU 设计在仿真和板级验证中最容易踩的坑。它们往往看起来微不足道却能让波形图一片混乱让你对着 ModelSim 抓耳挠腮一整晚。以下是最典型的 4 个“血泪经验”按现象、原因、解决三步给出。4.1 现象仿真波形中state信号在reset释放后不是从0开始而是跳到一个随机值如4hA原因state寄存器的复位逻辑不完整。在always (posedge clock)块中reset条件只覆盖了if (!reset)分支但else分支里没有对state进行默认赋值。Verilog 综合器会将未赋值的state推断为“锁存器latch”而锁存器在上电时的状态是未知的。reset释放的瞬间state就会保持这个未知态。解决必须在else分支的case语句之外为state设置一个默认值。修改代码如下always (posedge clock) begin if (!reset) begin // ... 所有寄存器清零 state 4h0; end else begin state 4h0; // 默认值防止锁存器推断 case(state) // ... 原来的 case 分支 endcase end end这个state 4h0必须放在case之前确保无论case的哪个分支都没有执行到state都有一个确定的值。4.2 现象R0寄存器的值在LDI指令后始终为0或者显示为X未知态原因LDI指令的实现中IR[11:8]字段被错误地解释。在State 0IR被赋值为{M_data_in, 8h00}这意味着IR的高 8 位是M_data_in低 8 位是0。因此IR[11:8]实际上是M_data_in[3:0]即指令字的高 4 位。如果测试程序的LDI指令写成16h10FF意图为加载0xFF那么M_data_in是8h10IR[11:8]就是4h1最终R0被加载为4h0001而非预期的8hFF。解决重新审视指令格式和IR的构建方式。要么修改State 0的IR赋值使其能正确捕获完整的 16 位指令这需要内存总线宽度为 16 位通常不现实要么接受这个设计事实并在编写 MIF 测试文件时将立即数指令的立即数字段放在指令字的高 4 位。例如要加载8hAB指令应为16h1AB0OP1,IR[11:8]4hAB这样M_data_in为8h1AIR[11:8]即为4hAB。这是教学设计的常见取舍关键是理解它而不是试图“修复”它。4.3 现象JMP或JZ指令执行后PC值没有跳转或者跳转到了错误的地址原因State 2和State 3中对IR的更新和MAR的赋值存在时序竞争。在State 2IR {IR[15:8], M_data_in}这一行IR[15:8]是上一个周期的值M_data_in是当前周期从内存读入的新数据。但如果M_data_in的数据在clock上升沿到来时还没有稳定即存在建立时间/保持时间违例IR就会被锁存一个错误的值导致MAR被加载了错误的地址。解决引入一个中间寄存器IR_low并在State 2的开始就将其更新然后在case(OP)中使用它。修改如下reg [7:0] IR_low; // 新增专门存储 IR 的低 8 位 // 在 State 2 的开头 4h2: begin IR_low M_data_in; // 稳定地锁存低8位 // ... 其余不变 case(OP) 4hB: begin // JMP MAR {IR[15:8], IR_low}; // 使用稳定的 IR_low end // ... 其他 endcase end这样IR_low的更新与IR的高位更新完全解耦消除了竞争。4.4 现象READ指令执行后R0的值是上一次READ的结果而不是本次读取的内存值原因READ指令的State 4写回逻辑缺少对OP的严格校验。在State 4代码是if (OP 4hD) R0 M_data_in;。但如果由于某种原因如state机跳错state误入4h4而此时OP并非4hDR0就会被错误地更新为当前的M_data_in可能是垃圾值。解决将READ的写回逻辑与state和OP双重绑定并确保state4只能由READ指令触发。在State 2的READ分支末尾显式地设置一个标志位read_flag并在State 4中检查它reg read_flag; // 新增标志位 // 在 State 2 的 READ 分支 4hD: begin MAR PC; read_flag 1b1; // 设置标志 end // 在 State 4 4h4: begin if (read_flag) begin // 双重保险 R0 M_data_in; end read_flag 1b0; // 清除标志 state 4h0; end这种“双重校验”是硬件设计的黄金习惯它让系统在面对意外状态时依然健壮。5. 课程设计验收与答辩从波形图到 PPT 的实战 checklist一份能顺利通过验收的计组课程设计绝不仅仅是代码能编译、波形能跑通。它是一场面向导师的“技术汇报”你需要用一套严谨、可追溯、可演示的证据链证明你不仅“做了”而且“做对了”、“理解了”。下面是我给学生梳理的、从实验室到答辩教室的全流程 checklist它基于无数次现场答辩的观察——哪些地方导师一定会问哪些截图是 PPT 里必须有的。5.1 仿真验证ModelSim 波形图的“黄金三帧”导师不会看你长达 1000 个周期的波形录像他只会关注三个关键时间点的截图。你的报告里必须包含这三张图并配上精准的文字说明。时间点截图内容为什么重要导师想看到什么T0复位释放瞬间reset信号从0变1的时刻PC,IR,state,R0~R3的值。这是整个系统的起点决定了后续所有行为的基准。所有寄存器是否在reset释放后的第一个clock上升沿全部变为0state是否为0PC是否为0这是系统可靠性的第一道关卡。T1第一条指令执行中state0→state1→state0的完整周期。重点截取state,PC,MAR,IR,OP,A,R0的波形。这是“取指-译码-执行”最基础循环本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?