Hello World by 阿水

思久欲知 知繁渴思 朝乾夕惕 焚膏继晷 日拱一卒 功不唐捐

0%

三段式状态机

[TOC]

1:三段式状态机基本格式:

  1. 第一个always语句实现同步状态跳转;
  2. 第二个always语句采用组合逻辑判断状态转移条件,这里每一个状态只保持一个时钟周期,也就是直接跳转到下一个状态,在实际应用中,一般根据输入的条件来判断是否跳转到其它状态或者停留在当前转态;
  3. 第三个always语句描述状态输出(可以用组合电路输出,也可以时序电路输出。一般推荐使用时序电路输出,因为状态机的设计和其它设计一样,最好使用同步时序方式设计,以提高设计的稳定性,消除毛刺

2:代码部分

通过parameter来定义各个不同状态的参数。每一个状态的位宽为7位,接下来还需要定义两个7位的寄存器,一个用来表示当前状态,另一个用来表示下一个状态,如下所示:

定义部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
//独热码定义方式
parameter S0 = 7'b0000001;
parameter S1 = 7'b0000010;
parameter S2 = 7'b0000100;
parameter S3 = 7'b0001000;
parameter S4 = 7'b0010000;
parameter S5 = 7'b0100000;
parameter S6 = 7'b1000000;

//reg define
reg [6:0] curr_st ; //当前状态
reg [6:0] next_st ; //下一状态

实现代码1:

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
//第一段采用同步时序描述状态转移
另有写法:
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
curr_st <= S0;
else
curr_st <= next_st;
end

//第二段采用组合逻辑判断状态转移条件
常用写法:
always @(*) begin
case (curr_st)
S0: next_st = S1;
S1: next_st = S2;
S2: next_st = S3;
S3: next_st = S4;
S4: next_st = S5;
S5: next_st = S6;
S6: next_st = S0;
default: next_st = S0;
endcase
end

//第三段描述状态输出(这里采用时序电路输出)
对应常用写法:
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_divide_7 <= 1'b0;
else if ((curr_st == S0) | (curr_st == S1) | (curr_st == S2) | (curr_st == S3))
clk_divide_7 <= 1'b0;
else if ((curr_st == S4) | (curr_st == S5) | (curr_st == S6))
clk_divide_7 <= 1'b1;
end


实现代码2:

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
//第一段采用同步时序描述状态转移
另有写法:
always @(posedge sys_clk or negedge sys_rst_n)
if (!sys_rst_n)
curr_st <= (1‘b1 << S0);
else
curr_st <= next_st;

//第二段采用组合逻辑判断状态转移条件

//next_st = 1'b1 << S6: 这行代码是一个赋值语句.
//将1'b1左移S6位后的结果赋值给变量next_st。
//1'b1是1位二进制数,<<是左移运算符,将1'b1左移S6位相当于将1左移S6位。
//例如,如果S6的值是3,则左移3位后的结果为0b1000,即8。
//所以,这行代码的含义是将1左移S6位,得到一个二进制数,然后将这个数赋值给next_st。
always @(*) begin
next_st = curr_st;
case (1'b1)
curr_st[S0]:
next_st = 1'b1 << S1;
curr_st[S1]:
next_st = 1'b1 << S2;
curr_st[S2]:
next_st = 1'b1 << S3;
curr_st[S3]:
next_st = 1'b1 << S4;
curr_st[S4]:
next_st = 1'b1 << S5;
curr_st[S5]:
next_st = 1'b1 << S6;
curr_st[S6]:
next_st = 1'b1 << S0;
default:
next_st = 1'b1 << S0;
endcase
end

//第三段描述状态输出(这里采用时序电路输出)
对应常用写法:
always @(posedge sys_clk or negedge sys_rst_n)
if (!sys_rst_n)
clk_divide_7 <= 1'b0;
else if (curr_st[S0] | curr_st[S1]) | curr_st[S2] | curr_st[S3])
clk_divide_7 <= 1'b0;
else if (curr_st[S4] | curr_st[S5]) | curr_st[S6])
clk_divide_7 <= 1'b1;

Verilog 中的 always @ (*) begin 是一种用于编写时序逻辑的语句。它表示无论在什么时候,如果任意输入变量发生变化,就立即执行 begin 和 end 之间的语句。这种类型的 always语句通常用于实现输入变量与输出变量之间的映射关系。