blob: 2139093b38fc6faa46642450ccc3ac93b9d3ce7e [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Primitive hardened counter module
//
// This module implements two styles of hardened counting
// 1. Duplicate count
// There are two copies of the relevant counter and they are constantly compared.
// 2. Cross count
// There is an up count and a down count, and the combined value must always
// combine to the same value.
//
// This counter supports a generic clr / set / en interface, where
// clr_i and set_i MUST NOT be set at the same time!
//
// In duplicate count mode
// - clr_i sets all (internal) counters to 0.
// - set_i sets the up_count's starting value to set_cnt_i.
// Note: the max_val is just the max possible value given by the counter's width.
// - en_i increments the counter by step_i, if neither of the above is set.
//
// In cross count mode
// - clr_i sets all (internal) counters to 0. This means:
// -- down_count is halted until set_i is set again
// Note: The up_count is still running.
// -- err_o is set to 0 (false),
// -- cnt_o is either all zero (OutSelDnCnt = 1) or the (running) up_count value
// (OutSelDnCnt = 0).
// - clr_i sets max_val to the max possible value given by the counter's width.
// - set_i sets
// -- the up_count to 0 and the down_count to set_cnt_i,
// -- the up_count's max_val to set_cnt_i.
// - en_i increments/decrements the up_count/down_count by step_i, if neither of the above is
// set.
module prim_count import prim_count_pkg::*; #(
parameter int Width = 2,
parameter bit OutSelDnCnt = 1, // 0 selects up count
parameter prim_count_style_e CntStyle = CrossCnt, // DupCnt or CrossCnt
// This should only be disabled in special circumstances, for example
// in non-comportable IPs where an error does not trigger an alert.
parameter bit EnableAlertTriggerSVA = 1
) (
input clk_i,
input rst_ni,
input clr_i,
input set_i,
input [Width-1:0] set_cnt_i,
input en_i,
input [Width-1:0] step_i, // increment/decrement step when enabled
output logic [Width-1:0] cnt_o,
output logic err_o
);
// if output selects down count, it MUST be the cross count style
`ASSERT_INIT(CntStyleMatch_A, OutSelDnCnt ? CntStyle == CrossCnt : 1'b1)
localparam int CntCopies = (CntStyle == DupCnt) ? 2 : 1;
// clear up count whenever there is an explicit clear, or
// when the max value is re-set during cross count.
logic clr_up_cnt;
assign clr_up_cnt = clr_i |
(set_i & (CntStyle == CrossCnt));
// set up count to desired value only during duplicate counts.
logic set_up_cnt;
assign set_up_cnt = set_i & (CntStyle == DupCnt);
logic [CntCopies-1:0][Width-1:0] up_cnt_d, up_cnt_d_buf;
logic [CntCopies-1:0][Width-1:0] up_cnt_q;
logic [Width-1:0] max_val;
logic err;
if (CntStyle == CrossCnt) begin : gen_crosscnt_max_val
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
max_val <= '1;
end else if (clr_i) begin
max_val <= '1;
end else if (set_i) begin
max_val <= set_cnt_i;
end
end
end else begin : gen_dupcnt_max_val
assign max_val = '1;
end
for (genvar i = 0; i < CntCopies; i++) begin : gen_cnts
// up-count
assign up_cnt_d[i] = (clr_up_cnt) ? '0 :
(set_up_cnt) ? set_cnt_i :
(en_i & (up_cnt_q[i] < max_val)) ? up_cnt_q[i] + step_i :
up_cnt_q[i];
prim_buf #(
.Width(Width)
) u_buf (
.in_i(up_cnt_d[i]),
.out_o(up_cnt_d_buf[i])
);
prim_flop #(
.Width(Width),
.ResetValue('0)
) u_cnt_flop (
.clk_i,
.rst_ni,
.d_i(up_cnt_d_buf[i]),
.q_o(up_cnt_q[i])
);
end
if (CntStyle == CrossCnt) begin : gen_cross_cnt_hardening
logic [Width-1:0] down_cnt;
logic [Width-1:0] sum;
// down-count
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
down_cnt <= '{default: '1};
end else if (clr_i) begin
down_cnt <= '{default: '1};
end else if (set_i) begin
down_cnt <= set_cnt_i;
end else if (en_i && (down_cnt > '0)) begin
down_cnt <= down_cnt - step_i;
end
end
logic msb;
assign {msb, sum} = down_cnt + up_cnt_q[0];
assign cnt_o = OutSelDnCnt ? down_cnt : up_cnt_q[0];
assign err = (max_val != sum) | msb;
`ASSERT(CrossCntErrForward_A,
((down_cnt + up_cnt_q[0]) != {1'b0, max_val}) |-> err_o)
`ASSERT(CrossCntErrBackward_A, err_o |->
((down_cnt + up_cnt_q[0]) != {1'b0, max_val}))
// Down counter assumption to control underflow
// We can also constrain the down counter underflow via `down_cnt % step_i == 0`.
// However, modulo operation can be very complex for formal analysis.
`ASSUME(DownCntStepInt_A, down_cnt == 0 || down_cnt >= step_i)
// Up counter assumption to control overflow
logic [Width:0] unused_cnt;
assign unused_cnt = up_cnt_q[0] + step_i;
logic unused_incr_cnt;
assign unused_incr_cnt = !clr_i & !set_i & en_i;
`ASSUME(UpCntOverFlow_A, unused_incr_cnt && !err |-> ~unused_cnt[Width])
end else if (CntStyle == DupCnt) begin : gen_dup_cnt_hardening
// duplicate count compare is always valid
assign cnt_o = up_cnt_q[0];
assign err = (up_cnt_q[0] != up_cnt_q[1]);
`ASSERT(DupCntErrForward_A, up_cnt_q[0] != up_cnt_q[1] |-> err_o)
`ASSERT(DupCntErrBackward_A, err_o |-> up_cnt_q[0] != up_cnt_q[1])
end
assign err_o = err;
// ASSERTIONS AND ASSUMPTIONS
`ifdef INC_ASSERT
// Helper variables to hold the previous valid `cnt_o` and `step_i` when `en_i` is set.
logic [Width-1:0] past_cnt_o, past_step_i;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
past_cnt_o <= cnt_o;
past_step_i <= step_i;
end else if (en_i) begin
past_cnt_o <= cnt_o;
past_step_i <= step_i;
end
end
`endif
// Clear and set should not be seen at the same time
`ASSUME(SimulClrSet_A, clr_i || set_i |-> clr_i != set_i)
// when the counter is forced by TB, it can be any value, but we should see err_o is set.
`ASSERT(OutClr_A, clr_i |=> (OutSelDnCnt ? &cnt_o : cnt_o == 0) || err_o)
// When `en_i` is set without `clr_i` and `set_i`, and counter does not reach max/min value,
// we expect `cnt_o` to increment or decrement base on `step_i`, or error occurs
`ASSERT(OutStep_A,
!(clr_i ||set_i) throughout en_i ##[1:$] en_i && max_val > cnt_o && cnt_o > 0 |->
((CntStyle == DupCnt || !OutSelDnCnt) ? cnt_o - past_cnt_o == past_step_i :
past_cnt_o - cnt_o == past_step_i) || err_o)
// When `set_i` is set, at next clock cycle:
// 1). For duplicate counter, sets the `cnt_o` to `set_cnt_i`.
// 2). For cross up counter, sets the `max_value` to `set_cnt_i`.
// 3). For cross down counter, sets the `cnt_o` and `max_value` to `set_cnt_i`.
// 4). error occurs due to a fault injection
`ASSERT(OutSet_A, ##1 set_i |=>
((CntStyle == DupCnt || OutSelDnCnt) ? cnt_o == $past(set_cnt_i) : cnt_o == 0) || err_o)
// If the up counter reaches its max value, the value won't increment or change, unless there is
// a fault injection
`ASSERT(MaxUpCntStable_A, up_cnt_q[0] == max_val && !clr_i && !set_i |=>
$stable(up_cnt_q[0]) || err_o || $past(err_o))
// This logic that will be assign to one, when user adds macro
// ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT to check the error with alert, in case that prim_count
// is used in design without adding this assertion check.
`ifdef INC_ASSERT
logic unused_assert_connected;
`ASSERT_INIT_NET(AssertConnected_A, unused_assert_connected === 1'b1 || !EnableAlertTriggerSVA)
`endif
endmodule // prim_count
`define ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(NAME_, PRIM_HIER_, ALERT_, MAX_CYCLES_ = 5) \
`ASSERT(NAME_, $rose(PRIM_HIER_.err_o) |-> ##[1:MAX_CYCLES_] $rose(ALERT_.alert_p)) \
`ifdef INC_ASSERT \
assign PRIM_HIER_.unused_assert_connected = 1'b1; \
`endif