Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
e73baaec2This file is pinned to the selected release. Download the original to open it in your design tool.
// Copyright 2025 ETH Zurich and University of Bologna.
// Solderpad Hardware License, Version 0.51, see LICENSE for details.
// SPDX-License-Identifier: SHL-0.51
//
// Author: Luca Colagrande <colluca@iis.ee.ethz.ch>
//
// This module implements a flexible ring buffer with decoupled write and read access.
// It supports sequential writes and random reads with a valid/ready handshake protocol.
// The buffer is implemented using a dual-pointer mechanism with one-bit extension to
// distinguish full from empty state. It supports wrapping around when full capacity is used.
//
// The read interface supports restricted random access: a consumer may request to read
// any address `raddr_i` as long as it falls within the valid range between the current
// read and write pointers. This allows re-reading previous entries.
//
// The read pointer is advanced independently using a dedicated `advance_i` and `step_i`
// interface. This decouples read consumption from read address requests.
`include "common_cells/registers.svh"
`include "common_cells/assertions.svh"
module cc_ring_buffer #(
parameter int unsigned Depth = 32,
parameter type data_t = logic,
/// Derived parameter *Do not override*
localparam int unsigned AddrWidth = cc_pkg::idx_width(Depth),
localparam int unsigned StepWidth = cc_pkg::idx_width(Depth+1),
localparam type addr_t = logic [AddrWidth-1:0],
localparam type step_t = logic [StepWidth-1:0]
) (
input logic clk_i, // Clock
input logic rst_ni, // Asynchronous reset active low
input logic clr_i, // Synchronous clear active high
// Write interface
input logic wvalid_i,
output logic wready_o,
input data_t wdata_i,
// Restricted random access read interface
input logic rvalid_i,
output logic rready_o,
input addr_t raddr_i,
output data_t rdata_o,
// Independent read pointer increment interface.
// Increments the read pointer by `step_i` when `advance_i` is asserted.
input logic advance_i,
input step_t step_i,
// Status signals
output addr_t wptr_o,
output addr_t rptr_o,
output logic full_o,
output logic empty_o
);
///////////
// State //
///////////
data_t [Depth-1:0] mem_d, mem_q;
// We allocate one more bit than needed to represent memory
// addresses, to compute full/empty status of the buffer.
logic [AddrWidth:0] rptr_d, rptr_q;
logic [AddrWidth:0] wptr_d, wptr_q;
`FFARNC(mem_q, mem_d, clr_i, '0, clk_i, rst_ni)
`FFARNC(rptr_q, rptr_d, clr_i, '0, clk_i, rst_ni)
`FFARNC(wptr_q, wptr_d, clr_i, '0, clk_i, rst_ni)
////////////////////////////
// State transition logic //
////////////////////////////
always_comb begin
// Default assignments
mem_d = mem_q;
rptr_d = rptr_q;
wptr_d = wptr_q;
// Write logic
if (wvalid_i && wready_o) begin
mem_d[wptr_q[AddrWidth-1:0]] = wdata_i;
wptr_d = wptr_q + 1;
end
// Read pointer increment logic
if (advance_i) begin
rptr_d = rptr_q + step_i;
end
end
//////////////////
// Output logic //
//////////////////
assign wptr_o = wptr_q[AddrWidth-1:0];
assign rptr_o = rptr_q[AddrWidth-1:0];
assign empty_o = wptr_q == rptr_q;
assign full_o = (wptr_q[AddrWidth-1:0] == rptr_q[AddrWidth-1:0]) && !empty_o;
// A read request can only be accepted if it is within the range of
// valid instructions (i.e. between the read and write pointers).
// This ready signal is provided as a backpressure mechanism to wait
// for the write pointer to advance, until the requested instruction
// is present in the ring buffer.
// When rptr_o < wptr_o, the valid range is [rptr_o, wptr_o).
// When rptr_o > wptr_o (wrap-around), the valid range is [rptr_o, Depth) U [0, wptr_o).
// When rptr_o == wptr_o and !empty (buffer is full), all addresses are valid.
assign rready_o = ((rptr_o < wptr_o) && ((raddr_i >= rptr_o) && (raddr_i < wptr_o))) ||
((rptr_o > wptr_o) && ((raddr_i >= rptr_o) || (raddr_i < wptr_o))) ||
((rptr_o == wptr_o) && !empty_o);
assign wready_o = !full_o;
assign rdata_o = mem_q[raddr_i];
////////////////
// Assertions //
////////////////
// Auxiliary signal for assertion
step_t max_step;
// Calculate maximum allowed step size, beyond which read pointer would overtake write pointer.
always_comb begin
if (rptr_q[AddrWidth] == wptr_d[AddrWidth])
max_step = wptr_d[AddrWidth-1:0] - rptr_q[AddrWidth-1:0];
else
max_step = Depth - rptr_q[AddrWidth-1:0] + wptr_d[AddrWidth-1:0];
end
`ifndef COMMON_CELLS_ASSERTS_OFF
// Read pointer should not overtake write pointer.
`ASSERT(
ReadPtrOvertakesWritePtr,
advance_i |-> step_i <= max_step,
clk_i, !rst_ni,
"Attempting to increment rptr beyond wptr"
);
// Write pointer should not overtake read pointer.
`ASSERT(
WritePtrOvertakesReadPtr,
wvalid_i && wready_o
|-> !((wptr_q[AddrWidth-1:0] == rptr_d[AddrWidth-1:0]) && !(wptr_q == rptr_d)),
clk_i, !rst_ni,
"Attempting to increment wptr beyond rptr"
);
// When rptr_o < wptr_o, the valid range is [rptr_o, wptr_o).
// When rptr_o > wptr_o (wrap-around), the valid range is [rptr_o, Depth) U [0, wptr_o).
// When rptr_o == wptr_o and !empty (buffer is full), all addresses are valid.
`ASSERT(
ReadAddrOutOfBounds,
rvalid_i && rready_o |->
(
((rptr_o < wptr_o) && ((raddr_i >= rptr_o) && (raddr_i < wptr_o))) ||
((rptr_o > wptr_o) && ((raddr_i >= rptr_o) || (raddr_i < wptr_o))) ||
((rptr_o == wptr_o) && !empty_o)
),
clk_i, !rst_ni,
"raddr_i is not within the valid range defined by rptr_o and wptr_o"
);
// Interfaces should be stable when valid is asserted but not ready
`ASSERT_STABLE(WriteStable, wvalid_i, wready_o, wdata_i)
`ASSERT_STABLE(ReadStable, rvalid_i, rready_o, raddr_i)
// Currently only power-of-2 depths are supported, could be loosened in future
`ASSERT_INIT(CheckDepthPow2, cc_pkg::is_power_of_2(Depth))
`endif
endmodule