欢迎来到数字逻辑设计的进阶课堂。如果你已经掌握了组合逻辑电路,并且准备涉足那些具有“记忆”能力的复杂系统,那么你来对地方了。在这篇文章中,我们将深入探讨数字系统核心组件——同步时序电路(Synchronous Sequential Circuits)。
我们不仅要理解它是什么,还要掌握它如何工作,更重要的是,我们要学会如何从零开始设计一个同步时序电路。我们将通过理论讲解、公式推导以及基于 Verilog HDL 的实战代码示例,带你一步步揭开时序逻辑的神秘面纱。
什么是时序电路?
在开始之前,让我们先快速回顾一下基础。你可能已经熟悉组合电路,比如加法器或多路复用器。在这些电路中,输出仅仅取决于当前的输入。只要输入变了,输出立刻就会改变,没有任何“记忆”可言。
但是,现实世界中的数字系统——比如你的 CPU、交通灯控制器或者是电梯的控制面板——都需要根据“过去发生了什么”来决定“现在做什么”。这就是时序电路的用武之地。
时序电路的输出不仅取决于当前的输入,还取决于过去的输入历史。为了实现这种记忆功能,我们需要存储元件。最常用的存储元件就是触发器(Flip-Flops)。
为什么选择“同步”?
在时序电路的世界里,主要分为两大派系:同步和异步。
- 异步时序电路:就像没有交通管制的十字路口,状态的变化可以随时发生,只要输入条件满足。这虽然快,但极易产生竞争条件,设计难度极大。
- 同步时序电路:这是我们今天的主角。它就像是一个听从统一指挥的交响乐团。所有的状态变化都受到一个全局信号的控制——这个信号就是时钟信号(Clock Signal)。
在同步电路中,电路状态仅在时钟信号的上升沿(Positive Edge)或下降沿(Negative Edge)发生变化。这种机制带来了巨大的好处:
- 可预测性:我们可以精确地知道电路的状态在什么时候更新。
- 消除竞争:由于所有存储元件同时动作,我们可以避免逻辑冒险。
- 易于调试:时序波形变得清晰可追踪。
核心构成
一个典型的同步时序电路由两部分组成:
- 存储元件:通常是触发器,用于保存电路的当前状态。
- 组合逻辑:决定了下一状态和当前的输出。
深入实战:串行加法器的设计之旅
理论看多了容易犯困,让我们通过一个经典的工程问题——串行加法器(Serial Adder),来实战演练同步时序电路的设计流程。我们的目标是设计一个电路,它能逐位处理两个二进制数的加法,并考虑到进位。
设计步骤一:绘制状态图
首先,我们需要从需求出发。加法器需要“记住”上一位计算是否产生了进位。这就构成了我们系统的“状态”。
- 状态 A (No Carry):表示上一次计算没有进位(初始状态)。
- 状态 B (Carry):表示上一次计算产生了进位。
输入是 $x1$ 和 $x2$(两个加数的当前位)。输出是 $z$(和的当前位)。
我们可以绘制出状态转换图(这里省略图片,让我们用逻辑描述):
- 如果当前在状态 A (0),且 $x1 + x2 = 0$ -> 输出 0,保持在 A。
- 如果当前在状态 A (0),且 $x1 + x2 = 1$ -> 输出 1,保持在 A。
- 如果当前在状态 A (0),且 $x1 + x2 = 2$ (即1+1) -> 输出 0,跳转到 B (产生进位)。
- 如果当前在状态 B (1),且 $x1 + x2 = 0$ -> 输出 1,跳转到 A。
- …以此类推。
设计步骤二:构建状态表
根据上面的逻辑,我们可以列出原始的状态表。这是将思维具象化的关键一步。
输入 (x1, x2)
输出 (z)
:—
:—
00
0
01
1
10
1
11
0
00
1
01
0
10
0
11
1在这个简单的例子中,我们已经只有两个状态,没有冗余状态,所以可以跳过状态简化步骤。
设计步骤三:状态分配与编码
现在,我们需要把抽象的“状态 A”和“状态 B”变成机器能读懂的二进制代码。这就是状态分配。
- 状态 A (No Carry) -> 编码
0 - 状态 B (Carry) -> 编码
1
我们使用一个触发器(设输出为 $y$)来存储这个状态。触发器的当前输出 $y(t)$ 代表当前状态,下一时刻的输出 $y(t+1)$ 代表下一状态。为了存储这个状态,我们决定使用 D 触发器,因为它在数据传输中最常用且简单。
设计步骤四:推导激励表与输出逻辑
这是数学发挥魔力的时刻。我们需要写出激励方程(Excitation Equation)和输出方程(Output Equation)。
#### 1. 输出方程推导
让我们观察输出 $z$。当进位为 0 (y=0) 且输入为 11,或者进位为 1 (y=1) 且输入为 00 时,输出为 1(也就是输入之和与进位异或)。我们可以通过卡诺图化简得到(为了节省篇幅,这里直接给出化简后的布尔表达式):
$$z = x1 x‘2 y‘ + x‘1 x2 y‘ + x1 x2 y + x1 x‘2 y$$
这本质上是一个全加器的和逻辑:$z = x1 \oplus x2 \oplus y$。
#### 2. 激励方程推导
对于 D 触发器,其特性方程是 $Q_{next} = D$。也就是说,我们需要推导出 D 的输入逻辑,使得在时钟沿到来时,触发器能正确翻转到所需的下一状态。
通过观察状态表中的“下一状态”列(其实就是下一时刻的进位 $C_{out}$):
$$D = y{next} = x1 x2 + x1 y + x_2 y$$
这正好也是全加器的进位逻辑:$C{out} = x1 x2 + (x1 + x2) C{in}$。
设计步骤五:Verilog HDL 实现代码
现在,让我们把这些逻辑转化为实际的代码。我们将使用 Verilog 来描述这个串行加法器。这是现代数字设计中最通用的语言之一。
// 模块定义:SynchronousSerialAdder
// 输入:clk (时钟), rst (复位), x1, x2 (串行数据输入)
// 输出:z (串行和输出)
module SynchronousSerialAdder(
input wire clk,
input wire rst,
input wire x1,
input wire x2,
output reg z
);
// 内部寄存器:用于存储进位状态
// 我们使用一个寄存器来代表刚才分析中的触发器
reg state_reg;
// -------------------------------------------------------------------
// 状态更新逻辑 (Sequential Block)
// 这里对应 D 触发器的行为,随时钟沿更新状态
// -------------------------------------------------------------------
always @(posedge clk or posedge rst) begin
if (rst) begin
// 复位逻辑:将状态清零
state_reg <= 1'b0;
end else begin
// 激励方程的实现:D = x1x2 + x1y + x2y
// 计算下一时刻的进位状态
state_reg <= (x1 & x2) | (x1 & state_reg) | (x2 & state_reg);
end
end
// -------------------------------------------------------------------
// 输出逻辑 (Combinational Block)
// 这里对应输出方程:z = x1 ^ x2 ^ y
// -------------------------------------------------------------------
always @(*) begin
// 使用位运算异或操作实现全加器和的逻辑
z = x1 ^ x2 ^ state_reg;
end
endmodule
#### 代码解析:
- 模块化思维:我们定义了清晰的输入输出接口。
rst信号用于系统初始化,确保电路从一个已知状态(无进位)开始。 - 时序逻辑块:INLINECODE55db03b9 块描述了触发器的行为。这里我们不仅存储了状态,还直接实现了激励方程。注意这里使用了非阻塞赋值 INLINECODE90c00d0e,这是时序逻辑设计的黄金法则,能有效防止仿真竞争。
- 组合逻辑块:INLINECODE8108741f 块描述了输出 $z$。由于输出仅取决于当前输入和当前状态,这是一个纯组合逻辑。这里使用了阻塞赋值 INLINECODE8ee13c64。
设计步骤六:测试平台
代码写好了,怎么验证它是对的呢?我们需要写一个测试程序。
`timescale 1ns / 1ps
module tb_SynchronousSerialAdder;
// 1. 声明信号
reg clk;
reg rst;
reg x1;
reg x2;
wire z;
// 2. 实例化被测模块
SynchronousSerialAdder uut (
.clk(clk),
.rst(rst),
.x1(x1),
.x2(x2),
.z(z)
);
// 3. 生成时钟信号 (周期为 10ns)
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 4. 激励生成
initial begin
// 初始化
rst = 1; x1 = 0; x2 = 0;
#10 rst = 0; // 释放复位
// 测试用例:1 + 3 (01 + 11)
// 期望结果:4 (100)
// 注意:串行加法器通常是低位先传
// 时刻1: 1 + 1 = 0 (进位 1)
#10;
x1 = 1; x2 = 1;
#10;
// 时刻2: 0 + 1 + 1(进位) = 0 (进位 1)
x1 = 0; x2 = 1;
#10;
// 时刻3: 0 + 0 + 1(进位) = 1 (进位 0)
x1 = 0; x2 = 0;
#20;
// 这里可以添加更多测试用例...
$finish;
end
endmodule
同步时序电路的优缺点分析
作为工程师,我们在选择技术方案时必须权衡利弊。同步时序电路虽然是目前的主流,但它并不完美。
核心优势
- 行为可预测:由于所有操作都在时钟沿对齐,我们可以静态分析时序,确保数据在建立时间前稳定到达。这对于实时控制系统至关重要。
- 设计自动化:同步设计风格非常适合EDA(电子设计自动化)工具。综合工具可以轻松地将我们的代码转换为门级电路。
- 鲁棒性:通过精心设计的时钟树,可以最大程度减少毛刺对系统的影响。
潜在挑战
- 时钟偏斜:这是一个物理限制。当时钟信号到达不同的触发器的时间不一致时,可能会导致数据错误。在大型芯片设计中,时钟树综合(CTS)就是为了解决这个难题。
- 功耗问题:时钟树是芯片中功耗最大的部分之一,因为时钟信号在每个周期都在跳变。这就是为什么现代处理器引入了时钟门控技术来降低功耗。
- 最大频率限制:电路的最大工作频率受限于最慢的那一级逻辑路径(关键路径)。为了提高速度,我们需要流水线技术,这增加了设计的复杂度。
总结与进阶建议
在这篇文章中,我们一步步构建了一个同步时序电路——串行加法器。从状态图的绘制,到布尔逻辑的化简,再到 Verilog 代码的实现,我们走完了数字设计的完整闭环。
关键要点回顾:
- 同步时序电路依赖于时钟信号来统一协调状态变化。
- D 触发器是构建同步系统的基石。
- 设计时,先画出状态图/表,再进行状态分配,最后推导逻辑方程,这一标准流程能有效避免逻辑错误。
- 在编写 HDL 代码时,务必区分时序逻辑(非阻塞赋值)和组合逻辑(阻塞赋值)。
接下来你可以做什么?
如果你想继续深入这个领域,我建议你尝试以下挑战:
- 设计一个交通灯控制器:定义状态(红、黄、绿),并根据计时器输入进行状态切换。
- 探索有限状态机(FSM):研究摩尔型和米利型状态机的区别,它们是时序电路的理论核心。
- 学习流水线设计:尝试将一个复杂的组合逻辑(如乘法器)切割成多级流水线,以此提升系统的运行频率。
希望这篇文章能帮助你建立起对同步时序电路的直观理解。动手去修改上面的代码,观察波形的变化,这才是掌握数字逻辑设计的最佳途径。