Verilog有限状态机(8)

HDLBits链接


前言

今天我们来学习HDLBits状态机部分的最后一组题目!这组题目涵盖了几个经典应用:

  1. 简单序列检测状态机
  2. 独热编码逻辑推导
  3. 资源仲裁器状态机
  4. 复杂协议状态机

状态机是数字电路设计的灵魂,把这些题目都掌握了,以后遇到类似问题就能得心应手!


题库

Q2a: FSM - 简单序列检测

1

题目理解

这是一道经典的”看图写状态机”题目。给了状态转移图,我们只需要照着实现就行。

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
module top_module (
input wire clk,
input wire reset, // 同步高电平复位
input wire w,
output reg z
);

// 状态参数定义
parameter A = 3'd0;
parameter B = 3'd1;
parameter C = 3'd2;
parameter D = 3'd3;
parameter E = 3'd4;
parameter F = 3'd5;

reg [2:0] current_state;
reg [2:0] next_state;

// 第一段:组合逻辑,状态转移
always @(*) begin
case (current_state)
A: next_state = w ? B : A;
B: next_state = w ? C : D;
C: next_state = w ? E : D;
D: next_state = w ? F : A;
E: next_state = w ? E : D;
F: next_state = w ? C : D;
default:next_state = A;
endcase
end

// 第二段:时序逻辑,状态更新
always @(posedge clk) begin
if (reset) begin
current_state <= A;
end else begin
current_state <= next_state;
end
end

// 第三段:输出逻辑
assign z = (current_state == E) | (current_state == F);

endmodule

Q2b: One-hot FSM equations - 独热编码逻辑推导

题目理解

这道题中状态使用独热编码,我们需要推导出Y1Y3的逻辑表达式。

独热编码的特点就是简单!每个状态对应一位,我们只需要看哪些状态会转移到目标状态就行。

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
module top_module (
input wire [5:0] y, // 独热编码的当前状态
input wire w,
output reg Y1,
output reg Y3
);

// 独热编码直接推表达式
assign Y1 = y[0] & w; // 只有S0在w=1时转到S1
assign Y3 = (y[1] | y[2] | y[4] | y[5]) & ~w; // 这些状态在w=0时转到S3

endmodule

Q2a: FSM - 资源仲裁器

2

题目理解

这道题是用状态机实现一个仲裁器。想象一下有三台设备都想用同一个资源:

  • r[1]r[2]r[3]分别表示三台设备的请求
  • g[1]g[2]g[3]表示资源分配给了哪台设备
  • 优先级:设备1 > 设备2 > 设备3(数字越小优先级越高)
  • 设备获得资源后,要等它释放(请求信号变低)才会重新仲裁

这就像三个人排队用打印机,优先级高的可以插队,但正在用的人要用完才会让出来。

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
module top_module (
input wire clk,
input wire resetn, // 同步低电平复位(注意:_n表示低电平有效)
input wire [3:1] r, // 请求信号:r1, r2, r3
output reg [3:1] g // 授权信号:g1, g2, g3
);

// 状态参数定义
parameter A = 2'd0; // 空闲状态,资源未分配
parameter B = 2'd1; // 资源分配给设备1
parameter C = 2'd2; // 资源分配给设备2
parameter D = 2'd3; // 资源分配给设备3

reg [1:0] current_state;
reg [1:0] next_state;

// 第一段:组合逻辑,状态转移
always @(*) begin
case (current_state)
A: begin
// 空闲状态:按优先级判断
if (r[1] == 1'b1) begin
next_state = B; // 设备1请求,优先级最高
end else if (r[2] == 1'b1) begin
next_state = C; // 设备2请求
end else if (r[3] == 1'b1) begin
next_state = D; // 设备3请求
end else begin
next_state = A; // 没人请求,保持空闲
end
end
B: begin
// 设备1在用:等它释放
next_state = r[1] ? B : A;
end
C: begin
// 设备2在用:等它释放
next_state = r[2] ? C : A;
end
D: begin
// 设备3在用:等它释放
next_state = r[3] ? D : A;
end
default: begin
next_state = A;
end
endcase
end

// 第二段:时序逻辑,状态更新(注意resetn是低电平有效)
always @(posedge clk) begin
if (~resetn) begin
current_state <= A;
end else begin
current_state <= next_state;
end
end

// 第三段:输出逻辑
assign g[1] = (current_state == B);
assign g[2] = (current_state == C);
assign g[3] = (current_state == D);

endmodule

设计要点

优先级判断很重要:在空闲状态A中,我们按优先级顺序判断:先看r1,再看r2,最后看r3。这样保证高优先级设备能先获得资源。

低电平复位:注意信号名resetn后缀_n表示低电平有效,所以复位条件是~resetn


Q2b: Another FSM - 复杂协议状态机

这道题比较复杂,让我们仔细分析一下要求:

题目理解

状态机有2个输入(xy),2个输出(fg),工作流程如下:

  1. 复位后:第一个周期输出f=1(只持续1个周期)
  2. 然后开始检测x序列:寻找连续的”1-0-1”序列
  3. 找到序列后g=1,此时看输入y:
    • 如果接下来2个周期内y有任意一个为1:g永远保持1
    • 如果接下来2个周期内y全是0:g永远保持0

这就像一个闯关游戏:

  • 第一关:复位后输出一个f=1的脉冲
  • 第二关:找到”1-0-1”的序列
  • 第三关:看y的表现,决定g是永远1还是永远0

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
module top_module (
input wire clk,
input wire resetn, // 同步低电平复位
input wire x,
input wire y,
output reg f,
output reg g
);

// 状态参数定义
parameter IDLE = 4'd0; // 复位后的初始状态
parameter FOUT = 4'd1; // 输出f=1的状态
parameter S1 = 4'd2; // 寻找序列:初始状态
parameter S2 = 4'd3; // 找到了第一个1
parameter S3 = 4'd4; // 找到了1-0
parameter S4 = 4'd5; // 找到了1-0-1!第一个周期看y
parameter S5 = 4'd6; // 第二个周期看y
parameter ALL_ONE = 4'd7; // g永远=1
parameter ALL_ZERO= 4'd8; // g永远=0

reg [3:0] current_state;
reg [3:0] next_state;

// 第一段:组合逻辑,状态转移
always @(*) begin
case (current_state)
IDLE: begin
next_state = FOUT; // 复位后先去FOUT状态
end
FOUT: begin
next_state = S1; // f只输出一个周期,然后去检测序列
end
S1: begin
next_state = x ? S2 : S1; // 找第一个1
end
S2: begin
next_state = x ? S2 : S3; // 找0
end
S3: begin
next_state = x ? S4 : S1; // 找第二个1(找到了去S4)
end
S4: begin
next_state = y ? ALL_ONE : S5; // 第一个周期看y
end
S5: begin
next_state = y ? ALL_ONE : ALL_ZERO; // 第二个周期看y
end
ALL_ONE: begin
next_state = ALL_ONE; // 永久状态:g=1
end
ALL_ZERO: begin
next_state = ALL_ZERO; // 永久状态:g=0
end
default: begin
next_state = IDLE;
end
endcase
end

// 第二段:时序逻辑,状态更新
always @(posedge clk) begin
if (~resetn) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end

// 第三段:输出逻辑
assign f = (current_state == FOUT);
assign g = (current_state == S4) | (current_state == S5) | (current_state == ALL_ONE);

endmodule

设计心得

这道题的关键是状态规划!在动手写代码前,先画好状态转移图,想好每个状态的功能,能大大减少错误。

3


入门者避坑指南

在做最后这组题目时,初学者最容易犯以下错误:

错误1:复位信号的电平搞反

错误表现:

1
2
3
4
5
6
// resetn是低电平有效,却用posedge和高电平判断
always @(posedge clk or posedge resetn) begin
if (resetn) begin
current_state <= A;
end
end

错误原因:

  • 信号名后缀_n通常表示低电平有效(negative)
  • 同步复位的话,只在时钟边沿判断,不用放敏感信号列表

正确做法:

1
2
3
4
5
6
7
8
// 同步低电平复位的正确写法
always @(posedge clk) begin
if (~resetn) begin
current_state <= A;
end else begin
current_state <= next_state;
end
end

错误2:仲裁器优先级搞反

错误表现:

1
2
3
4
5
6
// 优先级搞反了!先看r3再看r1
A: begin
if (r[3]) next_state = D;
else if (r[2]) next_state = C;
else if (r[1]) next_state = B; // 错了!r1应该优先级最高
end

错误原因:

  • 没有仔细看题目描述
  • 题目说:”设备1、2、3的优先级依次递减”

正确做法:

1
2
3
4
5
6
// 正确的优先级:r1 > r2 > r3
A: begin
if (r[1]) next_state = B; // 先看r1
else if (r[2]) next_state = C; // 再看r2
else if (r[3]) next_state = D; // 最后看r3
end

错误3:永久状态没有自环

错误表现:

1
2
3
ALL_ONE: begin
// 没有写next_state!
end

错误原因:

  • 永久状态应该永远保持在自己这个状态
  • 如果没有default分支,会生成锁存器

正确做法:

1
2
3
4
5
6
ALL_ONE: begin
next_state = ALL_ONE; // 自环
end
ALL_ZERO: begin
next_state = ALL_ZERO; // 自环
end

错误4:输出f的状态没有及时切换

错误表现:

1
2
// f输出多个周期
FOUT: next_state = FOUT; // 错了!f只应该输出1个周期

错误原因:

  • 题目明确说”在下一个周期内将f输出为1”
  • f只应该持续1个周期

正确做法:

1
FOUT: next_state = S1;  // 只待一个周期就走

错误5:g的输出漏掉S4和S5状态

错误表现:

1
2
// g只在ALL_ONE时输出,漏掉了S4和S5
assign g = (current_state == ALL_ONE);

错误原因:

  • 题目说:”下一周期将g输出为1,在保持g为1时判断y的输入”
  • 在S4和S5状态时,g也应该是1

正确做法:

1
2
// S4、S5、ALL_ONE时g都是1
assign g = (current_state == S4) | (current_state == S5) | (current_state == ALL_ONE);


小结

恭喜你!终于完成了HDLBits状态机部分的所有题目!

最后这组题目涵盖了状态机设计的几个典型场景:

  1. 基础序列检测:看图写状态机,三段式结构要熟练

  2. 独热编码逻辑推导:利用独热编码的特性,直接看对应位,逻辑更简单

  3. 资源仲裁器:优先级判断和状态保持,这在实际工程中很常用(比如总线仲裁、中断控制器)

  4. 复杂协议状态机:这道题比较综合,需要仔细规划每个状态,画状态转移图很重要!

作为一名通信IC设计师,笔者想说:状态机是数字电路设计中最重要的技能之一!从简单的接口协议(UART、SPI)到复杂的处理器控制器,都离不开状态机。

回顾整个状态机系列,我们学习了:

  • 摩尔型 vs 米利型状态机
  • 二进制编码 vs 独热编码
  • 同步复位 vs 异步复位
  • 三段式状态机结构
  • 状态+计数器的设计方法
  • 各种实际应用场景:协议解析、序列检测、仲裁器、补码计算…

多练习,多画状态转移图,慢慢你就能体会到状态机设计的乐趣了!


结语

HDLBits状态机这部分题目终于全部更新完了!笔者自己在做这些题目的过程中收获也非常大,很感谢HDLBits网站的作者。

状态机的设计没有绝对的对错,只有好不好。同样的功能,不同的人可能会画出不同的状态转移图。在画状态转移图时可以多留点时间,多想想有没有更简洁的方式,这是一个需要经验积累的过程。

如果代码哪里有误,欢迎大家指正!评论区也欢迎大家交流不同的解题思路。