简介:这份资源是西南交通大学SWJTU计算机组成实验1至8及课程设计的完整实践资料,面向正在修读计算机组成原理课程的高校学生,以及需要动手理解硬件底层逻辑的自学者。内容覆盖数据表示与运算、指令系统、CPU结构、存储层次、I/O与中断、总线接口、流水线技术等核心知识点,课程设计部分还涉及简单微处理器或加法器、乘法器、控制器等硬件功能的实现,适合配合实验报告与指导书同步使用。压缩包共1403个文件,约20.3MB,以vhd、v、bdf等硬件描述与原理图文件为主,辅以vwf、vcd波形文件、qsf、qpf工程配置、rpt报告、txt说明及do脚本,完整保留了各实验的工程与仿真痕迹。目前已有1769人学习下载,读者可借助其中的代码示例、解决方案与课程设计说明,逐步完成从基础验证到综合设计的训练,积累硬件调试与排错经验,为后续操作系统、编译原理等课程打下扎实基础。
1. 西南交大计组实验 1~8 加课设:一套能跑通的 Quartus 落地路线
如果你在西南交大(SWJTU)读计算机或相关专业,大概率绕不开计算机组成这门课,而实验 1~8 加课程设计就是它的硬核部分。这套实验通常围绕 Quartus 平台展开,从最基础的原理图输入、数码管译码,一路做到寄存器堆、ALU、存储器,最后在课程设计里拼出一个能跑指令的简易 CPU。很多人卡在波形仿真对不上、引脚分配报错、时序不收敛这些地方,其实问题往往出在没搞清每个实验的验证边界。这篇笔记按我实际带人做下来的顺序,把实验 1~8 和课设的落地路径拆开讲,重点放在每一步怎么配、参数怎么设、翻车了看哪里。适合刚上手 Quartus 的新手,也适合做到一半卡住想找排查思路的老手。
2. 实验 1~8 的依赖关系与 Quartus 工程骨架
2.1 先理清八个实验到底在堆什么
西南交大这套计组实验的编排逻辑,基本是沿着“组合逻辑 → 时序逻辑 → 功能部件 → 系统集成”这条线走的。实验 1 通常是 Quartus 入门和原理图输入,让你画一个简单的门电路并烧到开发板上;实验 2 开始碰数码管译码,这也是热搜里“quartus 原理图 数码管 实验”出现频率最高的点;实验 3 到实验 5 会逐步引入加法器、比较器、寄存器这些部件;实验 6 到实验 8 往往涉及存储器读写、总线结构和简单状态机。课程设计则是把这些部件串起来,做一个能执行几条指令的 CPU。
这里有个容易被忽略的点:每个实验的工程文件不要混在一起。我一般会按lab1到lab8加course_design建独立目录,每个目录里放自己的.qpf和.qsf。因为 Quartus 的引脚分配和顶层实体名是跟着工程走的,混用会导致综合时找不到顶层,或者引脚约束被覆盖。常见做法是每个实验单独建工程,课设再新建一个,把前面验证过的模块用File -> Create/Update -> Create Symbol Files for Current File生成符号,再在原理图里调用。
2.2 工程创建与器件选型的几个固定参数
新建工程时,器件选型要跟实验室开发板一致。西南交大实验室常用的是 Altera Cyclone IV 系列,具体型号看实验指导书,一般是 EP4CE6 或 EP4CE10。选错器件最直接的后果是引脚分配界面里找不到对应的管脚号,或者烧录时提示 device mismatch。
# 在 Quartus Tcl Console 里快速检查当前工程器件 project_open your_project_name set device_name [get_global_assignment -name DEVICE] puts "Current device: $device_name" project_close这段 Tcl 用来确认当前工程绑定的器件型号。get_global_assignment -name DEVICE读取的是.qsf里的DEVICE字段。如果输出和开发板丝印对不上,就去Assignments -> Device里重新选。参数上,Cyclone IV 的EP4CE6E22C8和EP4CE10E22C8封装都是 EQFP144,但逻辑单元数不同,课设如果模块多,建议直接用 EP4CE10,免得后面资源不够再回头换器件,那会触发一堆引脚重分配。
2.3 原理图输入和 Verilog 混用的边界
实验 1 到实验 3 很多学校要求用原理图输入,后面才转 Verilog。原理图的好处是直观,但坏处是改起来麻烦,尤其是总线位宽对不上时,Quartus 的报错信息很隐晦。我一般建议:组合逻辑小的用原理图,一旦超过三层或者涉及多位总线,就换成 Verilog 描述,再生成符号给原理图调用。
// 一个带使能的 4 位二进制计数器,实验 5 常用 module counter4 ( input wire clk, input wire rst_n, input wire en, output reg [3:0] q ); always @(posedge clk or negedge rst_n) begin if (!rst_n) q <= 4'b0000; else if (en) q <= q + 1'b1; end endmodule这段代码的关键在rst_n是低电平复位,en是高电平使能。参数上,q位宽是 4,对应数码管显示 0~F。综合后生成符号,在原理图里把clk接到开发板的 50MHz 时钟,rst_n接按键,en接拨码开关。注意时钟引脚必须分配到专用时钟管脚,否则 Quartus 会报no clock constraint的警告,仿真可能正常但烧板子不跑。
3. 数码管、ALU 与存储器:三个最容易翻车的实验
3.1 数码管译码:共阴共阳和动态扫描的坑
热搜里“quartus 原理图 数码管 实验”排得很靠前,说明这是多数人第一个卡点。数码管分共阴和共阳,译码逻辑正好相反。西南交大开发板上的数码管一般是共阳,段选低电平有效。如果你按共阴写译码表,烧进去要么全亮要么全灭。
// 共阳数码管 0~F 译码,段选低有效 module seg_decoder ( input wire [3:0] bin, output reg [6:0] seg ); always @(*) begin case (bin) 4'h0: seg = 7'b1000000; // 0 4'h1: seg = 7'b1111001; // 1 4'h2: seg = 7'b0100100; // 2 4'h3: seg = 7'b0110000; // 3 4'h4: seg = 7'b0011001; // 4 4'h5: seg = 7'b0010010; // 5 4'h6: seg = 7'b0000010; // 6 4'h7: seg = 7'b1111000; // 7 4'h8: seg = 7'b0000000; // 8 4'h9: seg = 7'b0010000; // 9 4'hA: seg = 7'b0001000; // A 4'hB: seg = 7'b0000011; // b 4'hC: seg = 7'b1000110; // C 4'hD: seg = 7'b0100001; // d 4'hE: seg = 7'b0000110; // E 4'hF: seg = 7'b0001110; // F default: seg = 7'b1111111; // 灭 endcase end endmoduleseg的 7 位对应g,f,e,d,c,b,a,共阳时 0 亮 1 灭,所以7'b1000000只有 g 段灭,显示 0。参数上,如果你板子是共阴,把每个值按位取反即可。动态扫描时,位选信号切换频率要够快,一般每位数码管点亮 1ms 左右,整屏刷新率高于 50Hz 人眼就不闪。我一般用 1kHz 的扫描时钟,分频系数按clk_freq / 1000 / 位数算。
3.2 ALU 实验:进位链和溢出标志的验证方法
实验里做 ALU 时,组间串行进位和超前进位是常见要求。热搜里“计算机组成原理组间串行进位”也印证了这点。串行进位结构简单但延迟大,超前进位快但逻辑复杂。仿真时不能只看结果对不对,还要看进位标志CF和溢出标志OF的时序。
// 4 位 ALU 核心片段,带进位和溢出 module alu4 ( input wire [3:0] a, input wire [3:0] b, input wire [2:0] op, output reg [3:0] y, output reg cf, output reg of ); reg [4:0] temp; always @(*) begin cf = 0; of = 0; temp = 5'b0; case (op) 3'b000: begin temp = {1'b0,a} + {1'b0,b}; y = temp[3:0]; cf = temp[4]; end 3'b001: begin temp = {1'b0,a} - {1'b0,b}; y = temp[3:0]; cf = temp[4]; end // 其他操作略 default: y = 4'b0; endcase // 溢出:同号相加结果异号,或异号相减结果与减数同号 of = (a[3] == b[3]) && (y[3] != a[3]); end endmoduletemp用 5 位是为了捕获进位。cf直接取最高位。of的判断逻辑是:两个同号数运算,如果结果符号变了,就溢出。仿真时给a=4'b0111,b=4'b0001,op=000,预期y=1000,cf=0,of=1。如果of没置起来,检查是不是把减法溢出判断也套用了加法逻辑,减法溢出条件是a[3] != b[3]且y[3] != a[3]。
3.3 存储器读写:时序仿真和板级差异
实验 7、8 通常涉及 RAM 或 ROM。Quartus 里用MegaWizard生成altsyncram是最稳的。但仿真和上板经常不一致,原因是仿真模型没有真实器件的建立保持时间。我一般会在 Testbench 里加 20ns 的输入延迟,模拟引脚上的走线延迟。
// 简单的 RAM 读写 Testbench 片段 initial begin clk = 0; forever #10 clk = ~clk; // 50MHz end initial begin we = 0; addr = 0; din = 0; #25; // 等待复位释放 we = 1; addr = 4'h0; din = 8'hA5; #20; we = 0; addr = 4'h0; #20; if (dout !== 8'hA5) $display("RAM read fail: %h", dout); $stop; end#25的延迟是为了避开复位释放后的第一个时钟沿竞争。we拉高一个周期写入,再拉低读同一地址。如果dout不是A5,先查altsyncram的读延时参数,是 1 还是 2 个时钟周期。板级差异还体现在按键抖动上,写使能如果直接接按键,不加消抖会写入多次。常见做法是加一个 20ms 的消抖模块,或者用开发板上的专用复位芯片。
4. 课程设计:把散件拼成能跑指令的 CPU
4.1 课设的最小指令集和模块划分
课程设计一般要求做一个简易 CPU,能执行加法、减法、跳转、存取数等几条指令。我建议先定指令集,再画数据通路。指令集不要贪多,5 条以内就能体现原理。模块划分上,控制器、ALU、寄存器堆、存储器、PC 这五个是基本盘。热搜里“计算机组成原理白中英”和“计算机组成与结构”的教材体系,基本也是这个划分。
// 顶层 CPU 模块的端口定义 module cpu_top ( input wire clk, input wire rst_n, input wire [7:0] sw, // 拨码开关输入 output wire [7:0] led, // LED 输出 output wire [6:0] seg, // 数码管段选 output wire [3:0] an // 数码管位选 ); wire [7:0] pc_out, instr, alu_y, reg_a, reg_b; wire [3:0] reg_addr; // 实例化各子模块 pc u_pc (.clk(clk), .rst_n(rst_n), .pc_out(pc_out)); imem u_imem (.addr(pc_out), .instr(instr)); ctrl u_ctrl (.instr(instr), .alu_op(alu_op), .reg_we(reg_we), .mem_we(mem_we)); regfile u_regfile (.clk(clk), .we(reg_we), .ra(ra), .rb(rb), .rw(rw), .wd(alu_y), .qa(reg_a), .qb(reg_b)); alu u_alu (.a(reg_a), .b(reg_b), .op(alu_op), .y(alu_y)); dmem u_dmem (.clk(clk), .we(mem_we), .addr(alu_y[3:0]), .din(reg_b), .dout(led)); endmodule这个顶层把 PC、指令存储器、控制器、寄存器堆、ALU、数据存储器串起来。instr从imem读出,ctrl根据instr产生控制信号。参数上,pc_out位宽 8 位,寻址 256 条指令;reg_addr4 位,对应 16 个寄存器。注意imem和dmem要分开,指令和数据共用存储器会引入结构冒险,课设阶段没必要增加这个复杂度。
4.2 控制器的状态机写法与仿真激励
控制器是课设里最容易写乱的部分。我一般用三段式状态机:第一段时序逻辑更新当前状态,第二段组合逻辑判断次态,第三段组合逻辑输出控制信号。这样写综合出来的电路稳定,也方便加default分支避免锁存器。
// 三段式状态机片段 localparam S_FETCH = 3'd0, S_DECODE = 3'd1, S_EXEC = 3'd2, S_WB = 3'd3; reg [2:0] cur_state, nxt_state; always @(posedge clk or negedge rst_n) if (!rst_n) cur_state <= S_FETCH; else cur_state <= nxt_state; always @(*) begin nxt_state = cur_state; case (cur_state) S_FETCH: nxt_state = S_DECODE; S_DECODE: nxt_state = S_EXEC; S_EXEC: nxt_state = S_WB; S_WB: nxt_state = S_FETCH; default: nxt_state = S_FETCH; endcase end always @(*) begin reg_we = 0; mem_we = 0; alu_op = 3'b000; case (cur_state) S_EXEC: begin if (instr[7:4] == 4'h1) begin alu_op = 3'b000; reg_we = 1; end else if (instr[7:4] == 4'h2) begin alu_op = 3'b001; reg_we = 1; end else if (instr[7:4] == 4'h3) begin mem_we = 1; end end default: ; endcase endcur_state和nxt_state用 3 位,四个状态够用。S_EXEC里根据instr[7:4]判断操作类型,4'h1是加法,4'h2是减法,4'h3是存数。仿真时给一个复位脉冲,然后观察pc_out是否每个周期加 1,reg_we是否在加法指令时拉高。如果pc_out不动,查时钟有没有接到pc模块,或者复位是不是一直有效。
4.3 引脚分配和烧录前的检查清单
引脚分配是课设最后一步,也是最容易把板子搞坏的一步。分配前先确认开发板的原理图,尤其是时钟、复位、数码管、拨码开关的管脚号。我一般会列一个检查清单:时钟是否分配到专用时钟脚,复位是否低有效,数码管位选是否经过三极管驱动,拨码开关是否有上拉。烧录前用Programmer里的Auto Detect确认器件识别正常,再点Start。
# 批量导入引脚分配的 Tcl 片段 set_location_assignment PIN_E1 -to clk set_location_assignment PIN_M15 -to rst_n set_location_assignment PIN_A3 -to seg[0] set_location_assignment PIN_B4 -to seg[1] # 其余引脚按开发板手册补全set_location_assignment第一个参数是管脚号,第二个是顶层端口名。PIN_E1是 Cyclone IV 的专用时钟输入脚。如果分配完编译报pin already assigned,说明.qsf里有重复条目,去Assignments -> Pin Planner里删掉冲突的。烧录后如果数码管不亮,先量位选三极管的基极电压,再看段选限流电阻有没有焊错。
5. 避坑与排查:实验 1~8 和课设里最常见的五类翻车
5.1 现象:波形仿真正常,烧到板子没反应
原因通常是引脚分配错了,或者时钟没接对。仿真时clk是 Testbench 里产生的理想信号,上板后如果clk分配到了普通 IO 而不是专用时钟脚,信号质量差,内部逻辑收不到有效时钟。解决方法是查开发板手册,把clk改到PIN_E1或对应的专用时钟脚,并在.qsf里加set_instance_assignment -name GLOBAL_SIGNAL ON -to clk。
5.2 现象:数码管显示乱码或重影
乱码多半是译码表共阴共阳搞反了,重影是动态扫描频率太低或者位选切换时没有消隐。解决方法是先确认板子数码管类型,共阳就把译码值取反;扫描频率提到 1kHz 以上,并在位选切换时先把段选置灭,再切换位选,最后输出新段码。
5.3 现象:综合报错 “top-level entity not found”
原因是工程顶层实体名和文件里的模块名不一致,或者.qpf里指定的顶层被删了。解决方法是右键顶层文件,选Set as Top-Level Entity,然后确认module后面的名字和文件名一致。如果还报错,检查.qsf里有没有TOP_LEVEL_ENTITY字段,手动改成正确的模块名。
5.4 现象:课设 CPU 跑飞,PC 值乱跳
多半是状态机没有default分支,综合出了锁存器,或者复位没有同步释放。解决方法是给case加default,复位信号用两级触发器同步后再送到各模块。另外检查pc的加法逻辑,如果pc_out位宽不够,溢出后会回绕到 0,看起来就像跑飞。
5.5 现象:存储器读写数据不一致
原因是读延时参数和 Testbench 里的采样时刻对不上。altsyncram默认读延时是 1 个时钟周期,如果 Testbench 在地址变化后立刻采样,读到的就是旧数据。解决方法是在 Testbench 里地址变化后等一个时钟沿再采样,或者把altsyncram的read_during_write参数设为OLD_DATA,明确读写冲突时的行为。
6. 进阶技巧:用 SignalTap 抓内部信号和课设验收前的自检
做到课设阶段,光靠仿真已经不够了,因为板级时序问题仿真覆盖不到。我一般会在 Quartus 里嵌一个 SignalTap II,抓pc_out、instr、alu_y这几个关键信号。SignalTap 的采样时钟用系统时钟,采样深度设 1K 到 2K,触发条件设在pc_out等于某个异常值上。这样跑飞的时候能直接看到是哪条指令出了问题,比盲猜快得多。
# 在 Tcl Console 里快速打开 SignalTap # 先确保工程已经编译过 execute_flow -compile # 然后手动在 GUI 里 File -> New -> SignalTap II Logic Analyzer File # 添加节点时,Filter 选 "SignalTap: pre-synthesis" 能看到更多内部信号SignalTap 的采样深度和器件 RAM 资源有关,EP4CE10 一般能开到 2K 深度而不影响布局。触发条件我通常设成pc_out == 8'hFF或者instr == 8'h00,因为跑飞后 PC 往往停在非法地址。抓到的波形可以导出 CSV,和仿真波形对比,很快能定位到是控制器状态错了还是数据通路断了。
验收前我还有一个自检习惯:把课设的每条指令单独写一个 Testbench,跑一遍,确认单条指令的reg_we、mem_we、alu_op和预期一致。然后再跑一个混合指令的序列,看 PC 是否按顺序推进。这个习惯帮我省了很多返工时间,因为课设验收时老师往往随机给几条指令,单条没问题,混合起来才暴露冒险和冲突。
最后说一个我踩过的坑:课设的顶层模块不要放太多逻辑,尽量只做例化。我有一次把译码逻辑直接写在顶层,结果 SignalTap 抓不到内部信号,排查花了整整一个下午。后来改成顶层只连线,所有逻辑下沉到子模块,SignalTap 一抓一个准。希望帮到你。
本文还有配套的精品资源,点击获取