mirror of
https://github.com/lowRISC/ibex.git
synced 2025-06-27 17:00:41 -04:00
This change has been informed by advice from the lowRISC legal committee. The Solderpad 0.51 license states "the Licensor permits any Work licensed under this License, at the option of the Licensee, to be treated as licensed under the Apache License Version 2.0". We use this freedom to convert license markings to Apache 2.0. This commit ensures that we retain all authorship and copyright attribution information.
494 lines
16 KiB
Systemverilog
494 lines
16 KiB
Systemverilog
// Copyright lowRISC contributors.
|
|
// Copyright 2018 ETH Zurich and University of Bologna.
|
|
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Engineer: Igor Loi - igor.loi@unibo.it //
|
|
// //
|
|
// Additional contributions by: //
|
|
// Andreas Traber - atraber@iis.ee.ethz.ch //
|
|
// Markus Wegmann - markus.wegmann@technokrat.ch //
|
|
// Davide Schiavone - pschiavo@iis.ee.ethz.ch //
|
|
// //
|
|
// Design Name: Load Store Unit //
|
|
// Project Name: ibex //
|
|
// Language: SystemVerilog //
|
|
// //
|
|
// Description: Load Store Unit, used to eliminate multiple access during //
|
|
// processor stalls, and to align bytes and halfwords //
|
|
// //
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
`include "ibex_config.sv"
|
|
|
|
module ibex_load_store_unit
|
|
(
|
|
input logic clk,
|
|
input logic rst_n,
|
|
|
|
// output to data memory
|
|
output logic data_req_o,
|
|
input logic data_gnt_i,
|
|
input logic data_rvalid_i,
|
|
input logic data_err_i,
|
|
|
|
output logic [31:0] data_addr_o,
|
|
output logic data_we_o,
|
|
output logic [3:0] data_be_o,
|
|
output logic [31:0] data_wdata_o,
|
|
input logic [31:0] data_rdata_i,
|
|
|
|
// signals from ex stage
|
|
input logic data_we_ex_i, // write enable -> from ex stage
|
|
input logic [1:0] data_type_ex_i, // Data type word, halfword, byte -> from ex stage
|
|
input logic [31:0] data_wdata_ex_i, // data to write to memory -> from ex stage
|
|
input logic [1:0] data_reg_offset_ex_i, // offset inside register for stores -> from ex stage
|
|
input logic data_sign_ext_ex_i, // sign extension -> from ex stage
|
|
|
|
output logic [31:0] data_rdata_ex_o, // requested data -> to ex stage
|
|
input logic data_req_ex_i, // data request -> from ex stage
|
|
|
|
input logic [31:0] adder_result_ex_i,
|
|
|
|
output logic data_misaligned_o, // misaligned access was detected -> to controller
|
|
output logic [31:0] misaligned_addr_o,
|
|
|
|
// exception signals
|
|
output logic load_err_o,
|
|
output logic store_err_o,
|
|
|
|
// stall signal
|
|
output logic lsu_update_addr_o, // LSU ready for new data in EX stage
|
|
output logic data_valid_o,
|
|
|
|
output logic busy_o
|
|
);
|
|
|
|
logic [31:0] data_addr_int;
|
|
|
|
// registers for data_rdata alignment and sign extension
|
|
logic [1:0] data_type_q;
|
|
logic [1:0] rdata_offset_q;
|
|
logic data_sign_ext_q;
|
|
logic data_we_q;
|
|
|
|
logic [1:0] wdata_offset; // mux control for data to be written to memory
|
|
|
|
logic [3:0] data_be;
|
|
logic [31:0] data_wdata;
|
|
|
|
logic misaligned_st; // high if we are currently performing the second part of a misaligned store
|
|
logic data_misaligned, data_misaligned_q;
|
|
logic increase_address;
|
|
|
|
enum logic [2:0] { IDLE, WAIT_GNT_MIS, WAIT_RVALID_MIS, WAIT_GNT, WAIT_RVALID } CS, NS;
|
|
|
|
logic [31:0] rdata_q;
|
|
|
|
///////////////////////////////// BE generation ////////////////////////////////
|
|
always_comb
|
|
begin
|
|
case (data_type_ex_i) // Data type 00 Word, 01 Half word, 11,10 byte
|
|
2'b00:
|
|
begin // Writing a word
|
|
if (misaligned_st == 1'b0)
|
|
begin // non-misaligned case
|
|
case (data_addr_int[1:0])
|
|
2'b00: data_be = 4'b1111;
|
|
2'b01: data_be = 4'b1110;
|
|
2'b10: data_be = 4'b1100;
|
|
2'b11: data_be = 4'b1000;
|
|
endcase; // case (data_addr_int[1:0])
|
|
end
|
|
else
|
|
begin // misaligned case
|
|
case (data_addr_int[1:0])
|
|
2'b00: data_be = 4'b0000; // this is not used, but included for completeness
|
|
2'b01: data_be = 4'b0001;
|
|
2'b10: data_be = 4'b0011;
|
|
2'b11: data_be = 4'b0111;
|
|
endcase; // case (data_addr_int[1:0])
|
|
end
|
|
end
|
|
|
|
2'b01:
|
|
begin // Writing a half word
|
|
if (misaligned_st == 1'b0)
|
|
begin // non-misaligned case
|
|
case (data_addr_int[1:0])
|
|
2'b00: data_be = 4'b0011;
|
|
2'b01: data_be = 4'b0110;
|
|
2'b10: data_be = 4'b1100;
|
|
2'b11: data_be = 4'b1000;
|
|
endcase; // case (data_addr_int[1:0])
|
|
end
|
|
else
|
|
begin // misaligned case
|
|
data_be = 4'b0001;
|
|
end
|
|
end
|
|
|
|
2'b10,
|
|
2'b11: begin // Writing a byte
|
|
case (data_addr_int[1:0])
|
|
2'b00: data_be = 4'b0001;
|
|
2'b01: data_be = 4'b0010;
|
|
2'b10: data_be = 4'b0100;
|
|
2'b11: data_be = 4'b1000;
|
|
endcase; // case (data_addr_int[1:0])
|
|
end
|
|
endcase; // case (data_type_ex_i)
|
|
end
|
|
|
|
// prepare data to be written to the memory
|
|
// we handle misaligned accesses, half word and byte accesses and
|
|
// register offsets here
|
|
assign wdata_offset = data_addr_int[1:0] - data_reg_offset_ex_i[1:0];
|
|
always_comb
|
|
begin
|
|
case (wdata_offset)
|
|
2'b00: data_wdata = data_wdata_ex_i[31:0];
|
|
2'b01: data_wdata = {data_wdata_ex_i[23:0], data_wdata_ex_i[31:24]};
|
|
2'b10: data_wdata = {data_wdata_ex_i[15:0], data_wdata_ex_i[31:16]};
|
|
2'b11: data_wdata = {data_wdata_ex_i[ 7:0], data_wdata_ex_i[31: 8]};
|
|
endcase; // case (wdata_offset)
|
|
end
|
|
|
|
|
|
// FF for rdata alignment and sign-extension
|
|
always_ff @(posedge clk, negedge rst_n)
|
|
begin
|
|
if(rst_n == 1'b0)
|
|
begin
|
|
data_type_q <= '0;
|
|
rdata_offset_q <= '0;
|
|
data_sign_ext_q <= '0;
|
|
data_we_q <= 1'b0;
|
|
end
|
|
else if (data_gnt_i == 1'b1) // request was granted, we wait for rvalid and can continue to WB
|
|
begin
|
|
data_type_q <= data_type_ex_i;
|
|
rdata_offset_q <= data_addr_int[1:0];
|
|
data_sign_ext_q <= data_sign_ext_ex_i;
|
|
data_we_q <= data_we_ex_i;
|
|
end
|
|
end
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// ____ _ _____ _ _ //
|
|
// / ___|(_) __ _ _ __ | ____|_ _| |_ ___ _ __ ___(_) ___ _ __ //
|
|
// \___ \| |/ _` | '_ \ | _| \ \/ / __/ _ \ '_ \/ __| |/ _ \| '_ \ //
|
|
// ___) | | (_| | | | | | |___ > <| || __/ | | \__ \ | (_) | | | | //
|
|
// |____/|_|\__, |_| |_| |_____/_/\_\\__\___|_| |_|___/_|\___/|_| |_| //
|
|
// |___/ //
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
logic [31:0] data_rdata_ext;
|
|
|
|
logic [31:0] rdata_w_ext; // sign extension for words, actually only misaligned assembly
|
|
logic [31:0] rdata_h_ext; // sign extension for half words
|
|
logic [31:0] rdata_b_ext; // sign extension for bytes
|
|
|
|
// take care of misaligned words
|
|
always_comb
|
|
begin
|
|
case (rdata_offset_q)
|
|
2'b00: rdata_w_ext = data_rdata_i[31:0];
|
|
2'b01: rdata_w_ext = {data_rdata_i[ 7:0], rdata_q[31:8]};
|
|
2'b10: rdata_w_ext = {data_rdata_i[15:0], rdata_q[31:16]};
|
|
2'b11: rdata_w_ext = {data_rdata_i[23:0], rdata_q[31:24]};
|
|
endcase
|
|
end
|
|
|
|
// sign extension for half words
|
|
always_comb
|
|
begin
|
|
case (rdata_offset_q)
|
|
2'b00:
|
|
begin
|
|
if (data_sign_ext_q == 1'b0)
|
|
rdata_h_ext = {16'h0000, data_rdata_i[15:0]};
|
|
else
|
|
rdata_h_ext = {{16{data_rdata_i[15]}}, data_rdata_i[15:0]};
|
|
end
|
|
|
|
2'b01:
|
|
begin
|
|
if (data_sign_ext_q == 1'b0)
|
|
rdata_h_ext = {16'h0000, data_rdata_i[23:8]};
|
|
else
|
|
rdata_h_ext = {{16{data_rdata_i[23]}}, data_rdata_i[23:8]};
|
|
end
|
|
|
|
2'b10:
|
|
begin
|
|
if (data_sign_ext_q == 1'b0)
|
|
rdata_h_ext = {16'h0000, data_rdata_i[31:16]};
|
|
else
|
|
rdata_h_ext = {{16{data_rdata_i[31]}}, data_rdata_i[31:16]};
|
|
end
|
|
|
|
2'b11:
|
|
begin
|
|
if (data_sign_ext_q == 1'b0)
|
|
rdata_h_ext = {16'h0000, data_rdata_i[7:0], rdata_q[31:24]};
|
|
else
|
|
rdata_h_ext = {{16{data_rdata_i[7]}}, data_rdata_i[7:0], rdata_q[31:24]};
|
|
end
|
|
endcase // case (rdata_offset_q)
|
|
end
|
|
|
|
// sign extension for bytes
|
|
always_comb
|
|
begin
|
|
case (rdata_offset_q)
|
|
2'b00:
|
|
begin
|
|
if (data_sign_ext_q == 1'b0)
|
|
rdata_b_ext = {24'h00_0000, data_rdata_i[7:0]};
|
|
else
|
|
rdata_b_ext = {{24{data_rdata_i[7]}}, data_rdata_i[7:0]};
|
|
end
|
|
|
|
2'b01: begin
|
|
if (data_sign_ext_q == 1'b0)
|
|
rdata_b_ext = {24'h00_0000, data_rdata_i[15:8]};
|
|
else
|
|
rdata_b_ext = {{24{data_rdata_i[15]}}, data_rdata_i[15:8]};
|
|
end
|
|
|
|
2'b10:
|
|
begin
|
|
if (data_sign_ext_q == 1'b0)
|
|
rdata_b_ext = {24'h00_0000, data_rdata_i[23:16]};
|
|
else
|
|
rdata_b_ext = {{24{data_rdata_i[23]}}, data_rdata_i[23:16]};
|
|
end
|
|
|
|
2'b11:
|
|
begin
|
|
if (data_sign_ext_q == 1'b0)
|
|
rdata_b_ext = {24'h00_0000, data_rdata_i[31:24]};
|
|
else
|
|
rdata_b_ext = {{24{data_rdata_i[31]}}, data_rdata_i[31:24]};
|
|
end
|
|
endcase // case (rdata_offset_q)
|
|
end
|
|
|
|
// select word, half word or byte sign extended version
|
|
always_comb
|
|
begin
|
|
case (data_type_q)
|
|
2'b00: data_rdata_ext = rdata_w_ext;
|
|
2'b01: data_rdata_ext = rdata_h_ext;
|
|
2'b10,2'b11: data_rdata_ext = rdata_b_ext;
|
|
endcase //~case(rdata_type_q)
|
|
end
|
|
|
|
|
|
|
|
always_ff @(posedge clk, negedge rst_n)
|
|
begin
|
|
if(rst_n == 1'b0)
|
|
begin
|
|
CS <= IDLE;
|
|
rdata_q <= '0;
|
|
data_misaligned_q <= '0;
|
|
misaligned_addr_o <= 32'b0;
|
|
end
|
|
else
|
|
begin
|
|
CS <= NS;
|
|
if (lsu_update_addr_o) begin
|
|
data_misaligned_q <= data_misaligned;
|
|
if(increase_address) begin
|
|
misaligned_addr_o <= data_addr_int;
|
|
end
|
|
end
|
|
if (data_rvalid_i && (~data_we_q))
|
|
begin
|
|
// if we have detected a misaligned access, and we are
|
|
// currently doing the first part of this access, then
|
|
// store the data coming from memory in rdata_q.
|
|
// In all other cases, rdata_q gets the value that we are
|
|
// writing to the register file
|
|
|
|
if ((data_misaligned_q == 1'b1) || (data_misaligned == 1'b1))
|
|
rdata_q <= data_rdata_i;
|
|
else
|
|
rdata_q <= data_rdata_ext;
|
|
end
|
|
end
|
|
end
|
|
|
|
// output to register file
|
|
assign data_rdata_ex_o = (data_rvalid_i == 1'b1) ? data_rdata_ext : rdata_q;
|
|
|
|
// output to data interface
|
|
assign data_addr_o = data_addr_int;
|
|
assign data_wdata_o = data_wdata;
|
|
assign data_we_o = data_we_ex_i;
|
|
assign data_be_o = data_be;
|
|
|
|
assign misaligned_st = data_misaligned_q;
|
|
|
|
assign load_err_o = 1'b0;
|
|
assign store_err_o = 1'b0;
|
|
|
|
// FSM
|
|
always_comb
|
|
begin
|
|
NS = CS;
|
|
|
|
data_req_o = 1'b0;
|
|
|
|
lsu_update_addr_o = 1'b0;
|
|
|
|
data_valid_o = 1'b0;
|
|
increase_address = 1'b0;
|
|
data_misaligned_o = 1'b0;
|
|
|
|
case(CS)
|
|
// starts from not active and stays in IDLE until request was granted
|
|
IDLE:
|
|
begin
|
|
if (data_req_ex_i) begin
|
|
data_req_o = data_req_ex_i;
|
|
if(data_gnt_i) begin
|
|
lsu_update_addr_o = 1'b1;
|
|
increase_address = data_misaligned;
|
|
NS = data_misaligned ? WAIT_RVALID_MIS : WAIT_RVALID;
|
|
end
|
|
else begin
|
|
NS = data_misaligned ? WAIT_GNT_MIS : WAIT_GNT;
|
|
end
|
|
end
|
|
end //~ IDLE
|
|
|
|
WAIT_GNT_MIS:
|
|
begin
|
|
data_req_o = 1'b1;
|
|
if(data_gnt_i) begin
|
|
lsu_update_addr_o = 1'b1;
|
|
increase_address = data_misaligned;
|
|
NS = WAIT_RVALID_MIS;
|
|
end
|
|
end //~ WAIT_GNT_MIS
|
|
|
|
// wait for rvalid in WB stage and send a new request if there is any
|
|
WAIT_RVALID_MIS:
|
|
begin
|
|
//increase_address goes down, we already have the proper address
|
|
increase_address = 1'b0;
|
|
//tell the controller to update the address
|
|
data_misaligned_o = 1'b1;
|
|
data_req_o = 1'b0;
|
|
lsu_update_addr_o = data_gnt_i;
|
|
|
|
if(data_rvalid_i) begin
|
|
//if first part rvalid is received
|
|
data_req_o = 1'b1;
|
|
if(data_gnt_i) begin
|
|
//second grant is received
|
|
NS = WAIT_RVALID;
|
|
//in this stage we already received the first valid but no the second one
|
|
//it differes from WAIT_RVALID_MIS because we do not send other requests
|
|
end
|
|
else begin
|
|
//second grant is NOT received, but first rvalid yes
|
|
//lsu_update_addr_o is 0 so data_misaligned_q stays high in WAIT_GNT
|
|
//increase address stays the same as well
|
|
NS = WAIT_GNT; // [1]
|
|
end
|
|
end
|
|
else begin
|
|
//if first part rvalid is NOT received
|
|
//the second grand is not received either by protocol.
|
|
//stay here
|
|
NS = WAIT_RVALID_MIS;
|
|
end
|
|
end
|
|
|
|
WAIT_GNT:
|
|
begin
|
|
data_misaligned_o = data_misaligned_q;
|
|
//useful in case [1]
|
|
data_req_o = 1'b1;
|
|
if(data_gnt_i) begin
|
|
lsu_update_addr_o = 1'b1;
|
|
NS = WAIT_RVALID;
|
|
end
|
|
end //~ WAIT_GNT
|
|
|
|
WAIT_RVALID:
|
|
begin
|
|
data_req_o = 1'b0;
|
|
|
|
if(data_rvalid_i) begin
|
|
data_valid_o = 1'b1;
|
|
NS = IDLE;
|
|
end
|
|
else begin
|
|
NS = WAIT_RVALID;
|
|
end
|
|
end //~ WAIT_RVALID
|
|
|
|
|
|
default: begin
|
|
NS = IDLE;
|
|
end
|
|
endcase
|
|
end
|
|
|
|
// check for misaligned accesses that need a second memory access
|
|
// If one is detected, this is signaled with data_misaligned_o to
|
|
// the controller which selectively stalls the pipeline
|
|
always_comb
|
|
begin
|
|
data_misaligned = 1'b0;
|
|
|
|
if((data_req_ex_i == 1'b1) && (data_misaligned_q == 1'b0))
|
|
begin
|
|
case (data_type_ex_i)
|
|
2'b00: // word
|
|
begin
|
|
if(data_addr_int[1:0] != 2'b00)
|
|
data_misaligned = 1'b1;
|
|
end
|
|
2'b01: // half word
|
|
begin
|
|
if(data_addr_int[1:0] == 2'b11)
|
|
data_misaligned = 1'b1;
|
|
end
|
|
default: ;
|
|
endcase // case (data_type_ex_i)
|
|
end
|
|
end
|
|
|
|
assign data_addr_int = adder_result_ex_i;
|
|
|
|
assign busy_o = (CS == WAIT_RVALID) || (data_req_o == 1'b1);
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Assertions
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
`ifndef VERILATOR
|
|
// make sure there is no new request when the old one is not yet completely done
|
|
// i.e. it should not be possible to get a grant without an rvalid for the
|
|
// last request
|
|
assert property (
|
|
@(posedge clk) ((CS == WAIT_RVALID) && (data_gnt_i == 1'b1)) |-> (data_rvalid_i == 1'b1) );
|
|
|
|
// there should be no rvalid when we are in IDLE
|
|
assert property (
|
|
@(posedge clk) (CS == IDLE) |-> (data_rvalid_i == 1'b0) );
|
|
|
|
// assert that errors are only sent at the same time as grant
|
|
assert property ( @(posedge clk) (data_err_i) |-> (data_gnt_i) );
|
|
|
|
// assert that the address does not contain X when request is sent
|
|
assert property ( @(posedge clk) (data_req_o) |-> (!$isunknown(data_addr_o)) );
|
|
`endif
|
|
endmodule
|