拓冰建站拓冰建站
首页 / 资讯中心 / 正文

8位模型CPU设计:从指令编码到微程序控制的完整改造

简介面向计算机组成原理课程的8位实验CPU设计与实现完整工程包适合高校学生、课程设计者及对CPU内部机制感兴趣的入门者。资源以16位参考CPU为基础将其数据通路、指令格式、OP码与地址码均改为8位/4位并完成指令系统、ALU、控制器、寄存器、存储器等模块的配套改造可用于TEC-CA硬件平台与VHDL语言环境下的模型机设计验证。包体共130个文件核心包括20个VHDL源码文件、27个cdb电路设计文件、24个hdb文件、19个bsf模块符号文件以及mif初始化文件、引脚分配与编译报告等辅助内容压缩包整体约1.28MB便于下载与本地工程复现。已有2253人学习浏览说明该方案具有较高的参考价值。通过分析工程中的指令格式设计、数据通路改造思路和模块连接方式能够帮助读者系统掌握CPU工作原理与整机概念也可作为课程设计或实验报告的实用参照。1. 把16位实验CPU改成8位真正的难关不在字长参考CPU是16位指令、8位操作码加8位地址码的模型机把数据通路改成8位最直观的想法是把总线从16根剪成8根但改到一半就会卡住真正的取舍点是操作码只剩4位、地址码只剩4位两个操作数还得同时塞进这4位里。这意味着通用寄存器只能安排4个R0~R3访存指令没法直接携带地址必须改成寄存器间接寻址条件转移也失去了相对偏移的空间。读唐朔飞那本王道复习笔记时会看到“指令字长决定寻址空间”这类结论在这里成了具体的取舍少1位就得改交互协议。本文适合在TEC-CA平台上做模型机实验、或者正在为计算机组成原理课设发愁的人按16位模板改8位时回避那些改了顶层却忘记改微程序的坑。2. 指令系统设计的三个坑编码空间、访存方式和条件转移2.1 8位指令格式从16位到44参考CPU的指令是OPCODE(8位) | 地址码(8位)地址码里可以放两个操作数。改造后整条指令只有8位按设计要求变成OPCODE(4位) | 操作数1(2位) | 操作数2(2位)这里的操作数不再是直接地址而是寄存器号。字段位宽语义OPCODE4位16种指令编码0000~1111Rd2位目的寄存器或第一操作数Rs2位源寄存器或第二操作数两个2位的寄存器号一共只能覆盖4个寄存器。一开始我试图保留8个寄存器、让指令变成OPCODE(4位) | 寄存器(3位) | 寄存器(3位)算下来只有8位前4位指令码、后4位两个寄存器号但要求写的是4位地址码于是寄存器必须缩到4个。这里有个常见的误用直接把参考CPU的16条指令拿来翻译把OP码从4位扩展到8位之后才发现空余编码太多、而寄存器和访存能力撑不起来。2.2 16条指令全集A组/B组如何取舍设计要求A组、B组指令都要覆盖。参考CPU的A组通常是算术逻辑指令、B组是访存和转移指令。我按“算逻8条访存转移8条”来排编码助记符功能组别0000ADD Rd, RsRd ← Rd Rs置C/ZA0001SUB Rd, RsRd ← Rd - Rs置C/ZA0010AND Rd, RsRd ← Rd AND RsA0011OR Rd, RsRd ← Rd OR RsA0100XOR Rd, RsRd ← Rd XOR RsA0101NOT RdRd ← NOT RdA0110SHL RdRd ← Rd 左移1位A0111SHR RdRd ← Rd 右移1位A1000LD Rd, RsRd ← Mem[Rs]B1001ST Rd, RsMem[Rs] ← RdB1010MOV Rd, RsRd ← RsB1011LDI Rd, immRd ← 零扩展的2位立即数B1100JMP RdPC ← RdB1101JZ Rd, RsZ1时PC ← RdB1110JC Rd, RsC1时PC ← RdB1111HLT停机B注意两个设计决策访存指令采用LD/ST 寄存器间址因为4位地址码放不下8位存储器地址条件转移也用寄存器作跳转目标相当于一个个“跳转表”。参考CPU里JMP [addr]这类直接寻址指令在8位格式里必须被丢掉这是设计约束不是偷懒。2.3 为什么不用教科书上的三地址格式计算机组成原理的经典指令格式分析里MIPS风格是OP(6位) RS(5位) RT(5位) RD(5位)8位完全塞不下。RISC-V用5位寄存器索引8位只剩3位指令码最多8条指令也不够用。这里被迫用“2位寄存器号双操作数”副作用是编程时寄存器不够用但模型机本来就是验证整机概念寄存器少反而让数据依赖更明显、仿更好写。边界条件也要想清LDI的立即数只有2位要扩展到8位扩展逻辑放在ALU输入端而不是控制器里。2.4 用汇编小程序验证指令系统设计完16条指令后先别写VHDL在纸上把一段测试程序翻译成机器码。比如; 计算 3 4结果存到内存地址 0x10 LDI R0, 3 ; R0 3 LDI R1, 4 ; R1 4 ADD R0, R1 ; R0 7 LDI R2, 0x10 ; R2 内存地址 ST R0, R2 ; Mem[0x10] 7 HLT对应机器码HEX为B0 B1 01 B2 92 F0。这里有两个容易写错的地方ADD R0, R1的编码是0000|00|01即0x01低4位是寄存器组合ST R0, R2是1001|00|10即0x92。这段程序会在后面做仿真时的“正确性基准”。3. 数据通路缩到8位寄存器、总线和ALU的位宽手术3.1 从BDF/BSF看整体结构拿到手的文件是cpu0.bdf、alu.bsf、controller.bsf、reg_mux.bsf、bus_mux.bsf、flag_reg.bsf、reg_out.bsf、reg_testa.bsf。.bdf是Quartus的原理图顶层.bsf是原理图符号说明这台模型机是用原理图方式做整机连线、VHDL写在符号内部。我的习惯是先按数据流把符号理出层次避免直接在BDF里乱拉线bus_mux.bsf总线多路选择器决定当前总线上是哪一路数据PC、ALU、存储器、立即数等reg_mux.bsf寄存器写端口的多路选择决定写回哪个通用寄存器reg_out.bsf8位通用寄存器组输出reg_testa.bsf寄存器组的第一操作数输出端口flag_reg.bsf标志寄存器保存C和Z位controller.bsf控制器输出全部控制信号alu.bsf8位ALUcpu0.bsf顶层CPU符号数据通路改造到8位第一刀砍在bus_mux的输入位宽参考CPU的16位总线要改成8位数据总线的输入引脚、输出引脚的Vector Map全部要重新定义。这个过程容易漏的是flag_reg标志位只需要1位的C和1位的Z位宽不受CPU字长影响但如果ALU的输出从16位改成8位flag_reg的输入信号来源要改成ALU的第8位进位输出。3.2 8位通用寄存器组的VHDL实现模型机的寄存器组不适合用一长串when语句写完我习惯用进程加数组这样要改成8位或加寄存器都方便。下面是一个简化版library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity reg_file is port ( clk : in std_logic; wr_en : in std_logic; -- 写使能来自控制器 rd_sel : in std_logic_vector(1 downto 0); -- 指定写入哪个寄存器 R0~R3 data : in std_logic_vector(7 downto 0); -- 总线输入 q0 : out std_logic_vector(7 downto 0); -- R0输出 q1 : out std_logic_vector(7 downto 0); q2 : out std_logic_vector(7 downto 0); q3 : out std_logic_vector(7 downto 0) ); end reg_file; architecture rtl of reg_file is type reg_array is array (0 to 3) of std_logic_vector(7 downto 0); signal regs : reg_array; begin process (clk) begin if rising_edge(clk) then if wr_en 1 then regs(conv_integer(rd_sel)) data; end if; end if; end process; q0 regs(0); q1 regs(1); q2 regs(2); q3 regs(3); end rtl;这段代码里rd_sel是寄存器选择信号和控制器的操作数字段相连低两位就是指令里的Rd或拼出来的写回目标。wr_en由控制器产生要避开取指周期写寄存器的时序防止IR里面的数据盖掉正在执行的寄存器值。用数组实现的另一个好处是regs(conv_integer(rd_sel))不用写4个分支后续改成16个寄存器也不费劲。3.3 ALU改造与标志位边界16位ALU改成8位加减法逻辑基本不变但要注意两点进位标志要从最高位第7位取而不是原来第15位减法借位的定义在模型机里各教材不同TEC-CA平台一般是用加法器实现减法A - B A (~B) 1C标志取的是这个加法过程最高位进位。如果直接用VHDL的std_logic_vector做加减仿真时容易因为进位溢出导致标志位取错位置所以更稳妥的是把操作数和结果都扩展到9位再运算signal a_ext : std_logic_vector(8 downto 0); signal b_ext : std_logic_vector(8 downto 0); signal alu_res : std_logic_vector(8 downto 0); a_ext 0 a; b_ext 0 b; -- 减法时由控制器控制 b 取反1 alu_res a_ext b_ext when alu_op 000 else a_ext - b_ext when alu_op 001 else (0 (a and b)) when alu_op 010 else (0 (a or b)) when alu_op 011 else (0 (a xor b)) when alu_op 100 else (0 (not a)) when alu_op 101 else a(7 downto 0) 0 when alu_op 110 else 0 a(6 downto 0) when alu_op 111; c_flag alu_res(8); z_flag 1 when alu_res(7 downto 0) x00 else 0;这段代码的关键是alu_res比结果多一位c_flag从第8位取z_flag只看低8位。SHL和SHR指令的移位结果进位标志应该取自移出的最高位或最低位上面的简写并没有覆盖这个语义实际写的时候要把移位单独拉出来移位出来的那一位直接赋给c_flag。这也是排错时最容易发现逻辑不对的地方做SHL R0后Z标志被错误置位。3.4 总线多路选择器的设计bus_mux决定数据总线上的值8位数据通路上总线源一般是PC低8位、ALU结果、存储器读数据、立即数扩展。对应关系控制信号总线来源00PC取指用01ALU结果10存储器数据输出11立即数零扩展值在BDF里做这个模块时把输入引脚设成多组8位输出8位总线控制引脚2位用case语句选择即可。注意参考CPU里PC是16位的但指令现在只有8位所以取指的PC值只取低8位送到总线高8位留着不用但PC溢出会影响到访存地址这个在顶层连线时要处理。模型机为了验证整机概念一般让PC范围不超过256字节就不用动高位。4. 控制器实现微程序ROM怎么适配16条指令4.1 微程序从长指令到短指令的映射TEC-CA平台的参考CPU是微程序控制的改造后控制器依然可以用微程序因为指令集只有16条微程序的规模完全可以控制在可接受范围内。控制器的输入是IR[7:4]操作码和flag条件标志输出是一组控制信号LD_MAR、LD_IR、PC_INC、MEM_RD、MEM_WR、ALU_OP[2:0]、REG_WR、REG_SEL[1:0]、BUS_SRC[1:0]等。每条机器指令对应的微程序一般是2~4个微周期取指期、译码期、执行期。取指期的微指令对所有指令都一样执行期才根据操作码跳到不同的微地址。参考CPU的微指令字长较长8位CPU缩小指令集后微地址只需4~5位就能覆盖全部微程序微指令ROM的地址位宽相应减少但微指令的内容字段不能随意裁剪因为ALU操作码和总线选择仍然需要那么多位。4.2 用mif文件写微程序在VHDL里初始化微程序ROM最常见的方式是用mif文件。下面是一个微型访存类指令LD的微指令片段假设每条微指令是12位依次表示控制字段LD_MAR、LD_IR、PC_INC、MEM_RD、R/W等。WIDTH 12; DEPTH 16; ADDRESS_RADIX HEX; DATA_RADIX HEX; CONTENT BEGIN -- 取指周期PC送MAR存储器读IR锁存PC加1 0 : 0A4; -- 1010_0100: LD_MAR1, PC_INC1, MEM_RD1 1 : 042; -- 取指结束进入译码 -- LD指令执行周期MEM[R2]送寄存器R0 2 : 091; -- MEM_RD1, REG_WR1, REG_SEL00 3 : 000; END;上面的数值是为便于讲解而简化的示例实际工程里必须按控制器输出的信号位顺序逐位列出。我的做法是先画一张“控制信号-位号”对照表再按位拼HEX值否则mif文件错一位整条指令就执行成另一条。提示在Quartus里修改mif后需要重新编译生成ROM初始化文件直接下载到实验板不会自动更新这一步漏了会反复出现“指令执行没变化”的假象。4.3 取指/译码/执行时序的配合8位模型机的时钟设计沿用了16位CPU的三拍结构但指令变短后取指只需要读一个字节而不是两个字节所以时序可以简化。取指周期第一拍把PC送往MAR第二拍存储器读且IR锁存、PC加1。译码和执行周期根据指令不同消耗1~3拍。用逻辑分析仪抓波形时重点看IR[7:4]在译码周期之后是否稳定为操作码以及REG_WR信号是否恰好落在寄存器数据已经稳定的时钟沿之后。控制器的状态机写法上直接在controller.vhd里用state信号配合case写时序比大型微代码ROM更容易调试。状态转移可以按取指 → 译码 → 执行 → 取指循环但JZ/JC这类条件跳转需要在执行周期根据flag_reg的输出决定下一条指令的PC是继续加1还是装载跳转地址这块是控制器里最容易写乱的部分。4.4 硬布线和微程序怎么选8位CPU如果完全用硬布线控制器有限状态机核心逻辑大概几十行状态转移代码而微程序则多了一个ROM初始化文件。参考CPU本身就是微程序结构为了在TEC-CA平台上复用现有的控制信号定义我建议保留微程序方案如果是从零设计且追求仿真速度硬布线更直接FSM的每个状态对应一个控制字不依赖ROM初始化。选型边界是当指令集超过32条时微程序改指令不用动逻辑只改mif16条指令时两者工作量差不多。5. TEC-CA平台下载与验证的实用技巧5.1 在Quartus里把cpu0.bdf的引脚对到实验板用BDF顶层时把CPU的8位数据总线引脚D[7:0]、地址总线A[7:0]、时钟clk、复位rst和手动的单步信号引到实验板的拨码开关和数码管接口。提示引脚分配在Assignments → Pin Planner里做TEC-CA平台一般把数据总线接到LED灯地址总线接到数码管的段选端方便观察PCA执行到哪条指令。上板之前先在Quartus里跑一遍Timing Analyzer如果出现保持时间违例优先检查时钟来源是不是接了实验板的慢速手动时钟而不是板载晶振手动按键时钟的抖动会让LD_IR边缘误触发。5.2 用ModelSim做指令集回归写一个自动比对测试台在ModelSim里喂入2.4节那段汇编程序的机器码每个时钟上升沿打印PC、IR、R0~R3、C、Z。测试台的关键是让存储器初始化成二进制文件然后按周期推进。等全部跑完后检查R07、Mem[0x10]7并且PC停在HLT处。这个方法比示波器抓波形高效得多它可以在一分钟内验证全部16条指令而实验板单步点按只能验证部分指令。5.3 三个典型故障的排查顺序第一执行加法后Z标志不对先查flag_reg的时钟沿TEC-CA平台的标志寄存区在指令执行周期的哪一拍被锁存是否与ALU结果的稳定时间错开。第二访存指令读出来的数据是旧值问题几乎都在MEM_RD信号时序——存储器读使能在总线选择之前先被拉低读的是上一条指令写入的干扰值。第三JZ指令无条件跳转十有八九是条件标志采集的是标志寄存器更新前的值而是不是ALU刚算出来的值处理办法是把条件判断放到下一个时钟沿。5.4 最后留一个可复现的验证清单上板调试时我用顺序执行LDI R0, 1 → LDI R1, 2 → ADD R0, R1 → ST R0, [R2]并观察LED灯逐周期变化作为整机工作正常的基准程序。检查顺序为取指总线值是否为OPCODE、寄存器写回是否发生在正确时钟沿、ALU加法结果是否为0x03、存储器写地址是否为R2内容。排除到这一层之后整个8位CPU的改造就算收尾了。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门