Source

License

Index

Watchdog Timer

Raises an alarm signal if not "pinged" before a given number of cycles have passed since the last ping or since start. This can be used as a simple event timer, or as part of a hardware failsafe.

ping and start are separate signals to separate the controlling process (hardware or software) which configures and controls the watchdog, and the supervised process which must periodically "ping" the watchdog.

While the watchdog is running, pulsing ping high for exactly one cycle restarts the watchdog's timeout countdown. Holding ping high for more than one cycle stops the watchdog and raises error and alarm, to detect stuck-at faults in the supervised process. ping has no effect while alarm is high, so as to prevent a case where the alarm is raised, but not noticed before ping is raised again, which would clear the alarm and lose the detection of a timeout.

Pulsing start for exactly one cycle while providing the desired cycle_count value in the same cycle will restart the watchdog and clear alarm and error. Holding start high for more than one cycle stops the watchdog and raises error and alarm to detect stuck-at faults in the controlling process. Restarting the watchdog with a cycle_count of zero halts the watchdog, clears alarm and error, and ping will have no effect. This special case allows us to choose to run without a watchdog, or to start the supervised process first, then the watchdog, in cases where we cannot be sure if the latency when configuring the watchdog or starting the supervised process will exceed the watchdog timeout. start overrides ping.

If more cycles than the last loaded cycle_count have passed (except for a value of zero) since the last cycle start or ping was pulsed high, the watchdog stops and raises and holds high alarm (but not error) until start is raised high to load a new cycle_count.

clear overrides all signals, lowers all outputs, and stops the watchdog. So it's possible for clear to hide a timeout. `clear may be held high for multiple cycles without error.

`default_nettype none

module Watchdog_Timer
#(
    parameter WORD_WIDTH                = 32
)
(
    input   wire                        clock,
    input   wire                        clear,

    input   wire                        ping,
    input   wire                        start,
    input   wire    [WORD_WIDTH-1:0]    cycle_count,

    output  wire                        alarm,
    output  wire                        error
);

    localparam WORD_ZERO = {WORD_WIDTH{1'b0}};
    localparam WORD_ONE  = {{WORD_WIDTH-1{1'b0}}, 1'b1};

We can detect if ping or start are pulsed for longer than one cycle by seeing if they are still asserted after we combinationally generate a pulse from their rising edge.

    wire ping_first_cycle;

    Pulse_Generator
    ping_check
    (
        .clock              (clock),
        .level_in           (ping),
        .pulse_posedge_out  (ping_first_cycle),
        // verilator lint_off PINCONNECTEMPTY
        .pulse_negedge_out  (),
        .pulse_anyedge_out  ()
        // verilator lint_on  PINCONNECTEMPTY
    );

    wire start_first_cycle;

    Pulse_Generator
    start_check
    (
        .clock              (clock),
        .level_in           (start),
        .pulse_posedge_out  (start_first_cycle),
        // verilator lint_off PINCONNECTEMPTY
        .pulse_negedge_out  (),
        .pulse_anyedge_out  ()
        // verilator lint_on  PINCONNECTEMPTY
    );

    reg ping_error  = 1'b0;
    reg start_error = 1'b0;

    always @(*) begin
        ping_error  = (ping  == 1'b1) && (ping_first_cycle  == 1'b0);
        start_error = (start == 1'b1) && (start_first_cycle == 1'b0);
    end

Store the last loaded cycle_count so ping can reload it.

    reg                     store_cycle_count = 1'b0;
    wire [WORD_WIDTH-1:0]   cycle_count_stored;

    Register
    #(
        .WORD_WIDTH     (WORD_WIDTH),
        .RESET_VALUE    (WORD_ZERO)
    )
    cycle_count_storage
    (
        .clock          (clock),
        .clock_enable   (store_cycle_count),
        .clear          (clear),
        .data_in        (cycle_count),
        .data_out       (cycle_count_stored)
    );

Simply a countdown timer which runs freely. Note that clear implicitly loads zero (INITIAL_COUNT).

    reg                     timer_run           = 1'b0;
    reg                     timer_load          = 1'b0;
    reg  [WORD_WIDTH-1:0]   timer_load_value    = WORD_ZERO;
    wire [WORD_WIDTH-1:0]   cycles_remaining;

    Counter_Binary
    #(
        .WORD_WIDTH     (WORD_WIDTH),
        .INCREMENT      (WORD_ONE),
        .INITIAL_COUNT  (WORD_ZERO)
    )
    timer_cycles
    (
        .clock          (clock),
        .clear          (clear),

        .up_down        (1'b1), // 0/1 --> up/down
        .run            (timer_run),

        .load           (timer_load),
        .load_count     (timer_load_value),

        .carry_in       (1'b0),
        // verilator lint_off PINCONNECTEMPTY
        .carry_out      (),
        .carries        (),
        .overflow       (),
        // verilator lint_on  PINCONNECTEMPTY

        .count          (cycles_remaining)
    );

We detect a timeout by the counter reaching 1, not zero, and raising the alarm in the next cycle. This means loading zero directly, to stop the counter, does not trigger an alarm, and that loading a cycle_count value of N means the alarm will raise exactly N cycles later.

    reg timer_expiring = 1'b0;

    always @(*) begin
        timer_expiring = (cycles_remaining == WORD_ONE);
    end

    wire timer_expired;

    Register
    #(
        .WORD_WIDTH     (1),
        .RESET_VALUE    (1'b0)
    )
    timeout
    (
        .clock          (clock),
        .clock_enable   (1'b1),
        .clear          (clear),
        .data_in        (timer_expiring),
        .data_out       (timer_expired)
    );

Alarm is a latch. clear overrides pulse_in.

    reg alarm_clear = 1'b0;
    reg alarm_set   = 1'b0;

    Pulse_Latch
    #(
        .RESET_VALUE    (1'b0)
    )
    alarm_hold
    (
        .clock          (clock),
        .clear          (alarm_clear),
        .pulse_in       (alarm_set),
        .level_out      (alarm)
    );

Error is a latch. clear overrides pulse_in.

    reg error_clear = 1'b0;
    reg error_set   = 1'b0;

    Pulse_Latch
    #(
        .RESET_VALUE    (1'b0)
    )
    error_hold
    (
        .clock          (clock),
        .clear          (error_clear),
        .pulse_in       (error_set),
        .level_out      (error)
    );

Now the control logic

    always @(*) begin
        store_cycle_count   = (start_first_cycle == 1'b1);

        timer_run           = (cycles_remaining != WORD_ZERO) && (alarm == 1'b0);
        timer_load          = (start_first_cycle == 1'b1) || ((ping_first_cycle == 1'b1) && (alarm == 1'b0));
        timer_load_value    = (start_first_cycle == 1'b1) ? cycle_count : cycle_count_stored;

        alarm_set           = (ping_error == 1'b1) || (start_error == 1'b1) || (timer_expired == 1'b1);
        alarm_clear         = (start_first_cycle == 1'b1) || (clear == 1'b1);

        error_set           = (ping_error == 1'b1) || (start_error == 1'b1);
        error_clear         = (start_first_cycle == 1'b1) || (clear == 1'b1);
    end

endmodule

Back to FPGA Design Elements

fpgacpu.ca