☰
16条指令模型机设计全攻略:从指令集到控制器与调试
2026/10/18 17:24:22 网站建设 项目流程

简介:面向计算机组成原理课程设计学习者,哈尔滨工程大学模型机设计资源完整覆盖16条指令的CPU设计与实现流程,包含工程源码、实验报告与仿真验证文件,适合高校本科生对照完成同类模型机或指令系统实验。压缩包共340个文件,以bdf原理图、vhd/tdf硬件描述、vwf仿真波形、qpf/qsf工程配置及sof下载文件为核心,另有doc实验报告与hex测试程序,整体约7.06MB,结构清晰便于按模块复用。已有348人学习下载,适合需要借鉴完整设计方案、排错思路和报告写作范式的同学。资源内还包括大量Quartus中间文件与调试记录,可作为从原理图设计到功能仿真的过程性参考,帮助理解微程序控制、ALU运算及16条指令的译码执行通路。

1. 模型机设计(16条指令):课设的最后一关,到底考什么?

模型机设计(16条指令)这个题目,是很多院校计算机组成原理课在期末阶段布置的经典综合项目:不依赖现成处理器,而是用 Verilog 或 Logisim 从指令集、数据通路到控制器,亲手搭一台只认识 16 条指令的小 CPU,最后在仿真环境里跑通一段汇编小程序。放倒学生的通常不是“不会写 Verilog”,而是指令集没定清楚、控制器时序没理顺、分支偏移算错这类前期设计问题——代码只是把流程图翻译成硬件描述语言而已。这个课设适合正在准备验收的同学,也适合想补体系结构基本功、搞清楚取指译码执行背后到底发生什么的从业者。下面这套方案,就是按“先定指令集,再画数据通路,最后填控制器”的顺序展开的。

2. 先定指令集:16 条指令的取舍、编码表与汇编脚本

2.1 16 条指令为什么这样凑:寄存器个数和指令格式先定死

做模型机设计第一步不是打开编辑器,而是把指令集定死。16 条指令看似不多,但每加一种寻址方式,控制器状态就要多出两三个,数据通路的控制信号也会跟着膨胀。我一般会把指令分成四类:算术逻辑运算、访存、分支跳转、停机,保证每一种典型操作都有覆盖,又不会多到状态机写不完。

寄存器个数建议砍到 4 个:R0、R1、R2、R3,并且把 R0 固定为常数 0。这样寄存器编号只需要 2 位,16 位指令字里才能同时放下操作码、寄存器字段和 8 位立即数。如果一开始就按 MIPS 的 32 个寄存器设计,寄存器编号占 5 位,一条 16 位指令根本装不下,最后只能缩立即数或缩操作码,反而更别扭。

指令字长我定为 16 位。四种类型按字段区分:

  • R 型:算术逻辑指令,操作码 4 位,rs、rt、rd 各 2 位,funct 3 位,移位量 3 位
  • I 型:立即数运算、访存、条件分支,操作码 4 位,rs、rt 各 2 位,立即数 8 位
  • J 型:无条件跳转,操作码 4 位,目标地址 12 位
  • 特殊:jr、halt 用独立操作码编码

这样 16 条指令分别定为:add、sub、and、or、sll、srl、addi、lw、sw、beq、bne、lui、j、jal、jr、halt。前 6 条是 R 型,中间 6 条是 I 型,然后 3 条跳转指令,最后 1 条停机。

2.2 一张表把机器码编码定完:R型、I型、J型和停机指令

确定格式后,操作码分配要尽早固定,因为后面控制器入口地址、微指令分支都依赖这张表。我的分配方案如下:

指令类型操作码 / funct功能
addRopcode=0000, funct=000rd = rs + rt
subRopcode=0000, funct=001rd = rs - rt
andRopcode=0000, funct=010rd = rs & rt
orRopcode=0000, funct=011rd = rs | rt
sllRopcode=0000, funct=100rd = rt << shamt
srlRopcode=0000, funct=101rd = rt >> shamt
addiIopcode=0001rt = rs + 符号扩展(imm)
lwIopcode=0010rt = mem[rs + 符号扩展(imm)]
swIopcode=0011mem[rs + 符号扩展(imm)] = rt
beqIopcode=0100若 rs==rt,PC = PC + 1 + 符号扩展(imm)
bneIopcode=0101若 rs!=rt,PC = PC + 1 + 符号扩展(imm)
luiIopcode=0110rt = {imm, 8’h00}
jJopcode=0111PC = {4’h0, addr12}
jalJopcode=1000R1 = PC + 1;PC = {4’h0, addr12}
jr特殊opcode=1001PC = rs
halt特殊opcode=1111停机

R 型指令的统一编码为:[15:12] opcode、[11:10] rs、[9:8] rt、[7:6] rd、[5:3] funct、[2:0] shamt。sll 和 srl 的 shamt 就是移位位数,其他 R 型指令该字段固定为 0。I 型指令编码为:[15:12] opcode、[11:10] rs、[9:8] rt、[7:0] imm。J 型指令编码为:[15:12] opcode、[11:0] addr。

这里有一个容易翻车的点:beq 和 bne 的 imm 是相对下一条指令的偏移量,计算方式是“目标地址 − (当前 PC + 1)”,并且按符号扩展参与加法。很多同学在这里直接写目标地址,波形仿真时分支出错,后面第 5 章会专门讲。

2.3 用一个小脚本把助记符翻译成机器码

手工逐条翻译机器码很容易看错位,我习惯写一个几十行的 Python 小脚本,把助记符转成 16 进制机器码,再喂给 Verilog 的$readmemh。下面是一个面向本指令集的最小实现:

# mini_asm.py —— 16条指令模型机微型汇编器 import re regs = {'r0':0, 'r1':1, 'r2':2, 'r3':3} rtype = {'add':0, 'sub':1, 'and':2, 'or':3, 'sll':4, 'srl':5} itype = {'addi':0x1, 'lw':0x2, 'sw':0x3, 'beq':0x4, 'bne':0x5, 'lui':0x6} jtype = {'j':0x7, 'jal':0x8, 'jr':0x9, 'halt':0xF} def reg(x): return regs[x.lower()] def asm16(line): t = line.replace(',', ' ').split() op = t[0].lower() if op in rtype: if op in ('sll', 'srl'): rd, rt, sh = reg(t[1]), reg(t[2]), int(t[3]) return (0 << 12) | (rt << 8) | (rd << 6) | (rtype[op] << 3) | (sh & 0x7) rd, rs, rt = reg(t[1]), reg(t[2]), reg(t[3]) return (0 << 12) | (rs << 10) | (rt << 8) | (rd << 6) | (rtype[op] << 3) if op in itype: rt, rs = reg(t[1]), reg(t[2]) imm = int(t[3]) & 0xFF return (itype[op] << 12) | (rs << 10) | (rt << 8) | imm if op in jtype: if op in ('j', 'jal'): target = int(t[1], 0) & 0xFFF return (jtype[op] << 12) | target if op == 'jr': rs = reg(t[1]) return (0x9 << 12) | (rs << 10) if op == 'halt': return 0xFFFF raise ValueError('无法识别: ' + line) if __name__ == '__main__': for line in open('test.s'): line = line.split(';')[0].strip() if line: print('%04X' % asm16(line))

逻辑说明:asm16根据助记符分发到 R 型、I 型、J 型三类编码函数,reg负责把 r0~r3 映射成 2 位寄存器编号。脚本只接收纯助记符文本,每行一条指令,分号后面可以写注释。使用%04X输出 4 位十六进制数,直接重定向到文件就能作为$readmemh的输入。

参数说明:R 型移位指令的语法是sll rd, rt, shamt,其他 R 型指令是add rd, rs, rt;I 型指令统一是op rt, rs, imm;分支指令的偏移直接写数字,比如 -3。脚本没有做标签解析,分支跳转地址需要自己算偏移,这是教学版本的有意取舍——能让你在算偏移的过程中把 PC 相对寻址彻底搞明白。

3. 数据通路:让 16 条指令能取指、运算、访存、写回

3.1 单周期还是多周期:这个选择决定后续所有代码结构

模型机设计有两种主流路线:单周期和多周期。单周期每条指令在一个时钟周期内完成,控制信号组合逻辑简单,Verilog 代码短,但在这个 16 条指令的规模下会发现一个麻烦:访存指令、分支指令和算术指令的延迟差异很大,周期长度只能按最慢的 lw 来定,整体效率低。多周期则把每条指令拆成取指、译码、执行、访存、写回几个微步骤,每个周期只做一个微操作,时钟可以跑得更“公平”,也更接近真实处理器的节奏。

但多周期有一个代价:控制器状态变多,每条指令至少 4 个周期,调试时波形数量多到眼花。作为课程设计,我建议选多周期,因为答辩时能讲清楚“为什么需要多个时钟周期”这个高频问题;如果项目时间只剩两天,单周期硬布线反而更容易跑通。

这个 16 条指令的规模,多周期的典型状态是:取指两个周期,译码一个周期,执行一至两个周期,额外写回一个周期。我的方案里取指用两个周期:第一拍把 PC 送到指令存储器地址端,第二拍把读出的指令锁存到 IR,同时 PC 完成加 1。这样即使指令存储器是同步读,也不会出现数据还没稳定就锁存的时序问题。

3.2 模块划分与关键接口:寄存器堆、ALU、存储器、PC和IR

模块划分我习惯按五块走:PC、指令存储器 IM、寄存器堆 RF、ALU、数据存储器 DM,外加一个集中控制所有使能信号的控制器。控制器单独占一个模块,其余模块只负责“被控制”,这样调试时只要盯着控制器的输出信号,就能定位是哪条指令在哪个周期出了岔子。

寄存器堆是第一个要写的模块。4 个寄存器中 R0 固定为 0,R1 同时作为 jal 的链接寄存器。模块接口如下:

module regfile ( input wire clk, input wire we, // 写使能 input wire [1:0] raddr_a, // 读口A地址 input wire [1:0] raddr_b, // 读口B地址 input wire [1:0] waddr, // 写地址 input wire [15:0] wdata, // 写数据 output reg [15:0] rdata_a, output reg [15:0] rdata_b ); reg [15:0] regs [1:3]; // R0 用常量0替代 always @(*) begin rdata_a = (raddr_a == 2'b00) ? 16'd0 : regs[raddr_a]; rdata_b = (raddr_b == 2'b00) ? 16'd0 : regs[raddr_b]; end always @(posedge clk) begin if (we && (waddr != 2'b00)) regs[waddr] <= wdata; end endmodule

逻辑说明:读口是组合逻辑,只要地址给定,数据立刻出现在输出端;写口在时钟上升沿写入。R0 没有实际存储单元,读口用三目运算符直接返回 0,写口用waddr != 2'b00屏蔽写入。这种写法比单独维护一个寄存器数组更省逻辑,也不容易在复位时漏掉 R0 的清零。

参数说明:we是寄存器堆写使能,由控制器给出;raddr_a和raddr_b分别接指令的 rs、rt 字段;waddr接 rd 字段或 rt 字段,由控制器根据指令类型选择。这个模块是整机调试时最容易出问题的地方,尤其是“同一时钟沿读旧值”的问题,第 5 章会展开。

ALU 模块要同时支持算术、逻辑、移位和零标志输出:

module alu ( input wire [15:0] a, // 操作数A input wire [15:0] b, // 操作数B input wire [2:0] funct, // 运算选择 input wire [2:0] shamt, // 移位量 output reg [15:0] y, output wire z // 零标志 ); always @(*) begin case (funct) 3'd0: y = a + b; 3'd1: y = a - b; 3'd2: y = a & b; 3'd3: y = a | b; 3'd4: y = b << shamt; 3'd5: y = b >> shamt; default: y = 16'd0; endcase end assign z = (y == 16'd0); endmodule

逻辑说明:a和b分别来自寄存器堆的读口 A、读口 B,funct直接复用指令的 funct 字段,这样控制器不需要额外解码 ALU 操作。z是零标志,供 beq、bne 使用——判断 rs 与 rt 是否相等,实际上就是做一次减法看结果是否为零。参数说明:shamt来自 R 型指令的低 3 位,非移位指令时该字段为 0,不影响运算结果。

访存部分,指令存储器和数据存储器我建议都用 Verilog 里的 reg 数组来建模,指令存储器用$readmemh初始化:

reg [15:0] imem [0:255]; reg [15:0] dmem [0:255]; initial begin $readmemh("imem.hex", imem); end // 取指:先给地址,下一拍锁存 IR wire [15:0] inst = imem[pc[7:0]]; // 数据写:时钟上升沿写入 always @(posedge clk) begin if (mem_we) dmem[addr[7:0]] <= data_in; end

这里的pc[7:0]用了低 8 位做索引,意味着程序空间限制在 256 条指令以内,对这个课设完全够用。数据存储器同理,256 个字够存放测试数据。如果后续要做复杂一点的排序、求和程序,可以把地址位宽扩到 10 位或 12 位,但对应的寻址范围也要在答辩时讲清楚。

3.3 最小 Verilog 骨架:顶层连线与信号命名规范

数据通路各模块拼起来之后,顶层模块的核心信号如下:

信号位宽作用
clk, rst_n1时钟与异步复位
pc16程序计数器
ir16指令寄存器
alu_a, alu_b16ALU 两个输入
alu_out16ALU 输出,接寄存器堆写数据或数据存储器地址
reg_we1寄存器堆写使能
mem_we1数据存储器写使能
mem_addr16数据存储器地址,来自 ALU 输出

顶层连线我一般这样接:PC 输出到指令存储器地址端,指令存储器的输出在取指第二拍打入 IR;IR 的 rs、rt 字段接寄存器堆读地址,寄存器堆输出接 ALU 输入;I 型指令的立即数经过符号扩展后也接 ALU 的 B 口,通过一个选择信号在“寄存器堆 B 口输出”和“立即数”之间切换。这样一通,控制器只需决定每个周期哪些信号有效,数据通路本身不需要看不懂的状态。

4. 控制器怎么落地:微程序、硬布线还是状态机?

4.1 微程序控制器和硬布线的课设如何选择?

控制器是模型机设计里区分度最高的部分。硬布线控制器用组合逻辑和触发器直接生成控制信号,速度快,但每条指令都要单独画逻辑表达式,16 条指令的规模下表达式会写到崩溃。微程序控制器则把控制信号预先存成一条条微指令,执行指令变成“逐条读出微指令并执行”的过程,修改指令集只改微指令 ROM,不动硬件结构。课程设计层面,微程序方案更好讲、更好改,也更容易拿高分。

但真写微程序控制器时,很多同学会死在微指令编码上:字段怎么分配、下址怎么生成、条件分支怎么处理。我的建议是折中:先按“状态机式微程序”把整机跑通,再用微指令 ROM 的表驱动方式重构。下面给一个最小可用的状态机写法,它和微程序的本质是一样的——每个状态对应一条微操作,不同指令走不同状态路径。

4.2 一个简化微指令格式:控制信号、下址与条件分支

微指令至少要包含三类信息:要执行什么微操作、下一条微指令地址是多少、在什么条件下转移。对应到 Verilog 里,我习惯把“微操作”写成一组使能信号,包括:

控制信号含义
ld_ir将指令存储器输出打入 IR
ld_pcPC 加 1
reg_we寄存器堆写使能
mem_we数据存储器写使能
alu_src_bALU 的 B 口选立即数还是寄存器
mem_to_reg寄存器堆写数据选 ALU 结果还是存储器读数据
br_taken分支条件成立时改写 PC

这些信号合起来就是一个微指令的控制字。真正的微程序控制器还会把下址字段编码进微指令 ROM,但在课设规模下,我用下面的case状态转移来生成下址,效果等同:

localparam S_FETCH0 = 0, S_FETCH1 = 1, S_DECODE = 2; localparam S_ADD = 3, S_SUB = 4, S_ADDI = 5, S_LW = 6, S_SW = 7, S_BRANCH = 8, S_JAL = 9, S_JUMP = 10, S_HALT = 11; always @(*) begin next_state = S_FETCH0; case (cur_state) S_FETCH0: next_state = S_FETCH1; S_FETCH1: next_state = S_DECODE; S_DECODE: begin case (opcode) 4'b0000: next_state = S_ADD; // R型,再按funct细分 4'b0001: next_state = S_ADDI; 4'b0010: next_state = S_LW; 4'b0011: next_state = S_SW; 4'b0100, 4'b0101: next_state = S_BRANCH; 4'b1000: next_state = S_JAL; 4'b1111: next_state = S_HALT; default: next_state = S_JUMP; endcase end ... endcase end

逻辑说明:cur_state是当前微指令状态,next_state是下一条微指令地址。opcode来自 IR 的高 4 位,译码阶段根据 opcode 分发到各指令的执行入口。这个分发表相当于微程序控制器里的“入口地址映射”,把操作码翻译成微指令序列的起始地址。

参数说明:R 型指令在 S_ADD 状态里还需要再判断 funct 字段来生成不同 ALU 操作,所以状态名写 S_ADD,实际执行时根据 funct 等于 000 到 101 分别走加、减、与、或、左移、右移。可以在 S_ADD 状态里用case (funct)生成alu_funct信号,不需要为每条 R 型指令单独开状态。

4.3 OPCODE入口映射表与Verilog的case写法

各指令的执行状态按我的习惯做如下映射:

opcode指令入口状态所需周期数
0000R型S_ADD4
0001addiS_ADDI4
0010lwS_LW5
0011swS_SW5
0100/0101beq/bneS_BRANCH4
0110luiS_ADDI4
0111jS_JUMP4
1000jalS_JAL4
1001jrS_JUMP4
1111haltS_HALT停住

控制信号在每个状态里怎么置位,是代码的核心部分。以 S_FETCH0 和 S_FETCH1 为例:

always @(*) begin {ld_ir, ld_pc, reg_we, mem_we, alu_src_b} = 5'b00000; case (cur_state) S_FETCH0: begin pc_addr_en = 1'b1; // 把PC送到指令存储器地址端 end S_FETCH1: begin ld_ir = 1'b1; // 锁存指令 ld_pc = 1'b1; // PC加1 end ... endcase end

这里的赋值风格很关键:我给每个状态一个完整的控制字,默认全为 0,避免综合出意外的锁存器。ld_ir和ld_pc在同一时钟沿生效,指令被锁存的同时 PC 完成加 1,正好衔接下一拍的译码。

4.4 让HALT真正停下来:停机指令控制时序

halt 指令是课设里验收老师最常看的地方。很多同学写完 halt,发现波形里 PC 还在继续跳,原因在于控制器虽然在 HALT 状态停了,但取指周期的公共序列还会被触发。正确的做法是:halt 状态的下一条微指令指向 HALT 自身,同时关闭 PC 使能和指令存储器读使能。

S_HALT: begin next_state = S_HALT; // 停在原地 pc_en = 1'b0; // PC 不再更新 imem_en = 1'b0; // 指令存储器不再输出 end

这样设计之后,PC 会停留在 halt 指令所在的地址上,uPC 也固定在 HALT 状态,波形上表现为所有关键信号都不再跳变。验收时看一眼这条信息,就知道停机逻辑是真正生效的,而不是靠仿真强制停止。

5. 模型机调试避坑:仿真过了不等于验收过了

5.1 复位后第一条指令不执行

现象:仿真开始后,PC 一直是 0,IR 始终没有锁存任何指令,程序完全不走。原因多半是复位信号释放的时机和时钟上升沿撞在一起,或者取指状态机的初始状态没复位干净。解决:在测试平台里把rst_n拉低至少两个时钟周期再释放,同时在控制器复位逻辑中把cur_state明确赋值为 S_FETCH0,IR 和 PC 也在复位时清零。还要检查$readmemh的路径是否写对,文件读不到时 imem 会保持全 X,IR 锁存出来全是未知值。

5.2 BNE/BEQ的偏移为什么总是跳不准

现象:程序单步执行没问题,一到分支指令就跳错地址,要么提前跳,要么跳完回不来。原因几乎都是相对偏移计算错误。beq/bne 的跳转目标是“当前 PC + 1 + 符号扩展(imm)”,不是“当前 PC + imm”。比如地址 5 的 bne,下一条指令地址是 6,目标地址是 3,偏移就是 3 − 6 = −3,用 8 位二进制表示是 11111101。很多同学把偏移写成 3 或者 −4,跳转就会偏一条。解决:把分支指令的地址、下一条指令地址、目标地址列成一张表,逐一核对偏移,再用第 2 章的 Python 脚本重新生成机器码。

5.3 寄存器堆同沿读写读出旧值

现象:add 指令执行后,下一个周期从寄存器堆读出的数据还是旧值,导致后续指令计算结果错。原因是寄存器堆的写操作和读操作发生在同一个时钟沿:如果控制信号把reg_we拉高,数据要等这个时钟沿结束后才写入寄存器,而同一时刻读口看到的还是旧数据。解决:把加法结果在写回周期写入,读发生在再下一个周期,因为模型机本来就是多周期设计,天然有缓冲;如果做成了单周期,就需要在寄存器堆内部做“写优先”处理,写地址等于读地址时直接旁路写数据到读输出。

5.4 HALT后波形还在往下走

现象:halt 指令执行完,PC 仍然加 1,程序计数器一路走到内存末尾。原因是没有在控制器里关闭取指序列,或者 PC 加 1 信号在译码阶段就被拉高。解决:按 4.4 的做法,让 HALT 状态的下址指向自身,同时把ld_pc强制为 0。另外要确认imem_en是否默认开启,如果指令存储器是组合读,HALT 之后地址变化虽然不影响程序运行,但波形上很难看,验收时容易被追问。

5.5 指令ROM数据RAM地址重叠

现象:lw/sw 指令明明写对了,但读写的数据地址总往程序区跑,导致内存内容被破坏。原因是没有对程序空间和数据空间做隔离。这个 16 条指令的模型机里,我习惯让指令存储器从地址 0 开始,数据存储器也从地址 0 开始,但两者在 Verilog 里是不同的 reg 数组,问题在于 ALU 输出的地址如果只有 16 位,程序很容易读写到同一索引。解决:在顶层把数据存储器和指令存储器的地址空间错开,比如数据存储器物理地址取alu_out[7:0],程序计数器取pc[7:0],二者互不干扰;或者给数据存储器加一个单独的高位偏移,从 0x100 开始编址。答辩时要说清楚“指令空间与数据空间是分开的”这个设计选择。

6. 用一个循环累加程序验证整机:从汇编到波形对比

6.1 验证程序选型:为什么选4+3+2+1

验证程序不必复杂,但必须覆盖 R 型运算、I 型立即数、条件分支、访存和停机五类操作。我用一个 4+3+2+1 的累加循环,从 4 递减到 1,逐次加到 R1 里,最后把结果写入数据存储器再读回。这个程序短小精悍,跑完大约 30 个时钟周期,波形一眼能看完。

addi r1, r0, 0 ; r1 = 0,累加器 addi r2, r0, 4 ; r2 = 4,循环计数器 addi r3, r0, 1 ; r3 = 1,步长 loop: add r1, r1, r2 ; r1 = r1 + r2 sub r2, r2, r3 ; r2 = r2 - 1 bne r2, r0, -3 ; r2 != 0 则回到 loop sw r1, r0, 0 ; 数据存储器[0] = 10 lw r3, r0, 0 ; r3 = 10 halt

用第 2 章的脚本翻译后,得到机器码序列:

地址助记符机器码
0addi r1, r0, 00x1100
1addi r2, r0, 40x1204
2addi r3, r0, 10x1301
3add r1, r1, r20x0640
4sub r2, r2, r30x0B88
5bne r2, r0, -30x58FD
6sw r1, r0, 00x3100
7lw r3, r0, 00x2300
8halt0xFFFF

注意地址 5 的 bne 偏移:下一条指令地址是 6,目标地址是 3,偏移 = 3 − 6 = −3,所以机器码低 8 位是 0xFD。这就是符号扩展后参与 PC 运算的结果。

6.2 机器码对照表与ModelSim验证

在测试平台里把 imem 初始化成上面的机器码,然后启动仿真。验证顺序我按三步走:先看 R1 的值是否依次变成 4、7、9、10;再看 PC 是否在地址 3、4、5 之间循环了 4 次;最后等 halt 之后确认 PC 停在 8,uPC 停在 HALT。

ModelSim 里添加pc、ir、rdata_a、rdata_b、alu_out、cur_state这几个信号。循环第一次跳转时,cur_state应该从 S_BRANCH 回到 S_FETCH0,PC 从 5 跳到 3;循环结束时,R2 变为 0,z 标志为 1,bne 不跳转,顺序执行到地址 6。看到alu_out变成 10,同时mem_we拉高,数据存储器写入成功,整机的基本功能就算是验证通过了。

6.3 验收时的加分技巧

课设答辩时,不要只给总波形图,那是自己都看不清的黑匣子。我习惯准备三张截图:第一张是整个程序的完整波形,第二张是循环跳转处放大后的波形,第三张是 halt 之后 PC 和 uPC 稳定不动的波形。第三张对应 5.4 的避坑点,最容易被问到,答得清楚就是加分项。

另外可以准备一个简单的计数显示:在dmem[0]写入结果后,用一个 testbench 里的$display打印寄存器状态,仿真结束时自动输出结果对错。这样验收老师不用从波形里数二进制数,一眼就知道设计是否正确。我当年就是靠这个习惯省去了大量从波形里数数的麻烦。

模型机设计(16条指令)这个题目,做到最后你会发现,真正的难点从来不是 Verilog 语法,而是“把需求变成状态机”的拆解能力。指令集定得好,数据通路就是搭积木;控制器写得干净,调试就是看信号。希望这篇笔记能把你的课设从“能跑”推到“讲得清”,这是比验收分数更值的东西。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询