blob: 106d1dd3e5e2e9c4c0ae6cfea67bac088aa5865b [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Combine InW data and write to OutW data if packed to full word or stop signal
`include "prim_assert.sv"
// ICEBOX(#12958): Revise to send out the empty status.
module prim_packer #(
parameter int unsigned InW = 32,
parameter int unsigned OutW = 32,
// If 1, The input/output are byte granularity
parameter int HintByteData = 0,
// Turn on protect against FI for the pos variable
parameter bit EnProtection = 1'b 0
) (
input clk_i ,
input rst_ni,
input valid_i,
input [InW-1:0] data_i,
input [InW-1:0] mask_i,
output ready_o,
output logic valid_o,
output logic [OutW-1:0] data_o,
output logic [OutW-1:0] mask_o,
input ready_i,
input flush_i, // If 1, send out remnant and clear state
output logic flush_done_o,
// When EnProtection is set, err_o raises an error case (position variable
// mismatch)
output logic err_o
);
localparam int unsigned Width = InW + OutW; // storage width
localparam int unsigned ConcatW = Width + InW; // Input concatenated width
localparam int unsigned PtrW = $clog2(ConcatW+1);
localparam int unsigned IdxW = prim_util_pkg::vbits(InW);
localparam int unsigned OnesCntW = $clog2(InW+1);
logic valid_next, ready_next;
logic [Width-1:0] stored_data, stored_mask;
logic [ConcatW-1:0] concat_data, concat_mask;
logic [ConcatW-1:0] shiftl_data, shiftl_mask;
logic [InW-1:0] shiftr_data, shiftr_mask;
logic [PtrW-1:0] pos_q; // Current write position
logic [IdxW-1:0] lod_idx; // result of Leading One Detector
logic [OnesCntW-1:0] inmask_ones; // Counting Ones for mask_i
logic ack_in, ack_out;
logic flush_valid; // flush data out request
logic flush_done;
// Computing next position ==================================================
always_comb begin
// counting mask_i ones
inmask_ones = '0;
for (int i = 0 ; i < InW ; i++) begin
inmask_ones = inmask_ones + OnesCntW'(mask_i[i]);
end
end
logic [PtrW-1:0] pos_with_input;
assign pos_with_input = pos_q + PtrW'(inmask_ones);
if (EnProtection == 1'b 0) begin : g_pos_nodup
logic [PtrW-1:0] pos_d;
always_comb begin
pos_d = pos_q;
unique case ({ack_in, ack_out})
2'b00: pos_d = pos_q;
2'b01: pos_d = (int'(pos_q) <= OutW) ? '0 : pos_q - PtrW'(OutW);
2'b10: pos_d = pos_with_input;
2'b11: pos_d = (int'(pos_with_input) <= OutW) ? '0 : pos_with_input - PtrW'(OutW);
default: pos_d = pos_q;
endcase
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
pos_q <= '0;
end else if (flush_done) begin
pos_q <= '0;
end else begin
pos_q <= pos_d;
end
end
assign err_o = 1'b 0; // No checker logic
end else begin : g_pos_dupcnt // EnProtection == 1'b 1
// incr_en: Increase the pos by cnt_step. ack_in && !ack_out
// decr_en: Decrease the pos by cnt_step. !ack_in && ack_out
// set_en: Set to specific value in case of ack_in && ack_out.
// This case, the value could be increased or descreased based on
// the input size (inmask_ones)
logic cnt_incr_en, cnt_decr_en, cnt_set_en;
logic [PtrW-1:0] cnt_step, cnt_set;
assign cnt_incr_en = ack_in && !ack_out;
assign cnt_decr_en = !ack_in && ack_out;
assign cnt_set_en = ack_in && ack_out;
// counter has underflow protection.
assign cnt_step = (cnt_incr_en) ? PtrW'(inmask_ones) : PtrW'(OutW);
always_comb begin : cnt_set_logic
// default, consuming all data
cnt_set = '0;
if (pos_with_input > PtrW'(OutW)) begin
// pos_q + inmask_ones is bigger than Output width. Still data remained.
cnt_set = pos_with_input - PtrW'(OutW);
end
end : cnt_set_logic
prim_count #(
.Width (PtrW),
.ResetValue ('0 )
) u_pos (
.clk_i,
.rst_ni,
.clr_i (flush_done),
.set_i (cnt_set_en),
.set_cnt_i (cnt_set ),
.incr_en_i (cnt_incr_en),
.decr_en_i (cnt_decr_en),
.step_i (cnt_step ),
.cnt_o (pos_q ), // Current counter state
.cnt_next_o ( ), // Next counter state
.err_o
);
end // g_pos_dupcnt
//---------------------------------------------------------------------------
// Leading one detector for mask_i
always_comb begin
lod_idx = 0;
for (int i = InW-1; i >= 0 ; i--) begin
if (mask_i[i] == 1'b1) begin
lod_idx = IdxW'(unsigned'(i));
end
end
end
assign ack_in = valid_i & ready_o;
assign ack_out = valid_o & ready_i;
// Data process =============================================================
// shiftr : Input data shifted right to put the leading one at bit zero
assign shiftr_data = (valid_i) ? data_i >> lod_idx : '0;
assign shiftr_mask = (valid_i) ? mask_i >> lod_idx : '0;
// shiftl : Input data shifted into the current stored position
assign shiftl_data = ConcatW'(shiftr_data) << pos_q;
assign shiftl_mask = ConcatW'(shiftr_mask) << pos_q;
// concat : Merging stored and shiftl
assign concat_data = {{(InW){1'b0}}, stored_data & stored_mask} |
(shiftl_data & shiftl_mask);
assign concat_mask = {{(InW){1'b0}}, stored_mask} | shiftl_mask;
logic [Width-1:0] stored_data_next, stored_mask_next;
always_comb begin
unique case ({ack_in, ack_out})
2'b 00: begin
stored_data_next = stored_data;
stored_mask_next = stored_mask;
end
2'b 01: begin
// ack_out : shift the amount of OutW
stored_data_next = {{OutW{1'b0}}, stored_data[Width-1:OutW]};
stored_mask_next = {{OutW{1'b0}}, stored_mask[Width-1:OutW]};
end
2'b 10: begin
// ack_in : Store concat data
stored_data_next = concat_data[0+:Width];
stored_mask_next = concat_mask[0+:Width];
end
2'b 11: begin
// both : shift the concat_data
stored_data_next = concat_data[ConcatW-1:OutW];
stored_mask_next = concat_mask[ConcatW-1:OutW];
end
default: begin
stored_data_next = stored_data;
stored_mask_next = stored_mask;
end
endcase
end
// Store the data temporary if it doesn't exceed OutW
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
stored_data <= '0;
stored_mask <= '0;
end else if (flush_done) begin
stored_data <= '0;
stored_mask <= '0;
end else begin
stored_data <= stored_data_next;
stored_mask <= stored_mask_next;
end
end
//---------------------------------------------------------------------------
// flush handling
typedef enum logic {
FlushIdle,
FlushSend
} flush_st_e;
flush_st_e flush_st, flush_st_next;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
flush_st <= FlushIdle;
end else begin
flush_st <= flush_st_next;
end
end
always_comb begin
flush_st_next = FlushIdle;
flush_valid = 1'b0;
flush_done = 1'b0;
unique case (flush_st)
FlushIdle: begin
if (flush_i) begin
flush_st_next = FlushSend;
end else begin
flush_st_next = FlushIdle;
end
end
FlushSend: begin
if (pos_q == '0) begin
flush_st_next = FlushIdle;
flush_valid = 1'b 0;
flush_done = 1'b 1;
end else begin
flush_st_next = FlushSend;
flush_valid = 1'b 1;
flush_done = 1'b 0;
end
end
default: begin
flush_st_next = FlushIdle;
flush_valid = 1'b 0;
flush_done = 1'b 0;
end
endcase
end
assign flush_done_o = flush_done;
// Output signals ===========================================================
assign valid_next = (int'(pos_q) >= OutW) ? 1'b 1 : flush_valid;
// storage space is InW + OutW. So technically, ready_o can be asserted even
// if `pos_q` is greater than OutW. But in order to do that, the logic should
// use `inmask_ones` value whether pos_q+inmask_ones is less than (InW+OutW)
// with `valid_i`. It creates a path from `valid_i` --> `ready_o`.
// It may create a timing loop in some modules that use `ready_o` to
// `valid_i` (which is not a good practice though)
assign ready_next = int'(pos_q) <= OutW;
// Output request
assign valid_o = valid_next;
assign data_o = stored_data[OutW-1:0];
assign mask_o = stored_mask[OutW-1:0];
// ready_o
assign ready_o = ready_next;
//---------------------------------------------------------------------------
//////////////////////////////////////////////
// Assertions, Assumptions, and Coverpoints //
//////////////////////////////////////////////
// Assumption: mask_i should be contiguous ones
// e.g: 0011100 --> OK
// 0100011 --> Not OK
if (InW > 1) begin : gen_mask_assert
`ASSUME(ContiguousOnesMask_M,
valid_i |-> $countones(mask_i ^ {mask_i[InW-2:0],1'b0}) <= 2)
end
// Flush and Write Enable cannot be asserted same time
`ASSUME(ExFlushValid_M, flush_i |-> !valid_i)
// While in flush state, new request shouldn't come
`ASSUME(ValidIDeassertedOnFlush_M,
flush_st == FlushSend |-> $stable(valid_i))
// If not acked, input port keeps asserting valid and data
`ASSUME(DataIStable_M,
##1 valid_i && $past(valid_i) && !$past(ready_o)
|-> $stable(data_i) && $stable(mask_i))
`ASSERT(FlushFollowedByDone_A,
##1 $rose(flush_i) && !flush_done_o |-> !flush_done_o [*0:$] ##1 flush_done_o)
// If not acked, valid_o should keep asserting
`ASSERT(ValidOPairedWidthReadyI_A,
valid_o && !ready_i |=> valid_o)
// If stored data is greater than the output width, valid should be asserted
`ASSERT(ValidOAssertedForStoredDataGTEOutW_A,
($countones(stored_mask) >= OutW) |-> valid_o)
// If output port doesn't accept the data, the data should be stable
`ASSERT(DataOStableWhenPending_A,
##1 valid_o && $past(valid_o)
&& !$past(ready_i) |-> $stable(data_o))
// If input data & stored data are greater than OutW, remained should be stored
`ASSERT(ExcessiveDataStored_A,
ack_in && ack_out && (($countones(mask_i) + $countones(stored_mask)) > OutW)
|=> (($past(data_i) & $past(mask_i)) >>
($past(lod_idx)+OutW-$countones($past(stored_mask))))
== stored_data)
`ASSERT(ExcessiveMaskStored_A,
ack_in && ack_out && (($countones(mask_i) + $countones(stored_mask)) > OutW)
|=> ($past(mask_i) >>
($past(lod_idx)+OutW-$countones($past(stored_mask))))
== stored_mask)
// Assertions for byte hint enabled
if (HintByteData != 0) begin : g_byte_assert
`ASSERT_INIT(InputDividedBy8_A, InW % 8 == 0)
`ASSERT_INIT(OutputDividedBy8_A, OutW % 8 == 0)
// Masking[8*i+:8] should be all zero or all one
for (genvar i = 0 ; i < InW/8 ; i++) begin : g_byte_input_masking
`ASSERT(InputMaskContiguous_A,
valid_i |-> (|mask_i[8*i+:8] == 1'b 0)
|| (&mask_i[8*i+:8] == 1'b 1))
end
for (genvar i = 0 ; i < OutW/8 ; i++) begin : g_byte_output_masking
`ASSERT(OutputMaskContiguous_A,
valid_o |-> (|mask_o[8*i+:8] == 1'b 0)
|| (&mask_o[8*i+:8] == 1'b 1))
end
end
endmodule