一条指令的完整旅程:取指、译码、执行与总线协作

📅 发布时间:2026/9/18 7:35:02
一条指令的完整旅程:取指、译码、执行与总线协作
简介针对计算机组成原理课程中“一条指令的执行过程”实验这份PDF实验报告完整记录了取指、译码、取数、执行等关键阶段并详细梳理CPU、RAM、总线AB/DB/CB、程序计数器PC、指令寄存器IR、通用寄存器GR及算术逻辑单元ALU的协作关系适合正在学习计算机组成原理的本科生或需要复习指令流水线基础概念的初学者使用。资源为单个PDF文件压缩包整体仅144KB内容以表格化实验记录表为主便于打印对照和随堂填写。已有204人学习下载说明该材料对同类实验具有较好的参考价值。通过报告中的操作数编码、地址传送路径和寄存器状态变化读者可以直观理解单条指令从内存取到CPU再经ALU运算并写回寄存器的完整数据通路有助于将抽象的总线与寄存器概念落实到具体实验场景中。1. 一条指令的完整旅程取指、译码、执行在总线上的真面目一条指令的执行过程是计算机组成原理实验里最值得对着总线一根根查的环节。表4-1的实验只做一次加法把3000和12相加。就这么简单的一条指令CPU却要依次走完取指、译码、取数、执行、回写五个阶段并且在地址总线AB、数据总线DB、控制总线CB上各完成一轮真正的事务。初学者看这一条指令能在一张图里同时看清程序计数器PC、指令寄存器IR、控制器CU、通用寄存器GR与算术逻辑单元ALU的分工对正在调教学CPU、写汇编或复习考研的读者这套拆解同样给出了指令字格式、访存时序和总线冲突这些绕不开的细节。下面以实验报告表4-1的记录为蓝本把这条加法指令从RAM取出来、再送进ALU算完的完整过程还原一遍所有编码、地址和寄存器状态都对照原始记录。2. 取指阶段PC 自增、地址总线与指令寄存器的配合2.1 指令在 RAM 中如何摆放实验里操作数并不是藏在CPU内部而是放在RAM中加数13000对应地址0034H加数212对应地址0036H。指令本身也存放在RAM里具体从哪个单元取由PC说了算。表4-1记录中取指前PC的值为00001011B即十六进制的000BHRAM在000BH这个单元保存着本次要执行的指令编码实验报告把它记录为00011101 00110110。把这一组对应关系先列出来后面每一步都要回到这张表上核对地址和数据是怎么流动的位置内容说明PC00001011B当前指向指令地址 000BHRAM[000BH]指令编码待取的加法指令后续进入IRRAM[0034H]3000第一个加数RAM[0036H]12第二个加数初学者最容易把PC里的值当成CPU正在执行的指令实际上PC在取指阶段保存的是“指令的地址”真正的指令字要等RAM响应之后才会出现在数据总线上。指令和操作数放在同一块RAM里这是冯诺依曼结构的典型特征——指令取出也走数据通路因此“取指”本质上也是一次存储器读操作只是读回来的内容被送进了指令寄存器而不是通用寄存器。2.2 取指阶段的四个动作取指阶段不是“CPU从内存读一个数”这么模糊它由四个严格顺序的动作组成PC把000BH送入地址寄存器MAR由MAR暂存这个地址CPU通过地址总线AB把MAR中的000BH发送给RAMRAM把地址收进自己的地址锁存器RAM按地址选出指令编码放入数据寄存器MDR再通过数据总线DB送回CPUCPU把DB上的指令编码锁存到指令寄存器IR取指完成。对应实验报告里的几个填空答案分别是AB、地址储存器、MDR、DB、IR。把这些答案连起来读就是一条完整的数据通路PC→MAR→AB→RAM→MDR→DB→IR。注意这里MAR和MDR不是可有可无的过渡变量。CPU内部时钟比RAM快得多如果不先把地址锁存到MARRAM还在译码选通的时候AB上的地址信号可能已经因为PC自增而改变取回来的指令就会错位。MDR的作用同理它把RAM返回的数据稳定地保持在总线上直到CPU在下一个时钟沿把它采样进IR。2.3 为什么取完后 PC 要自增实验记录里明确写着“PC被访问后其中的内容发生了什么变化自动加一”原因是这一条指令执行完下一步要执行下一条指令所以要提前把下一条指令的地址准备好。自增的单位是“一个存储单元”在本实验的存储系统中一条指令恰好占一个地址单元因此PC加1在常见的字节寻址机器上一条16位指令会让PC加232位指令则让PC加4。这个细节在后面看汇编代码时很有用顺序执行靠PC自增跳转执行靠把目标地址直接覆盖进PC两者是控制流的两条基本路径。# 按总线实际动作逐步展开的取指过程 pc 0x0B # 实验表4-1中取指前 PC 的地址 mar pc # 地址寄存器锁存 PC 输出 abus mar # 地址总线 AB 上出现 0x000BCPU 单向送 RAM ram_out load(0x0B) # RAM 按地址输出指令编码模拟读存储单元 mdr ram_out # RAM 的结果先进入数据寄存器 MDR dbus mdr # 数据总线 DB 把指令字送往 CPU ir dbus # 指令寄存器 IR 在时钟沿锁存指令 pc pc 1 # 取指结束PC 自增指向下一个编址单元这段代码刻意把mar、abus、mdr、dbus拆成不同变量为的是让每一步都能对应到实验表4-1里的提问。load(0x0B)代表RAM的一次读操作真实硬件里这一步不是立即返回的CPU需要等待存储器访问周期结束信号线上表现为等待状态或插入等待周期。如果把pc 0x0B换成其他合法地址RAM地址译码器会选通另一个单元如果地址超出RAM容量硬件通常不会主动报错而是出现地址回卷或总线未驱动这也是连线实验中指令错乱的最常见原因。提示在单周期CPU设计里PC自增通常在取指节拍结束前的时钟沿完成不能在同一个时钟沿同时读PC和写PC。自己搭CPU时在这个位置容易出现初值覆盖波形上会看到PC连续跳两次。3. 指令译码操作码 000001 与两个 6 位地址字段的拆解3.1 从 IR 到指令字段取指结束IR里保存着完整的指令字。本实验的指令按定长格式组织高6位是操作码中间6位是第一个操作数的内存地址低6位是第二个操作数的内存地址。这是教学CPU最常用的指令编码方式硬件译码时不需要先判断指令长度固定取高6位做判断就行。表4-1中IR的内容按666格式拆分后三个字段分别如下指令字段6位二进制十六进制含义操作码0000010001H加法指令 ADD操作数1地址1101000034H加数1所在RAM地址操作数2地址1101100036H加数2所在RAM地址汇编语言里程序员看到的是ADD GR1, [0034H], [0036H]这类助记符CPU眼里只有000001 110100 110110这串二进制。两者之间差一个译码器。6位地址字段可以覆盖0到63的地址范围0034H和0036H都在这个范围内如果把地址字段扩展到8位甚至12位RAM容量和PC自增逻辑都要同步调整指令字也会变长。# 把 0b000001_110100_110110 按 666 拆成三个字段 inst 0b000001110100110110 # 对应实验中的 IR 内容 opcode (inst 12) 0x3F # 高 6 位 addr1 (inst 6) 0x3F # 中间 6 位 addr2 inst 0x3F # 低 6 位 print(fopcode{opcode:#04x}, addr1{addr1:#04x}, addr2{addr2:#04x}) # 输出opcode0x01, addr10x34, addr20x36这里 0x3F是关键。0x3F是2的6次方减1也就是二进制的111111用它与右移后的结果做位与可以只保留低6位把高位的无关数据全部清掉。如果不做这个掩码操作从inst里取出的地址会带上高位干扰。addr10x34对应的十进制是52addr20x36对应54正好就是实验报告里的0034H和0036H三个字段拆完后可以直接映射到RAM地址。3.2 译码发生在 CU而不是 ALU实验报告问“译码在哪里进行”答案是CU控制器。这一步经常被误解成“指令到了CPU内部就算译码完成”实际流程比这严格IR里的操作码送进CU内部的译码器译码器把000001翻译成一组控制信号包括“让ALU执行加法”、“让RAM读第一个操作数”、“让RAM读第二个操作数”、“结果写回GR”。这些控制信号通过控制总线CB分发到各个部件。控制总线不是一条很宽的单一总线CB上承载的是读信号、写信号、ALU模式选择线这类独立控制引脚。调试时看CB上的信号状态比只看DB上的数据更能判断“某一步是否被激活”。比如RAM读操作发生时RD信号拉低同时AB上有有效地址如果RD没有变化而数据变了多半是控制信号和地址信号没有对齐。这也解释了为什么实验报告在译码阶段特别强调CB操作码本身不直接驱动RAM驱动RAM的是CU根据操作码生成的控制信号。3.3 操作码编码选型与非法指令操作码000001在本实验中对应ADD。如果沿用这种定长指令格式一个6位操作码最多可以定义64条指令算术、访存、跳转、移位都可以在同一个编码空间里分配译码器用一条case语句就能全部覆盖。MIPS等RISC处理器采用的就是类似思路固定长度指令让译码硬件简洁代价是指令密度比x86这种变长编码低但换来的是流水线前端更简单。如果IR里出现一个没有定义的操作码比如101010译码器输出要么全部为0要么直接触发异常入口。教学CPU通常把未定义编码当作NOP处理商业CPU则会触发非法指令异常由操作系统终止当前进程。反汇编工具里遇到无法识别的字节本质上和这里未定义操作码是同一类问题不同处理器对它的处理结果差异很大这也是某些代码混淆技术选择特殊字节序列的原因之一。提示在仿真环境里改操作码之前先确认译码器的case语句覆盖了所有可能值。Verilog里漏掉default分支的case会产生一个Latch综合后行为和你想要的完全不一样。4. 取数据阶段第二次访存与 MDR、GR 的角色4.1 为何指令译完还要再访存取指是一次访存取操作数是第二次访存。两次访存走同一套AB-DB通路区别只在地址来源第一次地址来自PC第二次地址来自IR中的地址字段0034H和0036H。这个差别决定了指令通路和数据通路能否复用。冯诺依曼结构里二者共用一套总线串行执行即可控制简单哈佛结构把指令存储与数据存储分开两条通路可以并行取数和取指代价是控制器多出一套地址译码逻辑。实验报告在步骤4里问“CPU通过哪组总线传送地址”答案是AB“RAM把第一个操作数存放在哪个寄存器上通过哪组总线传送给CPU”答案是数据寄存器和DB。和取指阶段对照看第二次访存的地址来源已经从PC移交给了CU控制的IR字段控制权转移清晰可见。读表4-1时如果只盯着“AB”“DB”“GR”这些零散答案看不到这条控制权移交的线索整条流水线的理解就会断在译码和执行之间。4.2 访存操作数的六拍总线动作取第一个操作数时总线上的完整动作可以拆成六拍节拍动作涉及部件总线/寄存器上的值1CU把IR中的地址字段写入MARIR→MAR无2MAR值送上地址总线ABMAR→RAM0034H3RAM地址译码选通存储单元RAM内部—4RAM读出数据到MDRRAM→MDR30005MDR输出到数据总线DBMDR→CPU30006CPU把DB上的值锁存到GRDB→GR3000取第二个加数时再完整走一遍同样的流程只是地址从0034H换成0036HMDR里的值从3000换成12。现实中CPU访问存储器时地址锁存和译码选通需要多个时钟周期DDR等同步存储器还要求额外的延迟周期但在教学实验的简化模型里把一次访存压缩成六拍来看已经足够。4.3 MAR/MDR 为什么不能省MAR和MDR在单周期CPU设计里几乎必不可少核心原因是锁存。CPU在时刻T0发出地址RAM可能到T3才返回数据如果不把地址锁存在MAR里AB上的电平一旦变化RAM译码到的就是错误单元。MDR则保证RAM返回的数据不会因为DB上后续其他设备占用而丢失。调教学CPU时如果数据“大部分时间正确但偶尔乱跳”优先检查这两个寄存器的时钟使能有没有配错——很多人把MAR的使能信号直接接到了寄存器堆的写使能上结果在非访存周期里MAR也会被无关的写操作改写。# 按总线动作模拟一次“取操作数”访存 def read_operand(addr, mem, mar, mdr): mar addr # 1) IR 地址字段写入 MAR abus mar # 2) AB 上出现目标地址 if abus not in mem: # 3) RAM 地址译码检查地址是否有数据 raise ValueError(address out of range: %#04x % addr) mdr mem[addr] # 4) RAM 输出到 MDR dbus mdr # 5) DB 传输 return dbus # 6) 调用方把结果锁存进 GR mem {0x34: 3000, 0x36: 12} gr1 read_operand(0x34, mem, 0, 0) gr2 read_operand(0x36, mem, 0, 0) print(gr1, gr2) # 输出3000 12mar和mdr作为入参传入体现的是它们作为“锁存器”而不是“运算器”的角色函数内部先更新mar再通过abus去读内存最后把读到的值赋给mdr再返回。地址越界检查是软件模拟里必要的防御性写法硬件里地址超出容量通常会表现为地址回卷或读到未驱动总线而不是抛异常。真实的CPU里GR锁存发生在下一个时钟沿而这里直接赋值等效于把“采样”简化成了“拷贝”如果要用Verilog或SystemVerilog重写GR的赋值语句要放在always块的时钟沿里。5. 执行与回写ALU 加法、结果覆盖与单指令模拟器验证5.1 ALU 的加法通路两个加数分别进入GR1和GR2后下一步交给ALU。ALU内部先把两个操作数送到加法器输入端加法器完成二进制加法实验用的数据是3000123012。表4-1问“CPU把两个加数放入哪里进行加法运算”答案是ALU又问“相加结束后CPU把结果存放在哪里”答案是“加数一所在的通用寄存器中”。这个写回目标值得注意如果后续汇编代码还需要用到加数1的原始值执行这条指令之前必须先把原始值复制到别的寄存器。编译器在生成a a b这类语句时通常会直接复用源寄存器生成a b且后续还要用旧值时则会先把旧值溢出到栈上这个行为在汇编层面看一目了然。5.2 标志位与结果宽度ALU完成加法后除了结果本身还会顺带产生标志位零标志ZF、进位/借位标志CF、溢出标志OF。教学实验只关心结果3012但实际CPU会把标志写进程序状态寄存器供条件跳转指令使用。3012的十六进制是0BC4H存入GR1后这条指令的执行周期才算真正结束。如果实验扩展到减法观察对象要换成借位标志运算换成补码则必须同时看溢出否则正负数相加的结果可能符号位翻转而不自知。这也是把ALU结果宽度与寄存器宽度对齐的原因GR1只有放得下结果回写才不会截断。5.3 单指令模拟器把五阶段串起来验证下面这个Python脚本把取指、译码、取数、执行、回写一次性串起来运行结果对应表4-1里每一步的寄存器状态适合在没有硬件仿真器的环境下验证全流程。# 单条加法指令(666格式)五阶段模拟器 inst 0b000001110100110110 # op000001, addr1110100, addr2110110 mem {0x34: 3000, 0x36: 12} # 与实验表4-1一致的加数 PC, MAR, MDR, IR, GR1, GR2 0x0B, 0, 0, 0, 0, 0 # 取指 MAR PC; MDR inst; IR MDR; PC 1 print(f取指: MAR{MAR:#06x} IR{IR:#018b} PC{PC:#06x}) # 译码 op (IR 12) 0x3F a1 (IR 6) 0x3F a2 IR 0x3F print(f译码: op{op:#04x}(ADD) addr1{a1:#06x} addr2{a2:#06x}) # 取数 MAR a1; MDR mem[MAR]; GR1 MDR MAR a2; MDR mem[MAR]; GR2 MDR print(f取数: GR1{GR1} GR2{GR2}) # 执行与回写 GR1 GR1 GR2 print(f执行回写: GR1{GR1} ({GR1:#06x}))运行后依次输出取指: MAR0x0b IR0b000001110100110110 PC0x0c、译码: op0x01(ADD) addr10x0034 addr20x0036、取数: GR13000 GR212、执行回写: GR13012 (0x0bc4)。每一步的寄存器变化都能和实验报告表4-1对上。IR{IR:#018b}按18位宽度打印指令字因为本实验指令是666结构如果改成16位打印低位两个地址字段会拼在一起读起来反而容易出错。验证时改mem字典里的两个加数即可比如{0x34: 100, 0x36: 200}改运算类型则需要同时换op编码和执行阶段的算术表达式否则模拟器算的和操作码表示的不是同一件事。提示PC自增发生在取指结束ALU结果写回发生在执行结束两者混在同一个时钟沿完成时软件模拟器看不出问题真实电路会因建立时间不足导致GR写入错误。仿真波形里重点比较这两个寄存器的写入使能信号。运行前把mem里的加数改成任意整数再次执行脚本时能看到同一套五阶段流程在寄存器状态里完整走一遍对照教学仿真器中AB、DB、CB上的波形重点查地址从000B到0034再到0036的变化时刻与PC自增沿是否对齐这两处是手工搭CPU时最容易出错的点。本文还有配套的精品资源点击获取