[吾题有解] HDLBits : Lemmings4

本题是HDLBits旅鼠系列的最后一题,旅鼠4,相比于上一题的变化是增加了“当旅鼠下落超过20个时钟周期时,其死亡(输出全部变为为0),且只能通过复位恢复”。

到这里我们不难想到如下两点:

  1. 通过设置计数器对保持下落状态的周期进行计数
  2. 通过设置新的状态——死亡(SPLAT)让旅鼠在满足一定条件的情况下从下落状态(FALL_L、FALL_R)进入,且非复位不得跳转至其他状态(复位后回到LEFT状态)。

基于上述分析可以得到如下代码:

module top_module(
    input clk,
    input areset,    // Freshly brainwashed Lemmings walk left.
    input bump_left,
    input bump_right,
    input ground,
    input dig,
    output walk_left,
    output walk_right,
    output aaah,
    output digging ); 

    localparam LEFT = 1, RIGHT = 2, FALL_L = 3, FALL_R = 4, DIG_L = 5, DIG_R = 6, SPLAT = 7;
    
    reg [2:0] state;
    reg [2:0] next_state;
    reg [6:0] cnt; // watch out this!
    
    always @(posedge clk or posedge areset)
        if(areset)
            state <= LEFT;
    	else
            state <= next_state;
    
    always @(posedge clk or posedge areset)
        if(areset)
            cnt <= 7'd0;
    	else if(state == FALL_L || state == FALL_R)
        	cnt <= cnt + 1'b1;
    	else
        	cnt <= 7'd0;

    always @(*)
        case(state)
            LEFT   : next_state = ~ground ? FALL_L : (dig ? DIG_L : (bump_left  ? RIGHT : LEFT));
            RIGHT  : next_state = ~ground ? FALL_R : (dig ? DIG_R : (bump_right ? LEFT : RIGHT));
            //								Why there is cnt > 5'd19?
            FALL_L : next_state =  ground ? (cnt > 5'd19 ? SPLAT : LEFT)  : FALL_L;
            FALL_R : next_state =  ground ? (cnt > 5'd19 ? SPLAT : RIGHT) : FALL_R;
            DIG_L  : next_state = ~ground ? FALL_L : DIG_L;
            DIG_R  : next_state = ~ground ? FALL_R : DIG_R;
            SPLAT  : next_state =  SPLAT;
        endcase
    
    assign walk_left  = (state == LEFT);
    assign walk_right = (state == RIGHT);
    assign aaah       = (state == FALL_L) || (state == FALL_R);
    assign digging    = (state == DIG_L)  || (state == DIG_R);
endmodule

这里解释一下代码中计数器寄存器以及判断逻辑中给出注释的部分。

  • 复位后计数器从0开始计数,第一个下落状态在时钟上升沿由次态赋予现态而出现时,计数器的值仍为0,到下一个时钟周期的上升沿,计数器检测到下落状态,计数值加1,如此推想到第20个下落状态出现时,计数值为19,表示计数器已经记录了20个下落周期。此时是次态转变的关键点,若在此时ground = 1,则下落周期满足 <= 20 cycles,次态变为地面状态(LEFT或RIGHT);若在此时ground = 0,则下一个时钟周期旅鼠仍处于下落状态,计数器值达到20,表示计数器已经记录了21个下落周期,满足 > 20 cycles,自此以后无论旅鼠再保持下落状态多少周期,一旦接触地面(ground = 1),就将在下个时钟周期上升沿进入死亡状态(SPLAT)。

以上描述看似已经解决了这个问题,但是实际运行发现,如果将计数寄存器设置为reg [4:0] cnt,则编译仿真后结果不正确。这是因为当前的设计只关注了逻辑设计的正确性,而忽略了实际硬件电路的运行需求。想当然地认为只要设置计数器寄存器能容纳大于20的数(比如21)就可以代表满足旅鼠进入死亡的条件,因此设置5位宽(可表示5’d0~5’d31)的寄存器就够了。实际上我们应该关注题目中的下面这句话。

There is no upper limit on how far a Lemming can fall before hitting the ground.

也就是说,旅鼠落地之前并不仅仅在空中停留满足“落地成盒”的最小时钟周期数21(即保持21个周期的下落状态)就接触地面并死亡,而是可能处于下落状态更久才接触地面(即保持n(n >> 21)个时钟周期下落状态后,ground = 1),这就可能导致寄存器溢出

这就延伸出了两种解决方法:

  • 一种就是上述代码,通过逐步加大寄存器的位宽,让代码通过样例测试为止(看似暴力,实际中很多问题就是这样解决的……多给些寄存器带宽,让计数值覆盖到所有可能出现的计数值即可,不必重新优化逻辑)。
  • 出于学习与严谨目的(无下落状态周期限制代表无论我们使用多大的计数寄存器,都有可能发生溢出),可以采用设置标志位的方法,该标志位的变化是基于时序逻辑,所以在时钟上升沿检测到计数器的值为19时,拉高标志位(此后只有复位才使其拉低),表示此时计数器计数值已经 > 20(cycles),自此之后无论何时拉高ground,等待小鼠的次态都是SPLAT。这时只要寄存器位宽满足“可计21个数(0~20)”用来触发timeout信号拉高即可,故而恢复reg [4:0] cnt

由此,给出最终的Verilog HDL如下:

module top_module(
    input clk,
    input areset,    // Freshly brainwashed Lemmings walk left.
    input bump_left,
    input bump_right,
    input ground,
    input dig,
    output walk_left,
    output walk_right,
    output aaah,
    output digging ); 

    localparam LEFT = 1, RIGHT = 2, FALL_L = 3, FALL_R = 4, DIG_L = 5, DIG_R = 6, SPLAT = 7;
    
    reg [2:0] state;
    reg [2:0] next_state;
    reg [4:0] cnt; // [4:0] is enough now.
    reg timeout;
    
    always @(posedge clk or posedge areset)
        if(areset)
            state <= LEFT;
    	else
            state <= next_state;
    
    always @(posedge clk or posedge areset)
        if(areset)
            cnt <= 5'd0;
    else if(state == FALL_L || state == FALL_R)
        	cnt <= cnt + 1'b1;
    	else
        	cnt <= 5'd0;
    
    always @(posedge clk or posedge areset)
        if(areset)
            timeout <= 1'b0;
    else if(cnt == 5'd19)
        	timeout <= 1'b1;
    	else
        	timeout <= timeout;
            
    always @(*)
        case(state)
            LEFT   : next_state = ~ground ? FALL_L : (dig ? DIG_L : (bump_left  ? RIGHT : LEFT));
            RIGHT  : next_state = ~ground ? FALL_R : (dig ? DIG_R : (bump_right ? LEFT : RIGHT));
            FALL_L : next_state =  ground ? (timeout ? SPLAT : LEFT)  : FALL_L;
            FALL_R : next_state =  ground ? (timeout ? SPLAT : RIGHT) : FALL_R;
            DIG_L  : next_state = ~ground ? FALL_L : DIG_L;
            DIG_R  : next_state = ~ground ? FALL_R : DIG_R;
            SPLAT  : next_state =  SPLAT;
        endcase
    
    assign walk_left  = (state == LEFT);
    assign walk_right = (state == RIGHT);
    assign aaah       = (state == FALL_L) || (state == FALL_R);
    assign digging    = (state == DIG_L)  || (state == DIG_R);
endmodule

总结:遇到一时理不清逻辑的电路一定要善加利用时序图(无论是手绘还是仿真软件)。

至此,HDLBits旅鼠系列完结!

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝莲花正开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值