跳转至

实验5:AXI总线与Cache

推荐兄弟文档,AXI总线与Cache

前景引入

Cache存在的目的是为了提高存储器的访问速度,其解决了CPU和主存之间速度不匹配的问题

问题

为什么CPU和主存之间速度不匹配?

该文档

到目前为止,我们已经明白,我们需要一个大小合适的BRAM作为与CPU直接交互的存储器。

现在还有一个问题,这个存储器不能够存下我们需要的所有内容,怎么办?

这里就是体现了Cache的核心思想。存下最有可能被访问的数据,每次访问的时候,先访问Cache,如果Cache中有,直接返回,如果没有,再去主存中取。

最有可能被访问的数据

什么数据最有可能被访问?

这是由时间局部性和空间局部性决定的,即刚刚被访问的数据,很有可能在短时间内再次被访问,而且相邻的数据也很有可能被访问。

综上所述,在实现Cache的时候,我们需要解决的问题就是:

  1. 访问Cache,什么情况下访问中
  2. 访问Cache,发现没有访问中,怎么从主存中取数据
  3. 如果Cache里面的数据都是有效的,现在需要写入数据,替换掉哪一个数据块

工作原理

L1 Cache的工作原理

L1缓存(Level 1 Cache)是CPU中最接近核心处理单元(如ALU,算术逻辑单元)的高速存储区域。它的主要作用是加速CPU对数据的访问,尤其是那些CPU频繁访问的数据。L1缓存的工作原理可以从以下几个方面进行详细阐述:

1. 缓存的基本概念

缓存(Cache)是计算机系统中的一种小型、高速存储器,用来存储频繁访问的数据。L1缓存是CPU内部的一级缓存,直接连接到CPU核心,它通常分为两个部分: - 指令缓存(L1 I-cache):存储指令,帮助CPU快速获取程序指令,减少访问内存的延迟。 - 数据缓存(L1 D-cache):存储数据,帮助CPU快速读取和写入数据。

2. 缓存的访问流程

CPU执行指令时,通常会首先访问L1缓存。以下是L1缓存的典型工作流程: 1. 查找数据:CPU首先在L1缓存中查找需要的数据或指令。 2. 命中(Cache Hit):如果数据在L1缓存中找到了,称为“命中”(Cache Hit)。此时,CPU可以直接从L1缓存中获取数据,避免了访问慢速主存(DRAM)的延迟。 3. 未命中(Cache Miss):如果数据在L1缓存中没有找到,称为“未命中”(Cache Miss)。此时,CPU会向L2缓存、L3缓存或者主存(RAM)请求数据。如果L2或L3缓存命中,数据将从那里获取;否则,最终从主存中获取数据。 4. 数据更新:如果从L2缓存、L3缓存或主存获取数据时,CPU会将数据加载到L1缓存中,以便下次使用。

助教的提示

Cache的状态机怎么设计,核心就是在上面的这段话。如果实验的时候,不知道怎么设计,请反复阅读,并且理解其中的逻辑。心里有数后,再去实现。

3. 缓存的局部性原理

L1缓存的高效工作依赖于局部性原则,主要包括: - 时间局部性(Temporal Locality):如果某个数据被访问过,那么它在短时间内很可能会再次被访问。例如,CPU在执行循环时,访问相同的数据多次,L1缓存可以存储这些数据,以减少重复访问主存的开销。 - 空间局部性(Spatial Locality):如果某个数据被访问,附近的数据也很可能被访问。例如,程序访问数组时,通常是访问连续的元素。L1缓存会将数据按块(block)加载到缓存中,这样相邻的数据也能在缓存中找到。

4. 缓存一致性和替换策略

由于L1缓存的容量较小,存储的数据量有限,因此需要使用一定的缓存替换策略来决定哪些数据应该被保留,哪些应该被丢弃。常见的缓存替换策略有: - LRU(Least Recently Used):替换最久没有被访问的数据。 - FIFO(First In First Out):按照数据进入缓存的顺序进行替换。 - 随机替换:随机选择一个缓存行进行替换。

5. L1缓存的大小和结构

L1缓存的大小通常比较小,通常在32KB到128KB之间,具体大小取决于CPU的架构。L1缓存通常是分离式(Separate)结构,即指令缓存和数据缓存是分开存储的,也可以是统一式(Unified)结构,即指令和数据缓存共享同一块存储区域。 - 分离式缓存:L1 I-cache和L1 D-cache各自独立管理指令和数据的缓存,适用于指令和数据访问模式不同的场景。 - 统一式缓存:L1缓存作为一个整体来管理指令和数据,适用于指令和数据访问模式比较相似的场景。

6. L1缓存的读写过程

  • 读取数据:当CPU需要读取数据时,它首先会查找L1数据缓存。如果数据存在于缓存中(命中),CPU直接使用。如果没有(未命中),则需要从L2缓存或主存读取,并将数据加载到L1缓存中。
  • 写入数据:L1缓存的数据写入通常有两种方式:
    • 写回(Write-back):数据写入L1缓存后,只有在该数据被替换出去时,才会写回主存。
    • 写直达(Write-through):数据在写入L1缓存的同时,也直接写入主存,确保缓存和主存的同步。
    • 更多请自行上网查询

基本结构

Cache的地址映射方式

指的是,Cache怎么"知道"(准确的说是维护),其某一个位置的数据与主存的哪一个位置对应。

常见的有三种方式

下面已两路组相连的方式给出一个设计框架

ICache的状态机与原理图

  1. 状态机:

    这里注意i_前缀的信号是BRAM发给Cache的,是AXI读数据通道的信号;而控制idle向lookup跳转的rvalid是外部(排序模块)传给Cache的,和BRAM的AXI信号没有关系

    alt text

  2. 这是一个两路组相联的原理图:

    其中TagV Memory是Tag和Valid的存储区域。

    为什么需要valid?valid代表对应的cache块的内容是否有效,本次实验中,初始cache内部所有块都无效,但是它们都有00000的tag,如何分辨一个块内的数据是无效的还是00000tag对应的有效数据呢,这时候就需要一位valid指示,初始valid全为0,每次调入块后,在记录tag的同时将对应valid置1

    alt text

  3. 相关的交互信号及其含义

    1. 输入信号
    信号名 意义
    raddr [31: 0] 需要访存的主存的地址
    rvalid [ 0: 0] 该地址访存是否有效
    extra 自己决定的
    1. 输出信号
    信号名 意义
    rdata [31: 0] 上一个周期,给点地址对应的数据
    extra 自己决定的

DCache的状态机与原理图

  1. 状态机:

    alt text

  2. 这是一个两路组相联的原理图:

    alt text

AXI接口的BRAM使用教程

手册/文档查询

计算机体系结构基础 第六章中对AXI的简介 有清晰的示例时序图(先看这个)

AXI协议技术文档AXI Protocol v1.0.pdf

AXI参考指导ug761_axi_reference_guide.pdf

中文版简介AXI 总线简介.pdf (再看这个,不需要看Intercontact部分)

IP核BRAM生成器的技术手册 pg058-blk-mem-gen.pdf

简单补充AXI协议的5种通道

以下是信息传输的主要方向,每个通道有相反方向的ready信号代表是否可以接收。a为address的缩写,对应的通道会提供地址,并发起请求。

Cache ---读请求ar---> BRAM

Cache <---读数据r--- BRAM

Cache ---写请求aw---> BRAM

Cache ---写数据w---> BRAM

Cache <---写响应b--- BRAM

以读请求通道设置arlen为8'h3(每次读传输4拍)为例,先经过一个周期的读请求握手,然后需要读数据通道的四个周期的握手,其中最后一次握手有rlast为1指示这是最后一拍。

下图T1没有完成握手,T2读请求通道握手成功,之后T6 T9 T10 T13完成四次读数据握手,最后一次有RLAST有效信号。这是一次完整的总线事务,包含5次总线传输。

image-20241208175747661

以下介绍IP核通用的文档查询方法,但你不必在本次实验中完成它。

在下载了docnav的情况下,你可以在IP核中打开对应的手册。本课程的vivado安装建议中没有勾选docnav,你可以通过HELP/Add Design Tools or Devices来安装docnav。这里的常见踩坑细节见课程群

image-20241208164504656

image-20241208165522356

IP核参数选择

接口类型选择AXI4,本次不需要错误收集(ECC)

image-20241208163200230

AXI类型建议选择完整的AXI4。

你也可以选择简化版的AXI4 Lite,只是AXI4 Lite每次读写都只能读写一次,而不能用arlen信号来指定单次读请求连续读取的拍数。

具体而言,本次实验要求规格为1024×32的BRAM,Cache的一块有四个字,对应四个字地址;Cache读取BRAM时,在AXI4下可以指定arlen=8'h3,单次读传输进行4拍(每一拍,是指一次读数据通道成功握手的时钟上升沿),每拍传一个字,只需要一次读请求握手,4次读数据握手;而在AXI4 Lite下由于单次传输只能传一个数据,需要进行四次读请求握手,每次读请求握手后分别有一次读数据握手。

image-20241208163214019

深度1024:BRAM有1024个单元

宽度32:每个单元32位

这样生成的BRAM的接口中,地址要求输入32位,而且是字节地址,单位为字节,最低两位可以视作在字中选择哪个字节,最高20位无效,中间10位为我们所使用的字地址,对应1024的深度。

image-20241208163229314

IP核例化方法

在Sources中选择IP Sources,你可以在.veo文件中找到IP核例化的示例,复制使用节约时间,并通过注释了解其位宽和「是输入还是输出信号」。

image-20241208170342819

以下给出一个参考的例化示例

blk_mem_gen_1 bram_axi (
  .rsta_busy(rsta_busy),          // output wire rsta_busy
  .rstb_busy(rstb_busy),          // output wire rstb_busy
  .s_aclk(clk),                // input wire s_aclk
  .s_aresetn(rstn),          // input wire s_aresetn
  .s_axi_awid(4'b0001),        // input wire [3 : 0] s_axi_awid
  .s_axi_awaddr({20'h0,addr,2'h0}),    // input wire [31 : 0] s_axi_awaddr
          //根据你的需要,也可以让读写地址不同
          //注意:这里的地址是字节地址,一个地址对应一个字节
  .s_axi_awlen(8'h0),      // input wire [7 : 0] s_axi_awlen 
          //写每次传输的拍数 cache可采用写直达策略,每次cache内容更改后,立即将这一字写入BRAM
  .s_axi_awsize(3'b010),    // input wire [2 : 0] s_axi_awsize
          //数据传输每拍传输的字节数,这里一拍传输4字节(1字32位) 
  .s_axi_awburst(2'b01),  // input wire [1 : 0] s_axi_awburst
          //突发类型为incrementing burst,即地址递增,对应常规的顺序存储
  .s_axi_awvalid(awvalid),  // input wire s_axi_awvalid
          //写请求有效信号,表示当前写请求通道的数据有效,cache希望开始写数据
  .s_axi_awready(awready),  // output wire s_axi_awready
          //写请求准备好信号,表示当前BRAM准备好开始接收数据
  .s_axi_wdata(wdata),      // input wire [31 : 0] s_axi_wdata
          //要写入的数据
  .s_axi_wstrb(4'b1111),      // input wire [3 : 0] s_axi_wstrb
          //写数据字节选通位,表示当前写数据通道的数据哪些字节有效要写入,这里一字的每个字节都有效
  .s_axi_wlast(wlast),      // input wire s_axi_wlast
          //写数据最后一个有效信号,表示当前写数据通道的数据是否是最后一个有效数据
          //如果采用每次写只写一拍,那么这个信号可以固定为1
  .s_axi_wvalid(wvalid),    // input wire s_axi_wvalid
          //写数据有效信号,表示当前cache向写数据通道提供的数据有效,要写入BRAM
  .s_axi_wready(wready),    // output wire s_axi_wready
          //写数据准备好信号,表示BRAM准备好接收数据
  .s_axi_bid(bid),          // output wire [3 : 0] s_axi_bid
          //写响应ID,BRAM告知Cache,对应ID的写请求已经完成,本次可忽略该信号
  .s_axi_bresp(bresp),      // output wire [1 : 0] s_axi_bresp
          //写响应response,BRAM告知Cache,对应ID的写请求是否成功完成,本次可忽略该信号
  .s_axi_bvalid(bvalid),    // output wire s_axi_bvalid
          //写响应握手信号,BRAM表示当前写响应通道的数据是有效的
  .s_axi_bready(bready),    // input wire s_axi_bready
          //写请求响应准备好信号,cache表示当前写响应通道准备好接收数据
  .s_axi_arid(4'b0001),        // input wire [3 : 0] s_axi_arid
  .s_axi_araddr({20'h0,addr,2'h0}),    // input wire [31 : 0] s_axi_araddr
            //注意:这里的地址是字节地址,一个地址对应一个字节
            //读取的地址将从此地址开始连续4个字,越界则从0开始
  .s_axi_arlen(8'h3),      // input wire [7 : 0] s_axi_arlen
          //读每次传输的拍数 cache一行四字 一拍传输一字 一次传输4拍
  .s_axi_arsize(3'b010),    // input wire [2 : 0] s_axi_arsize
          //数据传输每拍传输的字节数,这里一拍传输4字节(1字32位)
  .s_axi_arburst(2'b01),  // input wire [1 : 0] s_axi_arburst
          //突发类型为incrementing burst,即地址递增,对应常规的顺序存储
  .s_axi_arvalid(arvalid),  // input wire s_axi_arvalid
          //读请求有效信号,表示当前读请求通道的数据有效,cache希望开始读数据
  .s_axi_arready(arready),  // output wire s_axi_arready
          //读请求准备好信号,表示当前BRAM准备好开始提供数据
  .s_axi_rid(rid),                // output wire [3 : 0] s_axi_rid
          //读数据ID,BRAM告知Cache,本次读出的数据对应读请求的id,本次可忽略该信号
  .s_axi_rdata(rdata),      // output wire [31 : 0] s_axi_rdata
          //读出的数据
  .s_axi_rresp(rresp),      // output wire [1 : 0] s_axi_rresp
          //读数据response,BRAM告知Cache,本次读请求是否成功完成
  .s_axi_rlast(rlast),      // output wire s_axi_rlast
          //读数据最后一拍指示,4拍的传输中,第四拍的rlast为1,其余为0
  .s_axi_rvalid(rvalid),    // output wire s_axi_rvalid
          //读数据有效信号,BRAM表示当前读数据通道的数据是有效的
  .s_axi_rready(rready)    // input wire s_axi_rready
          //读请求准备好信号,cache表示当前读数据通道准备好接收数据
);