
简介这份《数字设计原理与实践 第四版》习题答案文档面向学习数字逻辑与数字设计课程的学生及备考读者用于对照课后练习、梳理基础概念与验证解题思路。包内共1个docx文件约68KB内容按章节组织涵盖数字电路、数制转换、编码与控制器等核心模块。具体包括逻辑门与真值表分析如带反相器的2输入与门等效为或门、二进制/八进制/十六进制互转、符号-数值/反码/补码表示、补码加法溢出判断以及状态机编码方式与机械编码盘坏边界等典型题目并给出对应参考答案与简要推导。已有1220人学习适合课堂同步练习、期末复习和查漏补缺时使用也可作为教师布置作业与讲解思路的参考。文档篇幅紧凑便于按章检索能帮助读者把抽象原理落到具体题型和计算过程上。1. 反相器包住与门为什么变成或门从一道习题看等价变换的底层把《数字设计原理与实践 第四版 答案》里第 1 章第 1-6 题摊开看一个 2 输入与门AND2的每个输入端、输出端上都各串了一个反相器题目要求画逻辑图、写真值表再问能不能简化。真值表排出来输出和 2 输入或门OR2逐行一致——直觉上“反相器加上去怎么也得变成与非门”结果却是原地反转到或门。这里不是巧合而是德摩根定律在电路层的具体展开(A·B) A B输入端的两个反相器先把 A、B 各自翻一次与门内部再运算一次输出端再翻回来等价于A B。这个现象值得拆开看因为它牵出数字设计里最容易被低估的一类机制同一种逻辑功能可以映射到完全不同的门级结构选哪一种不取决于布尔代数本身而取决于成本、速度、驱动能力和工艺约束。搞懂为什么等价比记住哪个门接哪个门重要得多。2. 数制转换为什么八进制和十六进制能被“一眼看穿”2.1 分组映射的数学本质2³ 8、2⁴ 16 不是巧合八进制和二进制之间可以做到“看一位、写三位”直接靠的是8 2³这个等式。每一位八进制数恰好覆盖 3 个二进制位取值区间 07 正好对应 000111不需要任何算术运算分组映射即可。十六进制同理16 2⁴每一位覆盖 4 个二进制位取值区间 0F 正好对应 00001111。代码片段 用 Python 模拟数制分组映射带注释# 将八进制字符串按位映射为二进制无需做除法 def oct_to_bin_group(s: str) - str: table {c: format(int(c), 03b) for c in 01234567} return .join(table[ch] for ch in s) # 每一位八进制稳定占 3 个二进制位 print(oct_to_bin_group(1234)) # 1 010 011 100 # 十六进制同理每一位稳定占 4 位 def hex_to_bin_group(s: str) - str: table {c: format(int(c, 16), 04b) for c in 0123456789ABCDEF} return .join(table[ch] for ch in s) print(hex_to_bin_group(29C)) # 0010 1001 1100这道分组映射关系是第 2 章 2.2、2.3 两题能成立的根本原因1234₈ 1 010 011 100₂ 29C₁₆中间没有发生过一次十进制算术走的是“八进制 → 三位二进制 → 四位二进制 → 十六进制”这条链。174637₈转十六进制是1 111 100 110 011 111₂再按四位归并就是F99F₁₆DEAD.BEEF₁₆反向拆回八进制是157255.575674₈顺序稍不注意就会把“整数部分从右往左三位、小数部分从左往右三位”这一点弄反这是后面避坑章要单独说的问题。2.2 十进制与其他进制的双向算法除基取余与乘基取整八进制和十六进制在二进制面前是“无算术”的但碰到十进制就不行。十进制和其他任意进制之间的转换要分整数和小数两条路径整数部分反复除基取余余数从低位往高位排小数部分反复乘基取整整数部分从高位往低位排。这两种操作本质上都是在做多项式展开一个十进制数的十进制表示a_n·10^n ... a_0·10^0是唯一的换到 r 进制就是求一组系数b_k满足Σ b_k·r^k N。除基取余之所以有效是因为每次余数恰好是“当前最低位的系数”商继续向高位收缩。代码片段 完整的双向算法带注释# 整数部分除基取余从低位到高位收集最后反序 def dec_to_base_int(n: int, base: int) - str: if n 0: return 0 digits [] while n 0: digits.append(n % base) # 当前最低位 n // base # 商继续收缩 return .join(str(d) for d in reversed(digits)) # 小数部分乘基取整从高位到低位 def dec_to_base_frac(frac: float, base: int, max_bits: int 16) - str: out [] for _ in range(max_bits): frac * base bit int(frac) # 取整得到当前位 out.append(str(bit)) frac - bit # 剩余继续乘 if frac 0: # 已经精确终止 break return .join(out) print(dec_to_base_int(209, 2)) # 11010001 print(dec_to_base_int(23851, 16)) # 5D2B print(dec_to_base_frac(0.3125, 2)) # 0101 → 0.3125 精确收敛第 2 章 2.5 那组十进制转换题里10100.1101₂ 20.8125、15C.38₁₆ 348.21875这类答案之所以“看起来很整”就是因为它们的二进制/十六进制展开恰好是有限位。反例是十进制 0.1转成二进制是 0.000110011…永远除不尽。意识到这一点后续在设计十进制→BCD 转换、浮点定点转换时就会自然地对“截断位宽”这个操作持有戒心。2.3 符号位、补码与反码为什么八位补码能装下 -128 却装不下 128第 2 章 2.11 那组题把三种表示法放在一起对比很有代表性。以-42为例符号-数值表示是1010 1010反码1101 0101补码1101 0110。以-6为例符号-数值1000 0110反码1111 1001补码1111 1010。补码的构造规则是“按位取反再 1”而反码是“按位取反”——两者只差最后一次加 1但它消掉了补码里的“负零”。8 位二进制能表达 256 种状态补码划分方式是-128 ~ 127其中 0 只出现一次0000 00001000 0000被保留给-128这是符号-数值和反码做不到的——它们的1000 0000和0000 0000会同时映射到 0白白浪费一个状态而且给硬件加法的取舍带来额外分支。补码能把减法并入加法的核心原因是模运算8 位补码的“加法”本质是模 2⁸ 的同余运算A - B等价于A (-B)-B就是B的按位取反加 1。第 2 章 2.12 那组判断溢出的题考察的不是补码本身而是补码加法的边界。判定规则可以拆成一句顺口溜两个符号相同的数相加如果结果的符号和加数不同就发生溢出。比如0101 1101 0011 0001 1000 1110两个正数加起来结果是负数最高位跳变判定为溢出1101 0100 1110 1011 1011 1111两个负数相加仍为负数没有溢出。这套规则的使用要点在后续避坑章单列因为“只看最高位进位”会翻车。2.4 状态编码与坏边界从 8P5 到 2^(n-1)第 2 章 2.33 那组排列组合题看起来像数论实际对应的是控制器状态编码的真实工程约束。5 个状态用 3 位二进制表示问有多少种编码方式——答案是“从 8 个可用码里取 5 个并按顺序分配”N 8×7×6×5×4 67207 状态是8P7 403208 状态与 7 状态数量相同因为 8 个码全用完了排列数就是8P7。2.34 加了“每个编码字至少含一个 0”的约束可用的 3 位码从 8 种缩到 7 种排除全 1 的111从中选 6 个N 7×6×5×4×3×2 5040。2.35 和 2.36 是关于机械编码盘的相邻位置之间的边界如果发生多位同时跳变就会产生“坏边界”对 3 位编码盘坏边界是001/010、011/100、101/110、111/000这四处n 位盘则是2^(n-1)处。这几道题为什么放在第 2 章而不是第 5 章答案其实很清楚状态编码的设计质量直接关系到状态机的稳定性和功耗编码分配阶段就决定了后面要不要为坏边界补逻辑。硬件里没有“后悔药”一个 3 位编码分配错了后面就得加边沿检测或格雷码转换电路来弥补成本和延迟都是在校正阶段没法回收的。这也是为什么后续做控制器时会优先考虑格雷码、one-hot 这类“单位跳变”编码本质上就是在设计阶段主动把坏边界消掉。3. 逻辑门与电路实现从 AOI/OAI 到 CMOS 晶体管数3.1 逻辑图、真值表、卡诺图三者的对应关系第 3 章 3.11、3.12 两题是 AOI/OAI 结构的反向推导给一个 AOI 电路图要求用 AND、OR、INV 重画。3.11 的答案是Z (A·B C D)3.12 的答案是Z ((A B)·C·D)。从形式上看这两个表达式不过是把 AOI 和 OAI 各自的运算次序颠倒了一下但在实际电路里它们对应的门级拓扑完全不同——AOI 是先“或”再“非”的与项组合OAI 是先“与”再“非”的或项组合CMOS 实现时串并联结构不一样晶体管数量也可能差一倍。代码片段 用 Python 枚举真值表验证两者是否等价from itertools import product def aoi(a, b, c, d): return not ((a and b) or c or d) # Z (A·B C D) def oai(a, b, c, d): return not ((a or b) and c and d) # Z ((A B)·C·D) for inputs in product([0, 1], repeat4): a, b, c, d map(bool, inputs) print(inputs, aoi(a, b, c, d), oai(a, b, c, d))真值表跑出来会发现AOI 和 OAI 两者并不是同一个函数——这是对的3.11 和 3.12 分别对应两个不同题目题目自身并不要求它们等价。这里要提的机制是真值表和卡诺图是同一种逻辑信息的不同表示但两者之间有信息转换成本。同一种逻辑用卡诺图化到最小积之和或最小和之积得到的门级结构一般不同前者偏 AND-OR-NOT或 NAND-NAND后者偏 OR-AND-NOT或 NOR-NOR。第 4 章 4.14、4.58 那几道卡诺图题的本质就是这种“不同表示形态下的优化取舍”。3.2 噪声容限与 DC 驱动能力静态电阻负载怎么算第 3 章 3.21 是整套答案里最接近“真实器件参数”的一道题。反相器的输入低电平阈值 1.5V、高电平阈值 3.5V从特性曲线上对应的输入值是 2.4V 和 2.5V于是高态噪声容限3.5 - 2.5 1V低态噪声容限2.4 - 1.5 0.9V。这两个数就是电路在“信号已被识别为 0 或 1”前提下还能容忍多少噪声的定量描述。一个常见的误解是噪声容限越大电路越“抗噪”实际上真正的约束在于接收端和发送端的阈值是否对齐高态噪声容限由发送端的最小高电平和接收端的最低高电平共同决定低态同理两端的阈值一旦不匹配容限就被压缩到零。3.27 那组题把驱动能力与负载电阻绑在一起74HC00 的IOLmax和IOHmax都限制在 4mA如果外接一个 820Ω 对地电阻高态输出电流3.84V / 0.82kΩ ≈ 4.683mA 4mA不能驱动如果外接一个 1kΩ 对 Vcc 电阻低态电流(5 - 0.33) / 1kΩ 4.67mA 4mA同样不能驱动。把 1.2kΩ 接 Vcc、820Ω 接地一起挂上去就变成两个方向都要分别校验低态(2.03 - 0.33) / 0.487kΩ ≈ 3.49mA高态(3.84 - 2.03) / 0.487kΩ ≈ 3.72mA两端都低于 4mA才是能驱动的。这里的核心思路是“两条边都校验”任何一边超了都不算能驱动。第 3 章 3.26 用两组极端工作点反解 p 沟道和 n 沟道的导通电阻得到Rp ≈ 151Ω、Rn ≈ 60Ω。这个数量级在 CMOS 器件里并不奇怪——p 管的载流子迁移率本来就比 n 管低一半左右同尺寸下电阻往往是 n 管的两到三倍。3.65 那道题算的是推挽级里 4kΩ 上拉电阻的浪费电流(5 - 0.24) / 4 ≈ 1.19mA浪费功率P 4.2kΩ × (1.19mA)² ≈ 5.95mW。看起来不大但那是单个输出级的静态浪费一整块板上几十上百个输出级一起看数字很可观这是 CMOS 逻辑在 5V 时代后期逐步转向低电压供电的直接动机之一。3.3 门级设计中的晶体管用量NOR3、OR2 的成本账第 3 章 3.13 和 3.15 是 CMOS 门级设计里非常经典的对照题。NOR3 的 CMake 结构是“三个 p 管串联、三个 n 管并联”而 OR2 的结构是“NOR2 后面级联一个 INV”。为什么 OR2 不直接做“或门”而是先做或非再取反真实原因是 CMOS 工艺天然擅长做 NAND 和 NOR而不擅长做 AND 和 OR。NAND2 是“两个 p 管并联、两个 n 管串联”从速度角度看p 管本来迁移率就低串联会进一步拖慢上升时间NOR2 则反过来p 管串联、n 管并联下降时间比上升时间长。一旦要做 OR2本质上就是“NOR2 INV”而 INV 的成本是两个晶体管的面积和一级门延迟。到了 6.32 那道 BUT 门的 CMOS 优化题这个“门级结构影响晶体管数”的机制被推到了极致。6.31 给出的最小积之和是Y1 A1·B1·A2 A1·B1·B2、Y2 A1·A2·B2 B1·A2·B2按常规二级 AND-OR 结构实现晶体管用量是 40 只。6.32 要求改用圈-圈逻辑并复用子表达式最终得到的是Y1 ((A1·B1) (A2·B2))、Y2 ((A2·B2) (A1·B1))晶体管用量降到 20 只——恰好减半。这里省下来的不是面积这么简单delay 也降了因为路径上少了一级反相器。这个结论在后续做全加器、比较器这类复合逻辑时非常有指导意义先看能不能把子表达式抽出来共用再决定要不要走最小积之和。代码片段 用 Python 验证 BUT 门优化前后逻辑等价def but_naive(a1, b1, a2, b2): y1 (a1 and b1 and not a2) or (a1 and b1 and not b2) y2 (not a1 and a2 and b2) or (not b1 and a2 and b2) return y1, y2 def but_optimized(a1, b1, a2, b2): t1 not (a1 and b1) # (A1·B1) t2 not (a2 and b2) # (A2·B2) y1 not (t1 or t2) # ((A1·B1) (A2·B2)) y2 not (t2 or t1) # ((A2·B2) (A1·B1)) return y1, y2 for a1 in (0, 1): for b1 in (0, 1): for a2 in (0, 1): for b2 in (0, 1): assert but_naive(a1, b1, a2, b2) but_optimized(a1, b1, a2, b2) print(all equivalent)3.4 传输延迟与三要素法上升时间、下降时间和 RC 时间常数第 3 章 3.33、3.68、3.69 三题把“RC 一阶电路 阈值穿越”这条分析路线串了起来。3.33 只是算时间常数τ RC把负载电容和等效电阻乘起来。3.68 分析下降时间输出从高态 4.45V 切换到低态 0.2V等效电阻 90Ωτ 9ns从 3.5V 到 1.5V 的下降时间由指数衰减三要素法给出约 9.1ns。3.69 分析上升时间输出从 0.2V 到 4.45V等效电阻 164Ωτ 16.4ns从 1.5V 到 3.5V 的上升时间约 19ns。代码片段 用三要素法复算升降时间带注释import math def fall_time(vh, vl, r, c, vt_high, vt_low): tau r * c # V(t) VL (VH - VL)·exp(-t/τ)解出穿越 VTH 和 VTL 的时间差 t_high tau * math.log((vh - vl) / (vt_high - vl)) t_low tau * math.log((vh - vl) / (vt_low - vl)) return t_low - t_high def rise_time(vl, vh, r, c, vt_low, vt_high): tau r * c # V(t) VL (VH - VL)·(1 - exp(-t/τ)) t_low tau * math.log((vh - vl) / (vh - vt_low)) t_high tau * math.log((vh - vl) / (vh - vt_high)) return t_high - t_low print(fall_time(4.45, 0.2, 900, 10e-12, 3.5, 1.5)) # ~9.1ns print(rise_time(0.2, 4.45, 1640, 10e-12, 1.5, 3.5)) # ~19ns这个结果在工程上的意义远不止“两个数字”。上升时间和下降时间的不对称性来自等效电阻的不对称性——上拉级用的是 p 管导通电阻大下拉级用 n 管电阻小所以上升沿通常比下降沿慢。这就是为什么 3.34 那道题在问“电源电压 5% 和负载电容 5% 哪个功率涨更多”时答案选“电压”而不是“电容”CMOS 动态功耗是P C·V²·f电压是二次方关系电容是一次方关系电源端那一点波动被平方放大了。4. 避坑与排查数字设计答案里最容易翻车的几个坑4.1 布尔代数与编码环节的误判提示这一节的坑都有一个共同特征——看起来只是代数变形但改完之后真值表就变脸。排查时优先把真值表跑一遍不要靠肉眼对表达式。坑 1德摩根展开时丢掉运算优先级现象第 4 章 4.5 题里X Y·Z的补函数很容易写成X · Y Z写成之后还会觉得“每项都翻了看起来没问题”。真实情况是(X Y·Z)的正确展开是X · (Y·Z) X · (Y Z) X·Y X·Z。对比一下X·Y Z两者在X0、Y1、Z0处的值分别是0 1 1和1·0 1 1看起来一样放到X1、Y0、Z1处前者是0 1 1后者是0 1 1看起来还是一样——这种“大部分场景碰巧一样”的误判是最危险的因为它过了样例测试但漏了边界。原因在于德摩根定律只对“括号内那个最外层的运算”取反X (Y·Z)这个式子里 Y 和 Z 之间的“与”是最内层运算先被翻掉之后剩下的X和(Y·Z)才是并列的“与”关系。正确做法是先在心里给表达式补上完整括号再逐层翻。坑 2判断补码溢出只看最高位进位现象第 2 章 2.12 题里1101 0100 1110 1011 1011 1111最高位有进位但题目答案写“不存在溢出”。同样0101 1101 0011 0001 1000 1110最高位也有进位这次写“存在溢出”。两次看起来行为一样结论却相反。原因在于溢出本质是“符号位被运算结果篡权”两个正数相加得到负数、或两个负数相加得到正数才叫溢出进位到符号位外面本身不构成溢出反而可能是正常的比如两个负数相加进位丢到模 2⁸ 外面剩下的符号仍为 1。正确的判断要三点同时看加数符号是否相同、结果符号是否与加数不同、最高位进位与次高位进位是否一致。三点对齐才下结论。坑 3状态编码用格雷码就不再数坏边界现象第 2 章 2.35 那组题里题目问的是“普通 3 位编码盘”的坏边界答案是2^(3-1) 4处。有同学套在格雷码上得到“0 处坏边界”于是认为格雷码彻底解决了编码盘问题。原因在于格雷码确实每步只翻一位坏边界数为零但它把问题从“多位同时跳变”转移到了“物理位置对齐”上编码盘上相邻传感器的机械安装误差仍然会让两个位置同时读到 0 或 1只不过出错形态从“错位跳变”变成“刻线遮挡”。所以机械编码盘本身在格雷码之外还叠加了“刻线对齐 传感器间隙”这层物理约束编码方案解决不了。4.2 卡诺图与冒险环节的误判注意这一节的坑在“写出最小表达式”时不会暴露跑静态时序仿真或者上电实测时才会冒出来属于典型的“纸上没问题、板上出问题”。坑 4卡诺图无关项dont-care被当成“1”用现象第 4 章 4.18 那组带 dont-care 的卡诺图题F Σ(0,1,3,5,14) d(8,15)若把 8 和 15 当成 1 全圈进去化简出来的积之和与正确答案不一致。原因在于无关项之所以“无关”是因为这些输入组合在实际电路里根本不会出现或者出现时输出无所谓一旦把它们强制当 1 圈进卡诺圈化简结果会在某些“实际可达”的边界条件上产生冒险——表达式是更小了但它引入了一个原本不存在的路径。正确做法是在画圈前把 dont-care 标记为独立的符号x 或 -只在“圈更大更省门”时借用绝不在逻辑上固定为 1 或 0。坑 5静态冒险被当成“表达式已经最小化不会再出问题”现象第 4 章 4.19 题里F W·X W·Y已经是最小积之和但题目又问“找出所有静态冒险并设计无冒险电路”。很多人的第一反应是“卡诺图已经化到最简怎么还会有冒险”。原因在于最小化追求的是门数最少牺牲的是“变量在切换时是否会出现瞬间的 0-1 跳变”。W·X和W·Y这两个圈在卡诺图上不相交当XY1不变、W翻转时理论上输出应保持 1但两个与门各自有延迟W从 1 变 0 时W·X先撤、W·Y后立中间出现一段“两个与门都输出 0”的窗口或门输出就掉一次 0这就是静态 1 冒险。消除办法是补一个共识项X·Y把这条缝隙堵上。坑 6估算 CMOS 晶体管数只数逻辑门不数反相器现象第 6 章 6.32 的 BUT 门优化题里很多人写出优化表达式之后数了数“NAND2 用 4 个晶体管、INV 用 2 个晶体管”得到 20 只然后和原方案 40 只对比觉得很合理。但代到 3.13 的 NOR3 题里如果按“NOR3 OR3 INV”来数会得到 6 2 8 只而实际 NOR3 只需要 6 只。原因在于 CMOS 门级设计里“NOR3 用 6 只晶体管”是标准结构3 个 p 管串联、3 个 n 管并联每个输入端只需要一对 p/n 管如果先做 OR3 再取反反而多搭了一级。所以晶体管数估算的前提是“按目标逻辑函数直接构造复合门”不能按“先拆成基本门再拼”的思路估。5. 设计取舍完全集合、静态冒险与“够用就好”的代价第 4 章 4.39 和 4.41 是两道非常经典的“完全集合判定”题答案对照值得单独拿出来咀嚼。NAND(2) 是完全集合证明路径是把它反向搭出 AND、OR、INV 这三种基本运算两个输入并接得到 INVINV 后接 NAND2 得到 AND2输入端各挂一个 INV 再进 NAND2 得到 OR2。三条都搭出来之后NAND(2) 就自动等于完全集合。XNOR 则相反它不能通过“接线”保留一个乘积项、消掉另一个乘积项所以做不出 AND 和 OR因此不是完全集合。这个结论的直接推论是如果整块板只允许一种门选 NAND如果只允许一种复合门那选 XNOR 就是死路。但这套判定在真实工程里的价值得打一些折扣。用 NAND 一种门搭出整块电路当然能做到“完全集合”的理论完美代价是门级数变多、路径变长、布线变绕反而会吞掉当初选型时省下来的那点面积。这也是为什么 4.50 题里“反相门延迟 5ns、非反相门延迟 8ns” 这三条电路速度对比a 是 16ns、c 是 18ns、d 是 10ns值得看不同的门级拓扑对应不同的延迟数字把“完全集合”推到极致延迟往往不是最优的。真正需要拿捏的是“够用就好”这条线。静态冒险这条线尤其典型——按 4.19 的答案F W·X W·Y补上共识项X·Y之后静态 1 冒险消失代价是电路多一个与门和一个或门面积和延迟都涨。问题回到这条电路的实际输入变量组合里XY1且W翻转的情况会不会真的出现如果这个输入组合在系统状态机里根本到不了补共识项就是纯浪费如果它会出现在某个开机自检边界上那补上就是物有所值。4.47 那道“自对偶函数”判定也是同一个逻辑F(X,Y,Z) Σ(1,2,5,7)和它的对偶函数逐项比对卡诺图不同就不是自对偶F X·Y·Z X·Y·Z X·Y和它的对偶比对卡诺图相同就是自对偶。用“如果真的要用会不会用这套性质”来筛选比理论判定本身更接近工程实际。我自己在这类取舍上翻过车。早些年做一个 4 位质数检测器的小模块按 4.11 和 4.16 的路径先化简成最小积之和发现表达式已经很短就默认“没问题”直接上了板。结果在N3N2N1N0 1101即十进制 13这个边界上出现了输出抖动。回头排查才发现卡诺图化简时把某些 dont-care 当 1 圈进了共享项表达式虽然是等价的但在N3N2N1N0从 1100 切到 1101 的瞬间出现了一段静态 1 冒险正好被后级采样到了。从那以后我每次用卡诺图化简这类小电路都会强制走一遍“先写最小积之和 → 标出所有共识项 → 在目标输入域上跑状态切换仿真”这条路即使是几行逻辑的模块也不省这步。省下来的不只是 debug 时间还有一种“看着简单其实有坑”的认知偏差。这份《数字设计原理与实践 第四版 答案》里从第 1 章到第 6 章其实一直在做同一件事把“结论看起来是什么”和“为什么能这么实现”这两层拉开读者能自己算出等价变换、自己推导编码方案、自己评估晶体管成本这比背下 6720 种编码方式或者 20 只晶体管的答案有意义得多。希望帮到你。本文还有配套的精品资源点击获取