Notes 笔记
sys1
Sys1部分笔记#
这份笔记按“编码与逻辑基础 -> 功能部件 -> ISA -> CPU -> 存储与 I/O -> 程序运行链路”的主线组织,将概念、手算例题、Verilog 模板、RISC-V 细节和实验相关知识放在对应章节中。阅读时可以先看每章开头的概念框架,再用例题和代码模板补齐可操作细节。
0. 总体主线#
数字逻辑与计算机组成可以从两条线一起理解:一条是“硬件从门电路长成 CPU”,另一条是“程序从高级语言落到机器指令并在硬件上运行”。
二进制编码
-> 数字逻辑基础
-> 组合逻辑电路
-> 时序逻辑电路
-> FPGA / Verilog HDL
-> 运算方法和功能部件
-> 指令系统 ISA
-> CPU 微体系结构
-> 存储器层次结构
-> 系统互连与输入/输出
-> 程序编译、链接、加载和运行抽象层关系#
| 抽象层 | 关注点 | 典型内容 |
|---|---|---|
| ISA 层 | 软件和硬件之间的接口 | 指令、寄存器、寻址方式、异常/中断 |
| 微体系结构层 | 如何实现 ISA | CPU 数据通路、控制器、流水线 |
| RTL / 功能部件层 | 寄存器传送和功能部件 | ALU、寄存器堆、存储器、总线 |
| 数字逻辑层 | 用门电路实现功能 | 组合逻辑、时序逻辑、FSM |
| 器件层 | 门电路的物理实现 | CMOS、FPGA、存储单元 |
章节之间的连接#
| 前面知识 | 后面用在哪里 |
|---|---|
| 补码 | ALU 加减法、signed comparison、branch compare |
| 移位 | shifter、乘除法优化、RISC-V immediate 对齐 |
| 布尔代数 | 组合逻辑化简、control signal 生成 |
| decoder / mux | register file、datapath 选择、instruction decode |
| flip-flop | register、PC、FSM、pipeline register |
| FSM | control unit、多周期 CPU、串口/协议控制 |
| timing | 最高频率、critical path、同步电路可靠性 |
| ISA | datapath 需要哪些硬件、control 需要哪些信号 |
| ELF / linking | 汇编代码如何成为操作系统能运行的程序 |
本课程使用 RISC-V 作为模型机,主要因为它开放、简洁、模块化、易扩展,适合清楚呈现 ISA 和底层硬件实现之间的关系。
1. 二进制编码与计算机系统抽象#
冯·诺依曼结构和程序执行#
Von-Neumann 模型#
Von-Neumann 模型把计算机抽象成:
| 部件 | 作用 |
|---|---|
| Memory | 存放指令和数据,每个地址对应一个内容 |
| CPU | 取指、译码、执行运算和控制 |
| I/O | 与外部设备交换数据 |
| System | 将硬件、软件、运行环境组合成完整系统 |
核心思想:
- Stored program:程序也作为二进制数据存放在内存中。
- Binary:指令、数据最后都编码为 0/1。
- CPU 自动执行指令序列,而不是人工设置每一步。
程序表示#
程序语言的层次:
| 层次 | 例子 | 转换工具 |
|---|---|---|
| 高级语言 | C, Java | compiler, interpreter |
| 汇编语言 | RISC-V assembly, MIPS assembly | assembler |
| 机器语言 | 0/1 bit pattern | CPU 直接执行 |
程序执行的典型阶段:
- Instruction Fetch:从
PC指向的内存地址取指令。 - Instruction Decode:解释 opcode 和 operand 字段。
- Execute:ALU 或控制逻辑执行操作。
- Memory Access:按需读写数据内存。
- 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$ 位无符号整数:
对 $n$ 位二进制补码整数:
常见有符号编码#
| 编码 | 英文 | 特点 |
|---|---|---|
| 原码 | sign-and-magnitude | 最高位为符号位,存在 \(+0\) 和 -0 |
| 反码 | one's complement | 负数按位取反,也存在两种 0 |
| 补码 | two's complement | 负数为模 $2^n$ 意义下的补数,硬件加减法统一 |
| 移码 | biased / frameshift code | 常用于浮点数阶码 |
补码的定义:
补码负数转换快捷法:
- 对绝对值的二进制按位取反。
- 加 1。
补码取负也满足:
补码手算完整流程#
例:用 8 bit 表示 -13。
- 先写
13的二进制:
13 = 0000 1101- 按位取反:
1111 0010- 加 1:
1111 0011所以:
-13 的 8-bit two's complement = 1111 0011反过来,看到 1111 0011:
- 最高位是 1,所以按补码解释为负数。
- 取反加 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$ 下计算。
无符号加法:
无符号乘法:
有符号补码加法和无符号加法在 bit-level 上完全相同,区别只是解释结果的方式。
signed 与 unsigned 的映射#
signed 和 unsigned 互相转换时:
- bit pattern 不变。
- 解释方式改变。
若 $w$ 位补码值 $x < 0$,转为 unsigned 时会变成:
这解释了 C 中很多“看起来奇怪”的比较:
-1 < 0U // false, 因为 -1 被转换成 unsigned 后是很大的数
(unsigned)-1 > -2 // true易错点:
- C 表达式中若 signed 和 unsigned 混用,signed 通常会隐式转换为 unsigned。
- 不要因为变量“应该非负”就随便用 unsigned,循环倒着写时很容易下溢。
signed / unsigned 转换例题#
以 4 bit 为例:
| bit pattern | unsigned | signed two's complement |
|---|---|---|
0000 | 0 | 0 |
0001 | 1 | 1 |
0111 | 7 | 7 |
1000 | 8 | -8 |
1001 | 9 | -7 |
1111 | 15 | -1 |
规律:
- 最高位为 0 时,signed 和 unsigned 值相同。
- 最高位为 1 时,signed 值 = unsigned 值 $-2^w$。
例如 $w=4$:
1001 unsigned = 9
1001 signed = 9 - 16 = -7C 语言比较例:
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这类题的稳定做法:
- 先写目标宽度的 bit pattern。
- 再决定按 signed 还是 unsigned 解释。
移位#
| 操作 | 作用 | 注意 |
|---|---|---|
| \(x << k\) | 左移,低位补 0 | 常等价于乘 $2^k$,但可能溢出 |
| unsigned \(x >> k\) | 逻辑右移,高位补 0 | 等价于向下取整除以 $2^k$ |
| signed \(x >> k\) | 通常为算术右移,高位补符号位 | 负数会向 $-\infty$ 方向取整 |
负数想实现 C 整数除法那种“向 0 截断”,右移前要加 bias:
这里适用于 $x<0$ 的情况。
有符号溢出判断#
二进制补码加法中:
- 正数 + 正数 = 负数,则正溢出。
- 负数 + 负数 = 正数,则负溢出。
- 一正一负相加不会溢出。
等价判断:
- 操作数符号相同,结果符号不同,则 overflow。
- 最高有效值位进位和符号位进位不同,则 overflow。
常见 flags:
| Flag | 含义 | 主要用于 |
|---|---|---|
| ZF | Zero Flag,结果为 0 | signed 和 unsigned |
| SF/NF | Sign Flag,结果符号位 | signed |
| CF | Carry Flag,加法进位或减法借位 | unsigned |
| OF | Overflow Flag,有符号溢出 | signed |
溢出判断例题#
4-bit signed two's complement 范围:
-8 ~ +7例 1:\(4 + 5\)
0100 // 4
+ 0101 // 5
------
10011001 按 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 浮点数的数值形式:
字段:
| 字段 | 含义 |
|---|---|
| \(s\) | sign bit |
| \(\exp\) | 指数字段,不直接等于 $E$ |
frac | 尾数字段,不直接等于 $M$ |
单精度:
| 字段 | 位数 |
|---|---|
| sign | 1 |
| exp | 8 |
| frac | 23 |
双精度:
| 字段 | 位数 |
|---|---|
| sign | 1 |
| exp | 11 |
| frac | 52 |
规格化数#
条件:
exp != 000...0 且 exp != 111...1指数:
其中:
尾数:
这个隐含的前导 1 是规格化数的关键。
非规格化数#
条件:
exp = 000...0指数:
尾数:
作用:
- 表示非常接近 0 的数。
- 实现 gradual underflow。
- \(\exp=0, frac=0\) 表示 \(+0\) 或
-0。
特殊值#
| exp | frac | 含义 |
|---|---|---|
| 全 1 | 全 0 | $\pm \infty$ |
| 全 1 | 非 0 | NaN |
| 全 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浮点运算的核心#
浮点加法:
- 对阶:把较小指数的尾数右移。
- 尾数相加或相减。
- 规格化结果。
- 舍入。
- 检查 overflow / underflow。
浮点乘法:
- 符号:$s = s_1 \oplus s_2$。
- 尾数:$M = M_1 \times M_2$。
- 指数:$E = E_1 + E_2$。
- 规格化和舍入。
重要性质:
- 浮点加法通常不满足结合律。
- 浮点乘法也通常不满足结合律和分配律。
- 默认舍入通常是 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。为抵抗噪声,需要输入/输出电压范围之间留出噪声容限:
CMOS 晶体管和门电路#
CMOS 中常见两类晶体管:
| 晶体管 | Gate=0 | Gate=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:
Canonical POS:
逻辑函数化简#
代数法通过布尔定律变换表达式。卡诺图法通过相邻最小项合并消去变量。
卡诺图化简步骤:
- 按变量顺序填入输出为 1 的格子。
- 把相邻 1 按 $1,2,4,8,\ldots$ 个格子成组。
- 分组越大,消去变量越多。
- 每个 1 至少覆盖一次。
- 只保留每组中不变的变量。
don't care 项可以视情况当作 1 使用,用来扩大分组、简化表达式。
布尔逻辑、标准形式与 CMOS 复习抓手#
二值逻辑#
基本逻辑:
| 运算 | 符号 | 含义 |
|---|---|---|
| AND | A · B 或 AB | 同时为 1 才为 1 |
| OR | \(A + B\) | 至少一个为 1 |
| NOT | \(A'\), ~A, overbar | 取反 |
优先级:
- Parentheses
- NOT
- AND
- 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 |
| SOP | sum of products |
| POS | product of sums |
Canonical SOP 使用输出为 1 的 minterms:
Canonical POS 使用输出为 0 的 maxterms:
布尔函数从真值表到表达式#
例:三输入函数 $F(x,y,z)$ 在编号 1, 2, 4, 7 时输出 1。
先写:
按变量顺序 x y z:
| 编号 | xyz | minterm |
|---|---|---|
| 1 | 001 | $x'y'z$ |
| 2 | 010 | $x'yz'$ |
| 4 | 100 | $xy'z'$ |
| 7 | 111 | $xyz$ |
所以 canonical SOP:
如果要用 decoder 实现:
- 使用一个 3-to-8 decoder。
- 把输出线
1,2,4,7接到 OR gate。 - OR 输出就是 $F$。
这种题的核心是:decoder 的每根输出线天然对应一个 minterm。
K-map 化简的思路#
K-map 的本质是把只差一个变量的 minterms 放在相邻格子。相邻项可以合并,因为:
化简步骤:
- 把输出为 1 的格子填入 K-map。
- 按 $1,2,4,8,\ldots$ 个格子分组。
- 每组必须是矩形,且可跨边界。
- 每组越大,消去变量越多。
- 每个 1 至少覆盖一次。
- 得到每组中保持不变的变量,组成 product term。
- 所有 product term 相加得到 SOP。
don't care 的使用:
- 可以当 1 用来扩大分组。
- 也可以不用。
- 目标是让表达式更简单,而不是机械地包含所有 don't care。
数字抽象与静态纪律#
数字系统故意把连续电压限制成离散逻辑值:
- 高电平范围表示 1。
- 低电平范围表示 0。
- 中间保留 forbidden zone 来提高抗噪能力。
噪声容限:
static discipline:合法输入必须产生合法输出。
CMOS 基本规则#
| 晶体管 | Gate=0 | Gate=1 | 适合传递 |
|---|---|---|---|
| nMOS | OFF | ON | good 0 |
| pMOS | ON | OFF | good 1 |
CMOS 互补结构:
- pMOS 网络负责把输出拉到 $V_{DD}$。
- nMOS 网络负责把输出拉到 GND。
- NAND 和 NOR 是 CMOS 中常见的基础门。
电路参数:
- propagation delay:输入变化到输出稳定的延迟。
- transition time:输出电压上升或下降所需时间。
- fan-in:一个门能接受的输入数量。
- fan-out:一个输出能驱动的后级输入数量。
- power dissipation:包括静态功耗和动态功耗。
3. 组合逻辑电路#
组合逻辑电路概述#
组合逻辑电路满足:
- 输出只由当前输入决定。
- 不含存储状态。
- 不允许形成无意义反馈环。
设计步骤:
- 分析功能,确定输入和输出。
- 列真值表或逻辑关系。
- 推导逻辑表达式。
- 化简表达式。
- 画逻辑电路图或写 HDL。
- 分析延迟和竞争冒险。
典型组合逻辑部件#
译码器:
- $n$ 位输入,最多 $2^n$ 个输出。
- 常用于地址译码和 minterm 生成。
编码器:
- 把多个输入信号编码成二进制编号。
- 优先编码器用于处理多个输入同时有效的情况。
多路选择器:
- 用选择信号从多个输入中选一路输出。
- 是数据通路设计中最常用的选择部件。
多路分配器:
- 把一路输入分配到多个输出之一。
- 可看作带使能的译码器。
半加器:
全加器:
组合逻辑时序分析#
传播延迟:
最小延迟:
关键路径决定组合电路最大延迟,从而影响时钟周期。竞争冒险指同一输入变化经不同延迟路径到达输出,可能导致短暂错误跳变,也称 glitch。
组合逻辑设计与例题#
定义#
组合逻辑电路:
- 有 $m$ 个输入和 $n$ 个输出。
- 每个输出只依赖当前输入。
- 没有存储状态。
- 不允许反馈环。
设计流程:
- 确定输入、输出和逻辑关系。
- 写 truth table。
- 得到逻辑函数并化简。
- 画电路图和 timing diagram。
- 做 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:
Full Adder:
时序分析#
传播延迟:
污染延迟:
critical path:
- 总传播延迟由最长路径决定。
shortest path:
- 总污染延迟由最短路径决定。
race hazard / glitch:
- 单个输入变化可能沿多条不同延迟路径影响输出。
- 如果输出发生短暂错误跳变,就是 glitch。
4. 时序逻辑电路#
时序逻辑与有限状态机#
时序逻辑电路的输出不仅依赖当前输入,还依赖过去状态:
有限状态机两种模型:
| 模型 | 输出依赖 | 特点 |
|---|---|---|
| Moore | 只依赖状态 | 输出稳定,状态数可能较多 |
| Mealy | 依赖状态和输入 | 状态数可能较少,输出可能更快变化 |
锁存器和触发器#
| 元件 | 触发方式 | 特点 |
|---|---|---|
| 锁存器 latch | 电平敏感 | 使能有效期间输入可透传 |
| 触发器 flip-flop | 边沿敏感 | 只在时钟边沿采样 |
常见元件:
- 双稳态元件:能保存 1 bit 状态。
- SR 锁存器:有 Set / Reset 输入,但存在非法输入组合。
- D 锁存器:用一个数据输入避免 SR 的非法组合。
- D 触发器:时钟边沿采样,是同步时序电路常用存储元件。
- T 触发器:\(T=1\) 翻转,\(T=0\) 保持。
同步时序逻辑设计#
设计步骤:
- 根据需求定义状态含义。
- 画状态图。
- 列状态表。
- 状态化简。
- 状态编码。
- 根据触发器类型推导激励方程。
- 推导输出方程。
- 画电路或写 HDL。
状态化简的依据是状态等价:如果两个状态对所有输入产生相同输出,并转向相同或等价的下一状态,则可以合并。
时序约束:
还要满足 hold time 约束,避免数据在时钟边沿后过早变化。
典型时序逻辑部件#
计数器:
- 按固定状态序列变化。
- 可用于计时、事件计数、程序计数器等。
寄存器:
- 多个触发器组成,用于保存多 bit 数据。
- 寄存器堆支持多读多写端口,是 CPU 数据通路核心部件。
移位寄存器:
- 支持左移、右移、并行装入、保持。
- 可用于串并转换、乘除法移位操作等。
FSM、寄存器和时序约束#
时序逻辑定义#
时序电路由组合逻辑和存储元件组成:
输出分两种:
| 模型 | 输出依赖 |
|---|---|
| Moore | 只依赖 state |
| Mealy | 依赖 input 和 state |
一般规律:
- Mealy 输出标在边上,常用
input/output。 - Moore 输出标在状态上,通常状态数更多。
组合逻辑与时序逻辑的判断题#
看到题目时可以这样判断:
| 现象 | 可能类型 |
|---|---|
| 输出只由当前输入决定 | combinational |
| 输出还依赖过去发生过什么 | sequential |
| 电路里有 flip-flop / register | sequential |
有 posedge clk | sequential |
| 有 feedback 但没有明确存储元件 | 需要谨慎,可能是 latch 或非法组合环 |
典型组合逻辑:
- decoder
- encoder
- mux
- adder
- ALU
- barrel shifter
典型时序逻辑:
- register
- counter
- shift register
- FSM
- PC
- register file 的写入部分
Latch 与 Flip-Flop#
| 元件 | 触发方式 | 特点 |
|---|---|---|
| latch | level-sensitive | 时钟有效电平期间透明 |
| flip-flop | edge-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 分析与设计流程#
分析一个时序电路:
- 找状态变量。
- 写 next-state equation。
- 写 output equation。
- 列 state table。
- 画 state diagram。
设计一个 FSM:
- Specification:明确功能。
- Formulation:得到 state diagram 或 state table。
- State assignment:给状态分配二进制编码。
- Flip-flop input equation:根据触发器类型推输入方程。
- Output equation:推输出方程。
- Optimization:化简。
- Technology mapping:映射到门和触发器。
- 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$ 位:
状态编码会影响逻辑化简成本。经验规则:
- 对某输入有相同 next state 的状态,尽量编码相邻。
- 同一个状态的多个 next states,尽量编码相邻。
- 输出相同的状态,尽量编码相邻。
时序约束#
对 D flip-flop:
- $t_{setup}$:时钟沿前输入必须稳定的时间。
- $t_{hold}$:时钟沿后输入必须继续稳定的时间。
- $t_{ffpd}$:clock-to-Q 延迟。
时钟周期约束:
hold 约束:
Setup / Hold 约束例题#
假设:
t_ffpd = 2 ns
t_comb = 7 ns
t_setup = 1 ns则时钟周期至少:
最大频率:
如果组合逻辑太长,解决方向:
- 优化组合逻辑。
- 插入 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}\) |
| arithmetic | add, subtract, increment |
| logic | AND, OR, XOR |
| shift | left 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 的数字电路设计通常按下面的链路推进:
- Logic design with HDL:用 HDL 描述电路结构和行为。
- Simulation:编写 testbench 验证功能是否正确。
- Synthesis:把 HDL 综合为门级网表。
- Physical design:布局布线,确定实际硬件连接。
- 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 描述组合逻辑 |
| 行为建模 | 使用 always、if、case 描述行为 |
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:
定义:
进位:
CLA 通过并行生成进位减少串行等待。
带标志加法器需要生成:
| 标志 | 含义 |
|---|---|
| ZF | 结果为 0 |
| SF | 结果符号位 |
| CF | 无符号进位/借位 |
| OF | 有符号溢出 |
ALU 支持多种算术逻辑操作,是 CPU 运算器核心。
定点数运算#
补码加减法:
所以加法和减法可以共用同一个加法器,只需控制 B 是否取反以及最低位进位输入是否为 1。
有符号溢出判断:
- 两个正数相加得到负数,正溢出。
- 两个负数相加得到正数,负溢出。
- 一正一负相加不会溢出。
原码乘法:
- 符号单独处理。
- 数值部分按无符号乘法。
补码乘法:
- 可用 Booth 编码等方法处理连续 1。
- 核心思想是将连续 1 的部分积转换为少量加/减操作。
除法:
- 恢复余数除法:减过头时加回除数。
- 不恢复余数除法:不立即恢复,而在下一步改为加除数修正。
浮点数运算#
浮点加减:
- 对阶。
- 尾数相加或相减。
- 规格化。
- 舍入。
- 检查溢出、下溢和特殊值。
浮点乘除:
- 符号由操作数符号决定。
- 指数相加或相减。
- 尾数相乘或相除。
- 再规格化和舍入。
浮点运算的重点不是“精确实数运算”,而是“有限位宽下的近似运算”。
加法器、ALU 和乘除法硬件#
Carry Propagate Adder#
常见 CPA:
| 类型 | 特点 |
|---|---|
| Ripple-Carry Adder | 硬件简单,但 carry 逐级传播,慢 |
| Carry-Lookahead Adder | 用 generate/propagate 提前计算 carry |
| Prefix Adder | $O(\log N)$ 级计算 carry,更快但硬件更多 |
RCA 延迟:
CLA generate 与 propagate#
第 $i$ 位:
carry:
sum:
CLA 的思想是先算每一位或每个 block 的 generate/propagate,减少 carry 串行传播。
Adder 设计细化#
RCA:
FA0 -> FA1 -> FA2 -> ... -> FAN-1carry 必须一级一级传,所以最坏情况很慢。
CLA 的关键是把:
展开。例如:
这样 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$ 可以两种实现:
- 串接 full subtractor,borrow ripple。
- 用加法器计算:
因此 adder-subtractor 通常通过控制信号决定是否取反 B 并把最低位 carry-in 设为 1。
ALU#
ALU 是 processor datapath 的核心组合逻辑,用来执行:
- arithmetic:add, subtract
- logic:AND, OR, XOR
- shift
- comparison:SLT
典型 datapath:
registers -> ALU -> destination registerALU 与寄存器配合时,一个 register transfer operation 通常在一个 clock cycle 内完成。
SLT#
SLT 即 set less than。常见做法:
- 计算 $A-B$。
- 根据符号位和 overflow 判断 $A<B$。
- 结果低位置 1,高位补 0。
不能只看减法结果符号位,因为 signed overflow 会影响符号位含义。
ALU 控制信号理解#
一个简单 ALU 可以把多个操作共用硬件:
| ALUOp | 操作 |
|---|---|
000 | AND |
001 | OR |
010 | ADD |
110 | SUB |
111 | SLT |
加法和减法可共用 adder:
SUB: A - B = A + ~B + 1因此控制信号可以做:
Binvert = 1
Cin = 1SLT 常基于 subtraction,但 signed SLT 要考虑 overflow:
less = sum_sign XOR overflow因为 overflow 会让符号位“翻错方向”。
乘法基本思想#
二进制乘法和十进制乘法类似:
- multiplier 的某位为 1,则加入 shifted multiplicand。
- multiplier 的某位为 0,则不加。
- 部分积相加得到结果。
硬件实现会在 area 和 time 间折中:
- 宽 ALU + 多寄存器:简单但硬件多。
- 复用较窄 ALU:硬件少但多周期。
- array multiplier:并行加部分积,快但面积大。
有符号乘法#
简单做法:
- 记录 operands 的符号。
- 转成正数做无符号乘法。
- 根据符号异或决定结果符号。
也可以通过符号扩展和 Booth encoding 直接处理补码。
Booth Encoding#
Booth 算法的核心:
- 把连续的一串
1变成“开始减一次,结束加一次”。 - 对连续
1很长的 multiplier,可以减少部分积数量。
1-bit Booth 规则:
| 当前位 | 右侧前一位 | 操作 |
|---|---|---|
| 0 | 0 | no op |
| 0 | 1 | add multiplicand |
| 1 | 0 | subtract multiplicand |
| 1 | 1 | no op |
直观理解:
Booth 算法更直观的例子#
计算 M x 01111000。
普通乘法看到四个连续的 1,需要:
M<<3 + M<<4 + M<<5 + M<<6Booth 把它看成:
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 的边界。
除法#
无符号除法本质上反复判断:
可以通过:
来判断。
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 每一步:
- \(R = R - D\)
- 若 \(R \ge 0\),商位写 1。
- 若 \(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 + 位宽 + 扩展例:
| 名称 | 含义 |
|---|---|
| RV32I | 32 位基础整数指令集 |
| RV32IM | RV32I 加整数乘除扩展 |
| RV32G | 常用通用扩展组合 |
RISC-V 常见指令格式:
| 格式 | 用途 |
|---|---|
| R-type | 寄存器-寄存器运算 |
| I-type | 立即数运算、load、jalr |
| S-type | store |
| 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, rtsubu rd, rs, rtori rt, rs, imm16lw rt, imm16(rs)sw rt, imm16(rs)beq rs, rt, imm16
需要的硬件部件:
| 部件 | 作用 |
|---|---|
| PC | 保存下一条指令地址 |
| Instruction Memory | 根据 PC 取指 |
| Register File | 两读一写 |
| ALU | 加、减、OR、比较 |
| Extender | 立即数扩展,zero/sign extension |
| Data Memory | load/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:
| 信号 | 值 |
|---|---|
| RegDst | 选 rd |
| ALUSrc | 选 register rt |
| MemToReg | 选 ALU result |
| RegWr | 1 |
| MemWr | 0 |
| ExtOpt | 不关心 |
| ALUCtr | add |
| nPC_sel | PC+4 |
ori rt, rs, imm16:
| 信号 | 值 |
|---|---|
| RegDst | 选 rt |
| ALUSrc | 选 immediate |
| ExtOpt | zero extension |
| MemToReg | 选 ALU result |
| RegWr | 1 |
| MemWr | 0 |
| ALUCtr | OR |
lw rt, imm16(rs):
| 信号 | 值 |
|---|---|
| RegDst | 选 rt |
| ALUSrc | 选 immediate |
| ExtOpt | sign extension |
| MemToReg | 选 memory data |
| RegWr | 1 |
| MemWr | 0 |
| ALUCtr | add address |
sw rt, imm16(rs):
| 信号 | 值 |
|---|---|
| ALUSrc | 选 immediate |
| ExtOpt | sign extension |
| RegWr | 0 |
| MemWr | 1 |
| ALUCtr | add address |
beq rs, rt, imm16:
| 信号 | 值 |
|---|---|
| ALUSrc | 选 register rt |
| RegWr | 0 |
| MemWr | 0 |
| ALUCtr | subtract / compare equal |
| nPC_sel | 取决于 zero flag 和 branch control |
做数据通路题的顺序:
- 这条指令要不要写寄存器?
- 写哪个寄存器?
- ALU 第二个输入来自寄存器还是立即数?
- 立即数是 sign extend 还是 zero extend?
- 结果来自 ALU 还是 memory?
- 要不要写 memory?
- PC 是 \(PC+4\) 还是 branch/jump target?
ISA 设计原则#
Hennessy 和 Patterson 总结的原则:
- Simplicity favors regularity。
- Make the common case fast。
- Smaller is faster。
- Good design demands good compromises。
ISA 设计要考虑:
- 支持哪些数据类型。
- 提供多少操作。
- 每条指令几个 operand。
- operand 放在寄存器还是内存。
- 地址模式如何设计。
- 指令长度固定、可变还是混合。
操作数数量#
| 类型 | 特点 |
|---|---|
| 3-address | 两个 source,一个 destination,灵活但指令长 |
| 2-address | destination 常复用一个 source,可能破坏原值 |
| 1-address | 隐含 accumulator,硬件简单但程序繁琐 |
| 0-address | stack machine,操作数隐含在栈顶 |
Addressing Modes#
| 模式 | 例子 | 含义 |
|---|---|---|
| Immediate | ADD #5 | operand 本身就是值 |
| Direct | ADD 100 | operand 是内存地址 |
| Indirect | ADD [100] | operand 指向的内存中存放真正地址 |
| Register Direct | ADD R5 | operand 在寄存器 |
| Register Indirect | ADD [R3] | R3 中保存内存地址 |
| Relative | PC + offset | 常用于 branch |
| Indexed | base address + index | 常用于数组 |
| Based | base 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。
标准扩展:
| 扩展 | 含义 |
|---|---|
| I | Integer base |
| M | Integer multiplication and division |
| A | Atomic |
| F | Single-precision floating point |
| D | Double-precision floating point |
| C | Compressed 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-type | register-register arithmetic |
| I-type | load, immediate arithmetic, jalr |
| S-type | store |
| B-type | conditional branch |
| U-type | upper immediate |
| J-type | unconditional 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, rs2I-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, labelU-type:
imm[31:12] | rd | opcode典型:
lui rd, imm20
auipc rd, imm20J-type:
imm[20,10:1,11,19:12] | rd | opcode典型:
jal rd, label设计上看起来 immediate 被“拆散”了,但好处是:
rd,rs1,rs2位置尽量固定。- sign bit 固定在 bit 31。
- 硬件解码和符号扩展更规整。
常见指令类别#
R-type:
ADD,SUBSLT,SLTUSLL,SRL,SRA
I-type:
ADDISLTI,SLTIUANDI,ORI,XORISLLI,SRLI,SRAI- load instructions
U-type:
LUIAUIPC
Control transfer:
JALJALR- conditional branches
Calling Convention#
函数调用阶段:
- 把参数放到 callee 可访问的位置。
- 用
jal跳转到函数。 - callee 分配局部资源,必要时保存寄存器。
- 执行函数体。
- 把返回值放到 caller 可访问的位置,恢复寄存器,释放栈帧。
- 用
ret返回。
概念:
| 名称 | 含义 |
|---|---|
| caller | 调用者 |
| callee | 被调用函数 |
| prologue | 函数入口保存寄存器、分配栈帧 |
| epilogue | 函数出口恢复寄存器、释放栈帧 |
| stack frame | 函数私有栈空间 |
fp | frame 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 中,每条指令在一个时钟周期内完成。设计步骤:
- 分析每条指令的功能。
- 确定需要哪些部件。
- 建立数据通路。
- 增加多路选择器以复用通路。
- 设计控制器产生控制信号。
- 根据最长指令路径确定时钟周期。
常见控制信号:
| 信号 | 作用 |
|---|---|
RegWrite | 是否写寄存器 |
MemWrite | 是否写数据存储器 |
ALUSrc | ALU 第二操作数来自寄存器还是立即数 |
MemToReg | 写回数据来自 ALU 还是内存 |
Branch | 是否为分支指令 |
ALUOp | 指定 ALU 操作 |
多周期 CPU 和控制器#
多周期 CPU 把一条指令拆成多个阶段执行,每个阶段完成一部分操作。优点是:
- 同一硬件可在不同周期复用。
- 时钟周期可按较短阶段设置。
- 控制器可用有限状态机描述。
控制器设计方式:
| 方式 | 特点 |
|---|---|
| 硬连线控制器 | 速度快,修改困难 |
| 微程序控制器 | 控制逻辑像程序一样组织,灵活但可能较慢 |
流水线 CPU#
典型五级流水:
| 阶段 | 含义 |
|---|---|
| IF | 取指 |
| ID | 译码/读寄存器 |
| EX | 执行/地址计算 |
| MEM | 访存 |
| WB | 写回 |
流水线冒险:
| 冒险 | 原因 | 常见处理 |
|---|---|---|
| 结构冒险 | 多条指令争用同一硬件 | 增加硬件或暂停 |
| 数据冒险 | 后续指令依赖前面结果 | forwarding 或 stall |
| 控制冒险 | 分支改变 PC | 分支预测、flush、stall |
流水线提高吞吐率,但不一定降低单条指令延迟。
和前面章节的关系#
CPU 设计把前面几章的部件放到同一个时间结构中:组合逻辑负责“算出下一步”,触发器和寄存器负责“在时钟边沿保存状态”,控制器负责“根据指令产生选择信号”。单周期 CPU 的关键是连出完整数据通路,多周期 CPU 的关键是把每条指令拆成状态序列,流水线 CPU 的关键是把不同指令的不同阶段重叠执行并处理冒险。
做 CPU 数据通路题时,建议按以下顺序判断:
- 这条指令是否写寄存器。
- 写哪个寄存器。
- ALU 第二操作数来自寄存器还是立即数。
- 立即数需要 sign extension 还是 zero extension。
- 写回值来自 ALU、内存还是 PC+4。
- 是否写数据存储器。
- PC 下一值是顺序地址、branch target 还是 jump target。
9. 存储器层次结构#
存储器概述#
存储器层次结构按速度、容量、成本组织:
寄存器
-> cache
-> 主存 DRAM
-> 外存 SSD/HDD越靠近 CPU:
- 速度越快。
- 容量越小。
- 单位成本越高。
局部性原理:
| 类型 | 含义 |
|---|---|
| 时间局部性 | 最近访问过的数据很可能再次访问 |
| 空间局部性 | 访问某地址后,附近地址也可能被访问 |
主存储器#
SRAM:
- 速度快。
- 成本高。
- 常用于 cache。
DRAM:
- 密度高。
- 成本低。
- 需要刷新。
- 常用于主存。
存储器扩展:
- 位扩展:增加数据宽度。
- 字扩展:增加地址空间容量。
- 字位同时扩展:同时增加宽度和容量。
Cache#
cache 是主存的高速缓存。基本工作方式:
- CPU 发出地址。
- cache 判断是否命中。
- 命中则直接返回数据。
- 缺失则从主存取块并填入 cache。
映射方式:
| 方式 | 特点 |
|---|---|
| 直接映射 | 每个主存块只能放到固定 cache 行 |
| 全相联映射 | 可放到任意 cache 行 |
| 组相联映射 | 先选组,再在组内任意放置 |
替换算法:
- 随机替换。
- FIFO。
- LRU 或近似 LRU。
写策略:
| 策略 | 含义 |
|---|---|
| write-through | 写 cache 同时写主存 |
| write-back | 先只写 cache,替换时写回主存 |
| write-allocate | 写缺失时把块调入 cache |
| no-write-allocate | 写缺失时直接写主存 |
虚拟存储器#
虚拟存储器把主存看作外存的缓存,为每个进程提供独立虚拟地址空间。
分页式虚拟存储:
- 虚拟地址空间划分为虚拟页。
- 物理内存划分为页框。
- 页表记录虚拟页到物理页框的映射。
- 访问未在主存中的页会发生缺页异常。
虚拟存储器的作用:
- 扩大程序可用地址空间。
- 实现进程隔离。
- 支持按需调页。
- 提供存储保护。
和 CPU 执行的关系#
存储层次结构的目标是在“容量、速度、成本”之间折中。寄存器和 cache 让 CPU 尽量少等主存,虚拟存储器让每个进程看到独立的地址空间。理解存储器时要抓住两个缓存关系:cache 是主存的缓存,主存又可以看作外存的缓存。
10. 系统互连与输入/输出#
外设与系统总线#
外设分为:
| 类型 | 例子 |
|---|---|
| 输入设备 | 键盘、鼠标、扫描仪 |
| 输出设备 | 显示器、打印机 |
| 输入/输出设备 | 磁盘、网卡、触摸屏 |
总线是部件之间传递信息的公共路径。系统总线通常包括:
- 地址线。
- 数据线。
- 控制线。
总线性能指标:
| 指标 | 含义 |
|---|---|
| 总线宽度 | 一次并行传输的数据位数 |
| 工作频率 | 数据传输频率 |
| 带宽 | 单位时间最大传输数据量 |
| 寻址能力 | 地址线决定可访问空间大小 |
带宽常用估算:
其中 $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 完成事件。
中断服务程序包含:
- 准备阶段:保存断点和现场,设置屏蔽字。
- 处理阶段:执行具体中断服务。
- 恢复阶段:恢复现场并返回。
三种 I/O 方式的直觉#
| 方式 | 谁等谁 | CPU 负担 | 适合场景 |
|---|---|---|---|
| 程序查询 | CPU 等设备 | 高 | 简单低速设备 |
| 中断 | 设备就绪后通知 CPU | 中 | 异步低速/中速设备 |
| DMA | DMA 控制器接管总线传块 | 低 | 磁盘、网卡等大批量高速传输 |
中断请求的是 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 和 libraries | executable | 符号解析、重定位 |
| loader | executable | process | 加载到内存并跳转入口 |
ELF#
ELF 是 Unix-like 系统常见目标文件格式。
三类 object file:
| 类型 | 含义 |
|---|---|
| relocatable file | 可与其他 object 链接,如 .o |
| executable file | 可执行程序 |
| shared object file | 动态库,如 .so |
常见 section:
| section | 内容 |
|---|---|
.text | instructions |
.rodata | read-only constants |
.data | initialized global/static data |
.bss | uninitialized 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 symbol | static 且已初始化 |
| \(global_{count}\) | .bss 中的 global symbol | 全局未初始化 |
add | .text 中的 global symbol | 函数定义 |
main | .text 中的 global symbol | 函数定义 |
malloc | imported / undefined symbol | 当前文件引用但未定义 |
这类题要分清:
- 宏不是运行时对象。
- initialized data 和 uninitialized data 放不同 section。
static限制链接可见性,不代表一定在栈上。- 外部库函数在当前
.o里通常是 unresolved external reference。
Assembler 与 Linker#
assembler 要处理:
- pseudo-instructions。
- local labels。
- forward references。
- 生成 object file。
linker 主要做:
- 合并各 object 的 text/data 等 segment。
- 符号解析:找到 undefined external symbol 的定义。
- 重定位:修正地址。
- 记录 entry point。
注意:C/C++ 程序真正入口通常不是 main,而是 _start 或 crt0 相关启动代码。启动代码初始化运行时环境后再调用 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 的工作:
- 读取 executable header。
- 分配地址空间。
- 把 text/data segment 放入内存。
- 清零
.bss。 - 建立 stack。
- 设置参数和环境变量。
- 跳转到 entry point。
典型内存布局:
high address
stack grows downward
...
heap grows upward
static data
text
low address12. 统一复习表#
各章重点#
| 章 | 重点 |
|---|---|
| 1 | 冯·诺依曼结构、抽象层、二进制编码、补码、浮点、大小端 |
| 2 | 逻辑门、数字抽象、CMOS、布尔代数、真值表、标准形式、化简 |
| 3 | 组合逻辑设计流程、译码器、编码器、MUX、加法器、时序分析 |
| 4 | FSM、锁存器、触发器、同步时序设计、寄存器、计数器 |
| 5 | FPGA、HDL 流程、Verilog 模块/数据类型/建模方式 |
| 6 | 加法器、标志位、ALU、补码加减、乘除法、浮点运算 |
| 7 | ISA、指令格式、寻址方式、RISC-V 指令集 |
| 8 | 单周期 CPU、多周期 CPU、控制器、流水线和冒险 |
| 9 | SRAM/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 中
wire和reg的使用语境。 - 组合逻辑和时序逻辑的根本区别。
- 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 |
| 组合逻辑 | 时序逻辑 | 前者无状态;后者依赖历史状态 |
| latch | flip-flop | 前者电平敏感;后者边沿敏感 |
| SRAM | DRAM | SRAM 快且贵;DRAM 密度高但需刷新 |
| cache | 虚拟存储器 | cache 缓存主存;虚拟存储器把主存作为外存缓存 |
| 中断 | DMA | 中断请求 CPU 时间;DMA 请求总线控制权 |
| 独立编址 | 统一编址 | 前者 I/O 地址空间独立;后者 I/O 映射到内存地址空间 |
常用公式#
补码范围:
无符号范围:
补码取负:
无符号加法:
无符号乘法:
CLA:
full adder:
时钟周期:
浮点数:
简化 MIPS branch target:
RISC-V branch target:
其中 B-type immediate 编码已经隐含最低位 0,因此目标地址按 2-byte 粒度对齐。
无符号整数范围:
补码范围:
补码取负:
浮点数值:
CLA 进位:
同步时序电路时钟周期:
总线带宽估算: