同步状态机(Synchronous FSM)是数字设计的基石——当前状态和输入通过组合逻辑产生下一状态,在时钟沿统一更新。异步状态机(Asynchronous FSM)没有时钟,状态转移由输入信号的变化直接触发。
| 维度 | 同步FSM | 异步FSM |
|---|---|---|
| 状态更新时机 | 时钟沿 | 输入变化 |
| 状态编码 | 任意(binary/one-hot/gray) | 必须无冒险 |
| 组合逻辑要求 | 功能正确即可 | 必须无冒险(hazard-free) |
| 竞争风险 | 时钟消除竞争 | 必须消除本质冒险 |
| 速度 | 受时钟周期限制 | 仅受逻辑延迟限制 |
| 设计方法 | 成熟(状态图→Verilog) | 复杂(需考虑冒险/竞争) |
组合逻辑中,当输入变化导致多个路径的延迟不同时,输出可能出现短暂的错误跳变。同步设计中这无关紧要(时钟沿前稳定即可),但异步设计中这会导致错误的状态转移。
消除方法:添加冗余项。Y = AB + AC + BC,其中BC是冗余的一致项(consensus term),确保B=C=1时Y始终为1。
即使组合逻辑无冒险,状态寄存器的延迟不均也可能导致错误转移。如果某条反馈路径比另一条快,状态机可能"跳过"预期状态。
异步状态机的状态编码必须保证每次状态转移只有一个状态位变化(类似格雷码),否则多位同时变化可能因路径延迟不同到达中间状态。
在实际工程中,我们不直接设计"纯异步FSM"(风险太高),而是在握手协议框架内实现状态机——用握手信号控制状态转移的时机,用时钟保证状态更新的确定性。这种方式兼顾了异步通信的灵活性和同步设计的可靠性。
我们实现一个具有4个状态的异步FSM,用于控制数据流处理:
// async_fsm.v
// 基于握手的异步状态机
// 状态转移由输入握手请求触发,完成后应答
module async_fsm #(
parameter DATA_WIDTH = 8
)(
input wire clk,
input wire rst_n,
// 异步输入握手
input wire [DATA_WIDTH-1:0] data_i,
input wire req_i,
output reg ack_o,
// 异步输出握手
output reg [DATA_WIDTH-1:0] data_o,
output reg req_o,
input wire ack_i,
// 状态输出(调试用)
output reg [2:0] current_state
);
// 状态定义(格雷码编码,确保每次只变1位)
localparam S_IDLE = 3'b000; // 空闲
localparam S_RECEIVE = 3'b001; // 接收数据
localparam S_PROCESS = 3'b011; // 处理数据
localparam S_SEND = 3'b010; // 发送结果
localparam S_WAIT = 3'b110; // 等待输出应答
reg [2:0] next_state;
reg [DATA_WIDTH-1:0] process_reg;
// 状态转移逻辑
always @(*) begin
next_state = current_state;
case (current_state)
S_IDLE: if (req_i) next_state = S_RECEIVE;
S_RECEIVE: next_state = S_PROCESS;
S_PROCESS: next_state = S_SEND;
S_SEND: if (ack_i) next_state = S_WAIT;
S_WAIT: if (!ack_i && !req_i) next_state = S_IDLE;
default: next_state = S_IDLE;
endcase
end
// 状态更新 + 握手控制
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= S_IDLE;
ack_o <= 1'b0;
req_o <= 1'b0;
data_o <= {DATA_WIDTH{1'b0}};
process_reg <= {DATA_WIDTH{1'b0}};
end else begin
current_state <= next_state;
case (current_state)
S_IDLE: begin
ack_o <= 1'b0;
if (req_i) begin
// 采样输入数据
process_reg <= data_i;
ack_o <= 1'b1; // 应答输入
end
end
S_RECEIVE: begin
// 输入应答已发出,等待处理
end
S_PROCESS: begin
// 数据处理:这里做简单的位反转作为示例
process_reg <= {<<{process_reg}}; // 位反转
ack_o <= 1'b0; // 撤回输入应答
end
S_SEND: begin
data_o <= process_reg;
req_o <= 1'b1; // 请求输出
end
S_WAIT: begin
if (ack_i) begin
req_o <= 1'b0; // 收到输出应答,撤回请求
end
if (!ack_i && req_o == 1'b0) begin
// 归零完成
end
end
endcase
end
end
endmodule
异步FSM的正确性需要通过信号转换图(Signal Transition Graph, STG)进行验证。STG是有向图,节点是信号跳变(如req+、ack-),边表示因果关系:
异步系统的行为更适合用Petri网描述。Petri网由库所(Place,圆圈)和变迁(Transition,竖线)组成,令牌(Token,黑点)在库所中流动表示系统状态:
仲裁器(Arbiter)是异步电路的典型应用——当多个请求同时到达时,决定谁先获得资源。异步仲裁器不需要时钟,可以在请求到达的瞬间开始仲裁:
// async_arbiter.v
// 异步两输入仲裁器
// 使用4-phase握手协议,先到先服务+优先级打破
module async_arbiter (
input wire clk,
input wire rst_n,
// 请求通道0
input wire req0_i,
output reg ack0_o,
// 请求通道1
input wire req1_i,
output reg ack1_o,
// 仲裁结果
output reg grant // 0=通道0获得, 1=通道1获得
);
// 仲裁器状态
localparam S_IDLE = 2'd0; // 无请求
localparam S_GRANT0 = 2'd1; // 授权通道0
localparam S_GRANT1 = 2'd2; // 授权通道1
reg [1:0] state;
reg last_grant; // 上次授权给谁(公平性)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
ack0_o <= 1'b0;
ack1_o <= 1'b0;
grant <= 1'b0;
last_grant <= 1'b0;
end else begin
case (state)
S_IDLE: begin
ack0_o <= 1'b0;
ack1_o <= 1'b0;
if (req0_i && !req1_i) begin
// 只有通道0请求
state <= S_GRANT0;
grant <= 1'b0;
ack0_o <= 1'b1;
end else if (!req0_i && req1_i) begin
// 只有通道1请求
state <= S_GRANT1;
grant <= 1'b1;
ack1_o <= 1'b1;
end else if (req0_i && req1_i) begin
// 同时请求:轮流优先(公平性)
if (!last_grant) begin
state <= S_GRANT1;
grant <= 1'b1;
ack1_o <= 1'b1;
last_grant <= 1'b1;
end else begin
state <= S_GRANT0;
grant <= 1'b0;
ack0_o <= 1'b1;
last_grant <= 1'b0;
end
end
end
S_GRANT0: begin
// 通道0已授权,等待其释放
if (!req0_i) begin
ack0_o <= 1'b0;
state <= S_IDLE;
end
end
S_GRANT1: begin
// 通道1已授权,等待其释放
if (!req1_i) begin
ack1_o <= 1'b0;
state <= S_IDLE;
end
end
default: state <= S_IDLE;
endcase
end
end
endmodule
异步仲裁器的核心问题:当两个请求几乎同时到达时,仲裁逻辑可能在亚稳态下做出不一致的判断。工业界使用互斥元素(Mutual Exclusion Element, MUTEX)来解决:
// mutex_element.v
// 互斥元素 — 异步仲裁的亚稳态过滤器
// 确保两个请求不会同时获得授权
// 当两个请求同时到达时,MUTEX进入亚稳态
// 但只有亚稳态解除后才输出授权
module mutex_element (
input wire r1, // 请求1
input wire r2, // 请求2
output wire g1, // 授权1
output wire g2 // 授权2
);
// 交叉耦合NAND实现互斥
// 当r1和r2同时为1时,两个NAND输出都可能
// 暂时处于亚稳态,但最终会稳定到某一侧
wire n1, n2;
assign n1 = ~(r1 & n2); // NAND门1
assign n2 = ~(r2 & n1); // NAND门2
assign g1 = n1;
assign g2 = n2;
// 关键性质:
// g1和g2永远不会同时为1
// 如果r1先到:n1先变0 → g1=1, g2=0
// 如果r2先到:n2先变0 → g1=0, g2=1
// 同时到达:亚稳态最终随机消解
endmodule
// tb_async_fsm.v
// 异步状态机测试台
\`timescale 1ns/1ps
module tb_async_fsm;
reg clk, rst_n;
reg [7:0] data_i;
reg req_i;
wire ack_o;
wire [7:0] data_o;
wire req_o;
wire ack_i;
wire [2:0] state;
reg ack_i_reg;
assign ack_i = ack_i_reg;
async_fsm #(.DATA_WIDTH(8)) uut (
.clk(clk), .rst_n(rst_n),
.data_i(data_i), .req_i(req_i),
.ack_o(ack_o),
.data_o(data_o), .req_o(req_o),
.ack_i(ack_i),
.current_state(state)
);
initial clk = 0;
always #5 clk = ~clk;
integer pass_count, fail_count;
initial begin
$dumpfile("async_fsm.vcd");
$dumpvars(0, tb_async_fsm);
rst_n = 0; req_i = 0; data_i = 0;
ack_i_reg = 0;
pass_count = 0; fail_count = 0;
#20 rst_n = 1;
#10;
// 测试1: 发送 0b10110011,期望位反转 0b11001101
$display("--- Test 1: Bit reverse of 0xB3 ---");
data_i = 8'hB3; // 10110011
@(posedge clk);
req_i = 1;
@(posedge clk);
req_i = 0;
// 等待输入应答
wait(ack_o);
$display(" Input acknowledged, state=%b", state);
// 等待输出请求
wait(req_o);
$display(" Output ready: data_out=0x%02h (expect 0xCD)", data_o);
if (data_o === 8'hCD) // 11001101
pass_count = pass_count + 1;
else
fail_count = fail_count + 1;
// 应答输出
@(posedge clk);
ack_i_reg = 1;
@(posedge clk);
ack_i_reg = 0;
// 等待归零完成
wait(!req_o);
#50;
// 测试2: 发送 0x0F
$display("--- Test 2: Bit reverse of 0x0F ---");
data_i = 8'h0F;
@(posedge clk);
req_i = 1;
@(posedge clk);
req_i = 0;
wait(req_o);
$display(" Output: data_out=0x%02h (expect 0xF0)", data_o);
if (data_o === 8'hF0)
pass_count = pass_count + 1;
else
fail_count = fail_count + 1;
@(posedge clk);
ack_i_reg = 1;
@(posedge clk);
ack_i_reg = 0;
wait(!req_o);
// 测试3: 快速连续发送
$display("--- Test 3: Back-to-back transfers ---");
repeat(5) begin
data_i = $random;
@(posedge clk);
req_i = 1;
@(posedge clk);
req_i = 0;
wait(req_o);
@(posedge clk);
ack_i_reg = 1;
@(posedge clk);
ack_i_reg = 0;
wait(!req_o && !ack_o);
#10;
end
#100;
$display("=== Results: Pass=%0d Fail=%0d ===", pass_count, fail_count);
$finish;
end
endmodule
| 编码方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 格雷码 | 每次只变1位,无冒险 | 状态数必须是2的幂 | 通用异步FSM |
| One-hot | 解码简单,速度快 | 多位同时变化有冒险 | 仅同步FSM |
| 点对点 | 完全定制,最优 | 设计工作量大 | 高性能定制异步 |
| Latch-based | 面积小,低功耗 | 时序验证困难 | 特殊应用 |
本课使用格雷码编码:S_IDLE=000, S_RECEIVE=001, S_PROCESS=011, S_SEND=010, S_WAIT=110,确保相邻状态只差1位。
1. 分析题:为什么异步FSM必须使用格雷码编码?如果两个状态位同时变化,画出最坏情况的时序图说明可能到达的中间状态。
2. 设计题:将async_fsm扩展为3输入仲裁器,添加第3个请求通道。确保公平性(每个通道的等待时间大致相同)。
3. 验证题:为async_fsm的STG添加形式化属性:确保ack_o永远不会在没有req_i的情况下产生,且req_o永远不会在没有完成输入握手的情况下产生。
4. 编程题:实现一个异步FSM,功能类似于同步Moore机——输出仅取决于当前状态,不取决于输入。用4-phase握手控制状态转移。
5. 思考题:MUTEX元素的亚稳态消解时间理论上没有上界。在实际芯片中,什么机制保证了它最终会稳定?这个时间上限如何影响仲裁器的性能?
🎯 掌握了异步状态机的设计与验证
📍 里程碑:能设计无冒险的异步FSM并用形式化工具验证
💡 下一步:学习Muller C门——异步电路的基本构建块