qqqzj@Crane
Back to home / 返回首页

Notes 笔记

sys1

系统 作者 qqqzj-crane 约 32 分钟

Sys1部分笔记#

这份笔记按“编码与逻辑基础 -> 功能部件 -> ISA -> CPU -> 存储与 I/O -> 程序运行链路”的主线组织,将概念、手算例题、Verilog 模板、RISC-V 细节和实验相关知识放在对应章节中。阅读时可以先看每章开头的概念框架,再用例题和代码模板补齐可操作细节。

0. 总体主线#

数字逻辑与计算机组成可以从两条线一起理解:一条是“硬件从门电路长成 CPU”,另一条是“程序从高级语言落到机器指令并在硬件上运行”。

二进制编码
-> 数字逻辑基础
-> 组合逻辑电路
-> 时序逻辑电路
-> FPGA / Verilog HDL
-> 运算方法和功能部件
-> 指令系统 ISA
-> CPU 微体系结构
-> 存储器层次结构
-> 系统互连与输入/输出
-> 程序编译、链接、加载和运行
抽象层关系#
抽象层关注点典型内容
ISA 层软件和硬件之间的接口指令、寄存器、寻址方式、异常/中断
微体系结构层如何实现 ISACPU 数据通路、控制器、流水线
RTL / 功能部件层寄存器传送和功能部件ALU、寄存器堆、存储器、总线
数字逻辑层用门电路实现功能组合逻辑、时序逻辑、FSM
器件层门电路的物理实现CMOS、FPGA、存储单元
章节之间的连接#
前面知识后面用在哪里
补码ALU 加减法、signed comparison、branch compare
移位shifter、乘除法优化、RISC-V immediate 对齐
布尔代数组合逻辑化简、control signal 生成
decoder / muxregister file、datapath 选择、instruction decode
flip-flopregister、PC、FSM、pipeline register
FSMcontrol unit、多周期 CPU、串口/协议控制
timing最高频率、critical path、同步电路可靠性
ISAdatapath 需要哪些硬件、control 需要哪些信号
ELF / linking汇编代码如何成为操作系统能运行的程序

本课程使用 RISC-V 作为模型机,主要因为它开放、简洁、模块化、易扩展,适合清楚呈现 ISA 和底层硬件实现之间的关系。

1. 二进制编码与计算机系统抽象#

冯·诺依曼结构和程序执行#
Von-Neumann 模型#

Von-Neumann 模型把计算机抽象成:

部件作用
Memory存放指令和数据,每个地址对应一个内容
CPU取指、译码、执行运算和控制
I/O与外部设备交换数据
System将硬件、软件、运行环境组合成完整系统

核心思想:

  • Stored program:程序也作为二进制数据存放在内存中。
  • Binary:指令、数据最后都编码为 0/1。
  • CPU 自动执行指令序列,而不是人工设置每一步。
程序表示#

程序语言的层次:

层次例子转换工具
高级语言C, Javacompiler, interpreter
汇编语言RISC-V assembly, MIPS assemblyassembler
机器语言0/1 bit patternCPU 直接执行

程序执行的典型阶段:

  1. Instruction Fetch:从 PC 指向的内存地址取指令。
  2. Instruction Decode:解释 opcode 和 operand 字段。
  3. Execute:ALU 或控制逻辑执行操作。
  4. Memory Access:按需读写数据内存。
  5. Write Back:把结果写回寄存器或状态。
计算机系统抽象层和 ISA 位置#

从应用问题到硬件电路,中间有多个抽象层:

应用问题
-> 算法
-> 高级语言程序
-> 操作系统 / 虚拟机
-> 指令集体系结构 ISA
-> 微体系结构
-> RTL / 功能部件
-> 数字逻辑电路
-> 器件

ISA 是软件和硬件之间的桥梁。它规定“机器能执行什么”,例如:

  • 有哪些指令。
  • 指令如何编码。
  • 有哪些寄存器。
  • 操作数如何寻址。
  • 异常和中断如何表现给软件。

微体系结构关注“如何实现 ISA”。例如,ISA 规定是否有乘法指令;乘法指令到底用移位加法实现,还是用专用乘法器实现,是微体系结构问题。

二进制数、数据类型、整数和补码#

计算机内部处理的所有信息都以 0 和 1 编码,原因包括:

  • 二进制只有两个稳定状态,物理实现可靠。
  • 二进制计数和运算规则简单。
  • 0/1 与逻辑真假自然对应,便于用逻辑门实现计算。

机器级程序中常见基本数据类型:

类型说明
无符号整数表示非负整数
带符号整数常用补码表示
浮点数表示实数近似值,常用 IEEE 754
字符如 ASCII、Unicode、汉字编码
逻辑值true / false,通常用 1 / 0 表示
固定宽度与机器数#

计算机内部的数是固定宽度的 bit pattern。这里要区分:

  • machine number:机器内部编码。
  • true value:该编码按某种解释方式得到的数学值。

对 $n$ 位无符号整数:

$$ 0 \le X \le 2^n - 1 $$

对 $n$ 位二进制补码整数:

$$ -2^{n-1} \le X \le 2^{n-1}-1 $$
常见有符号编码#
编码英文特点
原码sign-and-magnitude最高位为符号位,存在 \(+0\)-0
反码one's complement负数按位取反,也存在两种 0
补码two's complement负数为模 $2^n$ 意义下的补数,硬件加减法统一
移码biased / frameshift code常用于浮点数阶码

补码的定义:

$$ [X]_C = 2^n + X \pmod {2^n} $$

补码负数转换快捷法:

  1. 对绝对值的二进制按位取反。
  2. 加 1。

补码取负也满足:

$$ \sim x + 1 = -x $$
补码手算完整流程#

例:用 8 bit 表示 -13

  1. 先写 13 的二进制:
13 = 0000 1101
  1. 按位取反:
1111 0010
  1. 加 1:
1111 0011

所以:

-13 的 8-bit two's complement = 1111 0011

反过来,看到 1111 0011

  1. 最高位是 1,所以按补码解释为负数。
  2. 取反加 1 得绝对值:
1111 0011
0000 1100   // invert
0000 1101   // +1

所以它表示 -13

补码的一个直观理解是“模环”:

8-bit 下模数是 256
-13 ≡ 256 - 13 = 243
243 = 1111 0011

这也是为什么同一个 bit pattern:

1111 0011

按 unsigned 看是 243,按 signed two's complement 看是 -13

模运算与溢出#

固定 $w$ 位机器会丢弃超出的高位,因此本质是在模 $2^w$ 下计算。

无符号加法:

$$ UAdd_w(u,v) = (u+v) \bmod 2^w $$

无符号乘法:

$$ UMult_w(u,v) = (u \cdot v) \bmod 2^w $$

有符号补码加法和无符号加法在 bit-level 上完全相同,区别只是解释结果的方式。

signed 与 unsigned 的映射#

signed 和 unsigned 互相转换时:

  • bit pattern 不变。
  • 解释方式改变。

若 $w$ 位补码值 $x < 0$,转为 unsigned 时会变成:

$$ T2U(x) = x + 2^w $$

这解释了 C 中很多“看起来奇怪”的比较:

-1 < 0U     // false, 因为 -1 被转换成 unsigned 后是很大的数
(unsigned)-1 > -2  // true

易错点:

  • C 表达式中若 signed 和 unsigned 混用,signed 通常会隐式转换为 unsigned。
  • 不要因为变量“应该非负”就随便用 unsigned,循环倒着写时很容易下溢。
signed / unsigned 转换例题#

以 4 bit 为例:

bit patternunsignedsigned two's complement
000000
000111
011177
10008-8
10019-7
111115-1

规律:

  • 最高位为 0 时,signed 和 unsigned 值相同。
  • 最高位为 1 时,signed 值 = unsigned 值 $-2^w$。

例如 $w=4$:

1001 unsigned = 9
1001 signed   = 9 - 16 = -7

C 语言比较例:

int x = -1;
unsigned y = 1;
printf("%d\n", x < y);

这里 \(x\) 会被转换成 unsigned:

-1 的 32-bit pattern = 0xffffffff
按 unsigned 解释 = 4294967295

所以:

4294967295 < 1  // false
扩展与截断#

扩展:

类型规则
unsigned 扩展高位补 0
signed 扩展复制符号位

截断:

  • 直接丢掉高位。
  • 对 unsigned 相当于取模。
  • 对 signed 会先得到截断后的 bit pattern,再按补码解释。
截断和扩展例题#

例:把 8-bit signed 1111 0011 扩展到 16 bit。

最高位是 1,所以符号扩展:

1111 0011
1111 1111 1111 0011

值仍然是 -13

如果按 unsigned 扩展,则高位补 0:

1111 0011
0000 0000 1111 0011

值变成 243

截断例子:

int x = 53191;        // 十六进制 0xCFC7
short y = (short)x;   // 保留低 16 bit

如果 short 是 16-bit signed,则 0xCFC7 最高位为 1,所以 y 是负数:

0xCFC7 unsigned = 53191
0xCFC7 signed   = 53191 - 65536 = -12345

这类题的稳定做法:

  1. 先写目标宽度的 bit pattern。
  2. 再决定按 signed 还是 unsigned 解释。
移位#
操作作用注意
\(x << k\)左移,低位补 0常等价于乘 $2^k$,但可能溢出
unsigned \(x >> k\)逻辑右移,高位补 0等价于向下取整除以 $2^k$
signed \(x >> k\)通常为算术右移,高位补符号位负数会向 $-\infty$ 方向取整

负数想实现 C 整数除法那种“向 0 截断”,右移前要加 bias:

$$ (x + (1 \ll k) - 1) \gg k $$

这里适用于 $x<0$ 的情况。

有符号溢出判断#

二进制补码加法中:

  • 正数 + 正数 = 负数,则正溢出。
  • 负数 + 负数 = 正数,则负溢出。
  • 一正一负相加不会溢出。

等价判断:

  • 操作数符号相同,结果符号不同,则 overflow。
  • 最高有效值位进位和符号位进位不同,则 overflow。

常见 flags:

Flag含义主要用于
ZFZero Flag,结果为 0signed 和 unsigned
SF/NFSign Flag,结果符号位signed
CFCarry Flag,加法进位或减法借位unsigned
OFOverflow Flag,有符号溢出signed
溢出判断例题#

4-bit signed two's complement 范围:

-8 ~ +7

例 1:\(4 + 5\)

  0100   // 4
+ 0101   // 5
------
  1001

1001 按 signed 是 -7。两个正数相加得到负数,所以正溢出。

例 2:\(-4 + -5\)

  1100   // -4
+ 1011   // -5
------
1 0111

丢弃第 5 位后得到 0111,即 \(+7\)。两个负数相加得到正数,所以负溢出。

例 3:\(-4 + 5\)

  1100   // -4
+ 0101   // 5
------
1 0001

丢弃高位后是 0001,即 1。一正一负相加不会 signed overflow。

硬件中常用判断:

OF = carry into sign bit XOR carry out of sign bit

或者从符号看:

OF = (a_sign == b_sign) && (sum_sign != a_sign)
浮点数和数据存储顺序#
基本形式#

IEEE 浮点数的数值形式:

$$ (-1)^s \times M \times 2^E $$

字段:

字段含义
\(s\)sign bit
\(\exp\)指数字段,不直接等于 $E$
frac尾数字段,不直接等于 $M$

单精度:

字段位数
sign1
exp8
frac23

双精度:

字段位数
sign1
exp11
frac52
规格化数#

条件:

exp != 000...0 且 exp != 111...1

指数:

$$ E = Exp - Bias $$

其中:

$$ Bias = 2^{k-1}-1 $$

尾数:

$$ M = 1.xxx... $$

这个隐含的前导 1 是规格化数的关键。

非规格化数#

条件:

exp = 000...0

指数:

$$ E = -Bias + 1 $$

尾数:

$$ M = 0.xxx... $$

作用:

  • 表示非常接近 0 的数。
  • 实现 gradual underflow。
  • \(\exp=0, frac=0\) 表示 \(+0\)-0
特殊值#
expfrac含义
全 1全 0$\pm \infty$
全 1非 0NaN
全 0全 0$\pm 0$
浮点数编码例题#

例:把 13.25 表示成 IEEE 754 单精度的结构。

先转二进制:

13 = 1101
0.25 = 0.01
13.25 = 1101.01

规格化:

1101.01 = 1.10101 x 2^3

所以:

s = 0
E = 3
Bias = 127
Exp = E + Bias = 130 = 10000010
frac = 10101000000000000000000

最终结构:

0 10000010 10101000000000000000000

这类题不一定要求写完整 32 位十六进制,但必须能判断:

  • sign bit。
  • exponent 是否要加 bias。
  • frac 是去掉规格化前导 1. 后的小数部分。

非规格化数的识别题:

exp = 00000000, frac != 0

此时:

M = 0.frac
E = -126   // single precision

特殊值识别题:

exp = 11111111, frac = 0      -> infinity
exp = 11111111, frac != 0     -> NaN
浮点运算的核心#

浮点加法:

  1. 对阶:把较小指数的尾数右移。
  2. 尾数相加或相减。
  3. 规格化结果。
  4. 舍入。
  5. 检查 overflow / underflow。

浮点乘法:

  1. 符号:$s = s_1 \oplus s_2$。
  2. 尾数:$M = M_1 \times M_2$。
  3. 指数:$E = E_1 + E_2$。
  4. 规格化和舍入。

重要性质:

  • 浮点加法通常不满足结合律。
  • 浮点乘法也通常不满足结合律和分配律。
  • 默认舍入通常是 round-to-nearest-even。
浮点舍入为什么会出问题#

二进制浮点只能保存有限个 frac bit。若精确结果不能放进 frac,就必须舍入。

例:

float a = 1e20;
float b = -1e20;
float c = 3.14;

比较:

(a + b) + c
a + (b + c)

第一种:

a + b = 0
0 + c = 3.14

第二种:

b + c ≈ b   // c 太小,在对阶时被舍掉
a + b = 0

所以浮点加法不满足结合律。考试或调试时看到浮点比较,不要默认它像数学实数那样精确。

数据宽度常用单位:

单位含义
bit二进制位
byte字节,通常 8 bit
word字,具体宽度由机器约定

大端和小端:

方式含义
大端最高有效字节放在低地址
小端最低有效字节放在低地址

大小端只影响多字节数据在内存中的字节排列,不改变数据本身的数值。

2. 数字逻辑基础#

逻辑门和数字抽象#

基本逻辑门:

表达式含义
NOT$Y=\overline{A}$取反
AND$Y=AB$全 1 才为 1
OR$Y=A+B$至少一个为 1
NAND$Y=\overline{AB}$AND 后取反
NOR$Y=\overline{A+B}$OR 后取反
XOR$Y=A\oplus B$不同为 1
XNOR$Y=\overline{A\oplus B}$相同为 1

数字抽象把连续电压划分成逻辑 0 和逻辑 1。为抵抗噪声,需要输入/输出电压范围之间留出噪声容限:

$$ NM_H=V_{OH}-V_{IH} $$
$$ NM_L=V_{IL}-V_{OL} $$
CMOS 晶体管和门电路#

CMOS 中常见两类晶体管:

晶体管Gate=0Gate=1特点
nMOS关断导通传 0 好
pMOS导通关断传 1 好

CMOS 门电路用 pMOS 上拉网络和 nMOS 下拉网络互补实现逻辑函数。NAND、NOR、NOT 是常见基础结构。

电气特性要点:

  • 传播延迟:输入变化到输出稳定需要时间。
  • 上升/下降时间:输出电平转换不是瞬时完成。
  • 扇入:一个门的输入数量。
  • 扇出:一个输出能驱动的输入数量。
  • 功耗:包括静态功耗和动态功耗。
布尔代数#

常用定律:

名称公式
恒等律$A+0=A,\ A\cdot1=A$
零一律$A+1=1,\ A\cdot0=0$
幂等律$A+A=A,\ AA=A$
互补律$A+\overline A=1,\ A\overline A=0$
交换律$A+B=B+A,\ AB=BA$
结合律$(A+B)+C=A+(B+C)$
分配律$A(B+C)=AB+AC$
吸收律$A+AB=A$
德摩根律$\overline{AB}=\overline A+\overline B$,$\overline{A+B}=\overline A\overline B$

布尔代数用于:

  • 证明逻辑函数等价。
  • 化简逻辑表达式。
  • 从功能描述推导电路结构。
逻辑关系描述#

描述逻辑函数的常见方式:

方式特点
真值表唯一描述函数,变量多时规模指数增长
波形图适合描述信号随时间变化
逻辑表达式不唯一,便于化简和实现
标准形式便于系统化设计

术语:

名称含义
literal变量或变量的反
minterm所有变量都出现一次的乘积项
maxterm所有变量都出现一次的和项
SOP与项之和
POS或项之积

Canonical SOP:

$$ F=\sum m(\text{输出为 1 的行}) $$

Canonical POS:

$$ F=\prod M(\text{输出为 0 的行}) $$
逻辑函数化简#

代数法通过布尔定律变换表达式。卡诺图法通过相邻最小项合并消去变量。

卡诺图化简步骤:

  1. 按变量顺序填入输出为 1 的格子。
  2. 把相邻 1 按 $1,2,4,8,\ldots$ 个格子成组。
  3. 分组越大,消去变量越多。
  4. 每个 1 至少覆盖一次。
  5. 只保留每组中不变的变量。

don't care 项可以视情况当作 1 使用,用来扩大分组、简化表达式。

布尔逻辑、标准形式与 CMOS 复习抓手#
二值逻辑#

基本逻辑:

运算符号含义
ANDA · BAB同时为 1 才为 1
OR\(A + B\)至少一个为 1
NOT\(A'\), ~A, overbar取反

优先级:

  1. Parentheses
  2. NOT
  3. AND
  4. OR

常用定理:

  • Identity:$X + 0 = X$, $X \cdot 1 = X$
  • Null:$X + 1 = 1$, $X \cdot 0 = 0$
  • Idempotent:$X+X=X$, $XX=X$
  • Complement:$X+X'=1$, $XX'=0$
  • Absorption:$X + XY = X$
  • De Morgan:$(XY)' = X' + Y'$, $(X+Y)' = X'Y'$
真值表、表达式与标准形式#

真值表:

  • 唯一描述一个布尔函数。
  • 变量多时规模按 $2^n$ 增长。

布尔表达式:

  • 不唯一。
  • 可以通过布尔代数或 K-map 化简。

术语:

名称含义
literal变量或变量的反
product term若干 literal 的 AND
sum term若干 literal 的 OR
minterm所有变量都出现一次的 product term
maxterm所有变量都出现一次的 sum term
SOPsum of products
POSproduct of sums

Canonical SOP 使用输出为 1 的 minterms:

$$ F = \sum m(\text{rows where }F=1) $$

Canonical POS 使用输出为 0 的 maxterms:

$$ F = \prod M(\text{rows where }F=0) $$
布尔函数从真值表到表达式#

例:三输入函数 $F(x,y,z)$ 在编号 1, 2, 4, 7 时输出 1。

先写:

$$ F(x,y,z)=\sum m(1,2,4,7) $$

按变量顺序 x y z

编号xyzminterm
1001$x'y'z$
2010$x'yz'$
4100$xy'z'$
7111$xyz$

所以 canonical SOP:

$$ F=x'y'z+x'yz'+xy'z'+xyz $$

如果要用 decoder 实现:

  1. 使用一个 3-to-8 decoder。
  2. 把输出线 1,2,4,7 接到 OR gate。
  3. OR 输出就是 $F$。

这种题的核心是:decoder 的每根输出线天然对应一个 minterm。

K-map 化简的思路#

K-map 的本质是把只差一个变量的 minterms 放在相邻格子。相邻项可以合并,因为:

$$ AB + AB' = A $$

化简步骤:

  1. 把输出为 1 的格子填入 K-map。
  2. 按 $1,2,4,8,\ldots$ 个格子分组。
  3. 每组必须是矩形,且可跨边界。
  4. 每组越大,消去变量越多。
  5. 每个 1 至少覆盖一次。
  6. 得到每组中保持不变的变量,组成 product term。
  7. 所有 product term 相加得到 SOP。

don't care 的使用:

  • 可以当 1 用来扩大分组。
  • 也可以不用。
  • 目标是让表达式更简单,而不是机械地包含所有 don't care。
数字抽象与静态纪律#

数字系统故意把连续电压限制成离散逻辑值:

  • 高电平范围表示 1。
  • 低电平范围表示 0。
  • 中间保留 forbidden zone 来提高抗噪能力。

噪声容限:

$$ NM_H = V_{OH} - V_{IH} $$
$$ NM_L = V_{IL} - V_{OL} $$

static discipline:合法输入必须产生合法输出。

CMOS 基本规则#
晶体管Gate=0Gate=1适合传递
nMOSOFFONgood 0
pMOSONOFFgood 1

CMOS 互补结构:

  • pMOS 网络负责把输出拉到 $V_{DD}$。
  • nMOS 网络负责把输出拉到 GND。
  • NAND 和 NOR 是 CMOS 中常见的基础门。

电路参数:

  • propagation delay:输入变化到输出稳定的延迟。
  • transition time:输出电压上升或下降所需时间。
  • fan-in:一个门能接受的输入数量。
  • fan-out:一个输出能驱动的后级输入数量。
  • power dissipation:包括静态功耗和动态功耗。

3. 组合逻辑电路#

组合逻辑电路概述#

组合逻辑电路满足:

  • 输出只由当前输入决定。
  • 不含存储状态。
  • 不允许形成无意义反馈环。

设计步骤:

  1. 分析功能,确定输入和输出。
  2. 列真值表或逻辑关系。
  3. 推导逻辑表达式。
  4. 化简表达式。
  5. 画逻辑电路图或写 HDL。
  6. 分析延迟和竞争冒险。
典型组合逻辑部件#

译码器:

  • $n$ 位输入,最多 $2^n$ 个输出。
  • 常用于地址译码和 minterm 生成。

编码器:

  • 把多个输入信号编码成二进制编号。
  • 优先编码器用于处理多个输入同时有效的情况。

多路选择器:

  • 用选择信号从多个输入中选一路输出。
  • 是数据通路设计中最常用的选择部件。

多路分配器:

  • 把一路输入分配到多个输出之一。
  • 可看作带使能的译码器。

半加器:

$$ S=A\oplus B $$
$$ C=AB $$

全加器:

$$ S=A\oplus B\oplus C_{in} $$
$$ C_{out}=AB+AC_{in}+BC_{in} $$
组合逻辑时序分析#

传播延迟:

$$ T_{pd}=\text{输入变化到输出稳定的最大延迟} $$

最小延迟:

$$ T_{cd}=\text{输入变化到输出开始变化的最小延迟} $$

关键路径决定组合电路最大延迟,从而影响时钟周期。竞争冒险指同一输入变化经不同延迟路径到达输出,可能导致短暂错误跳变,也称 glitch。

组合逻辑设计与例题#
定义#

组合逻辑电路:

  • 有 $m$ 个输入和 $n$ 个输出。
  • 每个输出只依赖当前输入。
  • 没有存储状态。
  • 不允许反馈环。

设计流程:

  1. 确定输入、输出和逻辑关系。
  2. 写 truth table。
  3. 得到逻辑函数并化简。
  4. 画电路图和 timing diagram。
  5. 做 timing analysis。
典型组合电路#

Decoder:

  • $n$ 位输入。
  • 通常有 $2^n$ 个 one-hot 输出。
  • 可用于用 minterm 实现函数。

Encoder:

  • 把 one-hot 或 priority input 编成二进制输出。
  • 普通 encoder 的问题:多个输入同时为 1 时不明确。
  • priority encoder 通过优先级解决冲突,并常带 valid bit。

Multiplexer:

  • 用选择信号从多个输入中选一个输出。
  • $n$ 个 select bit 可以选择 $2^n$ 路输入。

Demultiplexer:

  • 一个输入分发到多个输出。
  • 本质上可看成带 enable 的 decoder。

Half Adder:

$$ S = A \oplus B $$
$$ C = AB $$

Full Adder:

$$ S = A \oplus B \oplus C_{in} $$
$$ C_{out} = AB + AC_{in} + BC_{in} $$
时序分析#

传播延迟:

$$ T_{pd} = \text{input 到 output 的最大延迟} $$

污染延迟:

$$ T_{cd} = \text{input 到 output 的最小延迟} $$

critical path:

  • 总传播延迟由最长路径决定。

shortest path:

  • 总污染延迟由最短路径决定。

race hazard / glitch:

  • 单个输入变化可能沿多条不同延迟路径影响输出。
  • 如果输出发生短暂错误跳变,就是 glitch。

4. 时序逻辑电路#

时序逻辑与有限状态机#

时序逻辑电路的输出不仅依赖当前输入,还依赖过去状态:

$$ S_{next}=f(S_{current},X) $$
$$ Y=g(S_{current},X) $$

有限状态机两种模型:

模型输出依赖特点
Moore只依赖状态输出稳定,状态数可能较多
Mealy依赖状态和输入状态数可能较少,输出可能更快变化
锁存器和触发器#
元件触发方式特点
锁存器 latch电平敏感使能有效期间输入可透传
触发器 flip-flop边沿敏感只在时钟边沿采样

常见元件:

  • 双稳态元件:能保存 1 bit 状态。
  • SR 锁存器:有 Set / Reset 输入,但存在非法输入组合。
  • D 锁存器:用一个数据输入避免 SR 的非法组合。
  • D 触发器:时钟边沿采样,是同步时序电路常用存储元件。
  • T 触发器:\(T=1\) 翻转,\(T=0\) 保持。
同步时序逻辑设计#

设计步骤:

  1. 根据需求定义状态含义。
  2. 画状态图。
  3. 列状态表。
  4. 状态化简。
  5. 状态编码。
  6. 根据触发器类型推导激励方程。
  7. 推导输出方程。
  8. 画电路或写 HDL。

状态化简的依据是状态等价:如果两个状态对所有输入产生相同输出,并转向相同或等价的下一状态,则可以合并。

时序约束:

$$ t_{clk}\ge t_{ffpd}+t_{comb}+t_{setup} $$

还要满足 hold time 约束,避免数据在时钟边沿后过早变化。

典型时序逻辑部件#

计数器:

  • 按固定状态序列变化。
  • 可用于计时、事件计数、程序计数器等。

寄存器:

  • 多个触发器组成,用于保存多 bit 数据。
  • 寄存器堆支持多读多写端口,是 CPU 数据通路核心部件。

移位寄存器:

  • 支持左移、右移、并行装入、保持。
  • 可用于串并转换、乘除法移位操作等。
FSM、寄存器和时序约束#
时序逻辑定义#

时序电路由组合逻辑和存储元件组成:

$$ next\ state = f(input, present\ state) $$

输出分两种:

模型输出依赖
Moore只依赖 state
Mealy依赖 input 和 state

一般规律:

  • Mealy 输出标在边上,常用 input/output
  • Moore 输出标在状态上,通常状态数更多。
组合逻辑与时序逻辑的判断题#

看到题目时可以这样判断:

现象可能类型
输出只由当前输入决定combinational
输出还依赖过去发生过什么sequential
电路里有 flip-flop / registersequential
posedge clksequential
有 feedback 但没有明确存储元件需要谨慎,可能是 latch 或非法组合环

典型组合逻辑:

  • decoder
  • encoder
  • mux
  • adder
  • ALU
  • barrel shifter

典型时序逻辑:

  • register
  • counter
  • shift register
  • FSM
  • PC
  • register file 的写入部分
Latch 与 Flip-Flop#
元件触发方式特点
latchlevel-sensitive时钟有效电平期间透明
flip-flopedge-triggered只在边沿采样

D latch:

  • \(C=1\) 时,\(D\) 传到 \(Q\)
  • \(C=0\) 时,保持旧值。

D flip-flop:

  • 在上升沿或下降沿采样 \(D\)
  • 更适合复杂同步电路的 timing control。

常见扩展:

  • enabled D flip-flop:\(EN=1\) 时更新,\(EN=0\) 时保持。
  • resettable D flip-flop:可以同步或异步 reset。
  • JK flip-flop:\(J=K=1\) 时翻转。
  • T flip-flop:\(T=1\) 时翻转,\(T=0\) 保持。
Verilog 时序逻辑模板#

同步寄存器:

always @(posedge clk) begin
    q <= d;
end

带同步 reset:

always @(posedge clk) begin
    if (rst)
        q <= 1'b0;
    else
        q <= d;
end

带异步 reset:

always @(posedge clk or posedge rst) begin
    if (rst)
        q <= 1'b0;
    else
        q <= d;
end

时序逻辑经验:

  • 时序逻辑用 nonblocking assignment:\(\le \)
  • 组合逻辑常用 blocking assignment:\(=\)
  • 一个寄存器最好只在一个 always block 里赋值。
  • reset 是同步还是异步,要和题目或实验框架一致。
FSM 分析与设计流程#

分析一个时序电路:

  1. 找状态变量。
  2. 写 next-state equation。
  3. 写 output equation。
  4. 列 state table。
  5. 画 state diagram。

设计一个 FSM:

  1. Specification:明确功能。
  2. Formulation:得到 state diagram 或 state table。
  3. State assignment:给状态分配二进制编码。
  4. Flip-flop input equation:根据触发器类型推输入方程。
  5. Output equation:推输出方程。
  6. Optimization:化简。
  7. Technology mapping:映射到门和触发器。
  8. Verification:验证。
等价状态与状态压缩#

两个状态等价的条件:

  • 对每个可能输入,输出完全相同。
  • 对每个可能输入,next state 相同或等价。

等价状态可以合并,从而减少状态数。

序列检测器例子:识别 1101#

Mealy 设计思路:

状态含义
A还没有匹配到有效前缀
B已看到 1
C已看到 11
D已看到 110

当在 D 状态读到 1 时,识别到 1101,输出 1。由于最后一个 1 也可能是下一次匹配的起点,所以转回 B。

Moore 版本需要额外状态 E 来输出 1,因此通常“Moore is more”。

FSM 的 Verilog 写法#

推荐三段式写法:

localparam S_IDLE = 2'b00;
localparam S_1    = 2'b01;
localparam S_11   = 2'b10;
localparam S_110  = 2'b11;

reg [1:0] state, next_state;

always @(posedge clk) begin
    if (rst)
        state <= S_IDLE;
    else
        state <= next_state;
end

always @(*) begin
    next_state = state;
    case (state)
        S_IDLE: next_state = x ? S_1 : S_IDLE;
        S_1:    next_state = x ? S_11 : S_IDLE;
        S_11:   next_state = x ? S_11 : S_110;
        S_110:  next_state = x ? S_1 : S_IDLE;
        default: next_state = S_IDLE;
    endcase
end

always @(*) begin
    y = 1'b0;
    if (state == S_110 && x)
        y = 1'b1;   // Mealy: output depends on state and input
end

对应的是 Mealy 版本 1101 序列检测器。输出在读到最后一个 1 的那个周期产生。

若改成 Moore,通常要增加一个 \(S_{1101}\) 状态,并在该状态输出 1

assign y = (state == S_1101);

Moore 输出更稳定,但可能晚一个周期,并且状态数可能更多。

状态编码#

若有 $m$ 个状态,至少需要 $n$ 位:

$$ n \ge \lceil \log_2 m \rceil $$

状态编码会影响逻辑化简成本。经验规则:

  • 对某输入有相同 next state 的状态,尽量编码相邻。
  • 同一个状态的多个 next states,尽量编码相邻。
  • 输出相同的状态,尽量编码相邻。
时序约束#

对 D flip-flop:

  • $t_{setup}$:时钟沿前输入必须稳定的时间。
  • $t_{hold}$:时钟沿后输入必须继续稳定的时间。
  • $t_{ffpd}$:clock-to-Q 延迟。

时钟周期约束:

$$ t_{clk} \ge t_{ffpd} + t_{comb} + t_{setup} $$

hold 约束:

$$ t_{hold} \le t_{ffpd} + t_{comb} $$
Setup / Hold 约束例题#

假设:

t_ffpd = 2 ns
t_comb = 7 ns
t_setup = 1 ns

则时钟周期至少:

$$ t_{clk} \ge 2 + 7 + 1 = 10\text{ ns} $$

最大频率:

$$ f_{max} = 1 / t_{clk} = 100\text{ MHz} $$

如果组合逻辑太长,解决方向:

  • 优化组合逻辑。
  • 插入 pipeline register,把长路径切短。
  • 降低 clock frequency。

hold violation 和 setup violation 的直觉不同:

  • setup violation:数据来得太晚。
  • hold violation:数据变得太早。

setup 可通过降频缓解,hold 通常不能单靠降频解决。

Register#

寄存器是多个触发器组成的多 bit 存储单元。

用途:

  • 临时保存数据。
  • 构建 datapath。
  • 支持 register transfer operation。

微操作 microoperation:

类型例子
transfer\(R_{2} <- R_{1}\)
arithmeticadd, subtract, increment
logicAND, OR, XOR
shiftleft shift, right shift
Datapath 与 Control Unit#

数字系统常分成:

部分作用
datapath执行数据处理,包含寄存器、ALU、bus、mux
control unit决定何时执行哪些微操作

寄存器传输结构:

  • dedicated multiplexer
  • shared bus
  • three-state bus
  • multiple buses
Shift Register#

移位寄存器可以把数据向 MSB 或 LSB 方向移动。

常见功能:

  • shift right
  • shift left
  • parallel load
  • hold

如果每个 D flip-flop 前放一个 4-input MUX,就可以统一支持这几种操作。

Counter#

计数器是按固定状态序列运行的时序电路。

用途:

  • 计时。
  • 计数事件。
  • 记录发送或接收了多少 bit。
  • CPU 中的 Program Counter。

Ripple counter:

  • 只有最低位直接接全局时钟。
  • 高位时钟来自低位输出。
  • 变化逐级 ripple。
  • 最坏延迟约为 $n \cdot t_{PHL}$。

Synchronous counter:

  • 所有触发器共享同一个 clock。
  • 组合逻辑计算 next state。
  • 更适合高速设计。

5. FPGA 设计和 Verilog HDL#

可编程逻辑器件和 FPGA#

FPGA 是可编程逻辑器件的一类。使用者通过配置文件改变内部逻辑连接,从而实现不同数字电路。它适合教学和实验,因为可以在硬件上验证 CPU、控制器、数据通路等设计。

相关概念:

概念含义
PLD可编程逻辑器件
PLA可编程逻辑阵列
FPGA现场可编程门阵列
ASIC专用集成电路
HDL 设计流程#

基于 HDL 的数字电路设计通常按下面的链路推进:

  1. Logic design with HDL:用 HDL 描述电路结构和行为。
  2. Simulation:编写 testbench 验证功能是否正确。
  3. Synthesis:把 HDL 综合为门级网表。
  4. Physical design:布局布线,确定实际硬件连接。
  5. ASIC tape-out 或 FPGA bitstream download:生成版图或下载到 FPGA。

仿真检查的是“逻辑行为是否符合预期”,综合关注的是“这些写法能不能变成实际硬件”。所以 Verilog 不是普通软件语言,写代码时要一直想它对应的电路是什么。

Verilog 基础语法#

模块结构:

module top(
    input a,
    input b,
    output c
);
    assign c = a & b;
endmodule

关键点:

  • Verilog 区分大小写。
  • 关键字小写。
  • // 表示单行注释,\(/* ... */\) 表示多行注释。
  • assign 常用于组合逻辑的连续赋值。

数字常量格式:

<位宽>'<进制><数值>

例:

4'b1010
8'hff
16'd255

常见进制:

base含义
\(b\)binary
\(o\)octal
\(d\)decimal
\(h\)hexadecimal

特殊值:

  • \(x\):unknown。
  • \(z\):high impedance。
  • ?:在数字常量中等价于 \(z\)

负数写法要注意位宽归属:

-6'd3   // 正确
4'd-2   // 错误

常见数据类型:

类型用途
wire连线型,常用于连续赋值
reg过程赋值变量,不一定综合成寄存器
vector多 bit 信号,如 [7:0]
array多元素存储结构

端口常见规则:

  • input 端口定义一般是 wire。
  • output 可为 wire 或 reg,取决于赋值方式。
  • 实例化时 output 接到外部 wire。
Verilog 建模方式和模板#
建模方式说明
结构建模实例化模块或门并连接
数据流建模使用 assign 描述组合逻辑
行为建模使用 alwaysifcase 描述行为

assign 适合简单组合逻辑:

assign y = sel ? a : b;
assign sum = a + b;

\(always @(*)\) 适合较复杂的组合逻辑:

always @(*) begin
    y = 1'b0;          // 默认值,避免 latch
    case (op)
        2'b00: y = a & b;
        2'b01: y = a | b;
        2'b10: y = a ^ b;
        2'b11: y = ~a;
        default: y = 1'b0;
    endcase
end

组合逻辑最重要的检查:

  • 每条路径都给输出赋值。
  • case 最好有 default,或者在前面给默认值。
  • 不要在组合逻辑里使用 clock edge。
  • 分支不全会让综合工具推导 latch。

错误例子:

always @(*) begin
    if (en)
        y = a;
end

\(en=0\) 时,\(y\) 没被赋值,综合工具会认为它必须“记住原值”,于是推导出 latch。

时序逻辑通常由触发器实现,模板是:

always @(posedge clk) begin
    if (rst)
        q <= 1'b0;
    else
        q <= d;
end

注意:

  • 时序逻辑通常用非阻塞赋值 \(\le \)
  • 组合逻辑常用阻塞赋值 \(=\)

6. 运算方法和运算部件#

基本运算部件#

串行进位加法器 RCA:

  • 由多个全加器串接。
  • 低位进位逐级传到高位。
  • 硬件简单,但延迟较大。

并行进位加法器 CLA:

定义:

$$ G_i=A_iB_i $$
$$ P_i=A_i+B_i $$

进位:

$$ C_{i+1}=G_i+P_iC_i $$

CLA 通过并行生成进位减少串行等待。

带标志加法器需要生成:

标志含义
ZF结果为 0
SF结果符号位
CF无符号进位/借位
OF有符号溢出

ALU 支持多种算术逻辑操作,是 CPU 运算器核心。

定点数运算#

补码加减法:

$$ A-B=A+\overline B+1 $$

所以加法和减法可以共用同一个加法器,只需控制 B 是否取反以及最低位进位输入是否为 1。

有符号溢出判断:

  • 两个正数相加得到负数,正溢出。
  • 两个负数相加得到正数,负溢出。
  • 一正一负相加不会溢出。

原码乘法:

  • 符号单独处理。
  • 数值部分按无符号乘法。

补码乘法:

  • 可用 Booth 编码等方法处理连续 1。
  • 核心思想是将连续 1 的部分积转换为少量加/减操作。

除法:

  • 恢复余数除法:减过头时加回除数。
  • 不恢复余数除法:不立即恢复,而在下一步改为加除数修正。
浮点数运算#

浮点加减:

  1. 对阶。
  2. 尾数相加或相减。
  3. 规格化。
  4. 舍入。
  5. 检查溢出、下溢和特殊值。

浮点乘除:

  • 符号由操作数符号决定。
  • 指数相加或相减。
  • 尾数相乘或相除。
  • 再规格化和舍入。

浮点运算的重点不是“精确实数运算”,而是“有限位宽下的近似运算”。

加法器、ALU 和乘除法硬件#
Carry Propagate Adder#

常见 CPA:

类型特点
Ripple-Carry Adder硬件简单,但 carry 逐级传播,慢
Carry-Lookahead Adder用 generate/propagate 提前计算 carry
Prefix Adder$O(\log N)$ 级计算 carry,更快但硬件更多

RCA 延迟:

$$ t_{ripple} = N t_{FA} $$
CLA generate 与 propagate#

第 $i$ 位:

$$ G_i = A_iB_i $$
$$ P_i = A_i + B_i $$

carry:

$$ C_{i+1} = G_i + P_i C_i $$

sum:

$$ S_i = A_i \oplus B_i \oplus C_i $$

CLA 的思想是先算每一位或每个 block 的 generate/propagate,减少 carry 串行传播。

Adder 设计细化#

RCA:

FA0 -> FA1 -> FA2 -> ... -> FAN-1

carry 必须一级一级传,所以最坏情况很慢。

CLA 的关键是把:

$$ C_{i+1}=G_i+P_iC_i $$

展开。例如:

$$ C_1 = G_0 + P_0C_0 $$
$$ C_2 = G_1 + P_1C_1 = G_1 + P_1G_0 + P_1P_0C_0 $$
$$ C_3 = G_2 + P_2G_1 + P_2P_1G_0 + P_2P_1P_0C_0 $$

这样 carry 可以由多级门并行算出,而不是等前一级 full adder 的 carry out。

Prefix adder 更进一步,把 generate/propagate 做成树形前缀运算:

1-bit group -> 2-bit group -> 4-bit group -> 8-bit group -> ...

所以延迟大致随 $\log N$ 增长,但布线和硬件成本更高。

减法器#

减法 $X-Y$ 可以两种实现:

  1. 串接 full subtractor,borrow ripple。
  2. 用加法器计算:
$$ X - Y = X + (\sim Y + 1) $$

因此 adder-subtractor 通常通过控制信号决定是否取反 B 并把最低位 carry-in 设为 1。

ALU#

ALU 是 processor datapath 的核心组合逻辑,用来执行:

  • arithmetic:add, subtract
  • logic:AND, OR, XOR
  • shift
  • comparison:SLT

典型 datapath:

registers -> ALU -> destination register

ALU 与寄存器配合时,一个 register transfer operation 通常在一个 clock cycle 内完成。

SLT#

SLT 即 set less than。常见做法:

  1. 计算 $A-B$。
  2. 根据符号位和 overflow 判断 $A<B$。
  3. 结果低位置 1,高位补 0。

不能只看减法结果符号位,因为 signed overflow 会影响符号位含义。

ALU 控制信号理解#

一个简单 ALU 可以把多个操作共用硬件:

ALUOp操作
000AND
001OR
010ADD
110SUB
111SLT

加法和减法可共用 adder:

SUB: A - B = A + ~B + 1

因此控制信号可以做:

Binvert = 1
Cin = 1

SLT 常基于 subtraction,但 signed SLT 要考虑 overflow:

less = sum_sign XOR overflow

因为 overflow 会让符号位“翻错方向”。

乘法基本思想#

二进制乘法和十进制乘法类似:

  • multiplier 的某位为 1,则加入 shifted multiplicand。
  • multiplier 的某位为 0,则不加。
  • 部分积相加得到结果。

硬件实现会在 area 和 time 间折中:

  • 宽 ALU + 多寄存器:简单但硬件多。
  • 复用较窄 ALU:硬件少但多周期。
  • array multiplier:并行加部分积,快但面积大。
有符号乘法#

简单做法:

  1. 记录 operands 的符号。
  2. 转成正数做无符号乘法。
  3. 根据符号异或决定结果符号。

也可以通过符号扩展和 Booth encoding 直接处理补码。

Booth Encoding#

Booth 算法的核心:

  • 把连续的一串 1 变成“开始减一次,结束加一次”。
  • 对连续 1 很长的 multiplier,可以减少部分积数量。

1-bit Booth 规则:

当前位右侧前一位操作
00no op
01add multiplicand
10subtract multiplicand
11no op

直观理解:

$$ 01111000 = 10000000 - 00001000 $$
Booth 算法更直观的例子#

计算 M x 01111000

普通乘法看到四个连续的 1,需要:

M<<3 + M<<4 + M<<5 + M<<6

Booth 把它看成:

01111000 = 10000000 - 00001000

所以:

M x 01111000 = (M << 7) - (M << 3)

从 4 次加法变成 1 次加法意义上的高位项和 1 次减法。连续 1 越长,Booth 越有优势。

判断边界:

0 -> 1 : run begins, subtract
1 -> 0 : run ends, add

从右往左扫描乘数时,本质就是检测这些连续 1 的边界。

除法#

无符号除法本质上反复判断:

$$ remainder \ge divisor $$

可以通过:

$$ remainder - divisor \ge 0 $$

来判断。

Restoring division:

  • 先减 divisor。
  • 如果 remainder 变负,恢复原值。
  • 每 bit 可能需要两次 ALU 操作。

Non-restoring division:

  • 如果上一步 remainder 为负,下一步改为加 divisor。
  • 避免立即 restore。
  • 可以做到约 1 bit per cycle。

有符号除法:

  • quotient 的符号由 operands 符号异或决定。
  • 非零 remainder 的符号应与 dividend 一致。
Restoring / Non-restoring Division 对比#

Restoring division 每一步:

  1. \(R = R - D\)
  2. \(R \ge 0\),商位写 1。
  3. \(R < 0\),商位写 0,并 \(R = R + D\) 恢复。

缺点:

  • 失败时要多做一次加法恢复。

Non-restoring division 的想法:

  • 如果上一步减过头导致 \(R < 0\),下一步不要先恢复。
  • 下一轮改成加 divisor,把效果补回来。

所以每轮根据 remainder 符号选择:

当前 remainder下一步
\(R \ge 0\)shift 后减 divisor
\(R < 0\)shift 后加 divisor

最后若 remainder 为负,再做一次修正。

7. 指令系统和 RISC-V#

指令系统概述#

指令系统是 ISA 的核心。每条指令规定:

  • 做什么操作。
  • 操作数从哪里来。
  • 结果写到哪里。
  • 下一条指令如何确定。

指令通常包含:

字段作用
操作码 opcode指定操作类型
源操作数字段指定输入数据位置
目的操作数字段指定结果位置
立即数字段指令中直接给出的常数
控制转移字段指定分支或跳转目标
指令系统设计#

设计 ISA 需要权衡:

  • 指令数量多少。
  • 指令格式固定还是可变。
  • 操作数个数。
  • 寻址方式多少。
  • 是否采用 load-store 风格。
  • 是否支持异常、中断和特权机制。

寻址方式:

方式含义
立即寻址操作数直接在指令中
寄存器寻址操作数在寄存器中
直接寻址地址字段给出内存地址
间接寻址地址字段指向一个保存有效地址的位置
基址/变址寻址基地址加偏移
PC 相对寻址目标地址由 PC 加偏移得到

指令系统风格:

风格特点
CISC指令复杂、格式多样、寻址方式丰富
RISC指令规整、load-store、易流水化
栈式操作数隐含在栈顶
累加器式一个操作数隐含在累加器
寄存器-寄存器式算术逻辑操作只在寄存器间进行
RISC-V 架构#

RISC-V 命名:

RV + 位宽 + 扩展

例:

名称含义
RV32I32 位基础整数指令集
RV32IMRV32I 加整数乘除扩展
RV32G常用通用扩展组合

RISC-V 常见指令格式:

格式用途
R-type寄存器-寄存器运算
I-type立即数运算、load、jalr
S-typestore
B-type条件分支
U-type高位立即数
J-type无条件跳转

RISC-V 设计特点:

  • 寄存器字段位置尽量固定。
  • 立即数符号位固定在指令最高位附近,便于符号扩展。
  • 无分支延迟槽。
  • 基础指令集小,扩展模块化。
ISA 设计、数据通路需求和 RISC-V 细节#
ISA 是什么#

ISA 是软件和硬件之间的契约:

  • instruction set
  • registers
  • memory organization
  • addressing modes
  • exception / interrupt behavior

ISA 不是具体微架构。同一个 ISA 可以有多个实现。

简化 MIPS 数据通路#

以简化 MIPS 为例,支持:

  • addu rd, rs, rt
  • subu rd, rs, rt
  • ori rt, rs, imm16
  • lw rt, imm16(rs)
  • sw rt, imm16(rs)
  • beq rs, rt, imm16

需要的硬件部件:

部件作用
PC保存下一条指令地址
Instruction Memory根据 PC 取指
Register File两读一写
ALU加、减、OR、比较
Extender立即数扩展,zero/sign extension
Data Memoryload/store
MUX在不同来源之间选择数据
Control生成 RegWr、ALUSrc、MemWr 等控制信号

PC 更新:

  • 顺序执行:\(PC <- PC + 4\)
  • branch taken:\(PC <- PC + 4 + (sign_{ext}(imm_{16}) << 2)\)
单周期数据通路信号怎么想#

以简化 MIPS / 类似单周期 CPU 为例,做题时按 instruction 分类。

R-type addu rd, rs, rt

信号
RegDstrd
ALUSrc选 register rt
MemToReg选 ALU result
RegWr1
MemWr0
ExtOpt不关心
ALUCtradd
nPC_selPC+4

ori rt, rs, imm16

信号
RegDstrt
ALUSrc选 immediate
ExtOptzero extension
MemToReg选 ALU result
RegWr1
MemWr0
ALUCtrOR

lw rt, imm16(rs)

信号
RegDstrt
ALUSrc选 immediate
ExtOptsign extension
MemToReg选 memory data
RegWr1
MemWr0
ALUCtradd address

sw rt, imm16(rs)

信号
ALUSrc选 immediate
ExtOptsign extension
RegWr0
MemWr1
ALUCtradd address

beq rs, rt, imm16

信号
ALUSrc选 register rt
RegWr0
MemWr0
ALUCtrsubtract / compare equal
nPC_sel取决于 zero flag 和 branch control

做数据通路题的顺序:

  1. 这条指令要不要写寄存器?
  2. 写哪个寄存器?
  3. ALU 第二个输入来自寄存器还是立即数?
  4. 立即数是 sign extend 还是 zero extend?
  5. 结果来自 ALU 还是 memory?
  6. 要不要写 memory?
  7. PC 是 \(PC+4\) 还是 branch/jump target?
ISA 设计原则#

Hennessy 和 Patterson 总结的原则:

  1. Simplicity favors regularity。
  2. Make the common case fast。
  3. Smaller is faster。
  4. Good design demands good compromises。

ISA 设计要考虑:

  • 支持哪些数据类型。
  • 提供多少操作。
  • 每条指令几个 operand。
  • operand 放在寄存器还是内存。
  • 地址模式如何设计。
  • 指令长度固定、可变还是混合。
操作数数量#
类型特点
3-address两个 source,一个 destination,灵活但指令长
2-addressdestination 常复用一个 source,可能破坏原值
1-address隐含 accumulator,硬件简单但程序繁琐
0-addressstack machine,操作数隐含在栈顶
Addressing Modes#
模式例子含义
ImmediateADD #5operand 本身就是值
DirectADD 100operand 是内存地址
IndirectADD [100]operand 指向的内存中存放真正地址
Register DirectADD R5operand 在寄存器
Register IndirectADD [R3]R3 中保存内存地址
RelativePC + offset常用于 branch
Indexedbase address + index常用于数组
Basedbase register + displacement常用于栈帧、结构体
CISC 与 RISC#

CISC:

  • variable length instructions。
  • 指令和寻址模式丰富。
  • 解码复杂。
  • 可能包含 memory-to-memory 操作。
  • 常使用 microcode。

RISC:

  • fixed length instructions。
  • load-store architecture。
  • 较少寻址模式。
  • 硬连线控制。
  • 寄存器数量较多。
  • 更容易 pipeline。

现代 x86 常见现象:

  • 外部 ISA 是 CISC。
  • 内部微架构会把复杂 x86 指令翻译成更类似 RISC 的 micro-ops。
基本特点#

RISC-V 是开放的 RISC ISA 标准。

命名方式:

RV + word width + extensions

例子:

  • RV32I:32-bit base integer ISA。
  • RV32IM:RV32I 加整数乘除扩展 M。
  • RV32G:通常表示 IMAFD。

标准扩展:

扩展含义
IInteger base
MInteger multiplication and division
AAtomic
FSingle-precision floating point
DDouble-precision floating point
CCompressed 16-bit instructions
RISC-V 处理器状态#

包括:

  • PC
  • 32 个整数寄存器 \(x_{0}-x_{31}\)
  • \(x_{0}\) 恒为 0
  • \(x_{1}\) 常作为 return address
  • 32 个浮点寄存器 \(f_{0}-f_{31}\)
  • floating-point status register
指令格式#

RISC-V base instruction 通常是 32-bit,低两位为 11

核心格式:

格式用途
R-typeregister-register arithmetic
I-typeload, immediate arithmetic, jalr
S-typestore
B-typeconditional branch
U-typeupper immediate
J-typeunconditional jump

重要规则:

  • register fields 的位置固定,便于硬件解码。
  • immediate 的 sign bit 总在 instruction bit 31。
  • branch/jump offset 按 2-byte 粒度组织,以兼容 compressed instructions。
RISC-V 指令格式字段细节#

R-type:

funct7 | rs2 | rs1 | funct3 | rd | opcode

典型:

add rd, rs1, rs2
sub rd, rs1, rs2

I-type:

imm[11:0] | rs1 | funct3 | rd | opcode

典型:

addi rd, rs1, imm
lw   rd, imm(rs1)
jalr rd, imm(rs1)

S-type:

imm[11:5] | rs2 | rs1 | funct3 | imm[4:0] | opcode

典型:

sw rs2, imm(rs1)

B-type:

imm[12,10:5] | rs2 | rs1 | funct3 | imm[4:1,11] | opcode

典型:

beq rs1, rs2, label

U-type:

imm[31:12] | rd | opcode

典型:

lui   rd, imm20
auipc rd, imm20

J-type:

imm[20,10:1,11,19:12] | rd | opcode

典型:

jal rd, label

设计上看起来 immediate 被“拆散”了,但好处是:

  • rd, rs1, rs2 位置尽量固定。
  • sign bit 固定在 bit 31。
  • 硬件解码和符号扩展更规整。
常见指令类别#

R-type:

  • ADD, SUB
  • SLT, SLTU
  • SLL, SRL, SRA

I-type:

  • ADDI
  • SLTI, SLTIU
  • ANDI, ORI, XORI
  • SLLI, SRLI, SRAI
  • load instructions

U-type:

  • LUI
  • AUIPC

Control transfer:

  • JAL
  • JALR
  • conditional branches
Calling Convention#

函数调用阶段:

  1. 把参数放到 callee 可访问的位置。
  2. jal 跳转到函数。
  3. callee 分配局部资源,必要时保存寄存器。
  4. 执行函数体。
  5. 把返回值放到 caller 可访问的位置,恢复寄存器,释放栈帧。
  6. ret 返回。

概念:

名称含义
caller调用者
callee被调用函数
prologue函数入口保存寄存器、分配栈帧
epilogue函数出口恢复寄存器、释放栈帧
stack frame函数私有栈空间
fpframe pointer,常为 \(x_{8}\)
RISC-V 函数调用小例子#

C 代码:

int add2(int a, int b) {
    return a + b;
}

RISC-V 中常见约定:

a0 = 第 1 个参数 / 返回值
a1 = 第 2 个参数
ra = return address
sp = stack pointer

可能的汇编:

add2:
    add a0, a0, a1
    ret

如果函数内部还要调用别的函数,就必须保护 ra,因为新的 jal 会覆盖它:

foo:
    addi sp, sp, -16
    sw   ra, 12(sp)

    jal  bar

    lw   ra, 12(sp)
    addi sp, sp, 16
    ret

理解 prologue / epilogue:

  • prologue:进入函数时开栈帧、保存该保存的寄存器。
  • epilogue:返回前恢复寄存器、释放栈帧。

8. 中央处理器#

CPU 概述#

CPU 基本功能:

  • 取指令。
  • 译码。
  • 取操作数。
  • 执行运算或访存。
  • 写回结果。
  • 处理中断和异常。

CPU 基本组成:

部件作用
数据通路数据实际流动和处理的路径
控制器产生控制信号
ALU运算
寄存器堆保存临时数据
PC保存指令地址
指令存储器/数据存储器接口取指和访存
单周期 CPU#

单周期 CPU 中,每条指令在一个时钟周期内完成。设计步骤:

  1. 分析每条指令的功能。
  2. 确定需要哪些部件。
  3. 建立数据通路。
  4. 增加多路选择器以复用通路。
  5. 设计控制器产生控制信号。
  6. 根据最长指令路径确定时钟周期。

常见控制信号:

信号作用
RegWrite是否写寄存器
MemWrite是否写数据存储器
ALUSrcALU 第二操作数来自寄存器还是立即数
MemToReg写回数据来自 ALU 还是内存
Branch是否为分支指令
ALUOp指定 ALU 操作
多周期 CPU 和控制器#

多周期 CPU 把一条指令拆成多个阶段执行,每个阶段完成一部分操作。优点是:

  • 同一硬件可在不同周期复用。
  • 时钟周期可按较短阶段设置。
  • 控制器可用有限状态机描述。

控制器设计方式:

方式特点
硬连线控制器速度快,修改困难
微程序控制器控制逻辑像程序一样组织,灵活但可能较慢
流水线 CPU#

典型五级流水:

阶段含义
IF取指
ID译码/读寄存器
EX执行/地址计算
MEM访存
WB写回

流水线冒险:

冒险原因常见处理
结构冒险多条指令争用同一硬件增加硬件或暂停
数据冒险后续指令依赖前面结果forwarding 或 stall
控制冒险分支改变 PC分支预测、flush、stall

流水线提高吞吐率,但不一定降低单条指令延迟。

和前面章节的关系#

CPU 设计把前面几章的部件放到同一个时间结构中:组合逻辑负责“算出下一步”,触发器和寄存器负责“在时钟边沿保存状态”,控制器负责“根据指令产生选择信号”。单周期 CPU 的关键是连出完整数据通路,多周期 CPU 的关键是把每条指令拆成状态序列,流水线 CPU 的关键是把不同指令的不同阶段重叠执行并处理冒险。

做 CPU 数据通路题时,建议按以下顺序判断:

  1. 这条指令是否写寄存器。
  2. 写哪个寄存器。
  3. ALU 第二操作数来自寄存器还是立即数。
  4. 立即数需要 sign extension 还是 zero extension。
  5. 写回值来自 ALU、内存还是 PC+4。
  6. 是否写数据存储器。
  7. PC 下一值是顺序地址、branch target 还是 jump target。

9. 存储器层次结构#

存储器概述#

存储器层次结构按速度、容量、成本组织:

寄存器
-> cache
-> 主存 DRAM
-> 外存 SSD/HDD

越靠近 CPU:

  • 速度越快。
  • 容量越小。
  • 单位成本越高。

局部性原理:

类型含义
时间局部性最近访问过的数据很可能再次访问
空间局部性访问某地址后,附近地址也可能被访问
主存储器#

SRAM:

  • 速度快。
  • 成本高。
  • 常用于 cache。

DRAM:

  • 密度高。
  • 成本低。
  • 需要刷新。
  • 常用于主存。

存储器扩展:

  • 位扩展:增加数据宽度。
  • 字扩展:增加地址空间容量。
  • 字位同时扩展:同时增加宽度和容量。
Cache#

cache 是主存的高速缓存。基本工作方式:

  1. CPU 发出地址。
  2. cache 判断是否命中。
  3. 命中则直接返回数据。
  4. 缺失则从主存取块并填入 cache。

映射方式:

方式特点
直接映射每个主存块只能放到固定 cache 行
全相联映射可放到任意 cache 行
组相联映射先选组,再在组内任意放置

替换算法:

  • 随机替换。
  • FIFO。
  • LRU 或近似 LRU。

写策略:

策略含义
write-through写 cache 同时写主存
write-back先只写 cache,替换时写回主存
write-allocate写缺失时把块调入 cache
no-write-allocate写缺失时直接写主存
虚拟存储器#

虚拟存储器把主存看作外存的缓存,为每个进程提供独立虚拟地址空间。

分页式虚拟存储:

  • 虚拟地址空间划分为虚拟页。
  • 物理内存划分为页框。
  • 页表记录虚拟页到物理页框的映射。
  • 访问未在主存中的页会发生缺页异常。

虚拟存储器的作用:

  • 扩大程序可用地址空间。
  • 实现进程隔离。
  • 支持按需调页。
  • 提供存储保护。
和 CPU 执行的关系#

存储层次结构的目标是在“容量、速度、成本”之间折中。寄存器和 cache 让 CPU 尽量少等主存,虚拟存储器让每个进程看到独立的地址空间。理解存储器时要抓住两个缓存关系:cache 是主存的缓存,主存又可以看作外存的缓存。

10. 系统互连与输入/输出#

外设与系统总线#

外设分为:

类型例子
输入设备键盘、鼠标、扫描仪
输出设备显示器、打印机
输入/输出设备磁盘、网卡、触摸屏

总线是部件之间传递信息的公共路径。系统总线通常包括:

  • 地址线。
  • 数据线。
  • 控制线。

总线性能指标:

指标含义
总线宽度一次并行传输的数据位数
工作频率数据传输频率
带宽单位时间最大传输数据量
寻址能力地址线决定可访问空间大小

带宽常用估算:

$$ B=\frac{W\times F}{N} $$

其中 $W$ 为一次传输数据量,$F$ 为时钟频率,$N$ 为一次传输所需周期数。

I/O 接口和端口#

I/O 接口也称设备控制器,位于外设和总线之间。功能包括:

  • 数据缓冲。
  • 状态检测。
  • 控制和定时。
  • 数据格式转换。

I/O 端口是 I/O 接口中 CPU 可访问的寄存器:

端口作用
数据端口传输数据
状态端口读取设备状态
控制端口写入控制命令

端口编址方式:

方式特点
独立编址I/O 地址空间和内存地址空间分离,需要专用 I/O 指令
统一编址I/O 端口像内存单元一样编址,也称 memory-mapped I/O
输入/输出控制方式#

程序直接控制:

  • CPU 主动查询设备状态。
  • 简单,但 CPU 可能长时间等待。

中断控制 I/O:

  • 设备完成或就绪后向 CPU 发中断。
  • CPU 可在等待期间执行其他程序。
  • 需要中断响应和中断服务程序。

DMA:

  • Direct Memory Access。
  • 外设和主存之间由 DMA 控制器直接传输数据。
  • CPU 只负责初始化和收尾。
  • 适合磁盘等高速块设备。

三种方式对比:

方式CPU 参与程度适用场景
程序查询简单低速设备
中断低速或中速异步设备
DMA大批量高速数据传输
I/O 软件层次#

一次 I/O 请求通常经过多个软件层次:

用户程序 / C 库函数
-> 系统调用
-> 与设备无关的 I/O 软件
-> 设备驱动程序
-> 中断服务程序
-> I/O 控制器和外设

与设备无关软件负责:

  • 统一设备驱动接口。
  • 缓冲区管理。
  • 错误报告。
  • 文件打开关闭等通用操作。
  • 逻辑块大小处理。

设备驱动程序负责:

  • 操作具体设备控制器。
  • 读写控制/状态/数据端口。
  • 启动设备。
  • 处理中断或 DMA 完成事件。

中断服务程序包含:

  1. 准备阶段:保存断点和现场,设置屏蔽字。
  2. 处理阶段:执行具体中断服务。
  3. 恢复阶段:恢复现场并返回。
三种 I/O 方式的直觉#
方式谁等谁CPU 负担适合场景
程序查询CPU 等设备简单低速设备
中断设备就绪后通知 CPU异步低速/中速设备
DMADMA 控制器接管总线传块磁盘、网卡等大批量高速传输

中断请求的是 CPU 时间,DMA 请求的是总线控制权。这个区别很重要。

11. 程序从源代码到运行#

这一部分把“程序如何落到机器上运行”接到前面的 ISA、存储器和操作系统接口上,重点看编译、汇编、链接、加载之间的分工。

编译链路#

C 程序到运行的逻辑步骤:

.c -> .i -> .s -> .o -> executable -> running process
阶段输入输出作用
preprocessor.c.i展开宏、include
compiler.i.s生成汇编
assembler.s.o生成 relocatable object
linker多个 .o 和 librariesexecutable符号解析、重定位
loaderexecutableprocess加载到内存并跳转入口
ELF#

ELF 是 Unix-like 系统常见目标文件格式。

三类 object file:

类型含义
relocatable file可与其他 object 链接,如 .o
executable file可执行程序
shared object file动态库,如 .so

常见 section:

section内容
.textinstructions
.rodataread-only constants
.datainitialized global/static data
.bssuninitialized global/static data
symbol table符号定义和引用
relocation info需要链接器修补的位置
ELF 中符号放在哪里#

看 C 代码:

#define ITEM_NUM 16
static int item_size = 4;
int global_count;

int add(int x, int y) {
    return x + y;
}

int main() {
    char *p = malloc(64);
    return add(item_size, ITEM_NUM);
}

分类:

符号位置 / 类型原因
\(ITEM_{NUM}\)不一定进入 symbol table宏在预处理阶段直接替换
\(item_{size}\).data 中的 local symbolstatic 且已初始化
\(global_{count}\).bss 中的 global symbol全局未初始化
add.text 中的 global symbol函数定义
main.text 中的 global symbol函数定义
mallocimported / undefined symbol当前文件引用但未定义

这类题要分清:

  • 宏不是运行时对象。
  • initialized data 和 uninitialized data 放不同 section。
  • static 限制链接可见性,不代表一定在栈上。
  • 外部库函数在当前 .o 里通常是 unresolved external reference。
Assembler 与 Linker#

assembler 要处理:

  • pseudo-instructions。
  • local labels。
  • forward references。
  • 生成 object file。

linker 主要做:

  1. 合并各 object 的 text/data 等 segment。
  2. 符号解析:找到 undefined external symbol 的定义。
  3. 重定位:修正地址。
  4. 记录 entry point。

注意:C/C++ 程序真正入口通常不是 main,而是 _startcrt0 相关启动代码。启动代码初始化运行时环境后再调用 main

Static Linking 与 Dynamic Linking#

Static linking:

  • 链接时把需要的库代码合入 executable。
  • 启动时简单。
  • 可执行文件可能更大。
  • 库更新后程序不自动使用新版库。

Dynamic linking:

  • 运行时加载并解析共享库。
  • 多个程序可共享同一份库代码。
  • 使用 GOT 和 PLT 支持延迟绑定。

PIC:

  • position-independent code。
  • 代码不依赖固定加载地址。
  • 常用于 shared libraries。
链接和加载的直观区别#

链接 linker 解决的是“文件之间怎么拼起来”:

  • 多个 .o 合并。
  • 找到函数和全局变量的定义。
  • 修补地址。
  • 生成 executable。

加载 loader 解决的是“executable 怎么变成进程”:

  • 创建地址空间。
  • 把 text/data 映射进内存。
  • 建 stack / heap 初始布局。
  • 设置参数、环境变量。
  • 跳到 entry point。

一句话:

linking makes a complete program file.
loading makes a running process.
Loader 与进程内存布局#

loader 的工作:

  1. 读取 executable header。
  2. 分配地址空间。
  3. 把 text/data segment 放入内存。
  4. 清零 .bss
  5. 建立 stack。
  6. 设置参数和环境变量。
  7. 跳转到 entry point。

典型内存布局:

high address
stack      grows downward
...
heap       grows upward
static data
text
low address

12. 统一复习表#

各章重点#
重点
1冯·诺依曼结构、抽象层、二进制编码、补码、浮点、大小端
2逻辑门、数字抽象、CMOS、布尔代数、真值表、标准形式、化简
3组合逻辑设计流程、译码器、编码器、MUX、加法器、时序分析
4FSM、锁存器、触发器、同步时序设计、寄存器、计数器
5FPGA、HDL 流程、Verilog 模块/数据类型/建模方式
6加法器、标志位、ALU、补码加减、乘除法、浮点运算
7ISA、指令格式、寻址方式、RISC-V 指令集
8单周期 CPU、多周期 CPU、控制器、流水线和冒险
9SRAM/DRAM、cache、映射、替换、写策略、虚拟存储器
10总线、I/O 接口、端口编址、查询/中断/DMA、I/O 软件层次
11预处理、编译、汇编、链接、ELF、加载、进程内存布局
必须能解释#
  • 为什么 signed 和 unsigned 转换时 bit pattern 不变但值会变。
  • 为什么补码可以用同一个加法器实现加减法。
  • 为什么 signed overflow 不能只看 carry out。
  • IEEE 754 中 normalized、denormalized、infinity、NaN 的判定。
  • Verilog 中 wirereg 的使用语境。
  • 组合逻辑和时序逻辑的根本区别。
  • Mealy 和 Moore FSM 的输出区别。
  • setup/hold 约束的意义。
  • RCA、CLA、prefix adder 的速度和硬件代价差异。
  • Booth algorithm 为什么能减少部分积。
  • ISA 和 microarchitecture 的区别。
  • RISC-V R/I/S/B/U/J 格式分别服务什么指令。
  • .c 到进程运行的每个阶段在解决什么问题。
易错点清单#
  • unsigned 循环变量做 \(i \ge 0\) 这种条件时可能永远为真。
  • signed 和 unsigned 混合比较时,signed 会被转换成 unsigned。
  • signed 右移负数通常是算术右移,不等价于 C 除法的向 0 截断。
  • 浮点数不满足普通实数的结合律。
  • \(always @(*)\) 中组合逻辑分支不完整会推导出 latch。
  • Moore FSM 通常比 Mealy FSM 多状态。
  • ripple counter 不是严格同步电路。
  • load-store ISA 中,算术指令通常不直接访问内存。
  • RISC-V 的 \(x_{0}\) 不能被改写。
  • object file 不是 executable,必须链接后才可直接运行。
易混概念#
概念 A概念 B区别
ISA微体系结构ISA 定义机器接口;微体系结构实现 ISA
组合逻辑时序逻辑前者无状态;后者依赖历史状态
latchflip-flop前者电平敏感;后者边沿敏感
SRAMDRAMSRAM 快且贵;DRAM 密度高但需刷新
cache虚拟存储器cache 缓存主存;虚拟存储器把主存作为外存缓存
中断DMA中断请求 CPU 时间;DMA 请求总线控制权
独立编址统一编址前者 I/O 地址空间独立;后者 I/O 映射到内存地址空间
常用公式#

补码范围:

$$ -2^{w-1} \le x \le 2^{w-1}-1 $$

无符号范围:

$$ 0 \le x \le 2^w-1 $$

补码取负:

$$ -x = \sim x + 1 $$

无符号加法:

$$ UAdd_w(u,v) = (u+v)\bmod 2^w $$

无符号乘法:

$$ UMult_w(u,v) = (uv)\bmod 2^w $$

CLA:

$$ G_i = A_iB_i,\quad P_i = A_i+B_i $$
$$ C_{i+1}=G_i+P_iC_i $$

full adder:

$$ S = A \oplus B \oplus C_{in} $$
$$ C_{out}=AB+AC_{in}+BC_{in} $$

时钟周期:

$$ t_{clk} \ge t_{ffpd}+t_{comb}+t_{setup} $$

浮点数:

$$ V=(-1)^sM2^E $$

简化 MIPS branch target:

$$ PC_{next}=PC+4+(sign\_ext(imm)\ll 2) $$

RISC-V branch target:

$$ PC_{next}=PC+sign\_ext(imm) $$

其中 B-type immediate 编码已经隐含最低位 0,因此目标地址按 2-byte 粒度对齐。

无符号整数范围:

$$ 0 \le x \le 2^n-1 $$

补码范围:

$$ -2^{n-1}\le x\le 2^{n-1}-1 $$

补码取负:

$$ -x=\sim x+1 $$

浮点数值:

$$ V=(-1)^sM2^E $$

CLA 进位:

$$ C_{i+1}=G_i+P_iC_i $$

同步时序电路时钟周期:

$$ t_{clk}\ge t_{ffpd}+t_{comb}+t_{setup} $$

总线带宽估算:

$$ B=\frac{W\times F}{N} $$