LJgibbs · 2020年11月03日

HDLBits:在线学习 Verilog (三十二 · Problem 155-159)

转载自:知乎
作者:ljgibbs

首先附上传送门:

Exams/review2015 fsm​hdlbits.01xz.net图标

Problem 155 FSM:The complete FSM

牛刀小试

本题实现复杂计数器的第四个组件。

在此题之前,我们已经分别实现了 FSM:Enable shift register 以及 FSM:1101 序列检测器。接下来我们继续前进,实现这个复杂计数器的完整 FSM。

复杂计数器需要如下这些功能特性:

  1. 在数据流中检测到特定序列后启动计数器,该序列为: 1101
  2. 将 4bits 数据输入移位寄存器,作为计数器的初值
  3. 等待计数器结束计数
  4. 告知上层应用计数完成,并等待用户通过 ack 信号确认

在本题练习中,只需要实现控制状态机,不需要实现数据通路,比如计数器本身以及数据比较器等。

数据流从模块的 data 信号输入,当检测到 1101 序列后,状态机需要置高输出信号 shft\_ena 并保持 4 个周期(用于将接下来 4bit 数据输入移位寄存器)。

之后,状态机置高 counting 信号,表示其正在等待计数器完成计数,当计数器完成计数输出 done\_counting 信号后,counting 信号置低。

再此后,状态机置高 done 信号通知上层应用计数器计数完成,等待 ack 信号置高后,状态机清除 done 信号,返回空闲状态等待捕获下一个 1101 序列。

本题给出了一个期望输入输出的例子。图中的斜线代表当前信号为 'X', 表示状态机不关心该信号当前的值。比如图例中,一旦 FSM 检测到 1101 序列后,在此次计数器事件完成前,对于当前的数据流不再关心。

module top_module (
    input clk,
    input reset,      // Synchronous reset
    input data,
    output shift_ena,
    output counting,
    input done_counting,
    output done,
    input ack );

    localparam FSM_W  = 8;
    localparam FSM_W1 = FSM_W - 1'b1;

    reg [FSM_W1:0]   state;
    reg [FSM_W1:0]   nxt_state;

    localparam  IDLE        = 0;
    localparam  S_0         = 1;
    localparam  S_1         = 2;
    localparam  S_11        = 3;
    localparam  S_110       = 4;
    localparam  S_SHFT_ENA  = 5;
    localparam  WAT_CNT_FIN = 6;
    localparam  WAT_ACK     = 7;

    reg [1:0]       asrt_cntr;
    wire            asrt_cntr_add;  
    wire            asrt_cntr_clr;  

    //assert signal cntr
    always @(posedge clk) begin
        if(reset) begin
            asrt_cntr              <= 'b0;
        end else if(asrt_cntr_add)begin
            asrt_cntr              <= asrt_cntr + 1'b1;
        end else if(asrt_cntr_clr)begin
            asrt_cntr              <= 'b0;
        end
    end
    assign                  asrt_cntr_add  = state[S_SHFT_ENA];
    assign                  asrt_cntr_clr  = 1'b0; // cntr clear itself

    // State transition logic (combinational)
    always @(*) begin
        nxt_state[IDLE   ]          =   1'b0; // never reach for nxt_state
        nxt_state[S_0    ]          =   (state[IDLE   ] && ~data) || (state[S_1    ] && ~data) || (state[S_0    ] && ~data) || (state[S_110   ] && ~data) || (state[WAT_ACK       ] && ack);
        nxt_state[S_1    ]          =   (state[IDLE   ] &&  data) || (state[S_0    ] &&  data);
        nxt_state[S_11   ]          =   (state[S_1    ] &&  data) || (state[S_11   ] &&  data);
        nxt_state[S_110  ]          =   (state[S_11   ] && ~data);
        nxt_state[S_SHFT_ENA ]      =   (state[S_110  ] &&  data) || (state[S_SHFT_ENA  ] && ~(asrt_cntr == 2'd3));

        nxt_state[WAT_CNT_FIN   ]   =   (state[S_SHFT_ENA] && asrt_cntr == 2'd3)
                                    || (state[WAT_CNT_FIN   ] && ~done_counting);

        nxt_state[WAT_ACK       ]   =   (state[WAT_CNT_FIN   ] && done_counting) 
        || (state[WAT_ACK       ] && ~ack);
    end

    // State flip-flops (sequential)
    always @(posedge clk) begin
        if(reset)
            state   <=  'b10; //SEQ_RCGN
        else begin
            state   <=  nxt_state;
        end  
    end

    //output logic
    assign  done        =   state[WAT_ACK] ;
    assign  counting    =   state[WAT_CNT_FIN];
    assign  shift_ena   =   state[S_SHFT_ENA];
endmodule

看到题目的时候,我第一时间的想法是可以利用前两个 FSM 作为底层模块来实现本题。

嗯,但我发现走了弯路,因为前两题的逻辑并不适合作为底层模块,最后只能通过疯狂凑时序实现。如果读者第一想法和我一样,或许可以一试,锻炼下凑时序的能力。(工程中很实用的 /狗头)

所以后来笔者放弃了最初的想法,改为利用先前 2 个 FSM 的逻辑,在同一个模块层次中重新实现一个 FSM。状态可以分为 4 类:序列检测/输出使能/等待 cntr\_done/等待 ack。

本题的状态机还算是比较复杂了,此处建议读者可以读一下英文原版的题目,学习一下原作者对状态机状态的描述,十分简洁明了。

Problem 156 The complete timer

牛刀小试

终于到了完整构建复杂计数器的时候,整体功能已经在上题中讨论,这里不再赘述。

在数据流中检测到序列 1101 后,电路需要将接下来的 4bit 数据移入移位寄存器。4bit 数据决定了计数器的计数周期,称为 delay[3:0]。首先到达的比特作为数据的高位。

之后,状态机置高 counting 信号,表示其正在等待计数器完成计数。在 FSM 中增加计数器状态,计数周期为 (delay[3:0] + 1 )* 1000 个时钟周期。比如 delay = 0 时,计数值为 1000 个周期。delay = 5 代表 6000 个周期。同时输出 count 当前剩余的计数周期,输出当前剩余计数周期的千位(比如,还剩1000个周期输出 1,还剩 999 个周期时输出 0)。当计数停止后,count 的输出可以为任意数。

当计数完成后,电路置高 done 信号通知上层应用计数器计数完成,等待 ack 信号置高后,状态机清除 done 信号,返回空闲状态等待捕获下一个 1101 序列。

本题给出了一个期望输入输出的例子。图中的斜线代表当前信号为 'X', 表示状态机不关心该信号当前的值。比如图例中,一旦 FSM 检测到 1101 序列并读取 delay[3:0] 后,在此次计数器事件完成前,对于当前的数据流不再关心。

在图例中,电路计数周期为 2000 ,因为 delay[3:0] 数值为 4'b0001 。在后续的第二个计数周期中,因为 delay[3:0] = 4‘b1110,所以计数周期为 15000。

解答与分析

module shft_reg_cntr (
    input clk,
    input shift_ena,
    input count_ena,
    input data,
    output reg [3:0] q);

    always @(posedge clk ) begin
        if(shift_ena)
            q   <=  {q[2:0],data};
        else if(count_ena) begin
            q   <=  q - 4'd1;
        end  
    end
endmodule

module cntr_1k (
    input clk,
    input reset,
    output reg [9:0] q);

    always @(posedge clk ) begin
        if(reset | q == 10'd999)
            q   <=  10'd0;
        else begin
            q   <=  q + 10'd1;
        end  
    end
endmodule

module top_module (
    input clk,
    input reset,      // Synchronous reset
    input data,
    output [3:0] count,
    output counting,
    output done,
    input ack );

    localparam FSM_W  = 8;
    localparam FSM_W1 = FSM_W - 1'b1;

    reg [FSM_W1:0]   state;
    reg [FSM_W1:0]   nxt_state;

    localparam  IDLE        = 0;
    localparam  S_0         = 1;
    localparam  S_1         = 2;
    localparam  S_11        = 3;
    localparam  S_110       = 4;
    localparam  S_SHFT_ENA  = 5;
    localparam  WAT_CNT_FIN = 6;
    localparam  WAT_ACK     = 7;

    wire            done_counting;

    //assert signal cntr
    reg [1:0]       asrt_cntr;
    wire            asrt_cntr_add;  
    wire            asrt_cntr_clr;  

    //delay val
    wire[3:0]       dly_val;

    wire            dly_val_shft_ena;
    wire            dly_val_dec;

    //delay cntr
    wire [9:0]      dly_cntr;
    wire            dly_cntr_ena;

    //assert signal cntr
    always @(posedge clk) begin
        if(reset) begin
            asrt_cntr              <= 'b0;
        end else if(asrt_cntr_add)begin
            asrt_cntr              <= asrt_cntr + 1'b1;
        end else if(asrt_cntr_clr)begin
            asrt_cntr              <= 'b0;
        end
    end
    assign                  asrt_cntr_add  = state[S_SHFT_ENA];
    assign                  asrt_cntr_clr  = 1'b0; // cntr clear itself

    //delay cntr
    cntr_1k U_cntr_1k(
        .clk        (clk),
        .reset      (~dly_cntr_ena),
        .q          (dly_cntr)
    );
    assign                  dly_cntr_ena  = state[WAT_CNT_FIN   ];

    //delay val
    shft_reg_cntr U_shft_reg_cntr
    (
        .clk        (clk),
        .shift_ena  (dly_val_shft_ena),
        .count_ena  (dly_val_dec),
        .data       (data),
        .q          (dly_val)
    );
    assign                  dly_val_shft_ena    =   state[S_SHFT_ENA ];
    assign                  dly_val_dec         =   (dly_cntr == 16'd999 ) && ~(dly_val == 4'd0);

    assign                  done_counting       =   dly_cntr == 16'd999 && dly_val == 4'd0;

    // State transition logic (combinational)
    always @(*) begin
        nxt_state[IDLE   ]          =   1'b0; // never reach for nxt_state
        nxt_state[S_0    ]          =   (state[IDLE   ] && ~data) || (state[S_1    ] && ~data) || (state[S_0    ] && ~data) 
                            || (state[S_110   ] && ~data) || (state[WAT_ACK       ] && ack);
        nxt_state[S_1    ]          =   (state[IDLE   ] &&  data) || (state[S_0    ] &&  data);
        nxt_state[S_11   ]          =   (state[S_1    ] &&  data) || (state[S_11   ] &&  data);
        nxt_state[S_110  ]          =   (state[S_11   ] && ~data);
        nxt_state[S_SHFT_ENA ]      =   (state[S_110  ] &&  data) || (state[S_SHFT_ENA  ] && ~(asrt_cntr == 2'd3));

        nxt_state[WAT_CNT_FIN   ]   =   (state[S_SHFT_ENA] && asrt_cntr == 2'd3)
                                    || (state[WAT_CNT_FIN   ] && ~done_counting);

        nxt_state[WAT_ACK       ]   =   (state[WAT_CNT_FIN   ] && done_counting) 
                                    || (state[WAT_ACK       ] && ~ack);
    end

    // State flip-flops (sequential)
    always @(posedge clk) begin
        if(reset)
            state   <=  'b10; //SEQ_RCGN
        else begin
            state   <=  nxt_state;
        end  
    end

    //output logic
    assign  done        =   state[WAT_ACK] ;
    assign  counting    =   state[WAT_CNT_FIN];
    assign  count       =   dly_val;
endmodule

相比上一题,本题增加了实际的计数逻辑以取代输入信号 done\_counting,但状态以及转移关系与上一题相同。

设计 dly\_val [3:0] ,在 shft\_ena 移位使能的情况下,从数据流中读取 4bit 延迟值。

计数逻辑为一个周期为 1000 的计数器,当计数器输出 16’d999 时,将 dly\_val 减 1。

功能是不是听上去很熟悉,没错,我们可以分别使用之前实现的 移位寄存器 以及 周期为 1000 的计数器。(笔者一开始忘了来着-\_-||),并在相应状态产生使能信号控制这两个下层模块。

Problem 157 FSM:One-hot logic equations

牛刀小试

本题给出了一个具有 3 输入,3 输出以及 10 个状态的 FSM 的状态转移图。

仅需要实现状态转移和输出逻辑的组合逻辑,tb 会检测是否按照要求使用了独热码。

PS:其实这就是上一题中的状态机

图中的状态依次进行了独热码编码,S 为 10b'1,S1、S11、S110、B0、B1、B2、B3、Count、Wait 以此类推。

本题仅要求产生以下状态的状态转移信号:

  • B3\_next ,B2 状态的次态(原题写的 B1,应该为笔误)
  • S\_next
  • S1\_next
  • Count\_next

以及下列输出信号

  • done
  • counting
  • shift\_ena

解答与分析

module top_module(
    input d,
    input done_counting,
    input ack,
    input [9:0] state,    // 10-bit one-hot current state
    output B3_next,
    output S_next,
    output S1_next,
    output Count_next,
    output Wait_next,
    output done,
    output counting,
    output shift_ena
); 

    localparam FSM_W  = 10;
    localparam FSM_W1 = FSM_W - 1'b1;

    reg [FSM_W1:0]   nxt_state;

    localparam  S_0         = 0;
    localparam  S_1         = 1;
    localparam  S_11        = 2;
    localparam  S_110       = 3;
    localparam  B0          = 4;
    localparam  B1          = 5;
    localparam  B2          = 6;
    localparam  B3          = 7;
    localparam  WAT_CNT_FIN = 8;
    localparam  WAT_ACK     = 9;

    always @(*) begin
        nxt_state[S_0    ]          =   (state[S_1    ] && ~d) || (state[S_0    ] && ~d) 
                            || (state[S_110   ] && ~d) || (state[WAT_ACK       ] && ack);
        nxt_state[S_1    ]          =   (state[S_0    ] &&  d);
        nxt_state[S_11   ]          =   (state[S_1    ] &&  d) || (state[S_11   ] &&  d);
        nxt_state[S_110  ]          =   (state[S_11   ] && ~d);
        nxt_state[B0     ]          =   (state[S_110  ] &&  d);
        nxt_state[B1     ]          =   (state[B0     ]);
        nxt_state[B2     ]          =   (state[B1     ]);
        nxt_state[B3     ]          =   (state[B2     ]);

        nxt_state[WAT_CNT_FIN   ]   =   state[B3]
                                    || (state[WAT_CNT_FIN   ] && ~done_counting);

        nxt_state[WAT_ACK       ]   =   (state[WAT_CNT_FIN   ] && done_counting) 
                                    || (state[WAT_ACK       ] && ~ack);
    end

    assign          B3_next         =   nxt_state[B3];
    assign          S_next          =   nxt_state[S_0];
    assign          S1_next         =   nxt_state[S_1];
    assign          Count_next      =   nxt_state[WAT_CNT_FIN];
    assign          Wait_next       =   nxt_state[WAT_ACK];
    assign          done            =   state[WAT_ACK] ;
    assign          counting        =   state[WAT_CNT_FIN];
    assign          shift_ena       =   state[B0] 
                                    ||  state[B1]
                                    ||  state[B2]
                                    ||  state[B3];

endmodule

Problem 158 Mux

牛刀小试

接下来的几题中,请从题目给出的 Verilog 中找出并修正 BUG。

本题为 8bit 位宽的 2 选 1 选择器。

解答与分析

module top_module (
    input sel,
    input [7:0] a,
    input [7:0] b,
    output [7:0] out  );
    //assign out = (~sel & a) | (sel & b);
    assign out = ({8{sel}} & a) | ({8{~sel}} & b);

endmodule

此题的问题在于,sel 是一个 1bit 信号,sel & a 相当于 {7'b0,sel} & a,实现正确的逻辑需要将 sel 复制延展为 8bit。

Problem 159 NAND

牛刀小试

本题中的三输入与非门不工作了,找出并修正 BUG。

读者必须使用提供的 5 输入与门来实现这个与非门。

解答与分析

module top_module (input a, input b, input c, output out);//
    wire out_rev;
    andgate  inst1 (out_rev, a, b, c,1'b1,1'b1);

    assign out  =   ~out_rev;
endmodule

这里实际用了一个非门,笔者想了想,与门本身肯定不能搭出与非门吧。

推荐阅读

关注此系列,请关注专栏FPGA的逻辑
推荐阅读
关注数
10614
内容数
577
FPGA Logic 二三事
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息