blob: b19e0864a19a77985562c2e375e59b0427e15d56 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
/**
* Simple stack parameterised on width and depth
*
* When a push and pop occur in the same cycle the pop is ordered before the push (so top_data_o
* reflects what was on top of the stack, which is retrieved by the pop, the push then immediately
* replaces this with a new piece of data). Internal checking is performed for full & empty
* conditions so a push on full/pop on empty is allowable, though meaningless. For a push on full
* the data will be dropped, for a pop no empty there is no valid data to pop. The exception is
* a combined push & pop on full, here the top is popped off and replaced with what is pushed, no
* data is dropped.
*
* The read and write pointers and read and write signals are exposed as `stack_rd_idx_i,
* `stack_wr_idx_o`, `stack_write_o` and `stack_read_o`. `next_top_data_o` and `next_top_valid_o`
* provide the top_data_o and top_valid_o output that will be seen in the following cycle. This is
* to enable users to extend the stack in case where it's not a simple matter of adding extra data
* bits (e.g. where this is a prim_count instance per stack entry).
*/
module otbn_stack
import otbn_pkg::*;
#(
parameter int unsigned StackWidth = 32,
parameter int unsigned StackDepth = 4,
localparam int unsigned StackDepthW = prim_util_pkg::vbits(StackDepth)
) (
input clk_i,
input rst_ni,
output logic full_o, // Stack is full
output logic cnt_err_o, // Stack counters are wrong
input logic clear_i, // Clear all data
input logic push_i, // Push the data
input logic [StackWidth-1:0] push_data_i, // Data to push
input logic pop_i, // Pop top of the stack
output logic [StackWidth-1:0] top_data_o, // Data on top of the stack
output logic top_valid_o, // Stack is non empty (`top_data_o` is valid)
output logic [StackDepthW-1:0] stack_wr_idx_o,
output logic stack_write_o,
output logic [StackDepthW-1:0] stack_rd_idx_o,
output logic stack_read_o,
output logic [StackWidth-1:0] next_top_data_o,
output logic next_top_valid_o
);
logic [StackWidth-1:0] stack_storage [StackDepth];
logic [StackDepthW:0] stack_wr_ptr;
logic [StackDepthW-1:0] stack_top_idx, stack_rd_idx, stack_wr_idx;
logic [StackDepthW-1:0] next_stack_rd_idx, next_stack_top_idx;
logic [StackDepthW-1:0] stack_top_idx_step;
logic stack_empty;
logic stack_full;
logic stack_write;
logic stack_read;
assign stack_empty = stack_wr_ptr == '0;
assign stack_full = stack_wr_ptr == StackDepth[StackDepthW:0];
assign stack_write = push_i & (~full_o | pop_i);
assign stack_read = top_valid_o & pop_i;
assign stack_top_idx = stack_wr_ptr[StackDepthW-1:0];
assign stack_rd_idx = stack_top_idx - 1'b1;
assign stack_wr_idx = pop_i ? stack_rd_idx : stack_top_idx;
logic signed [StackDepthW:0] stack_wr_ptr_step;
always_comb begin
unique case ({stack_write, stack_read})
2'b10: stack_wr_ptr_step = 1;
2'b01: stack_wr_ptr_step = -1;
default: stack_wr_ptr_step = '0;
endcase
end
// SEC_CM: STACK_WR_PTR.CTR.GLITCH_DETECT
// Detect glitches on the `step_i` input of the stack write pointer. If a glitch is detected,
// latch it until the stack gets cleared (or the module is reset). Detecting glitches on the
// clock edge instead of combinationally is required because the error output drives the control
// path, thus feeding the glitch detector output back combinationally would result in
// combinational loops.
logic stack_wr_ptr_step_err_d, stack_wr_ptr_step_err_q;
always_comb begin
stack_wr_ptr_step_err_d = stack_wr_ptr_step_err_q;
if (clear_i) stack_wr_ptr_step_err_d = 1'b0;
if (stack_wr_ptr_step > 1 || stack_wr_ptr_step < -1) stack_wr_ptr_step_err_d = 1'b1;
if (stack_wr_ptr_step == 1 && !stack_write) stack_wr_ptr_step_err_d = 1'b1;
if (stack_wr_ptr_step == -1 && !stack_read) stack_wr_ptr_step_err_d = 1'b1;
if (stack_wr_ptr_step == 0 && (stack_write ^ stack_read)) stack_wr_ptr_step_err_d = 1'b1;
end
logic stack_wr_ptr_step_err_buf;
prim_buf #(
.Width (1)
) u_stack_wr_ptr_step_err_buf (
.in_i (stack_wr_ptr_step_err_d),
.out_o (stack_wr_ptr_step_err_buf)
);
always_ff @(posedge clk_i, negedge rst_ni) begin
if (!rst_ni) begin
stack_wr_ptr_step_err_q <= 1'b0;
end else begin
stack_wr_ptr_step_err_q <= stack_wr_ptr_step_err_buf;
end
end
logic stack_wr_ptr_en;
assign stack_wr_ptr_en = stack_wr_ptr_step != '0;
// SEC_CM: STACK_WR_PTR.CTR.REDUN
logic stack_wr_ptr_err;
prim_count #(
.Width (StackDepthW+1),
.OutSelDnCnt (0),
.CntStyle (prim_count_pkg::DupCnt)
) u_stack_wr_ptr (
.clk_i,
.rst_ni,
.clr_i (clear_i),
.set_i (1'b0),
.set_cnt_i ('0),
.en_i (stack_wr_ptr_en),
// Since this signal has the same width as the counter, negative values will
// be correctly be subtracted due to the 2s complement representation.
.step_i (unsigned'(stack_wr_ptr_step)),
.cnt_o (stack_wr_ptr),
.err_o (stack_wr_ptr_err)
);
always_ff @(posedge clk_i) begin
if (stack_write) begin
stack_storage[stack_wr_idx] <= push_data_i;
end
end
assign full_o = stack_full;
assign top_data_o = stack_storage[stack_rd_idx];
assign top_valid_o = ~stack_empty;
assign cnt_err_o = stack_wr_ptr_err | stack_wr_ptr_step_err_q;
assign stack_wr_idx_o = stack_wr_idx;
assign stack_rd_idx_o = stack_rd_idx;
assign stack_write_o = stack_write;
assign stack_read_o = stack_read;
always_comb begin
next_stack_top_idx = '0;
stack_top_idx_step = '0;
if (clear_i) begin
next_stack_top_idx = '0;
end else begin
if (stack_wr_ptr_en) begin
stack_top_idx_step = stack_wr_ptr_step[StackDepthW-1:0];
end
next_stack_top_idx = stack_top_idx + stack_top_idx_step;
end
end
assign next_stack_rd_idx = next_stack_top_idx - 1'b1;
assign next_top_data_o = stack_write ? push_data_i : stack_storage[next_stack_rd_idx];
assign next_top_valid_o = next_stack_top_idx != '0;
`ASSERT(next_stack_top_idx_correct, stack_top_idx == $past(next_stack_top_idx))
endmodule