第 17 章 数据通路与控制器
1. 本章要解决的问题
第 16 章的有限状态机擅长回答“下一拍做什么”。当任务还要保存多个操作数、选择运算、移动数据并保存结果时,只用状态图会很快变得混乱。
考虑计算
\[ R=(A+B)-C. \]
一种直接实现是把加法器和减法器串起来,在一个周期内完成全部运算。另一种实现只放一个加减法器:第一拍计算 \(A+B\),保存中间结果;第二拍再减去 \(C\)。第二种结构需要回答:
- \(A\)、\(B\)、\(C\) 和中间结果保存在哪里?
- 每一拍把哪两个数据送入运算器?
- 运算器这一拍执行加法还是减法?
- 哪个寄存器在边沿写入,哪个寄存器保持?
- 谁决定这些选择与写入动作?
这就是数据通路(datapath)与控制器(controller)的分工。本章回答八个问题:
- 什么属于数据,什么属于控制?
- 寄存器、MUX、ALU 和总线怎样组成数据通路?
- 怎样用寄存器传输描述每个时钟边沿的动作?
- 控制器怎样向数据通路发出选择、运算和写使能信号?
- 数据通路怎样把比较结果和状态标志送回控制器?
- 怎样把多周期运算拆成状态与微操作?
- 单周期、多周期和流水线结构怎样权衡资源、周期、延迟与吞吐率?
- 怎样检查一个“控制器 + 数据通路”系统的功能和时序?
2. 与第 16 章和阶段复习三的联系
阶段复习三的四拍定时控制器已经包含两类状态:
IDLE/RUN/DONE表示任务处于哪个控制阶段;count表示已经完成多少个有效工作拍。
前者回答“现在该做什么”,后者保存“已经处理了多少数据”。把二者明确分开后:
- FSM 状态寄存器、下一状态逻辑属于控制器;
- 计数寄存器、加一逻辑和终点比较器属于数据通路;
count_en、count_clear是控制器发给数据通路的控制信号;terminal是数据通路返回控制器的状态信号。
第 16 章主要研究控制状态。本章把控制状态与数据状态放在同一张结构图中,形成寄存器传输级(register-transfer level, RTL)的系统视角。这里的 RTL 先是一种硬件思考方法;第 19 章再把它写成 Verilog/SystemVerilog。
3. 前置知识快速检查
- 带使能寄存器在
EN=0的有效边沿应写入新数据还是保持旧值? - 2 选 1 MUX 的作用是什么?
- ALU 可以完成哪些基础操作?
- Moore FSM 的输出取决于当前状态,还是还直接取决于当前输入?
- 两个寄存器在同一边沿更新时,右侧表达式读取边沿前还是边沿后的值?
答案是:保持旧值;选择一路数据送往输出;算术、逻辑、比较或移位等由接口规定的操作;只取决于当前状态;读取边沿前的旧值。若第 1、5 题不熟,回看第 12 章;若第 2、3 题不熟,回看第 8、9 章;若第 4 题不熟,回看第 16 章。
4. 学习目标
完成本章后,你应能够:
- 区分数据通路、控制器、控制信号和状态信号;
- 说明寄存器、MUX、ALU、比较器和总线各自承担的任务;
- 使用寄存器传输写法描述同步数据移动和微操作;
- 把一个多步运算排成逐拍调度表;
- 从调度表得到控制状态和控制字;
- 逐拍分析控制状态、数据寄存器与接口输出;
- 解释资源共享为什么节省硬件却增加完成周期;
- 区分延迟、吞吐率和时钟周期;
- 比较单周期、多周期和流水线实现;
- 检查控制优先级、输入保持、总线选择、寄存器写入和时序路径。
5. 先把系统拆成两部分
5.1 数据通路:保存、选择、运算和传送数据
数据通路由真正承载多 bit 数据的部件和连接组成,常见部件包括:
- 寄存器:保存输入、临时值和最终结果;
- MUX:决定某个运算输入或寄存器输入来自哪里;
- ALU:执行加、减、AND、OR、比较等操作;
- 移位器和计数器:执行规则的数据变换;
- 比较器:产生零、相等、大小或终点等状态;
- 总线:成组传送多 bit 数据。
数据通路并不自行理解“任务已经进行到第几步”。它只按照当前控制信号完成选择、计算和写入。
5.2 控制器:决定这一拍允许什么动作
控制器通常是 FSM。它读取:
start、pause等外部控制输入;zero、equal、terminal、carry等数据通路状态;- 当前控制状态。
它输出:
- 寄存器装载使能,例如
ld_a、ld_r; - MUX 选择,例如
sel_x、sel_y; - ALU 操作码,例如
alu_op; - 计数器清零或使能;
busy、done等系统接口信号。
观察图 17-1 时,先看箭头方向。紫色箭头从控制器进入数据通路,表示“命令”;绿色箭头返回控制器,表示“运算条件或状态”。多 bit 数据通常留在数据通路内部,不必穿过控制器。
图 17-1 控制器发出选择和写使能,数据通路返回状态,二者共享时钟完成逐拍协作
立即检查
count==3 的比较结果应属于控制信号还是状态信号?
它由数据通路中的计数器与比较器产生,并送给控制器作转移判断,因此是状态信号。控制器随后产生的
count_en 才是控制信号。
6. “数据”和“控制”取决于它在接口中的角色
不要只按位宽区分数据与控制。4 bit 操作码通常是控制;1 bit 串行数据仍然是数据。判断方法是看信号表达什么:
- 表达“被处理的值”时,它属于数据;
- 表达“选择哪条路径、是否写入、执行哪种动作”时,它属于控制;
- 表达“数据通路当前满足什么条件”并反馈给控制器时,它属于状态。
例题 1:给接口信号分类
某模块包含
din[7:0]、load、alu_op[1:0]、zero、result[7:0]
和 done。
din、result承载操作数与结果,是数据信号;load、alu_op决定写入与运算,是控制器到数据通路的控制信号;zero由结果比较产生并供控制器判断,是数据通路状态;done表达系统协议阶段,通常由控制器产生,是接口控制输出。
zero 虽然只有 1 bit,却不是控制器发出的命令。
变式: 若 zero
只作为最终用户输出、不参与控制转移,它仍是数据通路根据数据产生的状态标志,只是接收者从控制器变成模块外部。
7. 一个寄存器写入动作背后有什么硬件
写出“需要时更新,否则保持”时,硬件不能依靠软件式的“什么都不做”。寄存器 D 输入前通常需要一个 MUX:
\[ R^{+}= \begin{cases} D, & ld_R=1,\\ R, & ld_R=0. \end{cases} \]
这里:
- \(R\) 是边沿前寄存器保存的旧值;
- \(D\) 是候选新数据;
- \(ld_R\) 是高有效装载使能;
- \(R^+\) 是下一个有效边沿后保存的新值。
对每一位都可写成 MUX 关系:
\[ R^{+}=ld_R\cdot D+\overline{ld_R}\cdot R, \]
其中乘号和加号表示逐位的 AND 与 OR 选择,不是把两个多 bit 数做普通算术加法。由于 \(ld_R\) 与 \(overline{ld_R}\) 不会同时为 1,两路不会同时被选择。
图 17-2 把一句寄存器传输写法展开成 MUX、寄存器和反馈路径。观察
ld_R=0 时的粗线:旧 \(R\)
被选回 D 输入,所以边沿后保持。
图 17-2 装载使能通过 MUX 在新数据与旧状态之间选择,时钟边沿执行实际写入
例题 2:读取带装载使能的寄存器
4 bit 寄存器 \(R\) 初始为
0101。三个连续上升沿前的 \((ld_R,D)\) 依次为:
(0,1111),(1,1010),(0,0011)
逐拍结果:
- 第一拍
ld_R=0,MUX 选择旧 \(R\),边沿后仍为0101; - 第二拍
ld_R=1,MUX 选择1010,边沿后变为1010; - 第三拍
ld_R=0,即使 \(D=0011\),边沿后仍为1010。
变式: 若第三拍 ld_R=1,结果才会更新为
0011。
8. 寄存器传输与微操作
寄存器传输(register transfer)描述一个有效边沿把数据从哪里送到哪个寄存器。常用写法为
\[ R_B\leftarrow R_A. \]
它表示:在规定控制条件成立的有效边沿,\(R_B\) 写入边沿前 \(R_A\) 的值。箭头不是组合连线,也不表示数据在整周期中持续流动。
微操作(micro-operation)是在一个时钟步骤内对寄存器数据执行的基础操作,例如:
R_B ← R_A 传送
R_T ← R_A + R_B 加法后保存
R_A ← R_A + 1 加一
R_S ← R_S >> 1 右移
R_Z ← 0 清零
若写成条件形式:
EN: R_B ← R_A
表示只在 EN=1 的有效边沿执行,否则 \(R_B\) 保持。
同一拍的多个传输读取旧值
若某一拍同时规定
R_A ← R_B
R_B ← R_A
两个寄存器会交换旧值。它们共享时钟并在同一边沿采样:\(R_A\) 读取旧 \(R_B\),\(R_B\) 读取旧 \(R_A\)。这与程序按行执行完全不同。
同一拍能否执行多个微操作
可以,只要硬件资源和连接支持。例如两个独立寄存器可在同一边沿分别装载。但若两个操作同时要求使用唯一一台 ALU 完成不同运算,就发生资源冲突,必须拆到不同周期或增加第二台 ALU。
9. 总线把多 bit 数据成组传送
总线(bus)是一组并行信号线。例如 8 bit 总线有 8 条逻辑信号,共同传送一个 8 bit 位模式。图中一条粗线只是绘图简写,不是物理上只有一根线。
多个源要驱动同一内部数据入口时,基础同步设计通常使用 MUX 明确选择唯一来源:
bus = MUX(sel, R_A, R_B, R_C, ALU_out)
一个选择值只接通一个数据源。若多个普通逻辑输出直接并联到同一节点并给出相反电平,会形成电气冲突。
在很多现代 ASIC 和 FPGA 设计流程中,芯片内部的“共享总线”最终由 MUX 网络实现。三态驱动更多出现在芯片引脚或特定工艺结构中;本章内部数据通路统一按 MUX 理解。
10. 贯穿设计:计算 \(R=(A+B)-C\)
现在设计一个 4 bit 无符号多周期运算模块。输入为 \(A_{in}\)、\(B_{in}\)、\(C_{in}\) 和 start,输出为
\(R_{out}\)、busy、done。所有输入先假定与
CLK 同步。
接口规格:
- 空闲时采到
start=1,同时装载三个输入; - 下一拍计算 \(A+B\),只保留 4 bit
结果,最高进位单独保存为
carry_add; - 再下一拍计算临时结果减 \(C\),保存 4 bit 结果;
- 完成状态持续一个周期,
done=1; - 运行期间忽略新的
start; - 高有效同步复位优先级最高,使状态回空闲并清零全部数据寄存器。
本例故意使用固定 4 bit 运算,溢出按模 16 截断。若接口需要数学整数结果,就应扩宽中间结果与输出,这属于规格选择,不是控制器能够补救的问题。
11. 第一步:列出需要保存的数据
运算跨越多个周期,外部输入不能默认一直不变。启动时先把它们复制进内部寄存器:
- \(R_A\):保存 \(A_{in}\);
- \(R_B\):保存 \(B_{in}\);
- \(R_C\):保存 \(C_{in}\);
- \(R_T\):保存中间和 \(A+B\);
- \(R_K\):保存加法的 1 bit 最高进位
carry_add; - \(R_R\):保存最终结果。
为什么不能只保存 \(R_T\)?因为装载输入与执行加法之间有一个时钟边界。若外部 \(A_{in}\) 或 \(B_{in}\) 在任务期间改变,而内部没有输入寄存器,第二拍的运算可能混入新数据。
例题 3:识别必须保存的值
若接口额外规定:“从接受 start 到 done
期间,外部必须保持 \(A_{in}\)、\(B_{in}\)、\(C_{in}\)
不变”,内部可直接在后续周期读取这些端口,减少三个输入寄存器。
代价是模块把保持责任交给上游,接口耦合更强。本章采用启动拍内部捕获,使任务接受后不再依赖外部输入。
变式: 若只规定 \(C_{in}\) 保持稳定,则仍应保存 \(A_{in}\)、\(B_{in}\),而 \(C\) 可直接从接口读取。结构必须与接口契约一致。
12. 第二步:排出逐拍寄存器传输
把任务拆成三个工作步骤:
LOAD:R_A ← A_in,R_B ← B_in,R_C ← C_in
ADD: {R_K,R_T} ← R_A + R_B
SUB: R_R ← R_T - R_C
完成指示另占一个 DONE 状态。控制状态可定义为:
IDLE:等待启动;ADD:使用 ALU 做加法并写 \(R_T\);SUB:使用同一 ALU 做减法并写 \(R_R\);DONE:完成指示周期。
LOAD 不必单独占一个状态。IDLE 中采到
start=1 的边沿可以同时装载输入并进入
ADD。控制状态和寄存器数据都在该边沿后更新。
图 17-3 展示支持这些传输的最小数据通路。先沿蓝色粗线看数据,再沿紫色虚线看选择与装载。ALU 左输入在 \(R_A\) 与 \(R_T\) 中选择,右输入在 \(R_B\) 与 \(R_C\) 中选择;同一台 ALU 分两拍复用。
图 17-3 输入寄存器、两个操作数 MUX、共享 ALU、临时寄存器和结果寄存器组成的数据通路
13. 第三步:把微操作翻译成控制信号
定义以下控制信号:
| 信号 | 作用 |
|---|---|
ld_in |
同时装载 \(R_A\)、\(R_B\)、\(R_C\) |
sel_x |
0 选 \(R_A\),1 选 \(R_T\) |
sel_y |
0 选 \(R_B\),1 选 \(R_C\) |
alu_sub |
0 做加法,1 做减法 |
ld_t |
把 ALU 的低 4 bit 结果写入 \(R_T\),并把最高进位写入 \(R_K\) |
ld_r |
把 ALU 结果写入 \(R_R\) |
把同一周期需要的控制位排成一行,称为控制字(control word)。本例可写为:
| 控制状态/条件 | ld_in |
sel_x |
sel_y |
alu_sub |
ld_t |
ld_r |
微操作 |
|---|---|---|---|---|---|---|---|
IDLE & start |
1 | X | X | X | 0 | 0 | 装载三个输入 |
ADD |
0 | 0 | 0 | 0 | 1 | 0 | \(\{R_K,R_T\}\leftarrow R_A+R_B\) |
SUB |
0 | 1 | 1 | 1 | 0 | 1 | \(R_R\leftarrow R_T-R_C\) |
IDLE 未启动 |
0 | X | X | X | 0 | 0 | 全部数据寄存器保持 |
DONE |
0 | X | X | X | 0 | 0 | 结果保持,给出完成指示 |
表中的 X
表示这一拍对应选择不会被任何寄存器采样,因此对可见状态更新没有影响。它不是模拟不确定电压,也不是允许多路同时驱动。实际
RTL 常给这些位确定默认值,以避免锁存器和无谓翻转。
例题 4:从微操作得到控制字
要执行
\[ R_R\leftarrow R_T-R_C, \]
必须:
sel_x=1,把 \(R_T\) 送到 ALU 左端;sel_y=1,把 \(R_C\) 送到 ALU 右端;alu_sub=1,选择减法;ld_r=1,允许结果寄存器在边沿写入;ld_t=0、ld_in=0,其余数据寄存器保持。
只设置 alu_sub=1 不会自动保存结果;只设置
ld_r=1 而 MUX 选择错误,则会把错误运算写入 \(R_R\)。
变式: 加法拍对应
sel_x=0、sel_y=0、alu_sub=0、ld_t=1;同一边沿保存低
4 bit 和最高进位。
14. 第四步:控制器状态图与输出译码
控制器状态转移为:
IDLE: start=0 → IDLE
IDLE: start=1 → ADD,同时 ld_in=1
ADD: 无条件 → SUB
SUB: 无条件 → DONE
DONE: 无条件 → IDLE
busy=1 可定义在 ADD 与 SUB
状态;done=1 只定义在 DONE
状态。ld_t、ld_r、MUX 选择和 ALU
操作都可由当前状态译码产生,是 Moore 控制。ld_in 依赖
IDLE & start,是一个有条件的装载脉冲,时间上必须满足输入与寄存器的采样要求。
图 17-4
把状态、每拍控制动作和数据通路反馈放在一起。这个例子没有数据相关分支,所以数据通路暂时不必返回
zero 或 carry
来决定转移;carry_add
只作为结果状态保存。若改成“结果为零就提前结束”,zero
就会成为数据通路返回控制器的状态信号。
图
17-4 控制状态给每一拍分配唯一的寄存器传输,完成状态提供整周期
done
15. 第五步:用具体数值逐拍验证
例题 5:计算 \(R=(3+5)-2\)
令
A_in=0011,B_in=0101,C_in=0010
初始状态 IDLE,所有内部寄存器为 0。在第 1 个边沿采到
start=1:
| 边沿后 | 控制状态 | \(R_A\) | \(R_B\) | \(R_C\) | \(R_T\) | \(R_K\) | \(R_R\) | done |
|---|---|---|---|---|---|---|---|---|
| 0 | IDLE |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | ADD |
3 | 5 | 2 | 0 | 0 | 0 | 0 |
| 2 | SUB |
3 | 5 | 2 | 8 | 0 | 0 | 0 |
| 3 | DONE |
3 | 5 | 2 | 8 | 0 | 6 | 1 |
| 4 | IDLE |
3 | 5 | 2 | 8 | 0 | 6 | 0 |
边沿 1 只装载输入并进入 ADD;边沿 2 才保存加法结果;边沿
3 保存减法结果并进入 DONE。输出寄存器在回到
IDLE 后继续保持 6,直到下一项任务的 SUB
拍更新。
图 17-5
画出同一过程。注意控制状态名称表示“这一周期将执行的动作”:处于
ADD 周期时组合 ALU 计算加法,在该周期末的边沿把结果写入
\(R_T\) 并进入 SUB。
图 17-5 输入装载、加法、减法和完成指示分布在连续时钟边界上
变式: 若输入为 \(A=14\)、\(B=5\)、\(C=3\),4 bit 加法得到 \(R_T=3\) 且 carry_add=1;随后
\(R_R=0\)。若希望得到数学结果
16,必须扩宽中间和与减法通路。
16. 控制状态与数据状态不要混为一谈
在上述系统中:
IDLE/ADD/SUB/DONE是控制状态,表示任务阶段;- \(R_A\)、\(R_B\)、\(R_C\)、\(R_T\)、\(R_R\) 是数据状态,保存位模式;
carry_add、zero是由数据计算得到的状态标志;- 所有寄存器合起来才构成系统完整物理状态。
有限状态机加上受控数据通路,常称为带数据通路的有限状态机(finite-state machine with datapath, FSMD)。设计时分开画图,验证时必须一起逐拍检查,因为控制状态决定谁写入,而数据寄存器的内容又可能决定控制状态怎样分支。
一个判断标准
如果某个量的每种可能数值都用一个 FSM 状态表示,状态数可能迅速膨胀。例如 8 bit 计数值有 256 种,把它们全部画成控制状态没有教学或实现收益。更清楚的做法是:
- FSM 只保存
IDLE/RUN/DONE; - 8 bit 寄存器保存计数;
- 比较器产生
terminal; - FSM 根据
terminal决定是否结束。
17. 资源共享:用时间换硬件
资源共享(resource sharing)是让多个不同周期的微操作复用同一硬件资源。本章贯穿例子让加法和减法复用一台 ALU,因此需要两个运算周期。
共享成立的前提是这些操作不会要求在同一周期同时发生。控制器通过 MUX
选择操作数,并通过 alu_op 选择功能。共享通常带来:
- 更少的算术单元和较小的数据通路面积;
- 更多的 MUX、控制逻辑与布线;
- 更多完成周期,单项任务延迟可能增加;
- ALU 在不同周期切换不同数据,活动与功耗需结合实际序列判断。
“一个 ALU 一定比两个 ALU 省一半面积”不是可靠结论。MUX、寄存器、控制器和布线同样占用资源,综合结果还取决于位宽、库单元和时序目标。
18. 时钟周期、延迟和吞吐率是三个量
时钟周期
\(T_{clk}\) 是相邻有效边沿之间的时间。它必须覆盖本周期最长的寄存器到寄存器组合路径及建立时间预算。
延迟
延迟(latency)是一次任务从接受到结果可用所需的时间。若固定需要 \(N_{cycle}\) 个周期,可写成
\[ t_{latency}=N_{cycle}T_{clk}. \]
这里必须先明确起点与终点。例如本例从接受 start 的边沿到
done=1 的边沿经历 2
个完整周期;若把启动前的准备周期也算入,口径会不同。
吞吐率
吞吐率(throughput)描述单位时间可完成多少项任务。若每隔 \(II\) 个周期才能接受一项新任务,其中 \(II\) 是启动间隔(initiation interval),理想吞吐率为
\[ Throughput=\frac{1}{II\cdot T_{clk}}=\frac{f_{clk}}{II}. \]
本例在 DONE 后才回到
IDLE,若新任务只能在空闲边沿接受,必须按接口逐拍确定 \(II\)。缩短时钟周期不一定能抵消额外的多周期等待。
19. 单周期、多周期与流水线
三种结构可能实现相同数学功能,却具有不同硬件组织。
单周期
加法器与减法器串联,一项任务在一个长周期内完成。优点是控制简单、每周期可接受一项任务;缺点是组合路径长,并需要两套算术硬件。
多周期
使用一个共享 ALU,分加法拍和减法拍。单周期组合路径缩短,硬件可复用;一项任务占用多个周期,且同一 ALU 忙碌时不能同时处理另一项任务。
流水线
加法器和减法器之间插入寄存器。第一项任务进入减法级时,第二项任务可以同时进入加法级。流水线把长组合路径分段:
- 单项任务仍需穿过多个级,延迟按多个周期计算;
- 流水线填满后,可以每周期接收并完成一项;
- 代价是增加级间寄存器、时钟负载、控制对齐和暂停处理。
观察图 17-6 时,横向看一项任务经过多少周期,纵向看同一周期有几项任务并行。多周期结构在时间上复用一台 ALU;流水线结构在空间上保留多个运算级,让不同任务重叠。
图 17-6 单周期压入一个长组合周期,多周期复用硬件,流水线用级间寄存器换取任务重叠
例题 6:用给定延迟比较三种实现
题设给出教学用简化参数:4 bit 加法器最大延迟 300 ps,减法器最大延迟 320 ps,操作数 MUX 最大延迟 60 ps,每一级寄存器的 \(t_{cq,max}+t_{setup}\) 及其他时钟预算合计 100 ps。
单周期串联:
\[ T_{clk,min}=300+320+100=720\ \text{ps}. \]
共享 ALU 多周期:每个运算周期先经过 MUX,再经过最慢 ALU 操作:
\[ T_{clk,min}=60+320+100=480\ \text{ps}. \]
加法和减法占两个运算周期,所以仅运算部分至少需要 \(2\times480=960\) ps。
两级流水线:使用独立加、减法级,中间插入寄存器:
\[ T_{clk,min}=\max(300,320)+100=420\ \text{ps}. \]
一项任务的两级延迟为 \(2\times420=840\) ps;流水线填满后可达到每 420 ps 接受一项的理想吞吐节奏。
这个例子说明:最短时钟周期、单项任务延迟和连续任务吞吐率不会自动同时最优。
变式: 若级间寄存器预算很大,过度切分流水级可能不再提高频率,因为每一级都要承担寄存器开销。
20. 控制器与数据通路之间的组合环路
合法的同步结构可以形成跨周期反馈:
状态寄存器 → 控制逻辑 → 数据通路 → 状态信号 → 下一状态逻辑 → 状态寄存器
中间只要由寄存器在周期边界截断,就是普通寄存器到寄存器路径。危险结构是同一周期内没有寄存器隔开的组合环路,例如:
控制信号 → ALU/MUX → zero → 控制信号
如果 zero 立即改变 MUX 选择,而新的 MUX 选择又立即改变
zero,组合网络可能没有稳定的单向求值关系。设计时应让控制输出由当前状态与已定义输入产生,并通过下一个边沿更新状态;若需要数据相关分支,确保组合依赖无环。
21. 数据相关分支怎样工作
把贯穿例子改为“若 \(A+B=0\),跳过减法并直接完成”。数据通路在
ADD 周期形成 ALU 结果并产生
zero_alu。有两种清楚的实现:
- 在
ADD周期末同时把和写入 \(R_T\),并由控制器在该边沿采样稳定的zero_alu,选择下一状态DONE或SUB; - 先保存 \(R_T\),下一周期用寄存后的
zero_t=(R_T=0)决定转移。
第一种少一拍,但 ADD 周期的控制路径包含“寄存器 → MUX/ALU
→ 零检测 → 下一状态逻辑 →
状态寄存器”,可能成为关键路径。第二种多一拍,时序边界更简单。选择取决于周期目标和接口延迟要求。
22. 接口握手:何时可以接受,何时结果有效
一个多周期模块至少要明确三件事:
- 请求何时接受:例如只在
IDLE且上升沿采到start=1时接受; - 忙碌期间怎样处理新请求:忽略、排队或报告错误;
- 结果何时有效:例如
done=1的整周期内 \(R_{out}\) 有效,并在之后继续保持。
start/busy/done
是简化握手接口。它不是唯一方案,但足以建立周期契约。若
start
只高一拍且模块当时忙碌,事件会被丢弃;若系统不允许丢事件,就需要请求保持、队列或请求—应答协议。
23. 从功能结构回到物理时序
数据通路与控制器分开画,不代表它们在时序上互不影响。至少检查下列路径:
- 数据寄存器 → MUX → ALU → 数据寄存器;
- 数据寄存器 → 比较器 → 下一状态逻辑 → 状态寄存器;
- 状态寄存器 → 控制译码 → MUX/ALU → 数据寄存器;
- 状态寄存器 → 下一状态逻辑 → 状态寄存器;
- 外部同步输入 → 控制逻辑 → 状态或数据寄存器。
每条路径分别做最大延迟建立检查和最小延迟保持检查。控制器很小,不代表控制路径一定短;高扇出装载使能、复杂状态译码和跨模块布线都可能增加延迟。
24. 完整设计流程
面对一个多周期数字任务时,按下面顺序工作:
- 写清接口契约。 规定输入何时有效、请求何时接受、结果何时有效、忙碌时怎样处理新请求;
- 列出必须保存的数据。 包括输入副本、中间结果、计数值和最终结果;
- 列出需要的微操作。 传送、加减、移位、比较、清零分别写清;
- 安排逐拍调度。 同一拍的操作必须有足够硬件且没有资源冲突;
- 画数据通路。 放置寄存器、MUX、ALU、比较器和总线;
- 定义控制信号与状态信号。 每个信号只有一个明确方向和含义;
- 建立控制状态。 一个状态对应一组清楚的周期动作;
- 列控制字或输出表。 对每个状态给出所有写使能、选择与操作码;
- 定义复位、默认值和非法恢复。 数据与控制状态都进入已知安全行为;
- 逐拍验证。 同时记录控制状态、寄存器值、控制字和接口输出;
- 检查边界情况。 位宽截断、零值、最大值、重复启动、暂停或错误输入;
- 检查 CDC 与时序。 先同步异步输入,再对全部寄存器路径做建立/保持检查。
25. 常见误区与反例
误区 1:数据通路就是“很多组合逻辑”
数据通路通常包含大量寄存器。它不仅计算数据,也保存操作数、中间结果和输出。
误区 2:控制器保存全部数据
FSM 状态只保存控制阶段。把每个数据值展开为控制状态会导致状态爆炸;多 bit 数据应保存在数据寄存器中。
误区 3:写了 \(R_A\leftarrow R_B\),数据就立即移动
寄存器传输在规定有效边沿执行。边沿之前,组合路径只是在准备候选输入。
误区 4:两行寄存器传输像程序一样从上到下执行
同一拍的寄存器同时读取旧值并更新。交换两个寄存器不需要临时变量,只要数据通路支持双向输入选择。
误区 5:ALU 选择正确,结果就会自动保存
组合 ALU 只产生候选结果。目标寄存器的装载使能必须在正确边沿有效。
误区 6:未使用的控制位保持
X 最省事
表格中的 X
表示功能无关。实际组合控制应给出确定默认值,减少锁存器推断、未知传播和无谓切换。
误区 7:共享一台 ALU 只会节省面积
共享增加 MUX、控制和完成周期,也可能改变功耗与关键路径。它是资源、性能和复杂度之间的权衡。
误区 8:时钟更快就代表一项任务完成得更快
任务延迟等于周期数与周期时间的共同结果。多周期结构的时钟更短,任务仍可能需要更长总时间。
误区 9:流水线一定降低单项任务延迟
流水线主要提高吞吐率和可达频率。级间寄存器会增加周期边界,单项任务延迟可能增加。
误区 10:done=1
时结果自然有效
接口必须保证 done
与结果寄存器的更新关系。若二者来自不同组合路径,还要检查下游采样时刻;本章用寄存结果配合
Moore DONE 状态提供清楚边界。
误区 11:外部输入可以在后续运算拍继续直接读取
只有接口明确要求上游保持数据时才成立。更独立的模块在接受请求时捕获输入。
误区 12:数据状态和控制状态分开后可以分别验证
错误常发生在二者交界:控制信号晚一拍、MUX 选错、写使能漏开、状态标志取自错误数据。逐拍表必须同时记录两侧。
26. 工程中的实际意义
数据通路与控制器的划分出现在大量数字系统中:
- 小型算术加速器用控制器调度乘加、移位和累加;
- 通信模块用控制器组织接收阶段,数据通路保存移位数据与计数;
- 存储器接口控制器产生读写时序,数据通路保存地址和读写数据;
- 交通信号控制器用 FSM 选择灯光阶段,用计数器比较持续时间;
- 简单处理器由控制器解释操作步骤,数据通路包含寄存器、ALU、MUX 和总线。
这个划分也使验证更有条理。控制器检查“状态和优先级是否正确”,数据通路检查“运算和传送是否正确”,系统级检查“正确控制是否作用在正确数据上”。第 19、20 章会把这些规则写成 RTL 与自检查测试。
27. 与贯穿式交通信号项目的连接
贯穿式综合项目可以按本章方法划分:
- 控制器:保存
MAIN_GREEN、MAIN_YELLOW、SIDE_GREEN、SIDE_YELLOW等阶段; - 数据通路:保存阶段计数与可配置持续时间,执行加一和相等比较;
- 控制信号:
timer_clear、timer_en、配置寄存器装载使能; - 状态信号:
time_up、vehicle_req的同步事件; - 输出译码:根据控制状态产生两组红黄绿灯。
当 time_up=1
时,数据通路没有权力自行跳到下一灯光阶段;它只报告计时结束。控制器根据当前阶段、安全规则和车辆请求决定下一状态,并命令计时器清零。这正是图
17-1 的双向协作。
28. 本章知识链
文字任务与接口契约
↓
列出要保存的数据 + 列出每步微操作
↓
逐拍调度,检查资源冲突
↓
数据通路:寄存器 + MUX + ALU/比较器 + 总线
↕
控制信号向下,状态信号向上
↕
控制器:状态寄存器 + 下一状态逻辑 + 控制字
↓
逐拍联合验证控制状态与数据状态
↓
检查接口、位宽、CDC、建立与保持
↓
比较单周期 / 多周期 / 流水线的资源与性能
29. 本章小结
- 数据通路保存、选择、运算和传送多 bit 数据,控制器决定每个周期执行哪些动作。
- 控制信号从控制器进入数据通路,状态信号从数据通路返回控制器。
- 寄存器写使能通常由输入 MUX 与反馈路径实现;未写入时选择旧值保持。
- 寄存器传输描述有效边沿发生的数据更新,右侧读取边沿前旧值。
- 微操作是一个周期内完成的基础传送或运算,多个微操作能否并行取决于硬件资源。
- 总线是多根并行信号的集合;多个内部数据源通常由 MUX 选择。
- 控制字把一拍中的写使能、MUX 选择和 ALU 操作集中列出。
- FSMD 的控制状态表示任务阶段,数据状态保存操作数、中间结果和计数值。
- 资源共享用更多周期和控制复杂度换取较少运算资源。
- 时钟周期、单项任务延迟与吞吐率是三个不同指标。
- 流水线让不同任务在不同级重叠,主要提高吞吐率,不保证降低单项延迟。
- 完整设计必须联合检查控制、数据、接口、位宽、CDC 和所有寄存器时序路径。
30. 练习
基础题
- 用一句话分别说明数据通路和控制器的任务。
- 列出数据通路中五类常见部件。
ld_r、alu_op[1:0]、zero和data_in[7:0]分别属于控制、状态还是数据?- 写出带装载使能寄存器的下一值关系。
- 寄存器传输 \(R_B\leftarrow R_A\) 在什么时刻执行?右侧读取哪个时刻的值?
- 什么是微操作?给出一个传送和一个算术微操作例子。
- 为什么内部共享总线通常需要 MUX?
- 什么是控制字?
分析与计算题
- 4 bit 寄存器 \(R\) 初始
0110,三拍 \((ld_R,D)\) 为(1,1001)、(0,1111)、(1,0011),写出每拍后的 \(R\)。 - 同一拍执行 \(R_A\leftarrow R_B\) 与 \(R_B\leftarrow R_A\)。初始 \(R_A=3\)、\(R_B=9\),边沿后分别是多少?
- 两个微操作 \(R_X\leftarrow R_A+R_B\) 和 \(R_Y\leftarrow R_C-R_D\) 能否同拍完成?分别讨论只有一台 ALU和有两台独立 ALU 的情况。
- 对贯穿例子的控制信号,写出
ADD状态完整控制字。 - 对贯穿例子输入 \(A=4\)、\(B=7\)、\(C=3\),写出从接受
start到回到IDLE的控制状态、\(R_T\)、\(R_R\) 和done。 - 对输入 \(A=15\)、\(B=3\)、\(C=2\),按 4 bit 模运算求 \(R_T\)、
carry_add和最终 \(R_R\)。再说明若想保留数学结果,应改动哪里。 - 一个 8 bit 计时器有控制状态
IDLE/RUN/DONE。指出控制状态、数据状态、控制信号和返回状态信号各一个例子。 - 某任务固定经过 5 个周期,\(T_{clk}=4\) ns。求任务延迟。若 \(II=2\),理想吞吐率是多少项/秒?
- 某单周期通路包含 280 ps 与 350 ps 两段串联逻辑,寄存器及预算合计 120 ps。求最小时钟周期和最高频率。
- 把第 17 题两段逻辑之间加入级间寄存器,每级都承担 120 ps 寄存器及预算。求流水线最小时钟周期、两级延迟和填满后的理想吞吐率。
综合题
- 为操作 \(R=(A\land B)+C\) 设计两周期共享数据通路:列出所需寄存器、组合单元、MUX 和逐拍微操作。
- 把第 19 题翻译成控制状态与控制字。至少包含输入装载、AND/保存、ADD/保存和完成指示。
- 一个模块在忙碌时直接忽略单周期
start。说明什么情况下会丢任务,并给出两种接口改进方法。 - 某控制器使用组合信号
zero立即反过来改变生成zero的 ALU 输入选择。指出结构问题,并给出同步改写方向。 - 为贯穿式交通信号项目画出文字版“控制器—数据通路”边界:列出至少三个控制状态、两个数据寄存器、两个控制信号和两个状态信号。
- 独立设计一个 4 bit“重复加一到目标值”模块:启动时装载初值
init和目标target;每个运行拍把当前值加一;达到目标后done=1一周期。写出接口、数据通路、状态、逐拍规则、非法状态恢复和至少六个测试场景。规定init=target时怎样处理。
31. 练习答案
展开第 17 章练习答案
题 1
数据通路保存、选择、运算和传送数据;控制器根据当前阶段、输入和状态信号决定每拍的数据通路动作。
题 2
寄存器、MUX、ALU、比较器、移位器或计数器、总线均可作为答案,列出其中五类即可。
题 3
ld_r:寄存器装载控制;alu_op[1:0]:ALU 操作控制;zero:数据通路返回的状态;data_in[7:0]:输入数据。
位宽不是分类依据。
题 4
\[ R^{+}= \begin{cases} D,&ld_R=1,\\ R,&ld_R=0. \end{cases} \]
也可写成 \(R^{+}=ld_R D+\overline{ld_R}R\),其中逻辑运算逐位作用。
题 5
它在控制条件成立的有效时钟边沿执行,右侧读取边沿前 \(R_A\) 保存的旧值。
题 6
微操作是一个时钟步骤内对寄存器数据执行的基础传送或运算。例如传送 \(R_B\leftarrow R_A\),算术操作 \(R_T\leftarrow R_A+R_B\)。
题 7
多个数据源不能用普通逻辑输出同时直接驱动同一内部节点。MUX 根据选择信号只接通一个来源,给总线或目标输入确定值。
题 8
控制字是一组在同一周期共同定义数据通路动作的控制位,通常包含寄存器写使能、MUX 选择、ALU 操作码、计数使能等。
题 9
| 拍后 | ld_R |
\(D\) | \(R\) |
|---|---|---|---|
| 1 | 1 | 1001 |
1001 |
| 2 | 0 | 1111 |
1001 |
| 3 | 1 | 0011 |
0011 |
题 10
两个寄存器同拍读取旧值,因此边沿后 \(R_A=9\)、\(R_B=3\),完成交换。
题 11
只有一台 ALU 时,两种不同运算争用同一资源,不能同拍完成,必须分两个周期。若有两台独立 ALU,且四个源寄存器到两台 ALU、两台 ALU 到两个目标寄存器的连接和写使能齐全,则可以同拍完成。
题 12
ADD 状态控制字为:
ld_in=0,sel_x=0,sel_y=0,alu_sub=0,ld_t=1,ld_r=0
它选择 \(R_A\)、\(R_B\) 做加法,并在周期末写入 \(R_T\) 与 1 bit 进位寄存器 \(R_K\)。
题 13
\(4+7=11\),\(11-3=8\):
| 边沿后 | 状态 | \(R_T\) | \(R_R\) | done |
|---|---|---|---|---|
| 接受启动 | ADD |
0 | 0 | 0 |
| 加法完成 | SUB |
11 | 0 | 0 |
| 减法完成 | DONE |
11 | 8 | 1 |
| 下一拍 | IDLE |
11 | 8 | 0 |
输入寄存器在接受启动的边沿同时装载 4、7、3。
题 14
\[ 15+3=18=1\_0010, \]
所以 4 bit \(R_T=0010=2\),carry_add=1。随后
\[ R_R=2-2=0. \]
数学表达式的完整结果应为 16。要保留它,至少把中间寄存器、结果寄存器、ALU 与相关 MUX 扩为足够位宽,并明确 \(C\) 的扩展方式;只扩宽输出端不能恢复已经截断的进位。
题 15
- 控制状态:
RUN; - 数据状态:8 bit
count; - 控制信号:
count_en或count_clear; - 返回状态信号:
terminal=(count==limit)。
题 16
任务延迟为
\[ t_{latency}=5\times4\ \text{ns}=20\ \text{ns}. \]
理想吞吐率为
\[ \frac{1}{2\times4\ \text{ns}}=1.25\times10^8\ \text{项/s}. \]
题 17
\[ T_{clk,min}=280+350+120=750\ \text{ps}. \]
\[ f_{max}=\frac{1}{750\ \text{ps}}\approx1.333\ \text{GHz}. \]
题 18
最慢级为 350 ps,因此
\[ T_{clk,min}=350+120=470\ \text{ps}. \]
两级单项延迟为
\[ 2\times470=940\ \text{ps}. \]
填满后理想吞吐率为
\[ \frac{1}{470\ \text{ps}}\approx2.128\times10^9\ \text{项/s}. \]
它的单项延迟比原单周期 750 ps 更长,但连续任务吞吐率更高。
题 19
一种两运算周期结构包括:输入寄存器 \(R_A,R_B,R_C\),临时寄存器 \(R_T\),结果寄存器 \(R_R\),一个 AND 单元和一个加法器。若要求真正共享同一“运算单元”,可以使用支持 AND 与 ADD 的 ALU,并在两拍间复用。
逐拍微操作:
LOAD:R_A ← A,R_B ← B,R_C ← C
AND: R_T ← R_A AND R_B
ADD: R_R ← R_T + R_C
ALU 输入 MUX 左侧在 \(R_A/R_T\) 中选择,右侧在 \(R_B/R_C\) 中选择;操作码选择 AND 或 ADD。
题 20
可用 IDLE、AND、ADD、DONE 四个状态:
| 状态/条件 | 输入装载 | 左选 | 右选 | ALU 操作 | 写 \(R_T\) | 写 \(R_R\) | 下一状态 |
|---|---|---|---|---|---|---|---|
IDLE & start |
1 | X | X | X | 0 | 0 | AND |
AND |
0 | \(R_A\) | \(R_B\) | AND | 1 | 0 | ADD |
ADD |
0 | \(R_T\) | \(R_C\) | ADD | 0 | 1 | DONE |
DONE |
0 | X | X | 确定默认 | 0 | 0 | IDLE |
done 只在 DONE 为 1。
题 21
若 start 只高一个周期,而该周期模块处于非
IDLE
状态,请求不会被采纳,下一周期它已消失,因此任务丢失。改进方法包括:
- 上游保持
start,直到模块返回ready或accept; - 在模块入口增加请求寄存器或 FIFO,把忙碌期间的事件保存下来。
还可采用完整的请求—应答握手。
题 22
这形成 MUX 选择 → ALU → zero → MUX 选择
的无寄存器组合环路,输出可能无法得到唯一稳定解,也不适合普通同步时序分析。可让
MUX 选择只由当前寄存控制状态产生,把 zero
作为下一状态条件,在时钟边沿后再改变控制状态;或先把 ALU
结果/状态寄存,再在下一拍使用。
题 23
一个合格划分示例:
- 控制状态:
MAIN_GREEN、MAIN_YELLOW、SIDE_GREEN、SIDE_YELLOW; - 数据寄存器:阶段计数
timer_count、阶段限值phase_limit; - 控制信号:
timer_en、timer_clear; - 状态信号:
time_up、同步后的vehicle_pending; - 控制器输出:两组灯光译码与配置装载使能。
time_up
只报告比较结果,真正的灯光转移由控制状态与安全规则决定。
题 24
一种完整设计如下。
接口:输入 CLK、同步高有效
clear、start、init[3:0]、target[3:0];输出
value[3:0]、busy、done。只在空闲接受启动,运行时忽略新启动。
数据通路:当前值寄存器 \(R_V\)、目标寄存器 \(R_T\)、4 bit 加一器、相等比较器
equal=(R_V==R_T)。控制信号为
ld_inputs、inc_v;状态信号为
equal。
控制状态:
IDLE:等待启动;CHECK:检查初值是否已经等于目标;RUN:每拍加一;DONE:完成指示一周期。
规则:
任意状态 clear=1:state←IDLE,R_V←0,R_T←0
IDLE & start:R_V←init,R_T←target,state←CHECK
CHECK & equal:state←DONE
CHECK & !equal:state←RUN
RUN:R_V←R_V+1;若加一后的值等于目标,则下一状态←DONE,否则保持 RUN
DONE:下一状态←IDLE
非法状态:state←IDLE,并清零数据寄存器
这里规定 init=target 时不执行加一,CHECK
后直接进入 DONE。4 bit 加一按模 16
回卷,因此任意初值最终都能到达任意 4 bit
目标;若规格禁止回卷,应加入大小比较和错误状态。
至少六个测试场景:
init=3,target=5,检查 3→4→5;init=7,target=7,检查直接完成;init=15,target=1,检查模 16 回卷 15→0→1;- 运行期间再次
start,确认忽略; - 运行中
clear=1,确认同步回空闲并清零; - 强制非法控制编码,确认恢复;
- 检查
done只高一个完整周期; - 检查回到空闲后
value的保持或清零行为与接口规格一致。
32. 自测清单
若第 1、7 项不稳定,请重画图 17-1,并只让多 bit 操作数留在数据通路。若第 3、4、8 项不稳定,请重做例题 5 的逐拍表。若第 9~11 项不稳定,请遮住图 17-6 的文字,重新解释三行时间格。
33. 下一章衔接
本章的数据主要保存在少量独立寄存器中。随着数据数量增加,为每个值单独画一只寄存器和一组 MUX 会迅速变得笨重。第 18 章将学习存储器基础:地址怎样从大量存储位置中选择一项,ROM、SRAM、DRAM 和寄存器文件的接口与用途有何不同,以及读写时序怎样接入数据通路与控制器。