资讯详情

Verilog实战:16位指令集处理器与64×8存储器系统设计

📅 2026/10/28 5:07:03 | 华诺云谱 👁 阅读
Verilog实战:16位指令集处理器与64×8存储器系统设计
简介这份资源是面向电子信息类专业学生的《计算机组成实验》课程设计文档聚焦用Verilog HDL实现简化计算机核心部件帮助读者理解处理器与存储器协同工作的底层原理。压缩包内共1个doc文件约384KB内容涵盖实验目的、实验内容、预习要求与实验报告结构并附有顶层BLOCK图说明及处理器模块的Verilog源码节选涉及idle、load、move、addp、subp、mulp、logp、comp、sh、swap、jmp、jz、read、write、stop等指令的状态机实现。读者可从中获得完整的课程设计框架、64×8存储器模块的调用思路、处理器模块中IR、MDR、MAR、PC及通用寄存器的设计参考以及指令fetch、decode、execute、store各阶段的执行流程分析便于对照完成自己的Verilog实现与实验报告撰写。目前已有382人学习适合正在做计算机组成原理课程设计、需要参考指令系统与模块连接方案的学生使用。1. 从一条“跑不通”的指令说起这套简化计算机系统到底要解决什么课程设计里最让人头疼的往往不是写不出代码而是写完一堆 Verilog 模块之后发现它们根本连不起来。处理器在仿真里空转存储器里的指令读不进来波形图上信号全是红线——这种“玄学”现场几乎每个做计算机组成课程设计的人都遇到过。这套简化计算机系统的设计要解决的就是这个问题用 Verilog HDL 实现一个能真正跑通指令序列的处理器模块再配一块 64×8 的存储器顶层把两者接成一个最小可运行的计算机核心部件系统。它适合电子信息类专业正在做《计算机组成实验》课程设计的学生也适合需要一份可交付、可验收的处理器设计方案的从业者参考。整套方案的核心不是堆功能而是把“指令系统—处理器—存储器—顶层连接—执行流程分析”这条链路完整走通让每一条指令都能在波形上被解释清楚。2. 指令系统与总体架构16 条指令如何撑起一个最小处理器2.1 指令集设计从 idle 到 stop 的编码逻辑这套处理器的指令系统采用 16 位定长指令字高 4 位IR[15:12]是操作码低 12 位根据指令类型拆分为寄存器编号、寻址字段或立即数。操作码一共定义了 16 种状态正好对应 4 位编码空间操作码指令功能定位0000idle空操作/状态复位0001load立即数装入寄存器0010move寄存器间数据搬移0011addp加法运算0100subp减法运算0101mulp乘法运算0110logp逻辑运算与/或/异或/取反0111comp比较运算1000sh移位运算1001meth1自增/自减/加二/减二1010swap交换类操作1011jmp无条件跳转1100jz条件跳转1101read读存储器1110write写存储器1111stop停机这里的设计取舍值得说清楚指令字长固定为 16 位意味着取指阶段一次读入两个 8 位存储字IR {M_data_in, 8b00000000}这行代码就是取指动作的落点——先读高字节再拼上低字节。低 12 位的字段划分不是随意的IR[11:10]通常用作目标寄存器或运算模式选择IR[9:8]用作源寄存器或子操作选择IR[11:8]在 move 指令里直接编码源/目的寄存器对。这种“操作码 模式位 寄存器位”的三段式划分是教学型处理器里最常见也最好调试的做法因为每一位在波形上都能对应到一个明确的控制信号。提示指令编码表一定要在动手写代码之前定死后面写测试激励、写 mif 文件、分析执行流程都依赖这张表。中途改编码等于把已经调通的波形全部推翻。2.2 总体架构处理器、存储器与顶层连接的职责划分整个系统分三层。底层是处理器模块cpu端口包括reset、clock、Write_Read、M_address[11:0]、M_data_in[7:0]、M_data_out[7:0]和overflow。中间层是 64×8 的存储器模块地址宽度 6 位有效64 个存储单元数据宽度 8 位。顶层用原理图方式把两者连起来处理器输出地址和写数据存储器返回读数据Write_Read作为读写控制线。处理器内部的关键寄存器组包括16 位指令寄存器IR、8 位存储器数据寄存器MDR、12 位存储器地址寄存器MAR、3 位状态机寄存器status以及四个 8 位通用寄存器R0~R3、一个 8 位累加器A。PC在时序块内部声明为 12 位实际只用到低 6 位去寻址 64 个单元。这套寄存器配置不算豪华但足够覆盖算术、逻辑、移位、访存和跳转五类操作是一个“刚好够用”的最小集合。顶层连接时最容易翻车的地方是位宽不匹配M_address是 12 位存储器地址只认 6 位多出来的高位在顶层要么接地要么悬空通常的做法是只连低 6 位高位在处理器内部保证不越界。另一个点是M_data_in和M_data_out的方向——从处理器视角看M_data_in是“从存储器读进来的数据”M_data_out是“要写进存储器的数据”这两个命名和直觉相反接线时搞反了波形上就是读也读不对、写也写不进。2.3 状态机划分六个状态覆盖取指到写回处理器的控制核心是一个 3 位status状态机配合IR[15:12]的操作码做二级译码。状态流转的逻辑是状态 0取指。把存储器读出的数据装入IR高字节PC加一。状态 1译码与操作数准备。根据操作码把MAR指向PC为读操作数做准备同时完成 load、move、sh 以及算术逻辑指令的源操作数装载把源寄存器值送入A。状态 2执行。算术、逻辑、比较、乘法等指令在这一拍完成运算并写回目标寄存器。状态 3访存指令swap、jmp、jz、read、write的地址建立阶段。状态 4访存指令的数据传输阶段。状态 5read 指令的读数据回写阶段。这个状态划分的精髓在于“不是所有指令都走满六个状态”。load、move、addp 这类寄存器操作在状态 2 就结束了下一拍直接回到状态 0而 read 要走到状态 5 才能把存储器数据取回来。状态机里那些case (IR[15:12])的分支本质上是在做“哪些指令需要多走几拍”的调度。写代码时如果所有指令都强行走满状态功能上也许能跑但时序会变得很难解释答辩时被问“这条指令为什么用了六拍”就答不上来。3. 处理器模块实现时序块与组合逻辑的分工3.1 双时钟沿设计status 与 seq 两个 always 块处理器代码里有两个核心always块这是整个设计最需要讲清楚的地方。第一个块status_change对negedge reset和posedge clock敏感负责状态机流转第二个块seq对negedge reset和negedge clock敏感负责数据通路的寄存器更新。一个用上升沿、一个用下降沿形成半拍错开的节拍always (negedge reset or posedge clock) begin: status_change if (reset 1b0) status 0; else case (status) 0: status 1; 1: if (IR[15:12] stop) status 1; else status 2; 2: case (IR[15:12]) swap, jmp, jz, read, write: status 3; default: status 0; endcase 3: if (IR[15:12] swap) status 0; else status 4; 4: status 5; 5: case (IR[15:12]) read: status 6; default: status 0; endcase default: status 0; endcase end这段状态机的关键参数含义status是 3 位寄存器但case里出现了6说明实际用了 4 位编码空间reg [2:0]声明下写6会截断这是原代码里一个需要留意的细节实际综合时通常把status扩到 4 位更稳妥。stop指令在状态 1 就锁死在状态 1形成停机循环这是最朴素的停机实现——不关时钟只是不再推进状态。第二个块seq在negedge clock触发和状态机错开半拍保证状态先稳定、数据通路再动作。这种“上升沿改状态、下降沿改数据”的双沿写法在教学代码里很常见好处是波形上状态和数据的跳变沿清晰可辨坏处是综合到实际器件时对时钟质量要求更高。如果目标平台只允许单沿设计通常的做法是把seq也改成posedge clock然后在状态机里多插一拍等待用面积换时序安全。3.2 取指与译码IR 装载和 PC 推进取指动作落在seq块的状态 00: begin IR {M_data_in, 8b00000000}; PC PC 1b1; end这里IR只装了高 8 位低 8 位补零说明取指是分两步完成的——状态 0 取高字节后续状态再取低字节拼进去。PC用阻塞赋值而不是非阻塞在同一个always块里混用两种赋值方式是有风险的PC在块内被多处读写阻塞赋值能保证块内顺序执行时读到最新值但和IR的非阻塞赋值混在一起仿真时容易出现“这一拍到底用旧 PC 还是新 PC”的歧义。我一般会建议把PC也改成非阻塞或者在块内单独用一个组合逻辑算PC_next把时序和组合彻底分开波形会干净很多。译码在状态 1 完成。以 move 指令为例IR[11:8]直接编码源/目的寄存器对4b0001表示R0 R14b0100表示R1 R0一共 12 种有效组合其余编码走default: ;空操作。这种“用字段值直接查表”的译码方式写起来快但可读性差——答辩时评委问“4b0110是什么操作”得翻回编码表才能答。更稳妥的做法是用parameter给每种寄存器组合起名字或者至少加注释标明方向。3.3 算术与逻辑运算溢出判断和移位模式加法运算的溢出判断用的是经典的双符号位方法2b00: begin temp ({R0[7], R0[7:0]}) ({A[7], A[7:0]}); R0 temp[7:0]; overflow temp[8] ^ temp[7]; endtemp是 9 位把操作数符号位扩展后相加结果最高位temp[8]和次高位temp[7]异或得到溢出标志。减法用补码实现({R0[7], R0[7:0]}) (~({A[7], A[7:0]})) 1b1溢出判断逻辑相同。这个写法在 8 位数据通路上是标准做法参数上唯一要注意的是temp必须声明为reg [8:0]少一位就会丢掉进位。移位指令sh用IR[11:10]选移位类型、IR[9:8]选目标寄存器四种模式分别是算术右移、循环右移、逻辑右移、逻辑左移。原代码里循环右移那一支写成了R1 {R0[6:0], R1[0]}把R0的高 7 位和R1的最低位拼在一起这明显是复制粘贴时漏改了寄存器名——正确写法应该是{R1[6:0], R1[0]}。这类“看起来能跑、结果不对”的 bug是移位模块里最高频的翻车点因为语法没错、仿真不报错只有对着具体数据算一遍才能发现。乘法mulp用 16 位中间变量temp2存积低 8 位写目标寄存器、高 8 位写相邻寄存器。这里有个隐含约定R0存低字节时R1存高字节R2存低字节时R3存高字节。这个约定不在指令编码里体现只存在于实现中写测试激励时必须按这个约定去检查结果否则会误判为乘法出错。4. 存储器模块与顶层连接64×8 存储器的落地细节4.1 存储器模块设计地址、数据与控制信号存储器模块是一个 64×8 的 RAM6 位地址线、8 位数据线、一根读写控制线。处理器侧输出的Write_Read信号通常定义为高电平写、低电平读或反之取决于顶层约定关键是全系统统一。M_address是 12 位存储器只取低 6 位高位在顶层直接截断或接地。存储器初始化用 mif 文件完成把指令序列按地址逐条写入。这里有个容易忽略的点指令是 16 位的存储器是 8 位宽的所以一条指令要占两个存储单元。取指时先读偶地址取高字节再读奇地址取低字节PC每次加一实际指令地址是PC的一半。如果 mif 文件里按“一条指令一个地址”去写取指就会错位波形上表现为IR的高低位拼反或者指令完全对不上。我一般会在 mif 文件里按字节排布偶地址放高字节、奇地址放低字节并在文件头注释里写清楚这个约定。4.2 顶层原理图连接端口映射与位宽处理顶层用原理图方式连接核心是把cpu的端口和存储器端口一一对应处理器端口方向存储器端口说明M_address[11:0]输出addr[5:0]取低 6 位高位截断M_data_out[7:0]输出data_in[7:0]写数据M_data_in[7:0]输入data_out[7:0]读数据Write_Read输出we读写控制clock输入clk全局时钟reset输入—复位只接处理器位宽处理是顶层连接的第一道坎。M_address12 位接 6 位地址多出的 6 位如果悬空综合工具可能报 warning 也可能直接优化掉仿真时表现为高地址访问行为不确定。稳妥做法是在顶层加一个截断逻辑显式只连M_address[5:0]并在报告里说明“处理器设计保证地址不越界高位恒为 0”。第二个坎是读写时序。存储器是同步读还是异步读直接决定read指令要几个状态。如果存储器是同步读时钟沿采样地址、下一拍出数据那read指令从状态 3 建立地址到状态 5 取回数据正好三拍和状态机对得上如果是异步读状态 4 就能拿到数据状态 5 就多余了。顶层连接前一定要确认存储器的读时序模型否则状态机拍数和实际数据到达时间对不上读回来的永远是上一拍的旧数据。4.3 指令序列装载与执行流程分析把一段指令序列写进 mif 文件是验证整个系统是否跑通的关键动作。一个典型的测试序列可以这样组织先用 load 给R0、R1装立即数用 addp 做加法并检查 overflow用 move 搬移结果用 sh 做移位用 write 把结果写回存储器最后用 stop 停机。每条指令占两个字节地址序列写完后在仿真里跑足够长的时钟周期逐拍对照status、IR、PC和寄存器值。执行流程分析要落到“每一拍发生了什么”。以一条 addp 为例状态 0 取指IR高字节装入状态 1 译码把源寄存器值送入AMAR指向操作数地址状态 2 执行加法结果写回目标寄存器overflow更新状态回到 0取下一条指令。分析报告里通常用表格列出“时钟拍—状态—关键信号变化”这张表就是答辩时最有说服力的材料因为它把“指令怎么跑起来的”从抽象描述变成了可核对的时序证据。5. 避坑与排查这类设计里最容易翻车的五个点5.1 取指高低字节拼反指令全乱现象仿真跑起来IR的值和 mif 文件里写的指令对不上状态机跳到莫名其妙的分支。原因16 位指令存进 8 位存储器时高字节和低字节的存放顺序与取指拼接顺序不一致{M_data_in, 8b00000000}先装的是高字节如果 mif 里偶地址放的是低字节拼出来就是反的。解决在 mif 文件头写死约定“偶地址高字节、奇地址低字节”取指代码里加注释标明拼接方向仿真第一拍就检查IR[15:12]是否等于预期操作码。5.2 阻塞与非阻塞赋值混用波形对不上现象同一个always块里PC用、IR用仿真结果和手算不一致改一个信号牵连一片。原因阻塞赋值立即生效、非阻塞赋值在块末统一更新混用时块内后续语句读到的值取决于赋值方式形成隐式的执行顺序依赖。解决时序块内统一用非阻塞赋值需要组合逻辑的中间量单独开一个always (*)块用阻塞赋值两类逻辑物理分开。5.3 存储器读时序没对齐读回旧数据现象read指令执行完目标寄存器里是上一次读的值或者干脆是 X。原因状态机按“地址建立后两拍取数据”设计但存储器实际是同步读、需要三拍或者顶层把读写控制极性接反了。解决先单独仿真存储器模块确认从地址有效到数据有效的延迟拍数再回头调整状态机的状态数读写控制线在顶层用显式赋值固定极性不依赖默认值。5.4 移位和乘法模块的复制粘贴错误现象移位结果只有部分寄存器正确乘法高字节写错寄存器。原因多分支case里复制粘贴后漏改寄存器名比如循环右移写成{R0[6:0], R1[0]}语法合法、仿真不报错。解决每个分支写完后用一组已知数据手算预期结果和仿真值逐个比对代码里给每个分支加“目标寄存器R1”这类注释review 时逐行核对。5.5 状态位宽和编码越界现象状态机在某个状态卡死或者status波形出现不该有的值。原因status声明为reg [2:0]但case里写了63 位最大表示 7写 6 本身不越界但如果后续再加状态就会溢出更常见的是parameter定义用了 4 位编码而寄存器只有 3 位高位被截断。解决状态寄存器位宽按最大状态编号向上取整parameter编码和寄存器位宽保持一致综合后看 warning 里有没有位宽截断提示。6. 验收与答辩怎么证明这套系统真的跑通了验收这套设计核心不是看代码写了多少行而是看“指令序列能不能被完整解释”。我一般会准备三样东西一张指令编码表、一份 mif 文件、一张逐拍时序对照表。验收时按这个顺序走先复位确认PC0、status0、所有寄存器清零然后单步跑前三条指令每拍记录status、IR[15:12]、PC和目标寄存器值和手算结果比对最后全速跑完整序列确认stop指令让状态机锁在状态 1、PC不再推进。答辩时最容易被问的三个问题提前准备好答案第一“你的指令为什么这样编码”——答字段划分的依据和可扩展性第二“这条指令为什么用了这么多拍”——答状态机里哪些状态是访存专用、哪些指令走捷径第三“overflow 怎么算的”——答双符号位扩展加异或的判断方法最好能当场用一组数据手算演示。如果评委追问“如果存储器改成 128×8 要改哪里”能答出“地址位宽从 6 扩到 7、MAR和PC相应调整、mif 文件扩容、状态机拍数不变”就算过关。下面这张验收检查表可以直接拿去用检查项通过标准检查方法复位行为所有寄存器和状态清零复位后看波形首拍取指正确性IR 高 4 位等于预期操作码逐条比对 mif算术溢出正溢出/负溢出各测一组手算对照 overflow访存读写写入值能原样读回write 后紧跟 read跳转指令PC 跳到目标地址看 jmp 后 PC 值停机状态锁在 1PC 不变跑完序列看末态最后说个血泪经验这套设计里最耗时间的从来不是写模块而是“波形对不上但不知道错在哪”。从那以后我每次做这类处理器设计都强制先写一个最小测试序列——三条指令、跑十个周期、逐拍打印状态和寄存器——确认这条最短路径通了再往上堆功能。最短路径不通就加功能等于在流沙上盖楼。希望这套结构和验收方法能帮到你少走几段弯路。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑