第九篇 有限状态机

实验九 有限状态机

9.1 实验目的

  1. 学习有限状态机的组成与类型;

  2. 掌握有限状态机的设计方式;

  3. 学习有限状态机的编码方式;

  4. 掌握使用有限状态机进行设计的方法。

9.2 原理介绍

9.2.1 有限状态机的基本概念

有限状态机(Finite State Machine,FSM)通常又称为状态机,是时序逻辑电路设计中经常采用的一种方式,也是数字系统设计的重要组成部分,尤其适用于设计数字系统的控制模块。在一些需要控制高速器件的场合,用状态机进行设计是解决问题的一种很好的方案,具有速度快、结构简单、可靠性高等优点。同时有限状态机也是时序电路的通用模型,任何时序电路都可以表示为有限状态机。

状态机的组成

有限状态机一般包括寄存器逻辑和组合逻辑两部分,寄存器用于存储状态,组合逻辑用于状态译码和产生输出信号,其电路的结构如图9.1所示。寄存器(存储电路)部分接收组合逻辑电路的内部输出信号,由时钟信号控制,存储的现态在时钟作用下变为次态。组合逻辑电路接收的是输入信号和存储电路的现态,在现态和输入信号的共同作用下产生次态和输出信号。

image-20211109144847300

图9.1 有限状态机结构框图

状态机的分类

根据输出信号产生方式的不同,有限状态机可以分为米利型(Mealy)和摩尔型(Moore)两类。Mealy型状态机的输出不仅取决于电路当前状态,还取决于电路的输入信号,其输出是在输入变化后立即变化的,不依赖时钟信号的同步,其结构图如图9.2所示;Moore型状态机的输出仅依赖当前状态,而与输入信号无关,但输出发生变化时还需等待时钟的到来,必须等待状态发生变化时才能导致输出变化,因此比Mealy型要多等待一个时钟周期,其结构图如图9.3所示。

image-20211109150735071

图9.2 Mealy型状态机结构图

image-20211109151127376

图9.3 Moore型状态机结构图

状态机的状态图表示法

一般来说,状态机有三种表示方法:状态图、状态表和算法状态机图。实际上,这三种表示方法是等价的,相互之间可以进行任意转换。在用Verilog HDL描述状态机时,通常会用到状态图,下面介绍状态图的表示方法。

状态图是以信号流图方式表示出电路的状态转换过程。在状态图中,每个状态用一个圆圈(或者椭圆圈)表示,圆圈内有指示状态的符号。用带箭头的方向线指示状态转换的方向,当方向的起点和终点都在同一个圆圈上,则表示状态不变。标在方向线旁斜线左、右两侧的二进制数分别表示引起状态转移的输入信号以及当前输出信号。

图9.4是Mealy状态图的一个例子,其中A、B、C、D表示电路四个不同的状态,方向线旁边的X/Y表示引起状态转移的输入信号以及当前输出信号。一般来说,状态机中的状态转移有两种方式:无条件转移和有条件转移。在图9.4中,从状态A转移到状态B为无条件转移,其他状态之间的转移都是有条件要求的,例如,如果状态机的当前状态(现态)为B,当输入X=1时,状态机将从状态B转移到状态A;当X=0时,状态机将从状态B转移到状态C。引起状态发生改变的输入条件通常标在方向线的旁边,电路的输出结果也写在方向线的旁边,用斜线对输入和输出进行分隔,输入放在斜线左边,输出放在斜线右边。

需要强调的是,在Mealy状态图中,输出信号的表示方法容易引起读者的误解。当状态机处于所在的状态,并且在所示输入的作用下,就会产生输出,并非在状态机转移到下一状态时才会出现输出。例如,图9.4中,当状态机处于状态C时,输出Y只依赖于当前状态C和输入X,若X=1,则Y=0;若X=0,则Y=1。可见,输出信号Y是在状态转移之前产生的,与次态无关。

image-20211112153334489

图9.4 Mealy状态图

由于Moore状态机的输出只依赖于状态机的当前状态,它的状态图与Mealy状态图略有不同,通常将输出写在圆圈的内部,即将输出和状态标注在一起,二者以斜线隔开,所以Moore状态图的圆圈内的内容为“状态/输出”。图9.5是Moore状态图的一个例子。若当前状态为C,那么当输入X=0时,下一状态转换为状态D;当输入X=1时,下一状态转换为状态B。

image-20211109160247438

图9.5 Moore状态图

状态机的设计步骤

一般来说,状态机的设计步骤如下所示:

  1. 根据具体的设计原则,确定采用Moore状态机还是Mealy状态机。

  2. 分析设计要求,列出状态机的所有状态,并对每一个状态进行状态编码。

  3. 根据状态转移关系和输出函数,画出状态图。

  4. 根据所画的状态图,采用硬件描述语言对状态机进行描述。

在上面的设计步骤中,第3步是最困难也是最有创造性的一步。对同一个设计问题来说,不同的人可能会构造出不同的状态图。状态图直观地反映了状态机各个状态之间的转换关系以及转换条件,因而有利于理解状态机的工作机理,但此时要求设计的状态个数不能太多。对于状态个数较多的状态机,一般采用状态表的方法列出状态机的转移条件。如果输出信号较多,可以采用输出逻辑真值表进行表示。

9.2.2 有限状态机的Verilog HDL描述

下面通过一个例子来介绍状态机的设计过程。

【例子】

设计一个序列检测器电路,当检测到输入信号s出现二进制序列1010(自左至右的顺序)时,电路输出z=1,否则z=0。注意要考虑序列重叠的问题,如101010相当于出现两个1010序列。

【设计过程】

  1. 根据9.1小节介绍的状态机的设计步骤,首先要确定采用Mealy状态机还是Moore状态机。因为该电路在连续收到1010序列时,输出为1,否则输出0,即输出与输入信号有关,所以我们采用Mealy状态机。

  2. 第二步是列出状态机的所有状态,并进行状态编码。我们来分析电路的状态,根据设计要求,该电路必须能记忆收到的输入数据1、连续收到前两个数据10、连续收到前三个数据101、连续收到1010后的状态,可见该电路至少应该有四个状态,我们分别用S_1、S_2、S_3、S_4表示,若电路的初始状态用S_0表示,那么该电路有五个状态。

  3. 然后我们分析电路的状态转移关系,进而画出状态图。我们从第一个状态S_0开始分析,即从初始状态开始跳转。在分析时,我们也考虑到了序列重叠的可能性,例如101010相当于出现了两次1010序列。

    ① 开始时,电路处于S_0状态,此时会有两种情况,一种情况是收到第一个数据s=0时,电路仍旧处于S_0状态;另一种情况是当收到第一个有效数据s=1时,电路输出z=0,并且进入S_1状态,如图9.6所示。

    image-20211117140813119

    图9.6 序列检测器状态跳转1

    ② 然后我们分析S_1状态的跳转情况,在S_1状态下同样有两种情况,一种情况是电路收到第二个有效数据s=0,即电路连续收到序列10,则电路进入S_2状态,输出z=0;另一种情况是电路收到第二个数据s=1时,即电路连续收到序列11,考虑序列重叠的情况,电路仍旧处于S_1状态,如图9.7所示。

    image-20211117140910031

    图9.7 序列检测器状态跳转2

    ③ 接着我们分析S_2状态的跳转情况,在S_2状态下同样有两种情况,如果第三个数据s=0,即电路连续接收到序列100,那么电路的输出z=0,并且电路应该返回到初始状态S_0,重新开始检测;而如果第三个数据s=1,即电路连续接收到序列101,则电路转换到S_3状态,z=0,如图9.8所示。

    image-20211117140940273

    图9.8 序列检测器状态跳转3

    ④ 下面我们该分析S_3状态的跳转情况了,在S_3状态下同样有两种情况,若s=0,那么电路已经连续收到四个有效数据1010,电路应输出z=1,此时若时钟信号的有效沿到来,则电路应转向S_4状态;而若s=1,即电路连续接收到序列1011,考虑序列重叠的情况,相当于电路连续接收到序列1,所以电路输出z=0,并且电路应该转入S_1状态,如图9.9所示。

    image-20211117141008725

    图9.9 序列检测器状态跳转4

    ⑤ 最后我们看S_4状态的跳转,若s=0,电路连续收到序列10100,相当于电路检测完成一次序列1010后,又检测到0信号输入,即未检测到序列所需的有效数据,因此返回初始状态S_0,重新检测,并且z=0;而当s=1时,电路连续收到序列10101,考虑序列重叠的情况,相当于电路连续接收到序列101,所以电路应进入S_3状态,并且z=0,此时的状态跳转如图9.10所示。

    image-20211117141040989

    图9.10 序列检测器状态跳转5

    ⑥ 至此,我们已经完成了序列检测器的状态跳转分析,图9.10为序列检测器的状态图。通过该图可以看出,当状态机处于S_2、S_4时,如果输入s=0,电路会转移到相同的次态S_0,并且输出z都为0;如果s=1,电路会转移到相同的次态S_3,输出z都为0。所以S_2、S_4为等价的状态,可以用S_2状态代替S_4,于是我们可以得到序列检测器的简化状态图,如图9.11所示。

    实际上,可以直接用图9.10进行电路设计,不过此时会多用一个触发器。如果用内部触发器资源较多的FPGA器件实现该状态机,未简化的状态图是完全可以的。

    image-20211109170636532

    图9.11 序列检测器简化状态图

  4. 状态图完成后,就可以使用硬件描述语言对状态图进行描述了。

    利用Verilog HDL语言描述状态图主要包含四部分内容:

    ① 利用参数定义语句 parameter 描述状态机中各个状态的名称,并指定状态编码。例如,对序列检测器的状态分配可以使用最简单的自然二进制码,其描述如下:

    parameter S0 = 2'b00, S1 = 2'b01, S2 = 2'b10, S3 = 2'b11;

    ② 用 always 块描述状态触发器,实现状态存储;

    ③ 使用 case 语句(也可以使用 if-else 语句)描述状态转移逻辑;

    ④ 描述状态机的输出逻辑。

常见的描述状态图的方法有三种:一段式状态机、两段式状态机、三段式状态机。接下来我们将分别使用这三种状态机来实现刚刚介绍的序列检测器。

一段式状态机

将整个状态机写到一个always模块内部,在该模块中既描述时钟控制的状态转移,又描述状态机的下一状态和输出,这种写法被称为一段式状态机。这种写法仅仅适用于非常简单的状态机设计,不符合将时序逻辑和组合逻辑分开描述的代码风格,而且在描述当前状态时还要考虑下一个状态的逻辑,整个代码的结构不清晰,不利于修改和维护,不利于时序约束条件的加入,也不利于综合器对设计的优化,所以我们不推荐这种写法。

上述检测1010序列的一段式状态机代码如下:

module detector_1(
    input       clk,
    input       rst_n,
    input       s,
    output  reg z
);
​
// 状态声明及编码
parameter   S0  =   2'b00,
            S1  =   2'b01,
            S2  =   2'b10,
            S3  =   2'b11;
​
// 变量声明
reg [1:0]   state;
​
always@(posedge clk, negedge rst_n)
begin
    if(~rst_n)
    begin
        z       <=  1'b0;   // 异步复位,输出为0
        state   <=  S0;     // 异步复位,状态跳变为S0
    end
    else
        case(state)         // 状态跳转及输出
            S0:
            begin
                z       =   1'b0;
                state   =   (s == 1) ? S1 : S0;
            end
            S1:
            begin
                z       =   1'b0;
                state   =   (s == 0) ? S2 : S1;
            end
            S2:
            begin
                z       =   1'b0;
                state   =   (s == 1) ? S3 : S0;
            end
            S3:
            begin
                if(s == 1'b0)
                begin
                    z   =   1'b1;
                    state   =   S2;
                end
                else
                begin
                    z   =   1'b0;
                    state   =   S1;
                end     
            end
        endcase
end
​
endmodule

代码9.1 序列检测器一段式状态机

我们写的状态机综合器是可以识别出来的,点击Quartus Prime软件菜单栏的Tools --> Netlist Viewers --> State Machine Viewer,就会弹出如图9.12所示的状态图。另外,当我们打开RTL Viewer的时候看到的如图9.13所示的黄色块就是综合器自动识别出的状态机,双击进去也可以查看状态图,打开后显示的状态图与图9.12所示的状态图是一致的。

image-20210705102925520

图9.12 代码9.1的State Machine Viewer

image-20210705102822833

图9.13 代码9.1的RTL Viewer

我们再来看这段代码(代码9.1),严格地说,对序列检测器电路用单个always块进行描述存在着一个隐含的错误,即输出信号z的描述存在错误。本来信号z是由状态机的当前状态和输入信号共同决定的(该序列检测器是Mealy状态机),它是一个组合逻辑(具体可见图9.2),如果状态机的当前状态不变,而输入信号变了,信号z应该立即变化,但是按照上面的描述(代码9.1),信号z只有等到时钟上升沿到来时才会变化。在实际应用中,为了消除组合逻辑输出信号中的毛刺,在时序允许的情况下,通常允许Mealy状态机中输出信号通过寄存器输出。

总之,一段式状态机的写法仅仅适用于Moore状态机,它的电路结构可以用图9.14所示框图进行概述。

image-20211109180717503

图9.14 单个always块描述的FSM的结构图

两段式状态机

所谓两段式状态机就是采用两个always模块来实现状态机的功能。其中一个always模块采用同步时序逻辑描述状态转移,而另一个always模块采用组合逻辑来判断状态转移条件,描述状态转移规律和电路的输出。两段式状态机是值得推荐的写法之一,它的电路结构可以用图9.15所示的框图进行概括,每个方框用一个always块描述。

image-20211110091208043

图9.15 两个always块描述的FSM的结构图

上述检测1010序列的两段式状态机代码如下:

module detector_2(
    input       clk,
    input       rst_n,
    input       s,
    output  reg z
);

// 状态声明及编码
parameter   S0  =   2'b00,
            S1  =   2'b01,
            S2  =   2'b10,
            S3  =   2'b11;

// 变量声明
reg [1:0]   cs, ns;

// 时序逻辑,描述状态转移
always@(posedge clk, negedge rst_n)
begin
    if(~rst_n)
        cs  <=  S0;
    else
        cs  <=  ns;
end

// 组合逻辑,描述下一状态和输出
always@(cs, s)
begin
    ns  =   2'bxx;
    z   =   1'b0;
    case(cs)
        S0:
        begin
            z   =   1'b0;
            ns  =   (s == 1) ? S1 : S0;
        end
        S1:
        begin
            z   =   1'b0;
            ns  =   (s == 0) ? S2 : S1;
        end
        S2:
        begin
            z   =   1'b0;
            ns  =   (s == 1) ? S3 : S0;
        end
        S3:
        begin
            if(s == 1'b0)
            begin
                z   =   1'b1;
                ns  =   S2;
            end
            else
            begin
                z   =   1'b0;
                ns  =   S1;
            end
        end
    endcase
end

endmodule

代码9.2 序列检测器两段式状态机

上述写法通过两个并行执行的always结构描述电路的功能,通过公共变量进行相互通信。第一个时序型always块采用非阻塞赋值,使用边沿触发事件描述了状态机的触发器部分;第二个组合逻辑性always块采用阻塞赋值,使用电平敏感事件描述了状态机下一个状态逻辑和输出逻辑部分。

第一个always块说明了异步复位到初始状态S_0和同步时钟完成的操作,语句 cs <= ns 仅在时钟的上升沿被执行,这意味着第二个always块内部 ns 的值变化会在时钟上升沿到来时被传送给 cs。

第二个always块把现态 cs 和输入数据 s 作为敏感变量,只要其中的任何一个变量发生变化,就会执行顺序语句块内部的case语句,跟在case语句后面的各分支项说明了图9.11中状态的转换以及输出信号。

使用Quartus Prime综合后,代码9.2的状态机如图9.16所示,该状态图与代码9.1的状态图(图9.12)一致。另外,我们也可以查看其RTL Viewer,如图9.17所示,与代码9.1的RTL Viewer(图9.13)相比,其输出z未通过寄存器输出,可能会有毛刺出现。

image-20210705103741540

图9.16 代码9.2的State Machine Viewer

image-20210705103656841

图9.17 代码9.2的RTL Viewer

另外一个值得注意的是,在第二个always块敏感列表下面一行应该写出下一状态 ns 的默认赋值,然后根据当前的状态和当前的输入由后面的case或if-else语句确定正确的转移。

...
begin
    ns = 2'bxx;
    z = 1'b0;
    case(cs)
    ...
end

代码9.3 两段式状态机中第二个always块中ns的默认值

对下一状态ns的默认赋值有三种方式:全部设置成不定状态(x)、设置成预先规定的初始状态、设置成FSM中的某一有效状态,推荐将敏感列表后面的默认状态设置成不定状态(x),它的优点有两点,一是在仿真时可以很好地考察所涉及的FSM的完备性,若设计的FSM不完备,则会进入任意状态,仿真时容易发现;二是综合器对代码进行逻辑综合时,会忽略没有定义的状态触发器向量。

三段式状态机

使用三个always块描述状态机的功能,这种写法被称为三段式状态机,它的结构图如图9.18所示。第一个always模块采用同步时序逻辑方式描述状态转移(中间方框),第二个always模块采用组合逻辑方式描述状态转移规律(第一个方框),第三个always模块描述电路的输出信号(第三个方框),并且在时序允许的情况下,通常让输出信号经过一个寄存器再输出,保证输出信号没有毛刺。

image-20211110094413124

图9.18 三个always块描述的FSM的结构图

上述检测1010序列的三段式状态机代码如下:

module detector_3(
    input       clk,
    input       rst_n,
    input       s,
    output  reg z
);

// 状态声明及编码
parameter   S0  =   2'b00,
            S1  =   2'b01,
            S2  =   2'b10,
            S3  =   2'b11;

// 变量声明
reg [1:0]   cs, ns;

// 时序逻辑,描述状态转移
always@(posedge clk, negedge rst_n)
begin
    if(~rst_n)
        cs  <=  S0;
    else
        cs  <=  ns;
end

// 组合逻辑,描述下一状态
always@(cs, s)
begin
    ns  =   2'bxx;
    case(cs)
        S0:
            ns  =   (s == 1) ? S1 : S0;
        S1:
            ns  =   (s == 0) ? S2 : S1;
        S2:
            ns  =   (s == 1) ? S3 : S0;
        S3:
            ns  =   (s == 0) ? S2 : S1;
    endcase
end

// 输出逻辑,让输出信号经过一个寄存器再送出,可以消除z信号中的毛刺
always@(posedge clk, negedge rst_n)
begin
    if(~rst_n)
        z   =   1'b0;
    else
    begin
        z   =   1'b0;
        case(cs)
            S0:
                z   =   1'b0;
            S1:
                z   =   1'b0;
            S2:
                z   =   1'b0;
            S3:
                z   =   (s == 1'b0) ? 1'b1 : 1'b0;
        endcase
    end
end

endmodule

代码9.4 序列检测器三段式状态机

使用Quartus Prime综合后,代码9.4的状态机如图9.19所示,该状态图与代码9.1、9.2的状态图(图9.12、图9.16)一致。另外,我们也可以查看其RTL Viewer,如图9.20所示,可以看出其输出z通过寄存器输出,可以克服输出逻辑出现毛刺的问题,这在一些将输出信号作为控制逻辑的场合使用,可有效避免产生错误控制动作的可能性。

image-20210705104211520

图9.19 代码9.4的State Machine Viewer

image-20210705104128002

图9.20 代码9.4的RTL Viewer

9.2.3 有限状态机的状态编码

在状态机设计中,有一个重要的问题就是状态的编码,通常用 parameter 指定状态编码。状态机编码方式很多,由此产生的电路也不相同,常用的编码方式有三种:二进制编码、格雷码和独热(One-Hot)编码。表9.1给出了对六个状态进行编码的三种不同编码方式。

表9.1 三种编码方式的对比

状态二进制编码格雷编码独热码
state0000000000001
state1001001000010
state2010011000100
state3011010001000
state4100110010000
state5101111100000

二进制编码采用顺序的二进制数编码的每个状态。一个有 N 种状态的状态机至少需要 log_2{N} 个触发器来存储状态编码。例如,有8种状态的机器将至少需要3个触发器,这种方式使用的触发器最少。二进制编码的缺点是从一个状态转换到相邻状态时,有可能有多个比特位同时发生变化,如表9.1中,从state3转换到state4(从011变为100)时,三个比特位都需要同时发生变化,瞬变次数多,容易产生毛刺,引发逻辑错误。

格雷码可用相同的位数来实现编码,其特点是两个相邻码值之前仅有1位不同,用它来编码状态可减少瞬变的次数,也减少了产生毛刺和一些暂态的可能。

独热(One-Hot)编码使用n位状态触发器表示具有n个状态的状态机,每个状态与一个独立的触发器相对应,并且在任何时刻其中只有一个触发器有效(其值为1)。独热编码是一种流行的编码方式,虽然这种编码方案会使用较多的触发器,但这使得独热码状态机中的译码逻辑使用较少的门,因为它只是对寄存器中的一位进行译码,而不是一个矢量,所以独热码状态机可以有更快的速度,并且由于增加触发器而占用的面积可用简单的译码电路省下来的面积抵消。另外,修改独热码设计也非常容易,因为增加或去除一个状态不会影响到其余状态的编码和电路的性能。因此,推荐使用独热编码方式。

在设计过程中,可通过综合器指定编码方式,如在Quartus Prime软件中,选择菜单Assignments --> Settings,在Settings页面的Category栏中选Compiler Settings选项,单击Advanced Settings(Synthesis)...按钮,在出现的对话框的State Machine Processing栏中选择需要的编码方式,可选的编码方式有Auto、Gray、Johnson、Minimal Bits、One-Hot、Sequential、User-Encoded等几种,如图9.21所示,可以根据需要选择合适的编码方式。

image-20210705002836397

图9.21 在Quartus Prime中选择编码方式

9.3 实验目标

  • 使用Verilog HDL设计有限状态机,作为实验八实现的计算器的控制器;

9.4 设计实现

9.4.1 设计思路
系统框图

回顾实验8的系统框图:

image-20240527164140662

图9.23 实验8计算器系统框图

图9.23中只画出了计算器的数据通路部分,而计算器的控制器是使用有限状态机来实现,图9.24是使用有限状态机作为控制器的计算器的系统框图。该计算器的输入和输出端口如下所述:

  • SW3 ~ SW0:计算器的数据输入;

  • *SW16:选择进行加法运算还是减法运算;

  • KEY2:用于使能数字钟,按下按键KEY2,数字钟停止计时,释放KEY2后,数字钟继续计时;

  • FPGA_CLK1_50:50MHz时钟,为寄存器提供时钟信号;

  • KEY0:系统复位信号;

  • KEY1:寄存器使能信号;

  • SW9 ~ SW7:功能选择信号;

  • LEDR7 ~ LEDR0:显示计算器的计算结果,以二进制形式显示在红色LED上;

  • HEX5 HEX0:显示计算器数字钟计时结果,HEX1 ~ HEX0显示计时结果的厘秒,HEX3 ~ HEX2显示计时结果的秒,HEX5 ~ HEX4显示计时结果的分钟。

    image-20240528100611161

图9.24 计算器系统框图

该计算器的功能如下:

  1. 拨动滑动开关SW9 ~ SW7,选择要实现的运算;

    ① *SW9 ~ SW7位置为"down、down、down",计算器实现与运算;

    SW9 ~ SW7位置为"down、down、up",计算器实现或运算;

    SW9 ~ SW7位置为"down、up、down",计算器实现异或运算;

    SW9 ~ SW7位置为"down、up、up",计算器实现非(位取反)运算;

    SW9 ~ SW7位置为"up、down、down",计算器实现加/减运算;

    SW9 ~ SW7位置为"up、down、up",计算器实现乘法运算;

    SW9 ~ SW7位置为"up、up、down",计算器实现除2运算;

  2. 拨动滑动开关SW3 ~ SW0,输入第一个操作数;

  3. 按一次KEY1并释放;

  4. 对于 F = ~ A(位取反) 和 F = A / 2(除2) 运算,运算结果显示到LEDR7 ~ LEDR0

  5. 对于其他运算,拨动滑动开关SW3 ~ SW0,输入第二个操作数;

  6. 按一次KEY1并释放,其他运算结果显示到LEDR7 ~ LEDR0上;

  7. 运算结果显示到LEDR7 ~ LEDR0上后,重新回到步骤1等待下一次运算;

  8. 数字钟实现计数功能,并将计数结果显示到HEX5 ~ HEX0上,按下KEY2不松开,数字钟停止计时;释放KEY2后,数字钟从它上次停止的时刻继续计时。

计算器控制器(状态机)的实现

计算器的数据通路我们已在前面的实验中实现,在这个实验中,我们重点来看怎样用有限状态机来实现计算器的控制器。从图9.24可以看出,该控制器的输入信号有时钟clk、复位rst_n、寄存器使能en和功能选择sel[2:0],输出信号有输入寄存器的使能信号en1、结果寄存器的使能信号en2和计算器所进行的运算选择信号ope[2:0]。根据计算器的功能,我们来分析控制器(状态机)的功能:

  • 在初始状态S_0下,计算器不进行运算,状态机的输出信号en1=0、en2=0;

  • 当输入信号en=1(按键KEY1被按下),状态机转向下一状态S_1,此时状态机的输出信号en1=1、en2=0;该状态会一直保持,直到输入信号en=0(按键KEY1被释放)后,会跳转到下一状态S_2;

  • 在状态S_2下,根据输入信号sel[2:0],跳转到下一状态(S_3 ~ S_9);

  • 对于 F = ~ AF = A / 2 运算(仅需一个操作数),结果显示到LEDR7 ~ LEDR0上,故结果寄存器的使能信号en2=1,并且状态机跳转回初始状态S_0;

  • 对于其他运算,需要等待第二个操作数的输入;

  • 当输入信号en第二次等于1(按键KEY1第二次被按下)时,状态机跳转到下一状态S_{10},在该状态下,结果寄存器的使能信号en2=1,并且状态机跳转回初始状态S_0。

然后我们来进行状态机的设计,根据9.2.1小节介绍的状态机的设计步骤,该状态机的设计过程如下:

  1. 首先要确定采用Mealy状态机还是Moore状态机。从上述状态机的功能可以知道,输入信号en与两个输出信号en1、en2密切相关,故我们采用Mealy状态机;

  2. 第二步是列出状态机的所有状态,并进行状态编码。根据上述状态机的功能分析可知,该状态机共有十一个状态,我们分别使用 S_0 ~ S_{10}来表示。并且,由于我们使用的DE-Cloud开发板的FPGA资源丰富,我们采用独热编码方式,其Verilog HDL描述如下:

    // 状态声明及编码,使能独热码
    parameter   S0  =   11'b000_0000_0001,
                S1  =   11'b000_0000_0010,
                S2  =   11'b000_0000_0100,
                S3  =   11'b000_0000_1000,
                S4  =   11'b000_0001_0000,
                S5  =   11'b000_0010_0000,
                S6  =   11'b000_0100_0000,
                S7  =   11'b000_1000_0000,
                S8  =   11'b001_0000_0000,
                S9  =   11'b010_0000_0000,
                S10 =   11'b100_0000_0000;
  3. 第三步是分析电路的状态转移关系,进而画出状态图。

    ① 开始时,电路处于初始状态S_0,此时会有两种情况,一种情况是输入信号en=0(按键KEY1未被按下)时,电路仍旧处于S_0状态,输出信号en1=0、en2=0;另一种情况是输入信号en=1(按键KEY1被按下)时,电路进入状态S_1;

    ② 然后我们分析S_1状态的跳转情况,在S_1状态下同样有两种情况,一种情况是输入信号en=1(即按键KEY1被按下后未释放),电路仍处于S_1状态,输出信号en1=1、en2=0;另外一种情况是输入信号en=0(即按键KEY1被按下后释放),电路进入状态S_2;

    ③ 在S_2状态下,根据输入信号sel[2:0](功能选择信号),跳转到不同的状态(S_3 ~ S_9);

    ④ 在S_6和S_9状态下,输出信号en1=0、en2=1,并且会跳转回初始状态S_0,注意这次状态转移为无条件跳转;

    ⑤ 在S_3、S_4、S_5、S_7、S_8状态下,会有两种情况,一种情况是输入信号en=0(按键KEY1未被按下)时,电路仍旧处于当前的状态;另一种情况是输入信号en=1(按键KEY1第二次被按下)时,电路进入状态S_{10};

    ⑥ 在S_{10}状态下,输出信号en1=0、en2=1,并且会跳转回初始状态S_0,注意这次状态转移也是无条件跳转。

    根据上述状态转移关系,我们可以画出该状态机的状态图,如图9.25所示。

    image-20211117102133518

    图9.25 计算器的状态机的状态图

  4. 状态图完成后,就可以使用Verilog HDL进行描述了。为了避免毛刺的出现,我们采用三段式状态机。

    ① 首先是状态机端口的声明,从图9.24可以看出,该控制器的输入信号有时钟clk、复位rst_n、寄存器使能en和功能选择sel[2:0],输出信号有输入寄存器的使能信号en1、结果寄存器的使能信号en2。

    module fsmctrl(
        input               clk,        // 时钟信号
        input               rst_n,      // 复位信号,低电平有效
        input               en,         // 寄存器使能信号
        input       [2:0]   sel,        // 功能选择信号,选择计算器进行的运算
        
        output  reg         en1,        // 输入寄存器的使能信号
        output  reg         en2,        // 结果寄存器的使能信号
        output  reg [2:0]   ope         // 计算器进行的运算选择信号
    );

    代码9.5 状态机端口声明

    ② 然后是状态编码,我们使用独热编码方式;还有就是一些变量的声明,现态cs[10:0]、次态ns[10:0]。

    // 状态声明及编码,使能独热码
    parameter   S0  =   11'b000_0000_0001,
                S1  =   11'b000_0000_0010,
                S2  =   11'b000_0000_0100,
                S3  =   11'b000_0000_1000,
                S4  =   11'b000_0001_0000,
                S5  =   11'b000_0010_0000,
                S6  =   11'b000_0100_0000,
                S7  =   11'b000_1000_0000,
                S8  =   11'b001_0000_0000,
                S9  =   11'b010_0000_0000,
                S10 =   11'b100_0000_0000;
    
    // 变量声明
    reg [10:0] cs;
    reg [10:0] ns;

    代码9.6 状态声明及变量声明

    ③ 然后就是三段式状态机的主体实现部分了,第一个always模块采用同步时序逻辑方式描述状态转移,在时钟信号的作用下,ns 的值会在时钟上升沿到来时被传送给 cs。

    // 第一段状态机,描述状态转换
    always@(posedge clk, negedge rst_n)
    begin
        if(~rst_n)
            cs  <= S0;
        else
            cs  <= ns;
    end

    代码9.7 三段式状态机第一个always块

    ④ 第二个always模块采用组合逻辑方式描述状态转移,

    // 第二段状态机,组合逻辑,描述下一状态
    always@(cs, en, sel)
    begin
        case(cs)
            S0:
            begin
                if(en == 1'b1)      // 如果en=1,跳转到S1
                    ns = S1;        // 否则仍处于S0
                else
                    ns = S0;
            end
            S1:
            begin
                if(en == 1'b0)      // 如果en=0,跳转到S2
                    ns = S2;        // 否则仍处于S1
                else
                    ns = S1;
            end
            S2:
            begin
            case(sel)                   // 根据sel的值,跳转到不同的状态S3~S9,进行不同的运算
                    3'b000: ns = S3;    // 与运算
                    3'b001: ns = S4;    // 或运算
                    3'b010: ns = S5;    // 异或运算
                    3'b011: ns = S6;    // 位取反运算
                    3'b100: ns = S7;    // 加/减法运算
                    3'b101: ns = S8;    // 乘法运算
                    3'b110: ns = S9;    // 除2运算
                    default: ns = S3;
                endcase
            end
            S3:
            begin
                if(en == 1'b1)      // 与运算,如果en第二次为1,跳转到S10
                    ns = S10;       // 否则仍处于S3
                else
                    ns = S3;
            end
            S4:
            begin
                if(en == 1'b1)      // 或运算,如果en第二次为1,跳转到S10
                    ns = S10;       // 否则仍处于S4
                else
                    ns = S4;
            end
            S5:
            begin
                if(en == 1'b1)      // 异或运算,如果en第二次为1,跳转到S10
                    ns = S10;       // 否则仍处于S5
                else
                    ns = S5;
            end
            S6:
            begin
                ns = S0;            // 位取反运算,计算完成,返回S0
            end
            S7:
            begin
                if(en == 1'b1)      // 加/减法运算,如果en第二次为1,跳转到S10
                    ns = S10;       // 否则仍处于S7
                else
                    ns = S7;
            end
            S8:
            begin
                if(en == 1'b1)      // 乘法运算,如果en第二次为1,跳转到S10
                    ns = S10;       // 否则仍处于S8
                else
                    ns = S8;
            end
            S9:
            begin
                ns = S0;            // 除2运算,计算完成,返回S0
            end
            S10:
            begin
                ns = S0;            // 计算完成,返回S0
            end
            default: ns = S0;
        endcase
    end

    代码9.7 三段式状态机第二个always块

    ⑤ 第三个always模块描述电路的输出信号,这里我们使用了寄存器输出的方式。

    // 第三段状态机,时序逻辑,描述输出
    always@(posedge clk, negedge rst_n)
    begin
        if(~rst_n)
        begin
            en1 <= 1'b0;
            en2 <= 1'b0;
            ope <= 3'b000;
        end
        else
        begin
            case(ns)
                S0, S2:
                begin
                    en1 <= 1'b0;        // S0、S2状态,两个寄存器都不使能
                    en2 <= 1'b0;
                end
                S1:
                begin
                    en1 <= 1'b1;        // 使能输入寄存器
                    en2 <= 1'b0;
                end
                S3:
                begin
                    ope <= 3'b000;      // 与运算选择信号
                end
                S4:
                begin
                    ope <= 3'b001;      // 或运算选择信号
                end
                S5:
                begin
                    ope <= 3'b010;      // 异或运算选择信号
                end
                S6:
                begin
                    en1 <= 1'b0;
                    en2 <= 1'b1;        // 使能结果寄存器
                    ope <= 3'b011;      // 位取反运算选择信号
                end
                S7:
                begin
                    ope <= 3'b100;      // 加/减法运算选择信号
                end
                S8:
                begin
                    ope <= 3'b101;      // 乘法运算选择信号
                end
                S9:
                begin
                    en1 <= 1'b0;
                    en2 <= 1'b1;        // 使能结果寄存器
                    ope <= 3'b110;      // 除2运算选择信号
                end
                S10:
                begin
                    en1 <= 1'b0;
                    en2 <= 1'b1;        // 使能结果寄存器
                end
                default:
                begin
                    en1 <= 1'b0;
                    en2 <= 1'b0;
                    ope <= 3'b000;
                end
            endcase
        end
    end

    代码9.7 三段式状态机第三个always块

9.4.2 代码实现
计算器控制器(状态机)fsmctrl.v
module fsmctrl(
    input               clk,        // 时钟信号
    input               rst_n,      // 复位信号,低电平有效
    input               en,         // 寄存器使能信号
    input       [2:0]   sel,        // 功能选择信号,选择计算器进行的运算
    
    output  reg         en1,        // 输入寄存器的使能信号
    output  reg         en2,        // 结果寄存器的使能信号
    output  reg [2:0]   ope         // 计算器进行的运算选择信号
);

// 状态声明及编码,使能独热码
parameter   S0  =   11'b000_0000_0001,
            S1  =   11'b000_0000_0010,
            S2  =   11'b000_0000_0100,
            S3  =   11'b000_0000_1000,
            S4  =   11'b000_0001_0000,
            S5  =   11'b000_0010_0000,
            S6  =   11'b000_0100_0000,
            S7  =   11'b000_1000_0000,
            S8  =   11'b001_0000_0000,
            S9  =   11'b010_0000_0000,
            S10 =   11'b100_0000_0000;

// 变量声明
reg [10:0] cs;
reg [10:0] ns;


// 第一段状态机,描述状态转换
always@(posedge clk, negedge rst_n)
begin
    if(~rst_n)
        cs  <= S0;
    else
        cs  <= ns;
end

// 第二段状态机,组合逻辑,描述下一状态
always@(cs, en, sel)
begin
    case(cs)
        S0:
        begin
            if(en == 1'b1)      // 如果en=1,跳转到S1
                ns = S1;        // 否则仍处于S0
            else
                ns = S0;
        end
        S1:
        begin
            if(en == 1'b0)      // 如果en=0,跳转到S2
                ns = S2;        // 否则仍处于S1
            else
                ns = S1;
        end
        S2:
        begin
        case(sel)                   // 根据sel的值,跳转到不同的状态S3~S9,进行不同的运算
                3'b000: ns = S3;    // 与运算
                3'b001: ns = S4;    // 或运算
                3'b010: ns = S5;    // 异或运算
                3'b011: ns = S6;    // 位取反运算
                3'b100: ns = S7;    // 加/减法运算
                3'b101: ns = S8;    // 乘法运算
                3'b110: ns = S9;    // 除2运算
                default: ns = S3;
            endcase
        end
        S3:
        begin
            if(en == 1'b1)      // 与运算,如果en第二次为1,跳转到S10
                ns = S10;       // 否则仍处于S3
            else
                ns = S3;
        end
        S4:
        begin
            if(en == 1'b1)      // 或运算,如果en第二次为1,跳转到S10
                ns = S10;       // 否则仍处于S4
            else
                ns = S4;
        end
        S5:
        begin
            if(en == 1'b1)      // 异或运算,如果en第二次为1,跳转到S10
                ns = S10;       // 否则仍处于S5
            else
                ns = S5;
        end
        S6:
        begin
            ns = S0;            // 位取反运算,计算完成,返回S0
        end
        S7:
        begin
            if(en == 1'b1)      // 加/减法运算,如果en第二次为1,跳转到S10
                ns = S10;       // 否则仍处于S7
            else
                ns = S7;
        end
        S8:
        begin
            if(en == 1'b1)      // 乘法运算,如果en第二次为1,跳转到S10
                ns = S10;       // 否则仍处于S8
            else
                ns = S8;
        end
        S9:
        begin
            ns = S0;            // 除2运算,计算完成,返回S0
        end
        S10:
        begin
            ns = S0;            // 计算完成,返回S0
        end
        default: ns = S0;
    endcase
end


// 第三段状态机,时序逻辑,描述输出
always@(posedge clk, negedge rst_n)
begin
    if(~rst_n)
    begin
        en1 <= 1'b0;
        en2 <= 1'b0;
        ope <= 3'b000;
    end
    else
    begin
        case(ns)
            S0, S2:
            begin
                en1 <= 1'b0;        // S0、S2状态,两个寄存器都不使能
                en2 <= 1'b0;
            end
            S1:
            begin
                en1 <= 1'b1;        // 使能输入寄存器
                en2 <= 1'b0;
            end
            S3:
            begin
                ope <= 3'b000;      // 与运算选择信号
            end
            S4:
            begin
                ope <= 3'b001;      // 或运算选择信号
            end
            S5:
            begin
                ope <= 3'b010;      // 异或运算选择信号
            end
            S6:
            begin
                en1 <= 1'b0;
                en2 <= 1'b1;        // 使能结果寄存器
                ope <= 3'b011;      // 位取反运算选择信号
            end
            S7:
            begin
                ope <= 3'b100;      // 加/减法运算选择信号
            end
            S8:
            begin
                ope <= 3'b101;      // 乘法运算选择信号
            end
            S9:
            begin
                en1 <= 1'b0;
                en2 <= 1'b1;        // 使能结果寄存器
                ope <= 3'b110;      // 除2运算选择信号
            end
            S10:
            begin
                en1 <= 1'b0;
                en2 <= 1'b1;        // 使能结果寄存器
            end
            default:
            begin
                en1 <= 1'b0;
                en2 <= 1'b0;
                ope <= 3'b000;
            end
        endcase
    end
end


endmodule

代码9.8 fsmctrl.v

用Quartus Prime软件编译fsmctrl.v后,查看其State Machine Viewer如图9.26所示,这与我们画的状态图(图9.25)一致。

image-20211111090823285

图9.26 fsmctrl.v的State Machine Viewer

计算器模块calculator.v
module calculator(
    input           clk,            // 时钟信号
    input           rst_n,          // 复位信号,低电平有效
    input   [3:0]   a,              // 操作数,实验九修改为只有一个数据输入
    input           carry_in,       // 加/减法的进位

    input   [2:0]   sel,            // 功能选择,选择计算器进行的运算
    input           en,             // 计算器中寄存器的使能控制信号
    
    input           dig_clk_en,     // 计算器数字钟功能的使能控制信号
                                    // 0: 数字钟停止计时 1: 数字钟继续计时

    output	[ 7: 0] ledr_out,       // 显示计算器的计算结果,以二进制显示
    output	[ 6: 0] hex0_out,       // hex5_out ~ hex0_out显示数字钟计时结果
    output	[ 6: 0]	hex1_out,       // hex1_out ~ hex0_out显示计时结果的厘秒
    output	[ 6: 0]	hex2_out,       // hex3_out ~ hex2_out显示计时结果的秒
    output	[ 6: 0]	hex3_out,       // hex5_out ~ hex4_out显示计时结果的分钟
    output	[ 6: 0]	hex4_out,       
    output	[ 6: 0]	hex5_out      
   // output	[ 6: 0]	hex6_out,
   // output	[ 6: 0]	hex7_out
);

// 变量声明
// 状态机的输出信号
wire            reg_en1;
wire            reg_en2;
wire    [2:0]   operation_sel;
// 两个操作数
wire    [3:0]   operator_a;
wire    [3:0]   operator_b;
// 各种运算的结果变量
wire    [3:0]   f1;
wire    [3:0]   f2;
wire    [3:0]   f3;
wire    [3:0]   f4;
wire    [5:0]   f5;     // overflow, carryout, sum[3:0]
wire    [7:0]   f6;     // multiply
wire    [3:0]   f7;     // divide by 2
wire    [3:0]   m0;
wire    [3:0]   m1;
wire    [3:0]   s0;
wire    [3:0]   s1;
wire    [3:0]   cs0;
wire    [3:0]   cs1;
wire    [7:0]   f;
wire    [7:0]   g;


// 状态机做控制器
fsmctrl fsmctrl_inst(
    .clk    (clk),
    .rst_n  (rst_n),
    .en     (en),
    .sel    (sel),

    .en1    (reg_en1),
    .en2    (reg_en2),
    .ope    (operation_sel)
);


// 输入信号赋值
assign  operator_a      =   a;
// 将操作数寄存
reg4bits reg4bits_inst(
    .clk    (clk),
    .rst_n  (rst_n),
    .en     (~reg_en1),
    .d      (a),
    .q      (operator_b)
);

// 与运算
c1  c1_inst(
    .a  (operator_a),
    .b  (operator_b),
    .f  (f1)
);
// 或运算
c2  c2_inst(
    .a  (operator_a),
    .b  (operator_b),
    .f  (f2)
);
// 异或运算
c3  c3_inst(
    .a  (operator_a),
    .b  (operator_b),
    .f  (f3)
);
// 非运算
c4  c4_inst(
    .a  (operator_a),
    .f  (f4)
);
// 加(减)运算
c5 c5_inst (
    .a  (operator_a), 
    .b  (operator_b), 
    .ci (carry_in), 
    .f  (f5)
);
// 乘法运算
c6 c6_inst (
    .a  (operator_a), 
    .b  (operator_b), 
    .f  (f6)
);
// 除以2
c7 c7_inst (
    .clk    (clk),
    .rst_n  (rst_n),
    .a      (operator_a),
    .f      (f7)
);
// 数字钟
c8 c8_inst (
    .clk    (clk),
    .rst_n  (rst_n),
    .en     (dig_clk_en), 

    .m1     (m1),
    .m0     (m0),
    .s1     (s1),
    .s0     (s0),
    .cs1    (cs1),
    .cs0    (cs0)
);

// 在f1,f2,f3,f4,f5,f6,f7七种运算结果中,选择一个运算结果f
// 若位数不足8位,则需要高位补0
mux8x1 mux8x1_inst(
    .r  ({4'd0, f1}),
    .t  ({4'd0, f2}),
    .u  ({4'd0, f3}),
    .v  ({4'd0, f4}),
    .w  ({2'd0, f5}),
	.x  (f6), 
    .y  ({4'd0, f7}), 
    .z  (8'd0),
    .s  (operation_sel),
	.m  (f)
);

// 将选出的运算结果f存入寄存器中
reg8bits reg8bits_inst(
    .clk    (clk),
    .rst_n  (rst_n),
    .en     (~reg_en2),
    .d      (f),
    .q      (g)
);

// 将g[7:0]以二进制的形式显示在ledr_out上
assign ledr_out = g;


// 数字钟的显示
// hex1_out ~ hex0_out显示数字钟计时结果的厘秒
// hex3_out ~ hex2_out显示数字钟计时结果的秒
// hex5_out ~ hex4_out显示数字钟计时结果的分钟
decod7seg decod7seg_inst7 (.hex(m1),  .display(hex5_out));
decod7seg decod7seg_inst6 (.hex(m0),  .display(hex4_out));
decod7seg decod7seg_inst5 (.hex(s1),  .display(hex3_out));
decod7seg decod7seg_inst4 (.hex(s0),  .display(hex2_out));
decod7seg decod7seg_inst3 (.hex(cs1), .display(hex1_out));
decod7seg decod7seg_inst2 (.hex(cs0), .display(hex0_out));


endmodule

代码9.9 calculator.v

calculator.v的RTL Viewer如图9.27所示,这与我们在9.4.1小节画的系统框图(图9.24)一致。

image-20240527174339973

图9.27 calculator.v的RTL Viewer

9.5 实验步骤

9.5.1 创建工程和 共代码输入
  1. 点击电脑右下角的开始菜单找到Quartus软件,双击Quartus (Quartus Prime 17.1)打开Quartus Prime软件。

  2. 点击菜单File-->New Project Wizard弹出工程创建的对话框。在弹出的对话框中点击Next。

  3. 在您的DE1-SOC 工作文件夹下创建一个lab9的文件夹,并将工程路径指向该文件夹,且工程的名称也命名calculator。如图9.28所示。

    image-20240527170825305

    图9.28-1 创建lab9 工程

    image-20240527171010482

    图9.28-2 创建lab9 工程

  4. 创建Quartus工程报告如下。

    image-20240527171138471

    图9.29 创建Quartus工程的报告

  5. 创建完Quartus工程后,左侧会打开实验步骤手册,右侧会在Quartus Prime中打开lab9工程,如图9.30所示。

    image-20240527171202234

    图9.30 Quartus中打开lab9工程

  6. 浏览并打开lab9工程路径(默认为~/Desktop/UserDisk/Labs/Digital_Logic/lab9),点击右上侧的

    Screenshot from 2021-11-11 10-36-54

    按钮,然后选择New folder按钮,在弹出的New Folder对话框中输入v并点击Create按钮,这样就完成了在lab9文件夹下新建名为v的文件夹,如图9.31所示。

    image-20240527171303890

    图9.31 在lab9文件夹下新建v文件夹

  7. 将实验八中的c1.v、c2.v、c3.v、c4.v、c5.v、c6.v、c7.v、c8.v、calculator.v、counter.v、decod7seg.v、fa.v、mux2x1.v、mux8x1.v、reg4bits.v、reg8bits.v文件拷贝至lab9的v文件夹中,如图9.32所示。

    image-20240527171603908

    图9.32 拷贝文件至v文件夹

  8. 点击Quartus Prime工具栏的Assignments --> Settings --> Files,将v文件夹中的所有文件添加至本实验的Quartus工程,如图9.34所示。

    image-20240527171813028

    图9.34 将v文件夹中的所有文件添加至Quartus工程

  9. 点击Quartus Prime工具栏的File --> New,在弹出的New窗口中选择Verilog HDL File,点击OK,新建一个空白的Verilog HDL文件,如图9.35所示。

    image-20240527172723350

    图9.35 新建空白的Verilog HDL文件

  10. 拷贝代码9.8(fsmctrl.v)至新建的Verilog HDL文件,如图9.36所示。

    image-20240527172922332

    图9.36 将代码9.8拷贝至新建的Verilog HDL文件

  11. 点击File --> Save As...,在弹出的Save As窗口中选择至lab9/v目录,在File name选项中输入fsmctrl.v,点击Save,将文件保存为fsmctrl.v,如图9.37所示。

    image-20240527173008764

    图9.37 将文件保存为fsmctrl.v

  12. 在Quartus Prime的左上角,将Project Navigator由Hierarchy切换至Files,在Files栏中找到calculator.v,双击打开该文件;修改calculator.v文件的内容为代码9.9并保存,如图9.38所示。

    image-20240527174459725

    图9.38 修改calculator.v

  13. 点击Quartus Prime工具栏的Processing --> Start --> Start Analysis & Synthesis或点击

    image-20210603145513555

    按钮对Verilog HDL代码执行语法检查和综合。如果在该过程中提示有错误,请检查Verilog HDL代码语法,确保与上述代码块完全一致。

    image-20240527174553350

    图9.40 对Verilog HDL代码进行分析和综合

9.5.2 仿真
  1. 点击Quartus Prime工具栏的File --> New --> Verilog HDL File,点击OK,新建一个空白Verilog HDL文件;

  2. 在新建的Verilog HDL文件中添加如下代码,如图9.41所示。

    `timescale 1ns/1ps
    module calculator_tb();
    
    // 产生50MHz时钟信号
    reg clk;
    localparam  PERIOD = 20;                // 50MHz,周期为20ns
    initial 
    begin
        clk = 1'b0;
        forever #(PERIOD/2) clk = ~clk;     // 每隔10ns,clk翻转一次
    end
    
    // 产生复位信号,用作6位寄存器的异步复位
    reg rst_n;
    initial 
    begin
        rst_n = 1'b0;
        #(PERIOD)   
        rst_n = 1'b1;
    end
    
    // 产生计算器的操作数a、运算操作选择sel和寄存器的使能信号en
    reg [3:0] a;
    reg       carry_in;
    reg [2:0] sel;
    reg       en;
    reg       dig_clk_en;
    initial
    begin
        #0
        a           = 4'd0;
        carry_in    = 1'b0;
        en          = 1'b0;
        dig_clk_en  = 1'b1;
    
        sel = 3'b000;   // 与运算,1010 & 0010 = 0010
        #(2 * PERIOD)
        a   = 4'b1010;
        en  = 1'b1;     // 第一次en=1
        #(PERIOD)
        en  = 1'b0;
        #(3*PERIOD)
        a   = 4'b0010;
        en  = 1'b1;     // 第二次en=1
        #(PERIOD)
        en  = 1'b0;
    
        #(5 * PERIOD)
        sel = 3'b001;   // 或运算,1010 | 0010 = 1010
        #(PERIOD)
        a   = 4'b1010;
        en  = 1'b1;     // 第一次en=1
        #(PERIOD)
        en  = 1'b0;
        #(3*PERIOD)
        a   = 4'b0010;
        en  = 1'b1;     // 第二次en=1
        #(PERIOD)
        en  = 1'b0;
    
        #(5 * PERIOD)
        sel = 3'b010;   // 异或运算,1010 ^ 0010 = 1000
        #(PERIOD)
        a   = 4'b1010;
        en  = 1'b1;     // 第一次en=1
        #(PERIOD)
        en  = 1'b0;
        #(3*PERIOD)
        a   = 4'b0010;
        en  = 1'b1;     // 第二次en=1
        #(PERIOD)
        en  = 1'b0;
    
        #(5 * PERIOD)
        sel = 3'b011;   // 位取反运算
        #(PERIOD)
        a   = 4'b1010;  // ~1010 = 0101
        en  = 1'b1;     // 第一次en=1
        #(PERIOD)
        en  = 1'b0;
        #(3*PERIOD)
        a   = 4'b0010;  // ~0010 = 1101
        en  = 1'b1;     // 第二次en=1
        #(PERIOD)
        en  = 1'b0;
    
        #(5 * PERIOD)
        sel = 3'b100;   // 加(减)运算,1010 + 0010 = 1100
        #(PERIOD)
        a   = 4'b1010;
        en  = 1'b1;     // 第一次en=1
        #(PERIOD)
        en  = 1'b0;
        #(3*PERIOD)
        a   = 4'b0010;
        en  = 1'b1;     // 第二次en=1
        #(PERIOD)
        en  = 1'b0;
    
        #(5 * PERIOD)
        sel = 3'b101;   // 乘法运算,1010 * 0010 = 10100
        #(PERIOD)
        a   = 4'b1010;
        en  = 1'b1;     // 第一次en=1
        #(PERIOD)
        en  = 1'b0;
        #(3*PERIOD)
        a   = 4'b0010;
        en  = 1'b1;     // 第二次en=1
        #(PERIOD)
        en  = 1'b0;
    
        #(5 * PERIOD)  
        sel = 3'b110;   // 除2运算
        #(PERIOD)
        a   = 4'b1010;  // 1010 / 2 = 0101
        en  = 1'b1;     // 第一次en=1
        #(PERIOD)
        en  = 1'b0;
        #(3*PERIOD)
        a   = 4'b0010;  // 0010 /2 = 0001
        en  = 1'b1;     // 第二次en=1
        #(PERIOD)
        en  = 1'b0;
    
        #(8 * PERIOD)  
        $stop();
    end
    
    // 例化 calculator
    wire    [7:0]   ledr_out;
    wire    [6:0]   hex0_out;
    wire    [6:0]   hex1_out;
    wire    [6:0]   hex2_out;
    wire    [6:0]   hex3_out;
    wire    [6:0]   hex4_out;
    wire    [6:0]   hex5_out;
    //wire    [6:0]   hex6_out;
    //wire    [6:0]   hex7_out;
    calculator calculator_inst(
        .clk        (clk),
        .rst_n      (rst_n),
        .a          (a),
        .carry_in   (carry_in),
    
        .sel        (sel),
        .en         (en),
    
        .dig_clk_en (dig_clk_en),
    
        .ledr_out   (ledr_out),
        .hex0_out   (hex0_out),
        .hex1_out   (hex1_out),
        .hex2_out   (hex2_out),
        .hex3_out   (hex3_out),
        .hex4_out   (hex4_out),
        .hex5_out   (hex5_out)
       // .hex6_out   (hex6_out),
       // .hex7_out   (hex7_out)
    );
    
    endmodule

    代码9.12 calculator_tb.v

    image-20240527175108808

    图9.41 在新建的Verilog HDL文件中添加代码9.12

  3. 点击File --> Save As...,在弹出的Save As窗口中选择至lab9/v目录,在File name选项中输入calculator_tb.v,点击Save,将文件保存为calculator_tb.v,如图9.42所示。

    image-20240527175151859

    图9.42 将文件保存为calculator_tb.v

  4. 点击Quartus Prime工具栏的Assignments --> Settings,在弹出的Settings窗口中,在左侧选中Simulation栏,在右侧的Simulation页面下,Tool name选择ModelSim-Altera,如图9.43所示,Quartus Prime将会调用ModelSim进行仿真。

    image-20240527175740875

    图9.43 设置仿真工具

  5. 依旧是在Simulation页面下,依次执行下面的步骤设置TestBench为calculator_tb:

    ① 选中Compile test bench,然后点击右侧的Test Benches...按钮,如图9.44所示;

    image-20240527175402641

    图9.44 设置TestBench(步骤1)

    ② 在弹出的Test Benches窗口中点击New...

    image-20240527175330754

    图9.45 设置TestBench(步骤2)

    ③ 在弹出的New Test Bench Settings窗口中,在Test bench name栏输入calculator_tb

    ④ 点击File name右侧的

    图标;

    image-20240528101123661

    图9.46 设置TestBench(步骤3、4)

    ⑤ 在弹出的Select File窗口中选择lab9/v目录下的calculator_tb.v

    ⑥ 点击Open

    image-20240528101209082

    图9.47 设置TestBench(步骤5、6)

    ⑦ 然后在File name栏会出现选中的v/calculator_tb.v

    ⑧ 点击File name右侧的Add按钮;

    image-20240528101239845

    图9.48 设置TestBench(步骤7、8)

    ⑨ File name栏中的v/calculator_tb.v会出现在下面的框格中;

    ⑩ 点击OK,退出New Test Bench Settings窗口;

    image-20240528101308172

    图9.49 设置TestBench(步骤9、10)

    ⑪ 返回Test Benches窗口,在Existing test bench settings框格中会出现前面设置的calculator_tb

    ⑫ 点击OK,退出Test Benches窗口;

    image-20240528101344064

    图9.50 设置TestBench(步骤11、12)

    ⑬ 返回Simulation页面,可以看到Compile test bench栏成功添加了calculator_tb仿真文件;

    ⑭ 点击OK完成设置。

    image-20240528101427534

    图9.51 设置TestBench(步骤13、14)

  6. 点击Quartus Prime工具栏的Tools --> Run Simulation Tool --> RTL Simulation,Quartus会启动ModelSim进行仿真,仿真完成后,停在$stop()任务处,如图9.52所示。

    image-20240528102259330

    图9.52 仿真完成后停在$stop()任务处

  7. 点击Wave切换至仿真波形窗口,如图9.53所示,再点击Wave窗口右上角如图9.54所示的Zoom/Unzoom按钮将将窗口最大化,方便观察信号。

    图9.53 切换至仿真波形窗口

    image-20240528102649427

    图9.54 Zoom/Unzoom按钮

  8. 在Wave窗口中,点击如图9.55所示的Zoom Full按钮显示完整的波形。

    54

    图9.55 Zoom Full按钮

  9. 完整的仿真波形如图9.56所示。

    image-20240529162224684

    56_已标记

    图9.56 完整的仿真波形

下面对仿真波形进行分析:

本次仿真的重点是仿真计算器控制器的功能,因此将两个操作数固定为 (1010)_B 和 (0010)_B,即a的值第一次为(1010)_B,第二次为(0010)_B。

⓪ 20ns处,复位信号rst_n由0变为1,复位完成,计算器正常工作;

① 40ns ~ 240ns,sel=(000)_B,做与运算,(1010)_B \ \& \ (0010)_B = (0010)_B;在连续两次en=1后,ledr\_out = (0000\_0010)_B;

② 240ns ~ 460ns,sel=(001)_B,做与运算,(1010)_B \ | \ (0010)_B = (1010)_B;在连续两次en=1后,ledr\_out = (0000\_1010)_B;

③ 460ns ~ 680ns,sel=(010)_B,做异或运算,(1010)_B \ \wedge \ (0010)_B = (1000)_B;在连续两次en=1后,ledr\_out = (0000\_1000)_B;

④ 680ns ~ 900ns,sel=(011)_B,做非运算,\sim(1010)_B = (0101)_B,\sim(0010)_B = (1101)_B;由于非运算只需要一个操作数,在第一次en=1后,ledr\_out = (0000\_0101)_B;在第二次en=1后,ledr\_out = (0000\_1101)_B;

⑤ 900ns ~ 1120ns,sel=(100)_B,carry\_in \ = \ (0)_B,做加法运算,(1010)_B \ + \ (0010)_B \ + \ (0)_B = (1100)_B;连续两次en=1后,ledr\_out = (0000\_1100)_B;

⑥ 1120ns ~ 1340ns,sel=(101)_B,做乘法运算,(1010)_B \ * \ (0010)_B \ = (1\_0100)_B;连续两次en=1后,$ledr_out = (0001_0100)_B

⑦ 1340ns ~ 仿真结束,sel=(110)_B,做除2运算,(1010)_B \ / \ 2 = (0101)_B,(0010)_B \ / \ 2 = (0001)_B;由于除2运算只需要一个操作数,在第一次en=1后,ledr\_out = (0000\_0101)_B;在第二次en=1后,ledr\_out = (0000\_0001)_B;

9.5.3 引脚分配、全编译与烧录
  1. 点击Quartus菜单Assignments——Pin Planner进行引脚分配。

    img

    图4.41

    关于引脚分配信息可以查看DE1-SoC_v.5.1.3_HWrevF.revG_SystemCD\UserManual\DE1-SoC_User_manual.pdf第 22、25、26、28页或者E:\CD_Package\01-DE1-SoC\DE1-SoC_v.5.1.3_HWrevF.revG_SystemCD\Schematic\DE1-SoC.pdf的第3页

    img

    image-20240517165745573

    img

    image-20240527141737677

    image-20240521091242725

    image-20240524175232636

    image-20240524175307929

image-20240527142911770

image-20240527143003702

image-20240527143039056

  1. 其实实验9的引脚信息跟实验8是一样的,没有改动。所以为了节省时间,我们可以打开lab8\calculator.qsf文件,拷贝如下内容到lab8\calculator.qsf文件的最后面:

set_location_assignment PIN_AF14 -to clk
set_location_assignment PIN_AF10 -to a[3]
set_location_assignment PIN_AF9 -to a[2]
set_location_assignment PIN_AC12 -to a[1]
set_location_assignment PIN_AE11 -to carry_in
set_location_assignment PIN_AB12 -to a[0]
set_location_assignment PIN_AA15 -to en
set_location_assignment PIN_AA14 -to rst_n
set_location_assignment PIN_AE12 -to sel[2]
set_location_assignment PIN_AD10 -to sel[1]
set_location_assignment PIN_AC9 -to sel[0]
set_location_assignment PIN_W20 -to ledr_out[7]
set_location_assignment PIN_Y19 -to ledr_out[6]
set_location_assignment PIN_W19 -to ledr_out[5]
set_location_assignment PIN_W17 -to ledr_out[4]
set_location_assignment PIN_V18 -to ledr_out[3]
set_location_assignment PIN_V17 -to ledr_out[2]
set_location_assignment PIN_W16 -to ledr_out[1]
set_location_assignment PIN_V16 -to ledr_out[0]
set_location_assignment PIN_W15 -to dig_clk_en
set_location_assignment PIN_AH28 -to hex0_out[6]
set_location_assignment PIN_AG28 -to hex0_out[5]
set_location_assignment PIN_AF28 -to hex0_out[4]
set_location_assignment PIN_AG27 -to hex0_out[3]
set_location_assignment PIN_AE28 -to hex0_out[2]
set_location_assignment PIN_AE27 -to hex0_out[1]
set_location_assignment PIN_AE26 -to hex0_out[0]
set_location_assignment PIN_AD27 -to hex1_out[6]
set_location_assignment PIN_AF30 -to hex1_out[5]
set_location_assignment PIN_AF29 -to hex1_out[4]
set_location_assignment PIN_AG30 -to hex1_out[3]
set_location_assignment PIN_AH30 -to hex1_out[2]
set_location_assignment PIN_AH29 -to hex1_out[1]
set_location_assignment PIN_AJ29 -to hex1_out[0]
set_location_assignment PIN_AC30 -to hex2_out[6]
set_location_assignment PIN_AC29 -to hex2_out[5]
set_location_assignment PIN_AD30 -to hex2_out[4]
set_location_assignment PIN_AC28 -to hex2_out[3]
set_location_assignment PIN_AD29 -to hex2_out[2]
set_location_assignment PIN_AE29 -to hex2_out[1]
set_location_assignment PIN_AB23 -to hex2_out[0]
set_location_assignment PIN_AB22 -to hex3_out[6]
set_location_assignment PIN_AB25 -to hex3_out[5]
set_location_assignment PIN_AB28 -to hex3_out[4]
set_location_assignment PIN_AC25 -to hex3_out[3]
set_location_assignment PIN_AD25 -to hex3_out[2]
set_location_assignment PIN_AC27 -to hex3_out[1]
set_location_assignment PIN_AD26 -to hex3_out[0]
set_location_assignment PIN_W25 -to hex4_out[6]
set_location_assignment PIN_V23 -to hex4_out[5]
set_location_assignment PIN_W24 -to hex4_out[4]
set_location_assignment PIN_W22 -to hex4_out[3]
set_location_assignment PIN_Y24 -to hex4_out[2]
set_location_assignment PIN_Y23 -to hex4_out[1]
set_location_assignment PIN_AA24 -to hex4_out[0]
set_location_assignment PIN_AA25 -to hex5_out[6]
set_location_assignment PIN_AA26 -to hex5_out[5]
set_location_assignment PIN_AB26 -to hex5_out[4]
set_location_assignment PIN_AB27 -to hex5_out[3]
set_location_assignment PIN_Y27 -to hex5_out[2]
set_location_assignment PIN_AA28 -to hex5_out[1]
set_location_assignment PIN_V25 -to hex5_out[0]
set_instance_assignment -name PARTITION_HIERARCHY root_partition -to | -section_id Top
  1. Quartus Prime工具栏的Processing --> Start Compilation或点击

    image-20210603145755433

    按钮编译工程,编译完成后,如图9.57所示。此外还可以看到在lab9/output_files文件夹中生成了calculator.sof文件,如图9.58所示。

image-20240528170337349

图9.57 lab9编译完成

image-20240528170417119

图9.58 lab9.sof

  1. 使用上一步生成的calculator.sof文件对FPGA进行编程。用USB Blaster线将DE1-SOC开发板和PC连接起来,给开发板上电。

  2. 点击Quartus Prime工具栏的Tools --> Programmer或点击

    image-20210603150014201

    按钮打开Programmer窗口,如图9.59所示。

image-20240528170452280

图9.59 Programmer窗口

  1. 在Programmer窗口中,点击Hardware Setup...打开Hardware Setup窗口,在Currently selected hardware的下拉框中选择"DE-SoC[USB-1]",点击Close,如图9.60所示。

image-20240528170526403

图9.60 选择USB-Blaster

  1. 点击Auto Detect按钮,在弹出的Select Device窗口中,选择5CSEMA5(DE1-SoC开发板上的FPGA器件为Cyclone V SE 5CSEMA5F31C6),并点击OK,如图9.61所示;在弹出的Quartus Prime提示窗口中,点击Yes,如图9.62所示;然后在Programmer窗口会出现SOCVHPS和FPGA两个器件,如图9.63所示。

    image-20240528170559357

    图9.61 选择5CSEBA6器件

    image-20240528170708057

    图9.62 提示窗口

    image-20240528170623757

    图9.63 Programmer窗口的SOCVHPS和FPGA两个器件

  2. 左键单击选中5CSEMA5器件,然后点击Change File按钮,如图9.64所示。

    image-20240528170756611

    图9.64 点击Change File按钮

  3. 在弹出的Select New Programming File窗口中,选择lab9/output_files目录下的calculator.sof文件,点击Open,如图9.65所示,添加calculator.sof文件。

    image-20240528170818702

    图9.65 添加lab9.sof文件

  4. 添加calculator.sof文件完成后会自动返回Programmer窗口,勾选Program/Configure,点击Start按钮,开始烧录calculator.sof文件,如图9.66所示。

    image-20240528170842043

    图9.66 开始烧录lab9.sof文件

  5. 烧录完成后,Progress进度条显示100%,如图9.67所示。

    image-20240528170908189

    图9.67 烧录lab9.sof完成

9.5.4 实验现象观察
  1. 通过切换滑动开关SW9 ~ SW7SW3 ~ SW0到 up 或 down 位置,并按下按键KEY1,观察LEDR7~LEDR0的状态来测试计算器控制器的功能。

    本实验重点验证计算器控制器的功能,因此将两个操作数固定为 (1010)_B 和 (0010)_B,即滑动开关SW3 ~ SW0只会有两种输入:"up、down、up、down"和"down、down、up、down",而我们是通过切换滑动开关SW9 ~ SW7控制计算器进行不同的运算。

    ① 先验证与运算:

    • 切换滑动开关SW9~ SW7位置为"down、down、down",SW3 ~ SW0位置为"up、down、up、down",按下按键KEY1LEDR7~LEDR0显示为全熄灭,如图9.71所示;

      image-20240603161530062

      图9.71 与运算实验现象1

    • 切换滑动开关SW3 ~ SW0位置为"down、down、up、down",按下按键KEY1LEDR7 ~ LEDR0显示为熄灭、熄灭、熄灭、熄灭、熄灭、熄灭、点亮、熄灭,如图9.72所示;

      即(1010)_B \ \& \ (0010)_B = (0010)_B = (2)_H;

      即只有连续两次按下按键KEY1LEDR7 ~ LEDR0 上才会显示与运算的结果。

      image-20240603165024092

      图9.72 与运算实验现象2

    ② 我们继续验证或运算:

    • 切换滑动开关SW9 ~ SW7位置为"down、down、up",SW3 ~ SW0位置为"up、down、up、down",按下按键KEY1LEDR7 ~ LEDR0上仍旧显示上一步与运算的运算结果,即显示为熄灭、熄灭、熄灭、熄灭、熄灭、熄灭、点亮、熄灭,仍旧显示上一步与运算的运算结果,如图9.73所示;

      image-20240603161652420

      图9.73 或运算实验现象1

    • 切换滑动开关SW3 ~ SW0位置为"down、down、up、down",按下按键KEY1LEDR7 ~ LEDR0显示发生变化,即显示为熄灭、熄灭、熄灭、熄灭、点亮、熄灭、点亮、熄灭,如图9.74所示;

      即(1010)_B \ | \ (0010)_B = (1010)_B = (A)_H;

      即只有连续两次按下按键KEY1LEDR7 ~ LEDR0 上才会显示或运算的结果,而只按下一次按键KEY1LEDR7 ~ LEDR0上依旧保持上一次与运算的运算结果。

      image-20240603161830557

      图9.74 或运算实验现象2

    ③ 我们继续验证异或运算:

    • 切换滑动开关SW9 ~ SW7位置为"down、up、down",SW3 ~ SW0位置为"up、down、up、down",按下按键KEY1LEDR7 ~ LEDR0上仍旧显示上一步或运算的运算结果,即显示为熄灭、熄灭、熄灭、熄灭、点亮、熄灭、点亮、熄灭,,仍旧显示上一步或运算的运算结果,如图9.75所示;

      image-20240603164614750

      图9.75 异或运算实验现象1

    • 切换滑动开关SW3 ~ SW0位置为"down、down、up、down",按下按键KEY1LEDR7 ~ LEDR0显示发生变化,即显示为熄灭、熄灭、熄灭、熄灭、点亮、熄灭、熄灭、熄灭,如图9.76所示;

      即(1010)_B \ \wedge \ (0010)_B = (1000)_B = (8)_H;

      即只有连续两次按下按键KEY1LEDR7 ~ LEDR0 上才会显示异或运算的结果,而只按下一次按键KEY1LEDR7 ~ LEDR0 上依旧保持上一次或运算的运算结果。

      image-20240603164459710

      图9.76 异或运算实验现象2

    ④ 我们继续验证非(位取反)运算:

    • 切换滑动开关SW9 ~ SW7位置为"down、up、up",SW3 ~ SW0位置为"up、down、up、down",按下按键KEY1LEDR7 ~ LEDR0显示发生变化,即显示为熄灭、熄灭、熄灭、熄灭、熄灭、点亮、熄灭、点亮,如图9.77所示;

      即\sim(1010)_B = (0101)_B = (5)_H;

      由于非运算只需要一个操作数,所以在第一次按下按键KEY1后,非运算的运算结果就会显示到 LEDR7 ~ LEDR0

      image-20240603164336128

      图9.77 非运算实验现象1

    • 切换滑动开关SW3 ~ SW0位置为"down、down、up、down",按下按键KEY1LEDR7 ~ LEDR0显示发生变化,即显示为熄灭、熄灭、熄灭、熄灭、点亮、点亮、熄灭、点亮,如图9.78所示;

      即\sim(0010)_B = (1101)_B = (d)_H;

      即对于非运算在第一次按下按键KEY1后,非运算的运算结果就会显示到 LEDR7 ~ LEDR0 上。

      image-20240603164214463

      图9.78 非运算实验现象2

    ⑤ 我们再来验证加/减法运算:

    • 切换滑动开关SW9 ~ SW7位置为"up、down、down",SW3 ~ SW0位置为"up、down、up、down",SW11位置为"down",按下按键KEY1LEDR7 ~ LEDR0上仍旧显示上一步非运算的运算结果,即显示为熄灭、熄灭、熄灭、熄灭、点亮、点亮、熄灭、点亮,仍旧显示上一步非运算的运算结果,即显示十六进制数字0d,如图9.79所示;

      image-20240603164045502

      图9.79 加/减法运算实验现象1

    • 切换滑动开关SW3 ~ SW0位置为"down、down、up、down",按下按键KEY1LEDR7 ~ LEDR0显示发生变化,即显示为熄灭、熄灭、熄灭、熄灭、点亮、点亮、熄灭、熄灭,即显示十六进制数字0C,如图9.80所示;

      即(1010)_B \ + \ (0010)_B \ + \ (0)_B = (1100)_B = (C)_H;

      即只有连续两次按下按键KEY1LEDR7 ~ LEDR0HEX1 ~ HEX0 上才会显示加/减运算的结果,而只按下一次按键KEY1LEDR7 ~ LEDR0 上依旧保持上一次非运算的运算结果。

      image-20240603163853226

      图9.80 加/减法运算实验现象2

    ⑥ 在整个实验过程中,数字钟一直保持计数,HEX5 ~ HEX0上显示的数据会一直发生变化,如果按下KEY2不松开,数字钟停止计时;释放KEY2后,数字钟从它上次停止的时刻继续计时。

9.6 实验小结

通过本实验,我们学习了状态机的组成、分类、表示方法、状态编码以及设计步骤,同时还结合了常见的序列检测器的例子来介绍怎样设计状态机。最后,我们将状态机用作计算器的控制器,来控制计算器的整个运算过程,而利用状态机作为系统的控制模块,在数字系统设计中也是经常使用的方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/678029.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【TPAMI-2024】EfficientTrain++帮你降低网络训练的成本

写在前面&#xff1a;本博客仅作记录学习之用&#xff0c;部分图片来自网络&#xff0c;如需引用请注明出处&#xff0c;同时如有侵犯您的权益&#xff0c;请联系删除&#xff01; 文章目录 前言论文更容易学习的模式:频域易于学习的模式:空间域统一的训练课程 EFFICIENTTRAIN计…

计算机网络-BGP路由优选原则概述

前面我们已经学习了BGP的基础概念、对等体建立、报文类型等&#xff0c;也通过实践完成了IBGP和EBGP的实验配置&#xff0c;其实这些路由协议都是理论比较复杂&#xff0c;但是配置其实比较简单的&#xff0c;今天我们来学习BGP的路由优选原则。 一、IGP路由优选 前面我们学习了…

数据结构与算法-10_阻塞队列

文章目录 1.单锁实现2.双锁实现 1.单锁实现 Java 中防止代码段交错执行&#xff0c;有两种锁选择 synchronized 代码块&#xff0c;属于关键字级别提供锁保护&#xff0c;功能少ReentrantLock 类&#xff0c;功能丰富 以 ReentrantLock 为例 ReentrantLock lock new Reent…

tomcat-memcached会话共享配置

目录 1、安装memcache服务 2、把依赖的jar包移至tomcat/lib目录下 3、配置tomcat/conf/context.xml 4、重启tomcat服务 1、安装memcache服务 具体安装步骤此处不详细说明&#xff0c;自行根据实际情况安装即可 2、把依赖的jar包移至tomcat/lib目录下 3、配置tomcat/conf/c…

自定义类型:联合体和枚举

1. 联合体类型的声明 2. 联合体的特点 3. 联合体大小的计算 4. 枚举类型的声明 5. 枚举类型的优点 6. 枚举类型的使用 欢迎关注 熬夜学编程 创作不易&#xff0c;请多多支持 感谢大家的阅读、点赞、收藏和关注 如有问题&#xff0c;欢迎指正 1. 联合体 1.1 联合体类型的声…

java自学阶段一:基础知识学习

《项目案例—黑马tlias智能学习辅助系统》 目录&#xff1a; 异常 一&#xff1a;学习目标&#xff1a; 异常&#xff1a;能够看懂异常信息&#xff0c;了解异常体系结构和分类&#xff0c;掌握异常的两种处理方式&#xff0c;自定义异常。 二、异常&#xff1a; 1.异常的概…

yolo-v8window环境运行

源码https://github.com/ultralytics/ultralytics 1.用pycharm打开YOLOv8文件夹&#xff0c;下载依赖项&#xff0c;依赖项已经以作者的名字来封装好&#xff0c;所以直接在终端输入&#xff1a;pip install ultralytics&#xff0c;安装好之后会默认安装的cpu版本的torch&am…

WannaMine4.0病毒应急处置

一、前言 某日&#xff0c;通过流量监测设备和EDR发现挖矿请求告警&#xff0c;并存在长期445端口扫描。 二、病毒排查 上机排查&#xff0c;发现该服务器存在WannaMine4.0病毒&#xff0c;通过网上文章了解&#xff0c;如果请求挖矿域名遭安全设备拦截&#xff0c;会导致挖矿…

AI大模型页面

自己做的AI&#xff0c;模仿GPT。 访问地址&#xff1a;欢迎 请大家给点意见&#xff0c;需要追加哪些功能。

《企业应用架构模式》学习指南

导读&#xff1a;企业应用包括哪些&#xff1f;它们又分别有哪些架构模式&#xff1f; 世界著名软件开发大师Martin Fowler给你答案 01什么是企业应用 我的职业生涯专注于企业应用&#xff0c;因此&#xff0c;这里所谈及的模式也都是关于企业应用的。&#xff08;企业应用还有…

做视频号小店什么类目最容易爆单?其实,弄懂这三点就会选品了

大家好&#xff0c;我是电商花花。 我们做视频号小店做什么类目最容易爆单&#xff1f; 其实任何类目都有属于自己的受众人群和客户&#xff0c;都非常容易爆单&#xff0c;我们想要爆单&#xff0c;就要选对类目&#xff0c;选对产品。 视频号上所有的类目基本上可以分为标…

塑料焊接机熔深对激光焊接质量有什么影响

塑料焊接机的熔深对焊接质量具有直接且显著的影响。以下是熔深对焊接质量影响的详细解释&#xff1a; 1. 焊接强度&#xff1a;熔深直接决定了焊缝的截面积&#xff0c;从而影响焊接接头的强度。较深的熔深意味着焊缝的截面积更大&#xff0c;可以提供更强的结合力&#xff0c;…

Apache DolphinScheduler 社区5月月报更新!

各位热爱 DolphinScheduler 的小伙伴们&#xff0c;社区5月份月报更新啦&#xff01;这里将记录 DolphinScheduler 社区每月的重要更新&#xff0c;欢迎关注&#xff0c;期待下个月你也登上Merge Star月度榜单哦~ 月度Merge Star 感谢以下小伙伴5月份为 Apache DolphinSchedu…

SpringBoot发送Gmail邮件

1. 登录Gmail Gmail网址 点击右上角“小齿轮”&#xff0c;然后点击"查看所有设置" 点击“转发和 POP/IMAP”&#xff0c;按图中设置&#xff0c;然后点击保存&#xff1a; 2. 启用两步验证(https://myaccount.google.com/security) 登录上述网址&#xff0c;找…

【MyBatis-plus】saveBatch 性能调优和【MyBatis】的数据批量入库

总结最优的两种方法&#xff1a; 方法1&#xff1a; 使用了【MyBatis-plus】saveBatch 但是数据入库效率依旧很慢&#xff0c;那可能是是因为JDBC没有配置&#xff0c;saveBatch 批量写入并没有生效哦&#xff01;&#xff01;&#xff01; 详细配置如下&#xff1a;批量数据入…

用 HTML+CSS 实现全屏爱心滴落的动画效果,中间可显示名字

需求 在页面上显示一行白色文字,同时有爱心滴落的动画效果。 效果 HTML 和 CSS 代码 <!DOCTYPE html> <html lang="en"> <head

Kerberoasting攻击

一. Kerberoasting攻击原理 1. 原理 Kerberoasting 是域渗透中经常使用的一项技术&#xff0c;是Tim Medin 在 DerbyCon 2014 上发布的一种域口令攻击方法&#xff0c;Tim Medin 同时发布了配套的攻击工具 kerberoast。此后&#xff0c;不少研究人员对 Kerberoasting 进行了改…

揭秘Facebook:数字时代的社交奥秘

前言 在当今的数字时代&#xff0c;社交网络已经深刻改变了人们的沟通方式、信息获取方式和社交方式。其中&#xff0c;Facebook作为全球最大的社交网络平台之一&#xff0c;扮演了至关重要的角色。从一个大学生项目发展成覆盖全球的社交巨头&#xff0c;Facebook不仅见证了互…

大尺寸图像分类检测分割统一模型:Resource Efficient Perception for Vision Systems

论文题目&#xff1a;Resource Efficient Perception for Vision Systems 论文链接&#xff1a;http://arxiv.org/abs/2405.07166 代码链接&#xff1a;https://github.com/Visual-Conception-Group/Localized-Perception-Constrained-Vision-Systems 作者设计了一个统一的模…

2024年想转行WebGIS前端开发还有就业前景吗?

当然有。 无论是测绘外业、数据处理&#xff0c;还是城乡规划、遥感等等专业&#xff0c;只要你的行业就业水平一直停留在“工资低、工作条件差、对身体消耗大、没发展”的现状&#xff0c;我都劝你果断转GIS开发。 在新中地学习的学生无非就是因为上述原因&#xff0c;选择放…