计组课设做到实验三的时候,很多人的心态会经历一次从“我懂了”到“我哪儿没懂”的回撤。我在软院做这门课设那年,实验三验收前夜,宿舍群里突然有人发了一张仿真截图:RAM输出一列全是0,地址明明在跳,数据就是不出来。接下来二十分钟,大家围绕“是时钟接反了还是片选没拉低”吵成一团。最后发现问题特别简单——输出使能压根没接。这个画面我一直记到现在,因为它基本代表了计算机组成原理实验三的本质:把理论课上背过的引脚和时序,第一次真正接成一个能跑的电路。
不同学校的实验三内容会有差异,有的做运算器,有的做存储器,有的直接上CPU数据通路。我按当年山大软院课程设计里最常见的形态——存储器实验——来展开,你如果做的是别的方向,原理部分照样能对上。这篇文章会把实验前后我认为最关键的东西理一遍:从“为什么要做这个实验”到“搭电路时怎么排错”,再到“验收时老师到底在问什么”,都属于课程设计过程中不太会有人主动跟你讲清楚的部分。
1. 实验三在整门课里的位置:从“看懂”到“搭出来”的临界点
1.1 部件实验和系统实验之间的那道坎
计算机组成原理前几个实验,说句实话,大多数人都能靠“抄作业”混过去。实验一通常是寄存器堆或者简单的运算器,照着PPT把线一连,拨几个开关,结果就出来了。这时候你不需要真正理解数据是怎么流动的,因为所有模块都已经被封装好了,你只是在拨开关看现象。
实验三不一样。它往往是第一个需要你“自己设计接口”的实验。以存储器实验为例,RAM不是像寄存器堆那样只要给地址就同步输出,它有片选、有写使能、有输出使能、有时序要求,还有扩展问题。你被迫开始思考:地址到底什么时候稳定?数据应该什么时候打进去?为什么读出来的数据会晚半拍?这些问题在实验一里完全不会遇到,但它们是之后做CPU数据通路、做流水线控制的核心。
从课程设计的任务书来看,实验三通常被放在“部件实验”和“系统实验”之间,意思很明确:你前面对各个部件的理解,要在这一轮实验里凝结成一种“接口意识”。后面做单周期CPU的时候,寄存器堆、ALU、存储器要连在一起跑指令,任何一个接口没想清楚,整个CPU都跑不出正确结果。所以把实验三看成整门课的分水岭,一点都不夸张。
1.2 仿真平台和实验箱:从效率角度你应该怎么选
当年我们做实验可以选两条路:一是在虚拟仿真平台上搭电路,二是去实验室用实验箱连线。我自己的经验是,两个都要做,但顺序很关键——先在仿真环境里把电路跑通,再拿实验箱做一遍验证。如果你一上来就扎进实验箱,光查找断线和跳线开关就能耗掉一晚上。
仿真工具我用的是Logisim。它有个特别实在的优点:你能看见每一根线当下的状态值,能手动置位任何输入,能连续给时钟,还能在电路中间插探针。这种“看得见内部”的特性,对理解存储器时序帮助极大。你可以在RAM的数据输入口拉一组八位开关,先写入几个已知值,再切到读模式,观察输出是否稳定复现,整个过程都在自己的掌控里。
如果你们用的平台是star cop 2018这类虚拟实验系统,操作逻辑会有些不一样。这类平台一般更贴近真实实验箱,有面板、有接线区、有波形窗口。使用手册建议拿到手先别急着连电路,先把“存储器实验”这一章从头到尾翻一遍,重点关注两个地方:一是片选信号是低有效还是高有效,二是写使能是上升沿写入还是整个高电平期间都能写。这两个参数在不同平台上的实现往往不一样,纯靠猜很容易栽跟头。手册上通常都有时序图,照着时序图接线,比对着原理图接线可靠得多。
1.3 如果你做的是ALU方向的实验三
很多学校把ALU实验放在第三个,搜索词里“组间串行进位”出现频率很高,我多提一句。这类实验的核心是快速加法器的进位结构,和存储器实验是两个完全不同的方向。它更偏组合逻辑设计,不需要时钟,不需要考虑时序,验证方式就是把输入的加数和被加数拨成二进制,看结果满不满足预期。
这种实验最容易踩的坑是进位链层级错误。四位一组的CLA做内部先行进位,组与组之间再用串行方式连起来,听起来简单,连的时候经常把组内进位和组间进位混在一起。我的建议是画一张表,把每一组的进位输入、组内产生的进位、传出去的进位分别列出来,再动手连接。后面第4章我会专门把组间串行进位的延迟计算和实现思路写清楚,做这个方向的同学可以直接跳过去看。
2. 动手之前:地址、数据、控制三种信号要先在脑子里跑通
2.1 地址线根数和存储容量就是一道2的幂次题
很多同学动手搭存储器,习惯直接拖一个RAM模块进画布,然后开始连线,连到一半才发现地址线根数不对。正确顺序应该是先算清楚“我这个存储器到底该有多大”。
n根地址线能区分2的n次方个存储单元,k根数据线决定了每个单元能存多少位二进制数。比如16K×8位的存储系统,意思是有16K个单元,每个单元8位。因为2的14次方等于16384,也就是16K,所以地址线要14根,数据线要8根。这类题目期末考试几乎必考,在实验三里同样逃不掉,因为你扩展芯片的时候必须自己算地址线够不够。
我见过很多同学做字扩展的时候,低地址线和高地址线分不清。低地址线直接连到每个芯片的地址引脚,高地址线要通过译码器产生片选信号。比如4片16×4的芯片扩展成64×4,需要6根地址线,其中低4根给每片芯片,高2根进2-4译码器,译码器输出的4个片选信号分别控制4片芯片。这个结构你必须亲手画一遍、算一遍,才会真正明白为什么实际存储器系统都要在CPU和内存芯片之间加译码器。
2.2 读时序和写时序:仿真里不写时序,电路里全是玄学
存储器实验和之前实验最大的区别,就是引入了“时序”这个概念。RAM芯片的读操作和写操作对信号出现的先后顺序有严格规定。读的时候,地址要先稳定下来,然后等一小段地址建立时间,再把输出使能拉低,过了访问时间之后数据才出现在数据线上。写的时候更讲究,写使能拉低的这段时间内,地址和数据都不能变化。
Logisim里的RAM默认是异步读、时钟上升沿写,这里就有个特别容易误解的地方。很多人以为只要地址一变输出马上跟着变,其实不对。实际RAM芯片有访问时间,地址变化到数据输出稳定之间存在延迟。如果你连了一排LED灯用来显示数据,地址变化瞬间灯泡可能短暂闪烁一下,这是正常的物理表现。在仿真工具里你看到的是输出瞬间变化,但在硬件平台或者实际芯片上,这个延迟就是真实存在的。
做实验的时候,建议在报告里把读时序和写时序各画一张图,标清楚地址建立时间、数据建立时间、写使能宽度。这既是老师想看到的“理论结合实验”,也是你自己理清思路的过程。时序图画完,你会发现后面做CPU的时候,控制信号为什么要有那些奇怪的先后关系,一下子就讲得通了。
2.3 高阻态和总线冲突:为什么不能把输出直接挂在总线上
第三个必须在动手前想明白的概念是高阻态。三态门这个知识点,理论课上讲过,但很多人是从实验三开始才真正理解“第三态”存在的意义。
数据总线是共享的,多个芯片的输出本来就可能连在同一组线上。问题是,如果两个芯片同时把数据放到总线上,总线上的数据就会冲突,读出来就是乱的。解决办法是让每个芯片的输出通过三态门再连接到总线,平时输出端是高阻态,相当于把门关上,只有被片选选中的那个芯片才把数据放到总线上。
实验三做字扩展的时候,这个问题尤其致命。你用2-4译码器产生片选信号,每一时刻只有一个芯片被选中,其他芯片的输出都是高阻态,数据总线上的数据就是安全的。但如果连线疏忽,把片选信号接反或者译码器输出悬空,两个芯片同时被选中,总线直接打架,数据怎么读都是错。排查这类问题的方法很简单:把每个片选引脚上用探针或者LED灯显示出来,手动改变高地址位,看是不是只有对应的灯亮。如果是两个灯一起亮,说明译码器接线肯定有问题。
3. 用Logisim搭存储器的完整过程:从一片RAM到扩展阵列
3.1 第一步:先搭一个16×8的RAM子电路并验证读写
我不建议一上来就搭扩展结构,先把最基本的单芯片读写跑通。打开Logisim,在Memory库里找到RAM组件,双击修改属性:Data Bits设为8,Address Bits设为4。这表示16个存储单元,每个单元8位。
连接的时候把每个引脚弄清用途。地址端口接一组四位开关,数据输入接一组八位开关,数据输出接一组LED灯或八位探针。RAM的时钟引脚需要用时钟源提供,没有时钟它不会工作。写使能引脚接一个开关,你需要手动拉高或拉低来控制读写模式。
验证流程分成两步。第一步写:把地址设成0000,数据输入设成你想要的二进制数,比如00001111,把写使能拉高,手动触发一次时钟上升沿,数据就被写入。第二步读:把写使能拉低,地址保持0000,观察输出端是不是真的出现00001111。如果你把地址改成0001,输出应该变成之前写进0001单元的内容,而不是0000单元的内容。
这一步看起来简单,但它是后面所有工作的基础。如果写操作和读操作在这个阶段对不上,问题基本集中在三个方面:时钟极性设反了、写使能开关根本没生效、数据输入线和输出线接错。这三个问题解决掉,后面扩展时排错会少很多。
3.2 第二步:用计数器当地址发生器,观察连续读写
手拨开关验证读写没问题后,接下来就是把地址发生器加进来。这一步最有意思,它帮很多同学想明白了“程序计数器PC到底在干什么”。
做法也很直接:用一个四位计数器,时钟输入和RAM的时钟接同一根信号,计数器的四位输出连到RAM的地址端口。每来一个时钟上升沿,计数器加1,地址就自动从0走到15。RAM的数据输出端接上二进制显示器或者探针,你就能连续看到一串数据被读出来。
如果没有在对应地址预先写入数据,读出来全是0,这是正常的。所以更完整的做法是:先用3.1的手动写入方式,往16个单元里分别写入一组递增的数据,然后再切到计数器自动模式,让地址自己跑,验证读出顺序也是递增的。
这一步做完,你已经摸到了“取指令”的门槛。CPU里的程序计数器,本质上就是一个能自动加1的计数器,唯一区别是CPU的PC还能被跳转指令修改。实验三用计数器做地址发生器,其实就是把CPU取指令这段逻辑单独拆出来给全班看一遍。所以实验报告里一定要把这个联系写出来,点明“计数器产生地址→存储单元输出数据”这个过程和CPU取指令阶段的对应关系,这通常是加分项。
3.3 第三步:位扩展和字扩展,把容量变大
芯片扩展是实验三的重头戏,也是期末考试反复出现的计算题。扩展分成两条路,位扩展和字扩展。
位扩展是为了增加数据线宽度,比如用4片16×4的芯片组成16×16的存储系统。做法是把所有芯片的地址线并联,写使能并联,片选并联,但每个芯片的数据线分别接总线的不同位段,第一片接低4位,第二片接第5到第8位,以此类推。这样同一时刻所有芯片都在工作,合起来输出一个16位数据。
字扩展是为了增加存储单元的个数,比如用4片16×8的芯片组成64×8。这时候数据线是共享的,地址线要分成两组:低4位直接连到每片芯片的地址端口,高2位接一个2-4译码器,译码器输出分别接到4片芯片的片选端。在这个结构里,每一时刻只有一片芯片被选中,其他芯片输出都是高阻态,数据总线上才不会冲突。
实验里最常见的问题是位扩展和字扩展混着做时,地址线和数据线接串了。我的建议是画一张扩展前后对比表,把“扩展前几根地址线、几根数据线,扩展后几根地址线、几根数据线”填进去,再动手连线。这张表同时也是报告里很漂亮的一张图。
3.4 仿真中躲不过的三种异常和排查链路
做Logisim仿真,下面三个问题基本每个人都会碰到一次,我把排查思路写清楚,遇到了可以直接照着走。
数据读出全为0:先看写使能是不是被拉低了,再看RAM有没有接时钟,最后看数据输入引脚有没有被意外接地。很多人把一行未连接的悬空引脚当成0,其实悬空状态在某些电路里是既不是0也不是1的悬浮电平,读出来全0只是因为实际拉到了默认电位,并不代表写入成功。
输出数据和地址差了一拍:有时候你设置地址0001,但输出显示的还是0000单元的内容。这大概率是读操作存在延迟,或者时钟边沿还没触发。Logisim的RAM如果是同步读,输出会在时钟上升沿之后更新,你需要等一个时钟周期再观察。这种“差一拍”现象在后面做CPU的时候也经常出现,处理办法是调整控制信号的产生时机,而不是怀疑电路接错了。
扩展之后总线上数据乱跳:这是最典型的芯片同时选了问题。用探针分别检查每片芯片的片选引脚,看同一时刻是不是有两片以上同时有效。如果是,检查译码器的地址输入接的是不是高地址位,输出有没有接反。记得片选信号往往是低有效,译码器输出的正逻辑需要再加非门才能对上芯片的要求。
4. 实验三里的理论考点:报告里必须写清楚的几个“为什么”
4.1 读优先还是写优先,会产生什么实际影响
RAM芯片内部实现有两种基本策略:读优先和写优先。这俩词听起来像概念游戏,实际影响特别大,做CPU的时候绕不开。
所谓读优先,就是当读使能和写使能同时有效时,数据输出端口反映的是当前地址对应的存储内容,写操作先缓一缓。写优先则相反,数据输出端口反映的是正在被写入的数据。Logisim里的RAM默认是异步读,你在仿真里观察到的行为更接近读优先。而实验箱上有些SRAM芯片是写优先的,如果同一个时钟周期内既要读又要写,结果会不一样。
这个东西为什么要写进实验报告?因为单周期CPU设计里,寄存器堆和数据存储器对读写时序的要求完全不同。寄存器堆通常要求能同时读两个寄存器,写则发生在时钟边沿;数据存储器往往希望读操作是组合逻辑、立即出结果。搞清楚你用的存储器是读优先还是写优先,才能决定ALU的计算结果什么时候送进来、什么时候打入寄存器,控制信号怎么配合。很多同学后面做CPU卡壳,其实就是卡在这。
4.2 指令存储器和数据存储器:为什么一个只读一个可写
存储器实验做完后,有的同学会问:既然我有RAM了,是不是下一周直接拿它来存指令就行?这个问题背后是冯·诺依曼结构和哈佛结构的区别,老师很喜欢在验收时问。
RAM是可读可写的,适合放数据。指令存储器不需要在运行中被改写,所以通常做成ROM或者只读的RAM,FPGA内部也常见用ROM资源来实现。冯·诺依曼结构把指令和数据放在同一个存储器里,靠总线分时访问;哈佛结构把指令存储器和数据存储器物理分开,可以同时取指和访问数据,吞吐率更高。实验三做的RAM本质上对应的是数据存储器,后面的指令存储器实验,你大概率要再建一个只读存储块。
这个知识点我认为不要只停留在背定义。做CPU实验时,你会看到取指令阶段从“指令存储器”读出一条指令,执行阶段又从“数据存储器”读或写运算结果,两个存储器功能完全不同。理解了实验三的存储器是哪种,后面CPU的连线就不再是一团乱麻。
4.3 组间串行进位和实验三的关系
如果你做的是运算器方向的实验三,组间串行进位几乎就是核心考点。这里我把原理说透,免得光看PPT最后还是不会连。
组间串行进位是针对并行加法器的优化。一个4位先行进位加法器(CLA)可以在不看低位进位的情况下,同时算出四位和以及传出去的进位,组内延迟很短。但16位加法器不用直接做16位先行进位,那样逻辑太复杂了。实际做法是分成4组,每组内部用先行进位,组与组之间串行传递进位。这就是“组内并行、组间串行”的意思。
延迟怎么算?假设每组内部产生结果的延迟是t1,进位传到下一组的延迟是t2,那么16位加法器的总延迟大约是t1加上4个t2。对比普通串行加法器,每位进位都要一路传过去,延迟线性累加,组间串行的方案已经快了不少。实验里做这个东西,验证重点就是进位链是否正确传递,我建议测试的时候用边界值:先测0xFFFF加0x0001,看进位一路传到最高位,再测0x0FFF加0x0001,看进位是否只在一组内传播。
如果你们实验三恰恰又是存储器方向,组间串行进位不会直接出现在电路里,但它是理解ALU中加法器延迟的底层知识,期末问答题极爱考,所以我顺带写在这里,看热搜词的时候也留意一下。
4.4 计数器、PC和地址寄存器:实验三留下的三条线
实验三用计数器产生地址,这条线是和CPU联系最直观的。程序计数器PC本质上就是一个带自增功能的寄存器,每取完一条指令PC加1,遇到跳转指令再被修改成目标地址。你在实验三里验证过“计数器自动加1→存储器依次输出数据”,后面看到CPU里PC的功能时就会觉得非常自然。
另外两条线是地址寄存器和数据寄存器。地址寄存器(MAR)用来暂存CPU要访问的地址,保证整个访存周期内地址保持稳定;数据寄存器(MDR)用来暂存在存储器和CPU之间传输的数据。很多简化版教学CPU设计里并没有独立的MAR和MDR,而是直接用总线或者寄存器堆代替,但理解它们的存在对读复杂一点的CPU数据通路图很有帮助。
这三条线说到底是同一个思想:CPU访存时,地址从哪来、数据在哪停一下、谁控制什么时候读写。实验三你在做的正是“存储器的读写”,把这几个问题在实验层面体验一遍,后面学控制器、学总线时序,就不容易晕。
5. 实验报告和验收:从“做出来”到“讲清楚”才是高分的关键
5.1 报告里最容易丢分的三个位置
我做课设时帮同学改过不少实验报告,发现大家丢分的地方出奇地一致。第一个就是没有真值表或状态表。存储器实验至少要有两张表:一张是不同地址单元写入内容的汇总表,一张是读操作和写操作的控制信号组合表。这两张表一画,老师一眼就能看出你确实做了实验。
第二个是截图没有标注。很多报告截图里只有一堆线和信号灯,既不标信号名,也不写当前是读还是写,老师看得一头雾水。正确的做法是每张截图旁边用文字写清楚“当前地址是XXXX,WE为低电平,因此输出端为XX单元的数据”,这才是实验现象。
第三是把实验现象写成流水账。比如写“把地址设为0000,读出7,然后地址设为0001,读出8”,这种写法没有体现“设计预期”和“实际结果”的对照。更好的写法是分层:先写“根据电路设计,地址0000应当读出预写入的数据7”,再写“实际观察输出端为0111,与预期一致”。这块内容占不了多少篇幅,但整篇报告的逻辑层次会完全不同。
5.2 验收时老师最常追问的三个问题及参考答法
说句实话,大部分老师验收时并不会只看电路能不能跑通,他们真正想确认的是——这个实验是不是你自己亲手做出来的。所以追问的问题基本都是围绕电路设计的关键点展开。
第一个问题:为什么写使能和输出使能不建议同时有效?参考答法是:写使能有效表示总线数据要被写入存储单元,而输出使能有效表示要把存储单元的数据读到总线上,二者同时有效会产生信号冲突,导致总线数据不确定。实际SRAM芯片会通过内部时序来避免乒乓,但新手设计时还是应该人为保证二者互斥。
第二个问题:如果地址线增加一根,存储容量扩大几倍?这是送分题,答“翻倍”就行,但老师还会追问“为什么”,你要说出来:地址线每增加一根,可选择的存储单元数就乘以2,因为2的n+1次方除以2的n次方等于2。
第三个问题:扩展时片选信号从哪里来?这个问题考察的是字扩展的原理,参考答法是:片选信号由高地址位通过译码器产生,低地址位直接作为所有芯片的统一地址输入。每片芯片的片选端分别接译码器的一个输出,这样在任一时刻只有一片芯片被选中,其他芯片输出处于高阻态,保证总线不冲突。
这三个问题都不难,但突击复习很容易忽略。验收前我习惯自己做一次“自问自答”,指着电路图把每个引脚的作用讲一遍,讲不通的地方就去查手册,查完再讲。这个过程比多调一个晚上电路有用得多。
5.3 亲测有效的最后半小时检查清单
实验验收或者演示前半小时,别再改电路了,按下面的顺序过一遍,比临场加功能靠谱得多。
第一,检查悬空引脚。仿真平台里悬空引脚默认为0,但实验箱上悬空引脚可能引入噪声,导致偶尔读错数据。把所有未连接的输入引脚强制接上确定电平。
第二,检查时钟极性。特别是如果用了计数器又用了RAM,二者必须保证在同一时钟沿动作,否则地址先变还是数据先写会被打乱。最好从同一个时钟节点引出分支,不要用两个时钟源。
第三,准备几组“边界测试数据”。全0、全1、递增序列、随机序列各写几组,演示的时候用这些数据分别验证,看起来很专业,也能提前兜住意外情况。
第四,确认截图带时间信息或者实验箱面板带指示灯状态。这些现场证据能让报告和演示互相对应,老师会认为你的实验是完整、可追溯的。
第五,把实验手册里对应的时序图翻到那一页放旁边,验收时如果老师提到时序,你立刻能指出来,这种细节非常加分。
最后再补一条我自己的习惯:实验做完后,把每一根引脚为什么这么连写在一张草稿纸上,别急着扔。后面做单周期CPU实验,很多控制信号的接法都能从这张草稿纸里找到影子。计组课设最值钱的不是那几行仿真波形,而是你亲手把课本上的引脚和时序接成能跑的电路后,脑子里建立起的那张“信号连接网”。这张网一旦搭好,后面CPU实验再复杂,你也会觉得不过是在原来部件的基础上多加几条控制线而已。