ibex_load_store_unit.sv
//代码仓库:https://github.com/lowrisc/ibex。rtl\ibex_load_store_unit.sv
ibex_load_store_unit
ibex_load_store_unit 是一个负载/存储单元,专门用于处理数据负载(读取)和存储(写入)操作
// Load Store Unit
// 负载存储单元,用于处理处理器阻塞期间的多重访问,并对字节和半字进行对齐。
`include "prim_assert.sv"
`include "dv_fcov_macros.svh"
module ibex_load_store_unit #(
// 参数配置
parameter bit MemECC = 1'b0, // 内存ECC开关
parameter int unsigned MemDataWidth = MemECC ? 32 + 7 : 32 // 内存数据宽度,取决于ECC配置
) (
// 时钟和复位
input logic clk_i, // 时钟输入
input logic rst_ni, // 复位输入
// 数据接口
output logic data_req_o, // 数据请求输出
input logic data_gnt_i, // 数据授权输入
input logic data_rvalid_i, // 数据有效读取输入
input logic data_bus_err_i, // 数据总线错误输入
input logic data_pmp_err_i, // 数据保护管理单元错误输入
// 数据地址和写入控制
output logic [31:0] data_addr_o, // 数据地址输出
output logic data_we_o, // 数据写入使能输出
output logic [3:0] data_be_o, // 数据字节使能输出
output logic [MemDataWidth-1:0] data_wdata_o, // 要写入的数据输出
input logic [MemDataWidth-1:0] data_rdata_i, // 读取的数据输入
// ID/EX阶段信号
input logic lsu_we_i, // 写入使能输入 -> 来自ID/EX阶段
input logic [1:0] lsu_type_i, // 数据类型输入 (字、半字、字节) -> 来自ID/EX阶段
input logic [31:0] lsu_wdata_i, // 要写入内存的数据输入 -> 来自ID/EX阶段
input logic lsu_sign_ext_i, // 符号扩展输入 -> 来自ID/EX阶段
// 数据响应和状态
output logic [31:0] lsu_rdata_o, // 请求的数据输出 -> 发送到ID/EX阶段
output logic lsu_rdata_valid_o, // 请求数据有效输出
input logic lsu_req_i, // 数据请求输入 -> 来自ID/EX阶段
input logic [31:0] adder_result_ex_i, // ALU计算的地址输入 -> 来自ID/EX阶段
// 地址控制
output logic addr_incr_req_o, // 地址增量请求输出 -> 对齐访问
output logic [31:0] addr_last_o, // 最后交易的地址输出 -> 发送到控制器
// 数据请求完成和响应
output logic lsu_req_done_o, // 数据请求完成信号输出
output logic lsu_resp_valid_o, // LSU事务响应有效输出 -> 发送到ID/EX阶段
// 异常信号
output logic load_err_o, // 负载错误输出
output logic load_resp_intg_err_o, // 负载响应完整性错误输出
output logic store_err_o, // 存储错误输出
output logic store_resp_intg_err_o, // 存储响应完整性错误输出
// 状态和性能指标
output logic busy_o, // 忙信号输出
output logic perf_load_o, // 负载操作性能信号输出
output logic perf_store_o // 存储操作性能信号输出
);
注释的代码清晰地描述了模块的每个输入和输出信号的用途,以及模块的主要功能。这将有助于理解模块在整个处理器设计中的作用
logic [31:0] data_addr;
logic [31:0] data_addr_w_aligned;
logic [31:0] addr_last_q, addr_last_d;
logic addr_update;
logic ctrl_update;
logic rdata_update;
logic [31:8] rdata_q;
logic [1:0] rdata_offset_q;
logic [1:0] data_type_q;
logic data_sign_ext_q;
logic data_we_q;
logic [1:0] data_offset; // mux control for data to be written to memory
logic [3:0] data_be;
logic [31:0] data_wdata;
logic [31:0] data_rdata_ext;
logic [31:0] rdata_w_ext; // word realignment for misaligned loads
logic [31:0] rdata_h_ext; // sign extension for half words
logic [31:0] rdata_b_ext; // sign extension for bytes
logic split_misaligned_access;
logic handle_misaligned_q, handle_misaligned_d; // high after receiving grant for first
// part of a misaligned access
logic pmp_err_q, pmp_err_d;
logic lsu_err_q, lsu_err_d;
logic data_intg_err, data_or_pmp_err;
这段代码定义了一些逻辑信号和变量,它们在 ibex_load_store_unit
模块中用于处理负载和存储操作
-
data_addr, data_addr_w_aligned, addr_last_q, addr_last_d:
data_addr
: 当前数据访问的地址。data_addr_w_aligned
: 对齐后的数据地址,用于内存访问。addr_last_q, addr_last_d
: 上一次访问的地址,用于处理连续的存储或负载操作。
-
addr_update, ctrl_update, rdata_update:
addr_update
: 地址更新信号,表明地址需要更新。ctrl_update
: 控制信号更新标志。rdata_update
: 读取数据更新信号。
-
rdata_q, rdata_offset_q, data_type_q, data_sign_ext_q, data_we_q:
rdata_q
: 存储读取数据的寄存器。rdata_offset_q
: 读取数据的偏移量。data_type_q
: 当前数据操作的类型(如字、半字、字节)。data_sign_ext_q
: 符号扩展标志。data_we_q
: 数据写入使能标志。
-
data_offset:
data_offset
: 决定要写入内存的数据的多路复用控制信号。
-
data_be, data_wdata:
data_be
: 数据字节使能信号,用于确定哪些字节有效。data_wdata
: 要写入内存的数据。
-
data_rdata_ext, rdata_w_ext, rdata_h_ext, rdata_b_ext:
data_rdata_ext
: 扩展的读取数据,用于处理不同类型的负载操作。rdata_w_ext
: 用于未对齐负载的字重新对齐。rdata_h_ext
: 半字的符号扩展。rdata_b_ext
: 字节的符号扩展。
-
split_misaligned_access, handle_misaligned_q, handle_misaligned_d:
split_misaligned_access
: 标志不对齐的访问是否需要拆分。handle_misaligned_q, handle_misaligned_d
: 在处理不对齐访问的第一部分后设置为高。
-
pmp_err_q, pmp_err_d, lsu_err_q, lsu_err_d:
pmp_err_q, pmp_err_d
: 保护管理单元错误标志。lsu_err_q, lsu_err_d
: 负载存储单元错误标志。
-
data_intg_err, data_or_pmp_err:
data_intg_err
: 数据完整性错误标志。data_or_pmp_err
: 数据错误或保护管理单元错误的组合标志。
这些信号和变量共同支持 ibex_load_store_unit
模块的核心功能,包括处理数据负载和存储请求、地址对齐、错误处理以及处理不对齐访问。
typedef enum logic [2:0] {
IDLE, WAIT_GNT_MIS, WAIT_RVALID_MIS, WAIT_GNT,
WAIT_RVALID_MIS_GNTS_DONE
} ls_fsm_e;
ls_fsm_e ls_fsm_cs, ls_fsm_ns;
assign data_addr = adder_result_ex_i;
assign data_offset = data_addr[1:0];
这段代码定义了一个枚举类型 ls_fsm_e
用于表示负载/存储单元(LSU)的有限状态机(FSM)状态,并声明了两个变量 ls_fsm_cs
和 ls_fsm_ns
来分别表示当前状态(Current State)和下一个状态(Next State)。此外,还有两个赋值语句用于设置数据地址和偏移量。下面是对这些部分的解释:
有限状态机(FSM)枚举类型 ls_fsm_e
这个枚举定义了LSU FSM可能的状态,用于控制数据负载和存储的过程。每个状态表示LSU在处理数据请求时所处的不同阶段:
- IDLE: 空闲状态,LSU没有正在处理的数据请求。
- WAIT_GNT_MIS: 等待授权状态,用于不对齐的数据访问。
- WAIT_RVALID_MIS: 在不对齐的访问中,等待数据有效的状态。
- WAIT_GNT: 等待授权状态,用于普通(对齐的)数据访问。
- WAIT_RVALID_MIS_GNTS_DONE: 在不对齐的访问中,已获取所有授权并等待数据有效的状态。
FSM状态变量
ls_fsm_cs
: 用于保存当前的FSM状态。ls_fsm_ns
: 用于保存下一个周期的FSM状态。
数据地址和偏移量赋值
assign data_addr = adder_result_ex_i;
: 将由算术逻辑单元(ALU)计算出的地址赋值给data_addr
。这个地址用于当前的负载/存储操作。assign data_offset = data_addr[1:0];
: 提取data_addr
的最低两位作为偏移量,这对于处理字节或半字对齐的负载/存储操作是必要的。
这些代码部分共同支撑着LSU处理负载和存储请求的核心逻辑,包括状态管理和地址计算。通过对状态的精确控制和对地址/偏移量的处理,LSU能够有效地执行处理器的内存访问操作。
BE generation
///
// BE generation //
///
always_comb begin
unique case (lsu_type_i) // Data type 00 Word, 01 Half word, 11,10 byte
2'b00: begin // Writing a word
if (!handle_misaligned_q) begin // first part of potentially misaligned transaction
unique case (data_offset)
2'b00: data_be = 4'b1111;
2'b01: data_be = 4'b1110;
2'b10: data_be = 4'b1100;
2'b11: data_be = 4'b1000;
default: data_be = 4'b1111;
endcase // case (data_offset)
end else begin // second part of misaligned transaction
unique case (data_offset)
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;
default: data_be = 4'b1111;
endcase // case (data_offset)
end
end
2'b01: begin // Writing a half word
if (!handle_misaligned_q) begin // first part of potentially misaligned transaction
unique case (data_offset)
2'b00: data_be = 4'b0011;
2'b01: data_be = 4'b0110;
2'b10: data_be = 4'b1100;
2'b11: data_be = 4'b1000;
default: data_be = 4'b1111;
endcase // case (data_offset)
end else begin // second part of misaligned transaction
data_be = 4'b0001;
end
end
2'b10,
2'b11: begin // Writing a byte
unique case (data_offset)
2'b00: data_be = 4'b0001;
2'b01: data_be = 4'b0010;
2'b10: data_be = 4'b0100;
2'b11: data_be = 4'b1000;
default: data_be = 4'b1111;
endcase // case (data_offset)
end
default: data_be = 4'b1111;
endcase // case (lsu_type_i)
end
这段代码定义了一个 always_comb
块,用于根据负载/存储单元的操作类型和偏移量生成字节使能(BE)信号 data_be
。字节使能信号指示在内存访问操作中哪些字节是有效的,这对于正确执行字、半字和字节访问尤为重要。
字节使能(BE)生成
字节使能信号data_be
是一个4位的逻辑向量,其中每一位代表一个字节的使能状态。在32位数据总线上,data_be
的每个位分别代表最低字节到最高字节。
操作类型和偏移量的处理
lsu_type_i
指示操作类型(字、半字、字节),而data_offset
是基于当前地址的偏移量。根据这些输入,data_be
被设置为适当的值:
-
写入一个字(2’b00):
- 如果这是未对齐事务的第一部分,则根据
data_offset
设置data_be
。例如,如果data_offset
为2’b00,表示地址是对齐的,所以所有四个字节都是有效的(data_be = 4'b1111
)。 - 如果这是未对齐事务的第二部分,则根据
data_offset
设置剩余的字节。
- 如果这是未对齐事务的第一部分,则根据
-
写入半字(2’b01):
- 对于未对齐的事务,
data_be
根据偏移量设置,表示有效的两个字节。 - 对于未对齐事务的第二部分,只有最低字节有效(
data_be = 4'b0001
)。
- 对于未对齐的事务,
-
写入字节(2’b10, 2’b11):
- 根据
data_offset
设置单个有效字节。例如,如果data_offset
为2’b00,则最低字节有效(data_be = 4'b0001
)。
- 根据
默认情况:如果 lsu_type_i
不符合上述任何一种类型,data_be
被设置为全有效(4'b1111
)。
这段代码的关键作用是确保在不同类型的内存访问操作中,正确的字节被标记为有效。这对于确保数据正确地写入或从内存读取是非常重要的,特别是在处理不对齐的内存访问时。
WData alignment
/
// WData alignment //
/
// prepare data to be written to the memory
// we handle misaligned accesses, half word and byte accesses here
always_comb begin
unique case (data_offset)
2'b00: data_wdata = lsu_wdata_i[31:0];
2'b01: data_wdata = {lsu_wdata_i[23:0], lsu_wdata_i[31:24]};
2'b10: data_wdata = {lsu_wdata_i[15:0], lsu_wdata_i[31:16]};
2'b11: data_wdata = {lsu_wdata_i[ 7:0], lsu_wdata_i[31: 8]};
default: data_wdata = lsu_wdata_i[31:0];
endcase // case (data_offset)
end
这段代码定义了一个 always_comb
块,用于根据数据的偏移量对要写入内存的数据进行对齐。这是在处理负载/存储单元(LSU)中的数据写操作时非常重要的步骤,尤其是在处理不对齐的访问或不是字大小的访问(如半字或字节)时。
写数据(WData)对齐
data_wdata
是用于存储调整后要写入内存的数据。lsu_wdata_i
是从ID/EX阶段传入的原始写数据。根据 data_offset
的值,data_wdata
被设置为适当对齐的数据:
-
2’b00: 没有偏移,数据已经对齐。直接使用输入数据
lsu_wdata_i[31:0]
。 -
2’b01: 数据偏移了8位(1字节)。将数据向左旋转8位,使得原本位于高8位的数据移到低8位。
-
2’b10: 数据偏移了16位(2字节)。将数据向左旋转16位。
-
2’b11: 数据偏移了24位(3字节)。将数据向左旋转24位。
-
default: 如果偏移量不是上述任何一种情况(这通常不应该发生),则默认使用输入数据。
示例
假设 lsu_wdata_i
为32位值 0x12345678
,那么:
- 对于
data_offset = 2'b00
,data_wdata
将是0x12345678
。 - 对于
data_offset = 2'b01
,data_wdata
将是0x34127856
。 - 对于
data_offset = 2'b10
,data_wdata
将是0x56781234
。 - 对于
data_offset = 2'b11
,data_wdata
将是0x78563412
。
总结
这段代码的关键作用是确保在写入内存时,数据是正确对齐的,这对于处理不对齐的存储操作或非字大小的存储操作(如写半字或字节)尤其重要。通过调整 data_wdata
的值,该模块能够确保数据在内存中正确地对齐和存储。
RData alignment
/
// RData alignment //
/
// register for unaligned rdata
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
rdata_q <= '0;
end else if (rdata_update) begin
rdata_q <= data_rdata_i[31:8];
end
end
// registers for transaction control
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
rdata_offset_q <= 2'h0;
data_type_q <= 2'h0;
data_sign_ext_q <= 1'b0;
data_we_q <= 1'b0;
end else if (ctrl_update) begin
rdata_offset_q <= data_offset;
data_type_q <= lsu_type_i;
data_sign_ext_q <= lsu_sign_ext_i;
data_we_q <= lsu_we_i;
end
end
这段代码包含两个 always_ff
块,用于处理在 ibex_load_store_unit
模块中读取的数据(RData)的对齐以及事务控制相关的寄存器。
读取数据(RData)对齐
- 第一个
always_ff
块用于处理未对齐的读取数据(rdata_q
)。这个寄存器在每个时钟上升沿或复位信号下降沿时更新。 - 当复位信号
rst_ni
为低时(即在复位时),rdata_q
被清零。 - 当
rdata_update
信号为高时(表示有新的读取数据需要更新),rdata_q
寄存器被更新为data_rdata_i
的高24位。这通常用于处理字节和半字对齐。
事务控制寄存器
- 第二个
always_ff
块用于更新与事务控制相关的几个寄存器:rdata_offset_q
、data_type_q
、data_sign_ext_q
和data_we_q
。 - 在复位时,这些寄存器被初始化为零或适当的默认值。
- 当
ctrl_update
信号为高时,表示控制信息需要更新。此时:rdata_offset_q
更新为当前的数据偏移量data_offset
。data_type_q
更新为当前的加载/存储类型lsu_type_i
。data_sign_ext_q
更新为符号扩展信号lsu_sign_ext_i
。data_we_q
更新为写使能信号lsu_we_i
。
总结
这些代码块共同支持对读取数据的对齐处理以及加载/存储事务的控制信息更新。在处理器设计中,这些功能对于确保数据的正确对齐和事务的正确处理是非常重要的,特别是在处理不同数据宽度和不对齐访问的场景下。通过在适当的时刻更新这些寄存器,可以保证内存访问操作的正确性和有效性。
// Store last address for mtval + AGU for misaligned transactions. Do not update in case of
// errors, mtval needs the (first) failing address. Where an aligned access or the first half of
// a misaligned access sees an error provide the calculated access address. For the second half of
// a misaligned access provide the word aligned address of the second half.
assign addr_last_d = addr_incr_req_o ? data_addr_w_aligned : data_addr;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
addr_last_q <= '0;
end else if (addr_update) begin
addr_last_q <= addr_last_d;
end
end
// take care of misaligned words
always_comb begin
unique 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]};
default: rdata_w_ext = data_rdata_i[31:0];
endcase
end
这段代码涉及到在 ibex_load_store_unit
模块中处理存储的最后地址以及不对齐访问的处理。以下是对这些部分的详细解释:
存储最后地址 (
addr_last_d
和addr_last_q
)
-
作用:存储最后地址是为了处理异常情况(如错误处理)和不对齐访问。在发生错误时,这个地址可能会被用于
mtval
寄存器,该寄存器保存触发异常的指令的相关信息。 -
逻辑:
addr_last_d
是确定要存储的最后地址。如果addr_incr_req_o
为真(表示需要地址增量,通常用于不对齐访问的第二部分),则选择对齐后的地址data_addr_w_aligned
;否则,选择当前数据地址data_addr
。addr_last_q
寄存器在每个时钟周期更新以保存最后的地址。在复位时(!rst_ni
),该寄存器被清零。如果addr_update
为真,则addr_last_q
更新为addr_last_d
。
处理不对齐的字 (
rdata_w_ext
)
-
作用:这部分代码用于处理从内存中读取的不对齐的字数据。
-
逻辑:
- 根据
rdata_offset_q
(读取数据的偏移量)的值,rdata_w_ext
被设置为适当对齐的数据。 data_rdata_i
是从内存中读取的原始数据,而rdata_q
是之前读取的数据(用于不对齐的第二部分)。- 对于不同的偏移值,代码将
data_rdata_i
和rdata_q
的相应部分组合起来,形成一个对齐的32位字。
- 根据
总结
这些代码块共同支持对读取和存储操作中的地址和数据对齐的处理。在存储最后访问的地址时考虑错误和不对齐情况对于异常处理非常重要。对于从内存中读取的不对齐数据,正确地重组这些数据片段以形成完整的字是确保数据完整性的关键。通过这样的处理,LSU能够有效地支持不对齐的内存访问,同时为处理异常提供必要的信息。