HDLBits答案(20)_Verilog有限状态机(7)
Verilog有限状态机(7)
前言
今天我们继续学习状态机的题目,这组题目类型比较丰富,包括:
- 带计数器的状态机:统计一段时间内输入为1的次数
- 状态转移逻辑推导:给状态转移表,写逻辑表达式
- 独热编码状态机:利用独热编码的特性简化逻辑
通过这组题目,你能更全面地掌握状态机设计的各种技巧!
题库
Q3a: FSM - 带计数检测的状态机


题目理解
这个题目有点意思,让我们仔细分析一下规则:
- 状态切换:当
s为0时,保持在S0;当s为1时,进入S1状态 - 计数窗口:进入S1后,我们要检查连续3个周期内的
w值 - 判断条件:如果这3个周期中恰好有2个周期
w=1,则z=1,否则z=0 - 不重叠检测:3个周期为一组,不重叠
这就像老师检查学生的作业:连续3次作业中,如果刚好2次得优,就给奖励!
Solution:
1 | module top_module ( |
设计要点说明
为什么counter==2’d0时检查?
- 因为counter从0→1→2→0,刚好3个周期
- 当counter回到0时,我们已经统计完了上一组的3个周期
注意num_one的清零时机:
- 在counter==2’d0时,num_one重新开始计数
- 这样可以保证每组都是独立统计的
Q3b: FSM - 看图写状态机

题目理解
这是一个”看图说话”的题目,给了状态转移图,我们直接照着写就行。唯一需要注意的是:输出逻辑用的是next_state而不是state!
Solution:
1 | module top_module ( |
重要心得
题目中特意提醒了:当输出逻辑用到next_state时,记得要把reset条件加上!
为什么?因为:
reset只影响current_state,不直接影响next_state- 如果不加
reset条件,复位时输出可能不确定
Q3c: FSM logic - 状态转移表推导逻辑

题目理解
这道题给了状态转移表,让我们直接推导:
Y0:下一状态的第0位z:输出
这种题目不需要写完整的状态机,只需要根据表格写逻辑表达式就行。
Solution:
1 | module top_module ( |
Q6b: FSM next-state logic - 独热编码前的逻辑推导


题目理解
同样是给状态转移表,推导下一状态的Y2位。我们只需要:
- 找出所有下一状态
Y2=1的情况 - 把这些情况用逻辑表达式表示出来
Solution:
1 | module top_module ( |
Q6c: FSM one-hot next-state logic - 独热编码逻辑
题目理解
这道题使用独热编码,需要输出Y2和Y4。
独热编码的好处就在这里体现出来了!因为每个状态对应独立的一位,我们不需要复杂的比较,直接看对应的位就行!
Solution:
1 | module top_module ( |
对比一下
对比Q6b和Q6c:
- Q6b(二进制编码):需要比较
y等于多少 - Q6c(独热编码):直接看
y的某一位
这就是独热编码的优势!虽然用了更多寄存器,但组合逻辑更简单,速度更快。
Q6: FSM - 看图写状态机(另一版本)

题目理解
又是一道”看图说话”的题目,和Q3b类似,照着状态转移图写就行。
Solution:
1 | module top_module ( |
入门者避坑指南
在做这组题目时,初学者最容易犯以下错误:
错误1:计数器和统计逻辑的时序不对
错误表现:1
2
3
4
5
6
7
8
9// 统计num_one时,没有考虑counter的时机
always @(posedge clk) begin
if (reset) begin
num_one <= 1'd0;
end else if (current_state == S1) begin
num_one <= w ? (num_one + 1'd1) : num_one;
end
// 忘记了counter==0时要清零!
end
错误原因:
- 每组3个周期统计完后,num_one应该重新开始
- 如果不清零,下一组的统计会累加上一组的值
正确做法:1
2
3
4
5
6
7
8
9
10
11always @(posedge clk) begin
if (reset) begin
num_one <= 1'd0;
end else begin
if (counter == 2'd0) begin
num_one <= w ? 1'd1 : 1'd0; // 新的一组开始
end else if (current_state == S1) begin
num_one <= w ? (num_one + 1'd1) : num_one;
end
end
end
错误2:输出逻辑漏掉counter判断
错误表现:1
2// 只要num_one==2就输出,不管是不是在正确的时机
assign z = (current_state == S1) && (num_one == 2'd2);
错误原因:
- 应该在一组(3个周期)结束时才判断
- 也就是counter回到0的时候
正确做法:1
2// 加上counter==2'd0的判断
assign z = (current_state == S1) && (num_one == 2'd2) && (counter == 2'd0);
错误3:用next_state做输出时忘记reset
错误表现:1
2
3
4
5
6
7
8// 输出逻辑用next_state,但没有reset条件
always @(posedge clk) begin
case (next_state)
S3: z <= 1'b1;
S4: z <= 1'b1;
default: z <= 1'b0;
endcase
end
错误原因:
reset只影响current_state- 如果不加
reset条件,复位时输出可能不正确
正确做法:1
2
3
4
5
6
7
8
9
10
11always @(posedge clk) begin
if (reset) begin
z <= 1'b0; // 记得复位时清零
end else begin
case (next_state)
S3: z <= 1'b1;
S4: z <= 1'b1;
default: z <= 1'b0;
endcase
end
end
错误4:独热编码状态机用case语句
错误表现:1
2
3
4
5
6
7
8// 独热编码却用case(y)
always @(*) begin
case (y)
6'b000001: Y2 = 1;
6'b000010: Y2 = 0;
// ... 要写很多case!
endcase
end
错误原因:
- 独热编码的优势就是不需要用case
- 直接看对应的位就行
正确做法:1
2
3// 独热编码直接看位
assign Y2 = ~w & y[1];
assign Y4 = (w & y[2]) | (w & y[3]) | (w & y[5]) | (w & y[6]);
错误5:逻辑表达式中常数没有指定位宽
错误表现:1
assign Y0 = ((~y[2] & y[0]) | (y == 100)) & ~x; // 100是十进制!
错误原因:
- 没有指定位宽和进制,容易出错
y是3位二进制,应该写成3'b100
正确做法:1
assign Y0 = ((~y[2] & y[0]) | (y == 3'b100)) & ~x;
小结
今天我们学习了多种类型的状态机题目,主要收获有:
状态机+计数器:当需要统计一段时间内的数据时,可以用状态机控制计数器的启停和清零,用计数器记录周期数或特定事件的次数
输出逻辑的时机:要注意是用
state还是next_state,如果用next_state,记得加上reset条件状态转移表推导逻辑:给表格写表达式时,要仔细找出所有满足条件的情况,然后用逻辑或拼起来
独热编码的优势:独热编码虽然用了更多寄存器,但组合逻辑更简单,直接看对应的位就行,不需要复杂的比较
作为一名通信IC设计师,笔者想说:这组题目非常全面地涵盖了状态机设计的各种场景!从”看图说话”的基础题,到需要配合计数器的综合题,再到只写逻辑表达式的推导题。把这些题都吃透,你对状态机的理解就很扎实了!




