1. 存储器扩展到底在解决什么问题
1.1 从一个真实的踩坑经历说起
刚接触计算机组成原理那会儿,我做过一个很蠢的事情。当时在仿真软件里搭了一个简单的CPU模型,指令译码、ALU、寄存器堆都调通了,跑几条算术指令没问题。然后我信心满满地往指令存储器里塞了一段稍微长一点的程序,结果仿真直接跑飞了。查了半天才发现,我用的那片SRAM模型只有1K的地址空间,程序稍微大一点就溢出了。
这件事让我意识到一个很基础但极其重要的问题:单颗存储芯片的容量和位宽,往往满足不了实际系统的需求。CPU需要更大的程序空间、更大的数据缓冲区,而市面上能买到的单颗芯片容量是有限的,数据总线宽度也未必和CPU匹配。这时候就必须做存储器的扩展。
所谓存储器扩展,说白了就是用多片小容量的存储芯片,拼出一个大容量、宽位宽的存储系统。它要解决的核心问题有三个:位扩展(增加数据位宽)、字扩展(增加存储单元数量)、字位同时扩展(两者都要)。这三个概念是计算机组成原理里存储器章节的绝对重点,也是考试和实际工程中都绕不开的基本功。
这篇文章我会把存储器扩展的完整逻辑拆开讲透:为什么这么设计、地址怎么分配、片选怎么接、时序要注意什么、实际搭电路时容易踩哪些坑。不管你是正在准备考试的学生,还是想自己动手搭一个简易计算机系统的爱好者,这些内容都能直接拿来用。
1.2 先搞清楚几个基础概念
在讲扩展之前,得先把几个术语对齐,不然后面全是糊涂账。
存储单元和存储字长:一个存储单元存放一个数据单位,通常是一个字节(8位)或者一个字(比如16位、32位)。存储字长指的是每个存储单元能存多少位。比如一片芯片标注"1K×8位",意思是有1024个存储单元,每个单元8位。
地址线和数据线:地址线决定了能寻址多少个单元,n根地址线可以寻址2的n次方个单元。数据线决定了每次能读写多少位。1K×8的芯片,需要10根地址线(2的10次方=1024),8根数据线。
片选信号(CS)和读写控制(WE/OE):片选信号决定这片芯片是否被激活,只有被选中的芯片才会响应读写操作。读写控制决定是读还是写。这两类信号是扩展电路设计的核心。
地址译码:CPU发出的地址需要经过译码,转换成对具体芯片的片选信号。译码方式直接决定了地址空间的分配,是扩展设计里最容易出错的地方。
理解了这几个概念,后面的位扩展、字扩展就都是它们的组合应用了。我个人的经验是,把"地址线管选哪个单元,片选线管选哪片芯片,数据线管传什么"这三句话刻在脑子里,扩展电路的设计思路就永远不会乱。
2. 位扩展:让数据总线变宽
2.1 位扩展的核心逻辑
位扩展要解决的问题是:单颗芯片的数据位宽不够,需要多片并联来凑齐CPU要求的数据总线宽度。
举个最典型的例子。假设CPU的数据总线是8位,但你手头只有1K×1位的存储芯片(每片只能存1位数据)。这时候你需要8片这样的芯片,每片负责数据总线上的1位,8片合起来才能提供一个完整的8位数据。
位扩展的关键特征:所有芯片共享同一套地址线和片选信号,但各自的数据线分别接到数据总线的不同位上。因为每片芯片存储的是同一个地址下的不同数据位,所以它们的地址必须完全一致,片选也必须同时有效。
我打个比方。位扩展就像8个人合作记录一个8位数的电话号码,每个人只负责记其中一位数字。当你要查某个位置的号码时,8个人必须同时被叫到,各自报出自己负责的那一位,拼起来才是完整的号码。地址就是"第几个电话号码",片选就是"叫这8个人一起过来"。
2.2 位扩展的接线方法
具体接线规则如下:
- 地址线:所有芯片的地址线全部并联,接到CPU地址总线的低位部分。1K容量需要10根地址线,8片芯片的A0到A9全部对应相连。
- 数据线:第1片的数据线接数据总线的D0,第2片接D1,以此类推,第8片接D7。每片只占1位。
- 片选信号:所有芯片的CS端并联在一起,由同一个片选信号控制。因为位扩展的芯片是"同生共死"的,要选就一起选。
- 读写控制:所有芯片的WE(或OE)端并联,统一由CPU的读写信号控制。
这里有个容易搞混的点:位扩展之后,总的存储单元数量没有变,还是1K个单元,但每个单元的位数从1位变成了8位。所以地址线的数量不变,仍然是10根。
注意:位扩展时,如果某片芯片坏了,整个数据字的某一位就会出错,表现为读出的数据某一位恒为0或恒为1。排查时可以用写入全0和全1的方式,看哪一位不对,就能定位到具体是哪片芯片的问题。
2.3 位扩展的容量计算
位扩展后的总容量计算公式很简单:
总容量 = 单元数 × (单片位数 × 芯片数)
以上面的例子:1K × (1位 × 8片) = 1K × 8位 = 8Kb = 1KB。
这里要注意单位换算。1KB = 8Kb,因为1字节等于8位。很多人在计算时把Kb和KB搞混,导致算出来的容量差8倍。我的建议是统一换算成位(bit)来算,最后再根据需要转成字节。
位扩展在实际中还有一个变种:用2片4位芯片扩展成8位。这种场景更常见,因为4位宽的SRAM或DRAM芯片在历史上很普遍。接线逻辑完全一样,只是芯片数量从8片变成2片,每片负责4位数据。
3. 字扩展:让存储空间变大
3.1 字扩展要解决的核心矛盾
位扩展解决的是"数据不够宽"的问题,字扩展解决的是"装不下"的问题。
假设你有一片1K×8位的芯片,但你的系统需要4K×8位的存储空间。单片芯片只有1024个单元,怎么才能凑出4096个单元?答案是用4片1K×8的芯片,通过地址译码把它们的地址空间拼接起来。
字扩展的关键特征和位扩展正好相反:所有芯片的数据线并联(共享同一组数据总线),但片选信号各自独立。因为同一时刻只需要一片芯片工作(CPU一次只访问一个地址),所以4片芯片分时复用数据总线。
继续用打电话的比喻。字扩展就像4个接线员,每人负责一段电话号码区间。你拨的号码落在哪个区间,就由哪个接线员接听。同一时刻只有一个接线员在工作,但他们共用同一条电话线(数据总线)。
3.2 地址空间的分配逻辑
字扩展最核心的问题是:如何用高位地址产生片选信号。
还是用1K×8扩展成4K×8的例子。4K容量需要12根地址线(2的12次方=4096)。每片芯片需要10根地址线(1K=2的10次方)。那么12根地址线怎么分配?
答案是:低10位(A0-A9)接所有芯片的地址输入端,高2位(A10-A11)用来产生片选信号。
高2位有4种组合:00、01、10、11,正好对应4片芯片:
| A11 | A10 | 选中芯片 | 地址范围 |
|---|---|---|---|
| 0 | 0 | 芯片1 | 000H - 3FFH |
| 0 | 1 | 芯片2 | 400H - 7FFH |
| 1 | 0 | 芯片3 | 800H - BFFH |
| 1 | 1 | 芯片4 | C00H - FFFH |
这个表格是字扩展的核心。每一片芯片占据一段连续的地址空间,4片拼起来就是完整的4K空间。CPU发出一个地址,高2位决定选哪片,低10位决定片内哪个单元。
3.3 译码器的选择与使用
产生片选信号最常用的器件是译码器,比如2-4译码器、3-8译码器。2-4译码器有2个输入端、4个输出端,正好把A10和A11的4种组合翻译成4个片选信号。
用译码器的好处是:输出信号互斥,同一时刻只有一个输出有效,天然保证了不会同时选中多片芯片。如果用门电路自己搭,很容易出现竞争冒险或者逻辑错误。
常见的译码器型号有74LS139(双2-4译码器)、74LS138(3-8译码器)。选型时主要看两点:需要多少个片选信号,以及译码器的使能端怎么接。74LS138有3个使能端(G1、G2A、G2B),可以方便地实现地址空间的进一步划分。
实操心得:译码器的输出通常是低电平有效,而很多存储芯片的片选CS也是低电平有效,可以直接相连。但如果遇到高电平有效的片选,中间要加一个反相器。我第一次搭电路时就因为没注意极性,调了半天才发现片选一直是反的。
3.4 字扩展的容量计算
字扩展后的总容量:
总容量 = (单片单元数 × 芯片数) × 单片位数
上面的例子:1K × 4片 = 4K个单元,每个单元8位,总容量4K×8位 = 32Kb = 4KB。
注意字扩展后数据位宽不变,变的只是单元数量。地址线增加了log2(芯片数)根。4片需要增加2根地址线,8片需要增加3根。
4. 字位同时扩展:实战中最常见的场景
4.1 什么时候需要字位同时扩展
实际工程中,很少只做位扩展或只做字扩展,大多数情况是两者同时进行。
比如:用1K×4位的芯片,要组成4K×8位的存储系统。这时候既需要增加位数(从4位到8位),又需要增加单元数(从1K到4K)。
解决思路是分两步走:先做位扩展,再做字扩展。或者反过来,先字后位,结果一样。
具体来说:先用2片1K×4的芯片做位扩展,组成1K×8的存储模块。然后用4个这样的模块做字扩展,组成4K×8的系统。总共需要2×4=8片芯片。
4.2 芯片数量的计算方法
字位同时扩展时,芯片数量的计算公式:
所需芯片数 = (目标单元数 / 单片单元数) × (目标位数 / 单片位数)
以上面的例子:(4K / 1K) × (8位 / 4位) = 4 × 2 = 8片。
这个公式很好记:横向扩展倍数乘以纵向扩展倍数。横向是位数方向,纵向是单元数方向。
再举一个例子巩固一下。用16K×8位的芯片组成128K×32位的存储系统:
- 单元数方向:128K / 16K = 8倍
- 位数方向:32位 / 8位 = 4倍
- 总芯片数:8 × 4 = 32片
4.3 地址线的分配策略
字位同时扩展时,地址线的分配需要仔细规划。原则是:先满足片内寻址,再用剩余高位做片选。
假设用1K×4的芯片组成4K×8的系统:
- 总地址空间4K,需要12根地址线(A0-A11)
- 单片容量1K,片内寻址需要10根地址线(A0-A9)
- 剩余高位A10-A11共2根,用于片选译码,产生4个片选信号
每个片选信号同时控制2片芯片(因为这2片是位扩展关系,必须同时工作)。所以译码器的每个输出要驱动2片芯片的CS端。
这里有个细节:位扩展的芯片必须共享同一个片选信号,因为它们存储的是同一个地址下的不同数据位。如果给它们不同的片选,读出的数据就会错位。
4.4 一个完整的接线示例
我用一个具体的例子把整个接线过程走一遍。
目标:用1K×4位的SRAM芯片,组成2K×8位的存储系统。
第一步:确定芯片数量
- 单元数:2K / 1K = 2倍
- 位数:8位 / 4位 = 2倍
- 总芯片数:2 × 2 = 4片
第二步:分组
- 位扩展组1:芯片1和芯片2,组成1K×8
- 位扩展组2:芯片3和芯片4,组成1K×8
第三步:地址线分配
- 总地址空间2K,需要11根地址线(A0-A10)
- 片内寻址需要10根(A0-A9)
- 片选需要1根(A10)
第四步:接线
- A0-A9:4片芯片全部并联
- 数据线:芯片1和芯片3接D0-D3,芯片2和芯片4接D4-D7
- 片选:A10直接作为片选信号(或经译码器)。A10=0时选中组1(芯片1、2),A10=1时选中组2(芯片3、4)
- 读写控制:4片芯片的WE端全部并联
第五步:地址范围验证
- 组1:A10=0,地址范围000H-3FFH
- 组2:A10=1,地址范围400H-7FFH
- 合起来正好覆盖000H-7FFH,共2K个单元
这个例子里,A10只有1根,可以直接用作片选,不需要译码器。但如果片选信号多于2个,就必须用译码器了。
5. 片选信号的产生方式与地址译码
5.1 线选法:简单但浪费地址空间
产生片选信号最简单的方法是线选法:直接用某一根高位地址线作为某片芯片的片选信号。
比如用4片1K×8芯片组成4K×8系统,需要12根地址线。线选法可以用A10选芯片1、A11选芯片2……但这样只能选2片(因为只有A10和A11两根高位)。要选4片,得用4根高位地址线,那就意味着地址空间会变得很大,而且不连续。
线选法的缺点是:地址空间不连续,且大量地址空间被浪费。比如用A10选芯片1,那芯片1的地址范围是A10=0时所有A11-A15的组合,这占了很大的空间,但实际只用了其中一小部分。
线选法只适合芯片数量很少的场合,实际工程中基本不用。
5.2 译码法:工程中的标准做法
译码法是用译码器把高位地址翻译成片选信号,这是实际工程中的标准做法。
译码法的优点是:地址空间连续、无浪费、片选信号互斥。用2-4译码器处理2根高位地址,正好产生4个片选,对应4片芯片,地址空间从0开始连续排列。
译码法又分为全译码和部分译码:
- 全译码:所有高位地址线都参与译码。比如12根地址线,低10根片内寻址,高2根全部接译码器。这样每个存储单元都有唯一确定的地址,没有地址重叠。
- 部分译码:只用部分高位地址线参与译码,剩下的高位地址线不接或接固定电平。这样会出现地址重叠,同一个物理单元对应多个地址。部分译码可以简化电路,但会浪费地址空间,且编程时容易混淆。
我的建议:除非地址空间非常紧张或者成本极度敏感,否则一律用全译码。地址重叠带来的调试麻烦,远比省下几根线值钱。
5.3 译码器的级联与扩展
当需要的片选信号超过译码器的输出数量时,可以用级联的方式扩展。
比如需要16个片选信号,可以用2片3-8译码器(共16个输出),或者用1片4-16译码器。级联时,高位地址线接第一级译码器,第一级的某个输出作为第二级译码器的使能信号。
级联的关键是使能端的正确连接。74LS138的G1高有效,G2A和G2B低有效。级联时,通常把第一级的输出经过反相后接第二级的使能端,确保同一时刻只有一级译码器工作。
这里有个实际经验:译码器的输出驱动能力有限,如果一片译码器的输出要驱动很多片存储芯片的CS端,可能需要加缓冲器。74LS138的输出驱动能力大约是8个LS负载,超过这个数量就要考虑加74LS244之类的缓冲器。
6. 存储器扩展中的时序问题
6.1 为什么时序是扩展电路的关键
很多人搭扩展电路时,逻辑连接全对,但就是工作不稳定。问题往往出在时序上。
存储器扩展后,地址信号要经过译码器产生片选,片选信号再控制芯片的读写。这个过程中,地址变化、译码器延迟、片选建立时间、数据输出延迟,每一个环节都有时间要求。如果CPU的读写时序和存储芯片的时序不匹配,就会出现读出错数据、写入失败等问题。
6.2 关键时序参数
几个必须关注的时序参数:
- 地址建立时间:地址信号稳定后,片选信号才能有效。如果片选比地址先到,可能会选中错误的芯片。
- 片选建立时间:片选有效后,要等一段时间才能进行读写操作。这个时间由芯片手册给出。
- 数据输出延迟:片选和地址都有效后,芯片需要一定时间才能把数据放到数据总线上。CPU的读周期必须大于这个延迟。
- 写脉冲宽度:写信号的有效宽度必须大于芯片要求的最小值,否则写入不可靠。
这些参数在芯片的数据手册(Datasheet)里都有,设计时必须逐一核对。我见过太多人只看逻辑连接,不看时序参数,结果电路时好时坏,查几天都查不出问题。
6.3 时序匹配的实用技巧
如果CPU时序和存储芯片不匹配,有几种解决办法:
第一种:插入等待周期。如果CPU支持等待状态(Wait State),可以在读写周期中插入等待周期,延长访问时间。这是最常用的方法。
第二种:降低时钟频率。如果系统对速度要求不高,可以降低CPU时钟,让时序自然匹配。这是最简单的办法,但会牺牲性能。
第三种:使用更快的芯片。如果预算允许,换用访问速度更快的存储芯片,从根上解决时序问题。
第四种:加锁存器或缓冲器。在地址总线和数据总线上加锁存器,可以改善信号质量,但会增加延迟。
实操心得:调试时序问题时,示波器是最好的朋友。把片选信号、读写信号、数据总线都接上示波器,看它们的先后顺序和脉宽,一眼就能看出问题。没有示波器的话,用逻辑分析仪也行,但不如示波器直观。
7. 常见问题与排查技巧实录
7.1 存储器扩展常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决办法 |
|---|---|---|---|
| 读出的数据某一位恒为0或1 | 位扩展中某片芯片故障或接触不良 | 写入全0和全1,观察哪一位异常 | 更换芯片或重新焊接 |
| 部分地址范围读写正常,部分异常 | 片选译码错误或译码器故障 | 用示波器观察各片CS信号 | 检查译码器接线和使能端 |
| 读写完全无响应 | 片选信号极性错误或使能端未接 | 测量CS端电平 | 调整极性或补接使能 |
| 数据偶尔出错,不稳定 | 时序不匹配或信号干扰 | 示波器观察时序关系 | 插入等待周期或加滤波电容 |
| 地址重叠,同一单元多个地址 | 部分译码导致 | 检查高位地址线是否全部参与译码 | 改为全译码 |
| 写入后读出的数据不对 | 写脉冲宽度不够或写信号极性错误 | 测量WE信号脉宽 | 延长写脉冲或调整极性 |
7.2 几个我踩过的坑
坑一:忘记接使能端。有一次用74LS138做译码,电路搭好后怎么都不工作。查了半天才发现,G2A和G2B两个低有效使能端悬空了。悬空在TTL电路里通常被视为高电平,导致译码器一直被禁用。后来把这两个脚接地,立刻正常。这个教训是:TTL芯片的使能端绝对不能悬空,不用的话要接固定电平。
坑二:地址线接反。有一次把A0和A1接反了,结果读出的数据顺序全乱。这种错误很隐蔽,因为电路逻辑看起来是对的,只是地址映射错了。排查方法是:写入地址0的数据,然后逐个地址读,看数据出现在哪个地址,就能判断地址线是否接错。
坑三:数据总线冲突。字扩展时,如果两片芯片的片选同时有效,它们的输出会在数据总线上打架,导致数据错误甚至芯片发热。这种情况通常发生在译码器输出没有互斥,或者片选信号有毛刺的时候。解决办法是确保译码器输出互斥,必要时在片选信号上加滤波。
坑四:忽略负载效应。地址线并联太多芯片后,驱动能力不足,信号幅度下降,导致读写不稳定。这时候需要在地址总线上加缓冲器,比如74LS244。我一开始没注意这个问题,后来发现地址线电压只有2V多,远低于正常的3.5V,加了缓冲器就解决了。
7.3 调试存储器的系统方法
调试存储器扩展电路,我总结了一套流程:
- 静态检查:断电状态下,用万用表检查所有电源、地、使能端的连接,确保没有虚焊、短路。
- 片选验证:上电后,用示波器或逻辑笔逐个检查每片芯片的CS端,确认在对应地址范围内CS有效,其他时候无效。
- 单芯片测试:如果可能,先只焊一片芯片,测试读写是否正常,再逐步增加芯片。
- 全0全1测试:向每个地址写入全0,读出验证;再写入全1,读出验证。这能发现大部分数据线问题。
- 地址遍历测试:从起始地址到结束地址,逐个写入地址值(比如地址0写0,地址1写1),然后逐个读出,验证地址映射是否正确。
- 边界测试:重点测试每片芯片的起始地址和结束地址,这些地方最容易出片选问题。
这套流程走下来,基本能覆盖90%以上的问题。剩下的10%通常是时序问题,需要示波器深入分析。
8. 从理论到实践:一个可复现的扩展方案
8.1 方案设计
我以一个具体的方案为例,把整个设计过程完整走一遍。这个方案可以直接在仿真软件里复现,也可以在实际面包板上搭建。
目标:用1K×8位的SRAM芯片,组成4K×8位的存储系统。
芯片选型:假设手头有若干片1K×8的SRAM,型号不限,只要引脚兼容即可。典型引脚包括:A0-A9(地址输入)、D0-D7(数据输入输出)、CS(片选,低有效)、WE(写使能,低有效)、OE(输出使能,低有效)。
译码器:74LS139(双2-4译码器)或74LS138(3-8译码器)。这里用74LS139的一半即可。
8.2 详细接线步骤
第一步:确定地址分配
4K空间需要12根地址线(A0-A11)。低10位A0-A9用于片内寻址,高2位A10-A11用于片选。
| A11 | A10 | 选中芯片 | 地址范围 |
|---|---|---|---|
| 0 | 0 | 芯片1 | 000H-3FFH |
| 0 | 1 | 芯片2 | 400H-7FFH |
| 1 | 0 | 芯片3 | 800H-BFFH |
| 1 | 1 | 芯片4 | C00H-FFFH |
第二步:连接地址线
- A0-A9:4片芯片的对应引脚全部并联
- A10:接74LS139的A输入端
- A11:接74LS139的B输入端
第三步:连接译码器
74LS139的一半有2个输入(A、B)、4个输出(Y0-Y3)、1个使能端(G,低有效)。
- A接A10,B接A11
- G接地(始终使能)
- Y0-Y3分别接芯片1-4的CS端
74LS139的输出是低有效,正好匹配SRAM的低有效片选。
第四步:连接数据线
4片芯片的D0-D7全部并联,接到系统数据总线D0-D7。
第五步:连接控制线
- 所有芯片的WE端并联,接系统写信号
- 所有芯片的OE端并联,接系统读信号
第六步:电源和地
所有芯片的VCC接+5V,GND接地。译码器的VCC和GND也要接。
8.3 验证与测试
接线完成后,按以下步骤验证:
- 片选测试:分别设置A11A10为00、01、10、11,测量4片芯片的CS端,确认只有对应的那片有效。
- 写入测试:向地址000H写入55H,向400H写入AAH,向800H写入0FH,向C00H写入F0H。
- 读出测试:逐个读出上述地址,确认数据与写入一致。
- 边界测试:向3FFH、7FFH、BFFH、FFFH写入数据并读出,确认片选边界正确。
- 遍历测试:从000H到FFFH逐个写入地址低8位,再逐个读出验证。
如果所有测试通过,这个4K×8的存储系统就搭建成功了。
8.4 扩展思路的延伸
这个方案可以进一步扩展:
- 容量扩展:如果需要8K×8,用8片1K×8芯片,地址线增加到13根,用3-8译码器产生8个片选。
- 位宽扩展:如果需要4K×16,用8片1K×8芯片,4片一组做位扩展,两组做字扩展。
- 混合扩展:用1K×4芯片组成8K×16系统,计算芯片数量:(8K/1K)×(16/4)=8×4=32片。
不管怎么扩展,核心逻辑都是一样的:先分组,再分配地址,最后接译码器。把这三步做好,再复杂的扩展都能搞定。
我在实际搭建这些电路的过程中,最大的体会是:理论上的逻辑连接只是第一步,真正的功夫在调试。时序、负载、干扰、接触不良,这些问题在课本上不会讲,但实际中一定会遇到。多动手、多测量、多总结,才能真正把存储器扩展这件事吃透。