&&是逻辑与:即判断&&两侧的表达式是否都为真,都为真则此&&表达式值为真;逻辑与结果只讲真和假

& 是按位与:即将&两侧的数用二进制展开,每一位都求与运算(二进制与运算,跟逻辑与差不多),最后得到的二进制数即为结果;按位与得出的却是实实在在的一个数

Verilog FSM 设计流程

设计流程

用 Verilog 设计有限状态机的一般步骤:

  1. 逻辑抽象,得出状态转换图。
  2. 状态化简(该步骤可以省略),如果在状态转换图中出现两个一样的状态则需要将其合并为一个状态,以得到最简的状态转换图。
  3. 状态分配,也就是状态编码。通常有很多种编码方式,比如 Gray 编码、独热编码等。在实际电路中,需综合考虑电路复杂度与电路性能之间的折中。这里的设计没有用到特别复杂的电路逻辑,所以大家可以自行决定用哪种编码方式。(不过,对于需要在FPGA上运行的电路,推荐使用独热编码方式。因为 FPGA 有丰富的寄存器资源,门逻辑相对缺乏,采用独热编码可以有效提高电路的速度和可靠性,也有利于提高器件资源的利用率。)
  4. 根据状态转移图得出次态逻辑和输出逻辑。
  5. 按照相应逻辑,用 Verilog HDL 来描述有限状态机状态转移的情况。
  • 复位时回到起始状态(敏感信号为时钟复位信号,注意同步复位和异步复位的区别)
  • 用 case 或 if-else 语句描述出状态的转移(根据现态(和输入)产生次态,可以与复位时回到起始状态的语句放在同一个 always 块中,即敏感信号为时钟和复位信号)
  • 输出信号描述。用 case 语句或 if-else 语句描述状态机的输出信号

状态机编码风格

状态机的编码风格有一段式(也称高速状态机)、两段式、三段式等。

一段式状态机

一段式状态机将整个状态机编写在一个 always 模块里,该模块采用同步时序逻辑,全部使用非阻塞赋值。该模块既描述状态转移,又描述状态的输入和输出。

采用一段式状态机在思路上比较容易,书写的用时少,且运行速度快,在上机的时候可以节省编码的用时。但缺点是维护代码和调试比较困难。采用两段式和三段式状态机可以避免以上问题。

两段式状态机

两段式状态机使用两个 always 模块

  • 第一个 always 模块采用同步时序逻辑描述状态转移
  • 第二个 always 模块使用组合逻辑判断状态转移的条件,描述状态转移的规律。

和一段式状态机相比,采用两段式状态机实现了同步时序列逻辑和组合逻辑的分离,便于阅读、理解和维护,有利于综合工具优化代码,方便用户添加时序约束条件。但缺点是编码环节多,代码量稍大,较为繁琐,因为要将其拆分成两个不同的模块。

示例代码

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
module fsm_1010 (
input clk,
input in,
output reg out
);

reg [2:0] state = 0;
reg [2:0] next_state;

// 描述状态转移的时序逻辑
always @(posedge clk) begin
state <= next_state; // 这里还可以视情况添加复位功能
end

// 判断状态转移条件以及产生输出组合逻辑
always @(state, in) begin
case (state)
0:
begin
next_state = in == 1 ? 1 : 0;
out = 0;
end
1:
begin
next_state = in == 1 ? 1 : 2;
out = 0;
end
2:
begin
next_state = in == 1 ? 3 : 0;
out = 0;
end
3:
begin
next_state = in == 1 ? 1 : 4;
out = 0;
end
4:
begin
next_state = in == 1 ? 3 : 0;
out = 1;
end
default:
begin
next_state = 3;
out = 0;
end
endcase
end

end module

三段式状态机

三段式状态机使用三个 always 模块:

  • 第一个 always 模块采用同步时序逻辑描述状态转移
  • 第二个 always 模块采用组合逻辑判断状态转移条件,描述状态转移规律;
  • 第三个 always 模块采用组合逻辑或者时序逻辑述每个状态的输出

和两段式状态机相比,这样可以减少毛刺的产生。其缺点和两段式状态机相同,编码复杂,消耗的资源也比较多,因为需要初态和次态状态寄存器等。

这是因为三段式状态机将输出分离成独立的组合逻辑,减小了关键路径和时延,所以可以减少毛刺现象。

示例代码

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
module fsm_1010 (
input clk,
input in,
output reg out
);

reg [2:0] state = 0;
reg [2:0] next_state;

// 描述状态转移的时序逻辑
always @(posedge clk) begin
state <= next_state;
end

// 判断状态转移条件的组合逻辑
always @(state, in) begin
case (state)
0:
begin
next_state = in == 1 ? 1 : 0;
end
1:
begin
next_state = in == 1 ? 1 : 2;
end
2:
begin
next_state = in == 1 ? 3 : 0;
end
3:
begin
next_state = in == 1 ? 1 : 4;
end
4:
begin
next_state = in == 1 ? 3 : 0;
end
default:
begin
next_state = 3;
end
endcase
end

// 产生输出的组合逻辑
always @(state) begin
out = (state == 4) ? 1 : 0;
end

endmodule

代码样例

  • 流水线寄存器(P5)

    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
    module pipeline_sample(
    input clk,
    input reset,
    input stall,
    input flush,
    input [31:0] aluResultAtExe,
    // 可能还有其他来自前一级的输入
    output [31:0] aluResultAtMemory
    // 可能还有要输出到后一级的
    // 输入输出应配对
    );
    // 根据端口定义,可能还要开其他的临时寄存器
    reg [31:0] aluResult;
    // 根据临时寄存器的值,可能还要连其他的线
    assign aluResultAtMemory = aluResult;

    always @(posedge clk)
    // 可能还要处理其他的临时寄存器
    begin
    // 复位 或者 清除流水线寄存器(阻塞,异常中断处理会用到)
    if(reset | flush)
    begin
    aluResult <= 32'd0;
    end
    else begin
    // 判定是否应阻塞该级,不阻塞时才更新为前一级的值
    if(!stall)
    begin
    aluResult <= aluResultAtExe;
    end
    end
    end

    endmodule

P1做题记录

本次的三道题目主要考插入了组合逻辑中的乘法、moore状态机和状态机。

分享一下自己的做法,欢迎大家批评指正。

向量乘法

本题的要求是将ver_aver_b两个32位向量数进行逐位相乘,再把每一位的结果加起来作为输出。

可以把这道题分为两个部分:

  1. 逐位相乘
  2. 相加

首先,对于逐位相乘。1位二进制数的乘法和与操作等价,故可以把ver_aver_b进行与操作,这样便捷地实现了逐位相乘的效果。

其次,对于诸位相加,我采用的方法是使用**for循环实现**,在verilogfor循环只能在always块(或initial块)中使用。而本体是一道组合逻辑的题目,故采用always @(*),实现组合逻辑。

由于位数不是特别多,本体的另一种方法是Ctrl cv大法,万一以后有更大规模的数据,全面准备吧()

代码框架为:

1
2
3
4
assign ver=ver_a&ver_b;
//i也可以是integer型变量
for(i=0;i<32;i++) begin
sum=sum+ver[i];

筛子涂色问题

这道题是一道moore型状态机问题,根据当前状态的输入(涂色),进行状态转移(涂色后目前的颜色是什么)。但是需要注意的是,尽管是moore型状态机,但是错误信息需要在当前周期就进行输出。

我的思路是这样的,一共四个状态:初始状态S0、红S1、蓝S2、绿S3。再使用一个寄存器保存了当前颜色出现了多少次。

如果红和绿相遇了,或者当前的颜色出现到第三次,按照题意,清除上一次的输入,故只需要将输出置1,并不需要进行状态转移

展示一个状态的伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
//...
if(status==`Red) begin
if(in==`red) begin
if(count==2) Is_Error;
else Is_good;
end
else if(in==`green) Is_error;
else begin
count=1;
//balabala
end
end

异步复位问题

另外这一题值得一说的就是异步复位问题,在之前的课下中,我们所见过的复位都是高电平有效,而这道题目的异步复位信号是低电平有效。

先来看下这段代码:

1
2
3
4
5
6
7
8
9
always @(rst_n) begin
if(rst_n==0) begin
//reset
end
end

always @(posedge clk) begin
//balabala...
end

这段代码是不能实现异步复位操作的,为何:

  1. always模块是响应信号变化,如果rst_n一直是低电平,则只有最初的由高电平转为低电平的那个下降沿会被always响应。如果后续一直是低电平,则无法响应
    • 如果想要实现操作,则应该在下面一个always块中加上if(rst_n==0),但是这样此拆分成两个always块就显得有些多次一举了
  2. 这样写还有一个规范问题,那就是reset会无可避免地修改状态量,在两个always模块中都对变量进行赋值,这是不符合规范的,应该避免

正确的异步复位操作可以是这样的:

1
2
3
4
5
6
always @(posedge clk or negedge rst_n) begin
if(rst_n==0)
//reset
else
//balabala...
end

如果rst_n一直是低电平,则会在时钟沿被识别进行同步复位。

如果rst_n是由高电平变为低电平,则必然有一个下降沿过程,可以用negedge进行识别信号

数字上升下降匹配

这道题有两种做法,一种是设计具有6个状态的状态机,在读入时进行状态转移和匹配;另一种的做法更为普遍,是识别数据的上升下降,如果匹配了上升下降+数量匹配,则输出。

状态机

输入只有1-5,故可以设计一个6状态状态机,记录当前序列到哪里了。

如果连续输入的数是奇数个(分析见下),则顺利转移到下一个状态,否则报错。

这里状态机的书写并不难,就略去状态转移图和代码了。

上升下降匹配

这是一种更为普遍的做法。本体的数据输入只有1-5,还可以用状态机解决,但是如果大胆一点,把输入扩展到1-100,手写101中状态的状态机显然是不太显示,这时候这种方法依然能解决,具有较强的适应性。

分析题目:

  1. 对于数据,上升下降必须是相邻的,n的下一个输出只能是n-1或n+1,并且上升下降性质必须不变
  2. 同一个数字在上升下降中分别出现奇数次
    1. 这道题需要解决的就是12233221并不匹配,因为两个2完成了“贪婪匹配”,3并不包在序列中,像一个俄罗斯方块一样,只要同一个数字相邻出现偶数次就相消了:12223321123321相比,如果是正确的数字,重复的数字必须在相邻处完成匹配。
    2. 实际上这个问题相当好解决,只需要检查一侧的正确性,如果一个数,重复出现了奇数次,相当于只出现了一次;重复出现了偶数次,则相当于没出现。
    3. 用一个寄存器存下当前数字相邻出现了几次,在下一个更大数/更小数来临时,则可以判断是否是正确的序列

给出部分伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
if(IsDown) begin
if(number==num_now+1 && cnt%2==1) begin
cnt<=1;
end
else if(number==num_now) begin
cnt<=cnt+1;
end
else if(number==num_now-1) begin
IsDown=1;
cnt<=cnt+1;
end
num_now<=number;
end