IBEX系列之LSU源码系列<1>

本文围绕ibex_load_store_unit.sv代码展开,介绍了ibex_load_store_unit模块处理数据负载和存储操作的功能。详细阐述了逻辑信号和变量用途,还涉及有限状态机状态、字节使能信号生成、写数据和读取数据对齐,以及存储最后地址和不对齐访问处理等内容,确保内存访问操作正确有效。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 模块中用于处理负载和存储操作

  1. data_addr, data_addr_w_aligned, addr_last_q, addr_last_d:

    • data_addr: 当前数据访问的地址。
    • data_addr_w_aligned: 对齐后的数据地址,用于内存访问。
    • addr_last_q, addr_last_d: 上一次访问的地址,用于处理连续的存储或负载操作。
  2. addr_update, ctrl_update, rdata_update:

    • addr_update: 地址更新信号,表明地址需要更新。
    • ctrl_update: 控制信号更新标志。
    • rdata_update: 读取数据更新信号。
  3. 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: 数据写入使能标志。
  4. data_offset:

    • data_offset: 决定要写入内存的数据的多路复用控制信号。
  5. data_be, data_wdata:

    • data_be: 数据字节使能信号,用于确定哪些字节有效。
    • data_wdata: 要写入内存的数据。
  6. data_rdata_ext, rdata_w_ext, rdata_h_ext, rdata_b_ext:

    • data_rdata_ext: 扩展的读取数据,用于处理不同类型的负载操作。
    • rdata_w_ext: 用于未对齐负载的字重新对齐。
    • rdata_h_ext: 半字的符号扩展。
    • rdata_b_ext: 字节的符号扩展。
  7. split_misaligned_access, handle_misaligned_q, handle_misaligned_d:

    • split_misaligned_access: 标志不对齐的访问是否需要拆分。
    • handle_misaligned_q, handle_misaligned_d: 在处理不对齐访问的第一部分后设置为高。
  8. pmp_err_q, pmp_err_d, lsu_err_q, lsu_err_d:

    • pmp_err_q, pmp_err_d: 保护管理单元错误标志。
    • lsu_err_q, lsu_err_d: 负载存储单元错误标志。
  9. 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_csls_fsm_ns 来分别表示当前状态(Current State)和下一个状态(Next State)。此外,还有两个赋值语句用于设置数据地址和偏移量。下面是对这些部分的解释:

有限状态机(FSM)枚举类型 ls_fsm_e
这个枚举定义了LSU FSM可能的状态,用于控制数据负载和存储的过程。每个状态表示LSU在处理数据请求时所处的不同阶段:

  1. IDLE: 空闲状态,LSU没有正在处理的数据请求。
  2. WAIT_GNT_MIS: 等待授权状态,用于不对齐的数据访问。
  3. WAIT_RVALID_MIS: 在不对齐的访问中,等待数据有效的状态。
  4. WAIT_GNT: 等待授权状态,用于普通(对齐的)数据访问。
  5. 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 被设置为适当的值:

  1. 写入一个字(2’b00):

    • 如果这是未对齐事务的第一部分,则根据 data_offset 设置 data_be。例如,如果 data_offset 为2’b00,表示地址是对齐的,所以所有四个字节都是有效的(data_be = 4'b1111)。
    • 如果这是未对齐事务的第二部分,则根据 data_offset 设置剩余的字节。
  2. 写入半字(2’b01):

    • 对于未对齐的事务,data_be 根据偏移量设置,表示有效的两个字节。
    • 对于未对齐事务的第二部分,只有最低字节有效(data_be = 4'b0001)。
  3. 写入字节(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 被设置为适当对齐的数据:

  1. 2’b00: 没有偏移,数据已经对齐。直接使用输入数据 lsu_wdata_i[31:0]

  2. 2’b01: 数据偏移了8位(1字节)。将数据向左旋转8位,使得原本位于高8位的数据移到低8位。

  3. 2’b10: 数据偏移了16位(2字节)。将数据向左旋转16位。

  4. 2’b11: 数据偏移了24位(3字节)。将数据向左旋转24位。

  5. default: 如果偏移量不是上述任何一种情况(这通常不应该发生),则默认使用输入数据。

示例

假设 lsu_wdata_i 为32位值 0x12345678,那么:

  • 对于 data_offset = 2'b00data_wdata 将是 0x12345678
  • 对于 data_offset = 2'b01data_wdata 将是 0x34127856
  • 对于 data_offset = 2'b10data_wdata 将是 0x56781234
  • 对于 data_offset = 2'b11data_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_qdata_type_qdata_sign_ext_qdata_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_daddr_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_irdata_q 的相应部分组合起来,形成一个对齐的32位字。

总结
这些代码块共同支持对读取和存储操作中的地址和数据对齐的处理。在存储最后访问的地址时考虑错误和不对齐情况对于异常处理非常重要。对于从内存中读取的不对齐数据,正确地重组这些数据片段以形成完整的字是确保数据完整性的关键。通过这样的处理,LSU能够有效地支持不对齐的内存访问,同时为处理异常提供必要的信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值