diff options
Diffstat (limited to '')
| -rw-r--r-- | rtl/shifter.sv | 109 |
1 files changed, 109 insertions, 0 deletions
diff --git a/rtl/shifter.sv b/rtl/shifter.sv new file mode 100644 index 0000000..953560e --- /dev/null +++ b/rtl/shifter.sv @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: CERN-OHL-P-2.0 */ + +`include "types.svh" + +module shifter #( + parameter int unsigned XLEN = config_pkg::XLEN, + parameter int unsigned SLICE_WIDTH = config_pkg::SLICE_WIDTH, + + localparam int unsigned SLICE_ADDR_WIDTH = $clog2(XLEN / SLICE_WIDTH), + localparam int unsigned SLICE_SHAMT_WIDTH = $clog2(SLICE_WIDTH) +) ( + input logic clk_i, + input logic rst_ni, + + /* Shift direction. 0: left; 1: right. */ + input logic shift_type_i, + /* Arithmetic shift. */ + input logic shift_arithmetic_i, + input logic [$clog2(XLEN)-1:0] shamt_i, + + input slice_t data_i, + input logic [SLICE_ADDR_WIDTH-1:0] slice_i, + + output slice_t result_o, + output logic [SLICE_ADDR_WIDTH-1:0] slice_o +); + + slice_t next_slice_d, next_slice_q; + + /* Slice shift amounts. shamt1: bits kept in this slice; shamt2: bits spilled into + * the carry for the next slice. If shamt1 is 0, then the carry should be 0. */ + logic next_slice_zero; + logic [SLICE_SHAMT_WIDTH-1:0] shamt1, shamt2; + assign shamt1 = shamt_i[SLICE_SHAMT_WIDTH-1:0]; + assign {next_slice_zero, shamt2} + = (SLICE_SHAMT_WIDTH+1)'(SLICE_WIDTH) - (SLICE_SHAMT_WIDTH+1)'(shamt1); + + /* Signed difference between input and output slices. Must be 1 bit wider + * so the wrap can be detected in the MSB. */ + logic [SLICE_ADDR_WIDTH:0] slice_diff; + assign slice_diff = (SLICE_ADDR_WIDTH+1)'(slice_i) - (SLICE_ADDR_WIDTH+1)'(slice_o); + + /* Latch the fill bit once, on the most significant slice of a right shift. */ + logic fill_d, fill_q; + assign fill_d = (shift_type_i && &slice_i) + ? (shift_arithmetic_i ? data_i[SLICE_WIDTH-1] : '0) + : fill_q; + + always_comb begin + /* Right shifts: goes from most to least significant slice. */ + if (shift_type_i) begin + /* Vacated slice: fill with the fill bit (0 for logical shifts, + * sign bit for arithmetic shifts). */ + if (slice_diff[SLICE_ADDR_WIDTH]) begin + result_o = {SLICE_WIDTH{fill_q}}; + + /* First slice: shift only as there is no carry. */ + end else if (&slice_i) begin + if (shift_arithmetic_i) begin + result_o = $signed(data_i) >>> shamt1; + end else begin + result_o = data_i >> shamt1; + end + + /* Other slices: shift and combine with the carried bits. */ + end else begin + result_o = next_slice_q | (data_i >> shamt1); + end + + /* These are the bits that spill past the slice's boundary. */ + next_slice_d = next_slice_zero ? '0 : (data_i << shamt2); + + /* Output slice address. */ + slice_o = slice_i - SLICE_ADDR_WIDTH'(shamt_i >> SLICE_SHAMT_WIDTH); + + /* Left shifts: goes from least to most significant slice. */ + end else begin + /* Vacated slice: fill with 0. */ + if (!slice_diff[SLICE_ADDR_WIDTH] && |slice_diff) begin + result_o = '0; + + /* First slice: shift only as there is no carry. */ + end else if (~|slice_i) begin + result_o = data_i << shamt1; + + /* Other slices: shift and combine with the carried bits. */ + end else begin + result_o = next_slice_q | (data_i << shamt1); + end + + /* These are the bits that spill past the slice's boundary. */ + next_slice_d = next_slice_zero ? '0 : (data_i >> shamt2); + + /* Output slice address. */ + slice_o = slice_i + SLICE_ADDR_WIDTH'(shamt_i >> SLICE_SHAMT_WIDTH); + end + end + + always_ff @(posedge clk_i) begin + if (!rst_ni) begin + next_slice_q <= '0; + fill_q <= '0; + end else begin + next_slice_q <= next_slice_d; + fill_q <= fill_d; + end + end + +endmodule : shifter |