这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

验证文档规范

本规范规定了“万众一芯”验证文档的必要形式和结构(不是验证报告的模板),已发布和将来将要发布的文档都需要遵循这一规范。

万众一芯验证文档格式规范

验证文档标题请用一号标题格式(一个#),以加法器验证文档为例,其标题可以为:进位加法器设计与验证。

文档概述

文档概述标题请用二号标题格式(两个#)。

【必填项】 在该部分对整个文档进行简约描述,例如内容概述,待验证模块的基本功能、特殊需求、特定规格、目标读者、知识前置等。目的是通过对该部分,读者便了解是否具有其感兴趣的内容。例如本文档是对验证文档的编写要求进行描述,便于多文档协作,规范验证的数据输入,特定数据标签等。

术语说明

术语说明标题请用二号标题格式(两个#)。

【必填项】 该部分需要列出术语和关键概念解释,方便读者参考。

  1. 优先解释模块专有缩写(如TLB, FIFO等),且用缩写(全名)的格式填写在“名称”一栏中。
  2. 对容易混淆的概念请务必明确(如虚拟地址和物理地址等)
  3. 示例格式如下:
名称 定义
TLB(Translation Lookaside Buffer) 地址转换的缓存单元,用于加速虚拟地址到物理的转换
FIFO(First In First Out) 先进先出队列
写回 发生在Cache替换时,如果被替换块为脏块,需要将缓存行写回对应内存位置

如果有其他补充情况请在此说明,例如:上述命名描述仅针对香山处理器,不代表RISC-V标准或者其他处理器。

前置知识

前置知识标题请用二号标题格式(两个#)。

【可选项】 在阅读文档或进行验证之前,建议掌握一些关键前置知识,以便更深入理解相关内容。例如,在撰写LoadStoreQueue(LSQ)文档时,讲述RAW(Read After Write)违例有助于理解操作之间的依赖关系。在撰写Icache或L2Cache文档时,介绍缓存层级、替换策略和一致性模型等基本概念也有助于读者理解。如果涉及复杂算法,也应对其进行简要描述。

基本要求:

  1. 该部分内容应简洁,易于理解。如篇幅较长,可将内容移至附录。
  2. 针对较为复杂的内容,可以通过图像、伪代码和案例进行解释,以降低理解难度。

下面是一个举例:

st-ld违例

在现代处理器中,Load 和 Store 指令通常采用乱序执行的方式进行处理。这种执行策略旨在提高处理器的并行性和整体性能。然而,由于 Load 和 Store 指令在流水线中的乱序执行,常常会出现 Load 指令越过更早的相同地址的 Store 指令的情况。这意味着,Load 指令本应通过前递(forwarding)机制从 Store 指令获取数据,但由于 Store 指令的地址或数据尚未准备好,导致 Load 指令未能成功前递到 Store 的数据,而 Store 指令已被提交。由此,后续依赖于该 Load 指令结果的指令可能会出现错误,这就是 st-ld 违例。

考虑以下伪代码示例:

ST R1, 0(R2)  ; 将 R1 的值存储到 R2 指向的内存地址
LD R3, 0(R2)  ; 从 R2 指向的内存地址加载值到 R3
ADD R4, R3, R5 ; 使用 R3 的值进行计算

假设在这个过程中,Store 指令由于某种原因(如缓存未命中)未能及时完成,而 Load 指令已经执行并读取了旧的数据(例如,从内存中读取到的值为 0)。此时,Load 指令并未获得 Store 指令更新后的值,导致后续计算的数据错误。

通过上述例子,可以清楚地看到 Store-to-Load 违例如何在乱序执行的环境中导致数据一致性问题。这种问题强调了在指令调度和执行过程中,确保正确的数据流动的重要性。现代处理器通过多种机制来检测和解决这种违例,以维护程序的正确性和稳定性。

整体框图

整体框图标题请用二号标题格式(两个#)。

【可选项】 该部分为可选章节,若模块含多个子模块或复杂数据流,需提供框图辅助说明,便于读者理解。

基本要求:

  1. 图必须清晰,最好为矢量图,可使用Visio/Draw.io等工具绘制,导出为PNG/SVG格式;
  2. 图中需标注关键信号流向;
  3. 框图中子模块命名需与“子模块列表”章节严格一致;
  4. 图像和图表标题的位置需要居中;
  5. 如果有多个图表,图表题目需要添加相应标号,如图1、图2等;

示例:

示例图像

示例图1:IFU 整体框图

流水级示意图

流水级示意图标题请用二号标题格式(两个#)。

【可选项】 若为流水线型模块,需说明各级流水功能与时序关系。

编写要求

  1. 图必须清晰,最好为矢量图,可使用Visio/Draw.io等工具绘制,导出为PNG/SVG格式;
  2. 涉及到的模块名称需要与上下文保持一致;
  3. 重要信号除了列出信号名称以外,还需要标明位宽等信息;
  4. 图像和图表标题的位置需要居中;
  5. 如果有多个图表,图表题目需要添加相应标号,如图1、图2等。

示例:

示例图像

示例图2:LSU-LoadUnit 流水线架构图

子模块列表

子模块列表标题请用二号标题格式(两个#)。

【可选项】 如果一个模块由多个子模块组成,则需要在此处列出所有相关的子模块,并进行简要说明。这有助于清晰地展示模块的结构和功能,便于读者理解各个子模块之间的关系及其在整体系统中的作用。

以下是IFU top文档中的一个示例:

子模块 描述
PreDecoder 预译码模块,用于生成有效指令标识和类型信息
F3Predecoder F3阶段预译码模块,从PreDecoder中时序优化出来的模块,负责判定CFI指令的类型
RVCExpander RVC指令扩展模块,负责对传入的指令进行指令扩展,并解码计算非法信息
PredChecker 预检查模块,校验并修正预测信息
FrontendTrigger 前端断点模块,用于在前端设置硬件断点和检查

模块功能说明

模块功能说明标题请用二号标题格式(两个#)。

【必填项】 需采用功能树形式逐级分解DUT的各项功能,并对所有功能进行描述,确保每个功能点都对应相应的测试点。这种结构化的方法不仅有助于全面覆盖所有功能,还便于后续文档的维护和更新。

编写规则:

  1. 请使用 <mrs-functions>``</mrs-functions> 标签包裹整个“模块功能说明”部分;
  2. 采用 X.Y.Z 多级编号(如 1.2.3 表示主功能 1 → 子功能 2 → 测试点 3,且可进一步细分);
  3. 多级编号的标题格式按照级别增加,例如:“1. 读FIFO操作”应为三号标题格式 “1.1. 常规读取”应为四号标题格式;
  4. 功能描述应清晰列出输入条件、处理过程和输出结果。
  5. 针对每个功能进行测试点分解,应详细列出每个测试点,明确其目的和预期结果。
  6. 如果测试点较多可以先列一个小表格。

具体来说,可以按照如下的格式写作(示例内容仅供参考,并不代表实际逻辑或内容):

示例:FIFO模块功能说明

示例1. 读FIFO操作

示例1.1. 常规读取

功能描述:当rd_en=1且empty=0时,在时钟上升沿输出rdata

建议观测点

  • 读指针递增逻辑
  • rdata与预期数据匹配

示例2. 写FIFO操作

示例2.1. 常规写入

功能描述:当wr_en=1且full=0时,在时钟上升沿存储wdata

观测点

  • 写指针递增逻辑
  • 存储阵列数据更新

示例3. 接收FTQ取指令请求(F0流水级)

​在F0流水级,IFU接收来自FTQ以预测块为单位的取指令请求。请求内容包括预测块起始地址、起始地址所在cache line的下一个cache line开始地址、下一个预测块的起始地址、该预测块在FTQ里的队列指针、该预测块有无taken的CFI指令(控制流指令)和该taken的CFI指令在预测块里的位置以及请求控制信号(请求是否有效和IFU是否ready)。每个预测块最多包含32字节指令码,最多为16条指令。IFU需要置位ready驱动FTQ向ICache发送请求。

示例3.1. F0流水级接收请求

IFU应当能向FTQ报告自己已ready。

所以,对于这一测试点我们只需要在发送请求后检查和ftq相关的的ready情况即可。

序号 功能名称 测试点名称 描述
1.1 IFU_RCV_REQ READY IFU接收FTQ请求后,设置ready

常量说明

常量说明标题请用二号标题格式(两个#)。

【可选项】 需要列出模块中所有可配置参数及其物理意义,以便于用户理解各参数的作用和影响。

示例:

常量名 常量值 解释
ADDR_WIDTH 64 地址总线位宽
FIFO_DEPTH 8 深度配置

接口说明

接口说明标题请用二号标题格式(两个#)。

【必填项】 详细解释各种接口的含义和来源,包括接口的功能、用途。这有助于用户理解各接口的工作原理和应用场景,从而更有效地使用这些接口。

编写规则

  1. 信号按功能(如时钟复位、数据输入、控制信号等)或来源(其他模块)分组;
  2. 可以将一些同质的信号一起解释;
  3. 特殊协议信号需注明时序要求(如AXI的VALID/READY握手)。

接口时序

接口时序标题请用二号标题格式(两个#)。

【可选项】 针对复杂接口,可以提供波形图案例,以直观展示信号变化和时间关系。

以下是节选自IFU top文档的一个例子:

接口时序

FTQ 请求接口时序示例

port1

上图示意了三个 FTQ 请求的示例,req1 只请求缓存行 line0,紧接着 req2 请求 line1 和 line2,当到 req3 时,由于指令缓存 SRAM 写优先,此时指令缓存的读请求 ready 被指低,req3 请求的 valid 和地址保持直到请求被接收。

ICache 返回接口以及到 Ibuffer 和写回 FTQ 接口时序示例

port2

上图展示了指令缓存返回数据到 IFU 发现误预测直到 FTQ 发送正确地址的时序,group0 对应的请求在 f2 阶段了两个缓存行 line0 和 line1,下一拍 IFU 做误预测检查并同时把指令给 Ibuffer,但此时后端流水线阻塞导致 Ibuffer 满,Ibuffer 接收端的 ready 置低,goup0 相关信号保持直到请求被 Ibuffer 接收。但是 IFU 到 FTQ 的写回在 tio_toIbuffer_valid 有效的下一拍就拉高,因为此时请求已经无阻塞地进入 wb 阶段,这个阶段锁存的了 PredChecker 的检查结果,报告 group0 第 4(从 0 开始)个 2 字节位置对应的指令发生了错误预测,应该重定向到 vaddrA,之后经过 4 拍(冲刷和重新走预测器流水线),FTQ 重新发送给 IFU 以 vaddrA 为起始地址的预测块。

测试点总表

测试点总表标题请用二号标题格式(两个#)。

【必填项】 对模块功能说明中细分的测试点进行综合整理,采用表格形式列出,便于用户快速查阅和理解。

表格规范

  1. 请用<mrs-testpoints></mrs-testpoints>标签包裹测试点总表,方便我们后续使用脚本提取测试点
  2. 表格共列四项,序号,功能名称,测试点名称和解释
    1. 序号:测试点的序号格式和功能点类似。即,即测试点1.2.3.4 可能是功能点1.2.3的第4个测试点
    2. 功能名称:用英文大写命名,可用下划线分割单词,可以使用markdown语法为功能名称添加到具体功能点解释的链接,即形如[文本](跳转目标)
    3. 测试点名称:用英文大写命名,可用下划线分割单词
    4. 解释:简单描述测试点所需的输入和输出需求,明确判断条件

表格示例

以下是节选自IFU top文档的一个例子:

序号 功能名称 测试点名称 描述
1.1 IFU_RCV_REQ READY IFU接收FTQ请求后,设置ready
2.1.1 IFU_F1_INFOS PC IFU接收FTQ请求后,在F1流水级生成PC
2.1.2 IFU_F1_INFOS CUT_PTR IFU接收FTQ请求后,在F1流水级生成后续切取缓存行的指针
2.2.1 IFU_F2_INFOS EXCP_VEC IFU接收ICache内容后,会根据ICache的结果生成属于每个指令的异常向量

附录

附录标题请用二号标题格式(两个#)。

【可选项】 此部分用于存放正文的补充内容,以便进行扩展和详细说明,旨在使文档格式更加清晰,排版更加合理。

以下是节选自IFU RVCExpander文档的一个例子:

RVC扩展辅助阅读材料

为方便参考模型的书写,在这里根据20240411版本的手册内容整理了部分指令扩展的思路。

对于RVC指令来说,op = instr(1, 0);funct = instr(15, 13)

op\funct 000 001 010 011 100 101 110 111
00 addi4spn fld lw ld lbu
lhu;lh
sb;sh
fsd sw sd
01 addi addiw li lui
addi16sp
zcmop
ARITHs
zcb
j beqz bnez
10 slli fldsp lwsp ldsp jr;mv
ebreak
jalr;add
fsdsp fwsp sdsp

在开始阅读各指令的扩展规则时,需要了解一些RVC扩展的前置知识,比如:

rd’, rs1’和rs2’寄存器:受限于16位指令的位宽限制,这几个寄存器只有3位来表示,他们对应到x8~x15寄存器。

op = b'00'

funct = b'000’: ADDI4SPN

addi4spn

该指令将一个0扩展的非0立即数加到栈指针寄存器x2上,并将结果写入rd'

其中,nzuimm[5:4|9:6|2|3]的含义是: ···

下方展示了模板和两个验证案例:

1 - 文档模板

以下是一份验证文档的完整模板(请一定同提交的验证报告区分开来)


# 验证文档各部分说明

## 文档概述【必填项】 

在该部分对整个文档进行简约描述,例如内容概述,待验证模块的基本功能、特殊需求、特定规格、目标读者、知识前置等。目的是通过对该部分,读者便了解是否具有其感兴趣的内容。例如本文档是对验证文档的编写要求进行描述,便于多文档协作,规范验证的数据输入,特定数据标签等。

## 术语说明 【必填项】 列出术语和关键概念解释,方便读者参考

优先解释模块专有缩写(如TLB, FIFO等),如果有缩写,请用`缩写(全称)的方式填在表格的“名称”栏目中`

对容易混淆的概念请务必明确(如虚拟地址和物理地址等)

| 名称 | 定义 |
| ------- | ---|
| 缩写1(FULL_NAME_1)	| 描述1 |
| 缩写2(FULL_NAME_2)	| 描述2 |
| 概念名1	| 描述3 |

## 前置知识【可选项】

在阅读文档或进行验证之前,建议掌握一些关键前置知识,以便更深入理解相关内容。例如,在撰写LoadStoreQueue(LSQ)文档时,讲述RAW(Read After Write)违例有助于理解操作之间的依赖关系。在撰写Icache或L2Cache文档时,介绍缓存层级、替换策略和一致性模型等基本概念也有助于读者理解。如果涉及复杂算法,也应对其进行简要描述。

基本要求:
1. 该部分内容应简洁,易于理解。如篇幅较长,可将内容移至附录。
2. 针对较为复杂的内容,可以通过图像、伪代码和案例进行解释,以降低理解难度。


## 整体框图 【可选项】 若模块含多个子模块或复杂数据流,需提供框图辅助说明

可使用Visio/Draw.io等工具绘制,导出为PNG/SVG格式;
需标注关键信号流向;
框图中子模块命名需与“子模块列表”章节严格一致。

## 流水级示意图 【可选项】 若为复杂流水线型模块,需说明各级流水功能与时序关系

可使用Visio/Draw.io等工具绘制,导出为PNG/SVG格式;
涉及到的模块名称需要保持一致性
重要数据除了列出名称以外,还需要标明位宽等信息

## 子模块列表 【可选项】 若模块由多个子模块组成,需在此列出

以下是IFU top文档中的一个示例:

| 子模块                 | 描述                |
| ---------------------- | ------------------- |
| [子模块1](子模块1文档位置) | 子模块1描述       |
| [子模块2](子模块2文档位置) | 子模块2描述       |
| [子模块3](子模块3文档位置) | 子模块3描述       |


<mrs-functions>

## 模块功能说明 【必填项】 需按功能树形式逐级分解,每个功能点需对应后续测试点。

请用<mrs-functions></functions>包裹整个“模块功能说明”部分。

采用X.Y.Z多级编号(如1.2.3表示主功能1→子功能2→测试点3,也可以继续细分)

功能描述需明确输入条件、处理过程、输出结果

### 1. 功能A说明
针对功能A分解测试点

如果测试点较多可以先列一个小表格

### 2. 功能B说明

针对功能B分解测试点

如果测试点较多可以先列一个小表格

### 3. 功能C说明

针对功能C分解测试点

如果测试点较多可以先列一个小表格;针对每个测试点,给出设置cov_group的建议

</mrs-functions>


## 常量说明 【可选项】 需列出模块中所有可配置参数及其物理意义


| 常量名 | 常量值 | 解释 |
| ---- | ---- | ---- |
| 常量1 | 64 | 常量1解释 |
| 常量2 | 8 | 常量2解释 |
| 常量3 | 16 | 常量3解释 |


## 接口说明 【必填项】 详细解释各种接口的含义、来源

信号按功能(如时钟复位、数据输入、控制信号等)或来源(其他模块)分组;

可以将一些同质的信号一起解释;

特殊协议信号需注明时序要求(如AXI的VALID/READY握手)。

使用时,请将下面的接口组名称和说明替换为符合您模块实际意义的内容

### 接口组1说明

请在这里填充接口组1的说明

#### 接口组1_1说明

请在这里填充接口组1_1的说明

如果不能细分,请进一步说明该组中所有接口

### 接口组2说明

请在这里填充接口组2的说明

如果不能细分,请进一步说明该组中所有接口

...

## 接口时序 【可选项】 对复杂接口,提供波形图的案例

### 案例1

请在这里填充时序案例1

### 案例2

请在这里填充时序案例2

## 测试点总表 (【必填项】针对细分的测试点,列出表格)

实际使用下面的表格时,请用有意义的英文大写的功能名称和测试点名称替换下面表格中的名称

<mrs-testpoints>

| 序号 |  功能名称 | 测试点名称      | 描述                  |
| ----- |-----------------|---------------------|------------------------------------|
| 1\.1\.1 | FUNCTION_1_1 | TESTPOINT_A | 功能1\.1的测试点A,使用时请替换为您的测试点的输入输出和判断方法 | 
| 1\.1\.2 | FUNCTION_1_1 | TESTPOINT_B | 功能1\.1的测试点B,使用时请替换为您的测试点的输入输出和判断方法 | 
| 1\.1\.3 | FUNCTION_1_1 | TESTPOINT_C | 功能1\.1的测试点C,使用时请替换为您的测试点的输入输出和判断方法 | 
| 1\.2\.1 | FUNCTION_1_2 | TESTPOINT_X | 功能1\.2的测试点X,使用时请替换为您的测试点的输入输出和判断方法 | 
| 1\.2\.2 | FUNCTION_1_2 | TESTPOINT_Y | 功能1\.2的测试点Y,使用时请替换为您的测试点的输入输出和判断方法 | 
| 2\.1 | FUNCTION_2 | TESTPOINT_2A | 功能2的测试点2A,使用时请替换为您的测试点的输入输出和判断方法 | 
| 2\.2 | FUNCTION_2 | TESTPOINT_2B | 功能2的测试点2B,使用时请替换为您的测试点的输入输出和判断方法 | 

</mrs-testpoints>

## 附录【可选项】 

此部分用于存放正文的补充内容,以便进行扩展和详细说明,旨在使文档格式更加清晰,排版更加合理。

2 - FIFO文档案例

以下以FIFO为例,展示了一个简单的文档案例

`timescale 1ns / 1ps
module FIFO ( //data_width = 8  data depth =8
  input clk,
  input rst_n,
  input wr_en,          //写使能
  input rd_en,          //读使能
  input [7:0]wdata,     //写入数据输入
  output [7:0]rdata,    //读取数据输出
  output empty,         //读空标志信号
  output full           //写满标志信号
);
  reg [7:0] rdata_reg = 8'd0;
  assign rdata = rdata_reg;

  reg  [7:0] data [7:0];     //数据存储单元(8bit数据8个)
  reg  [3:0] wr_ptr = 4'd0;  //写指针
  reg  [3:0] rd_ptr = 4'd0;  //读指针
  wire [2:0] wr_addr;        //写地址(写指针的低3位)
  wire [2:0] rd_addr;        //读地址(读指针的低3位)

assign wr_addr = wr_ptr[2:0];
assign rd_addr = rd_ptr[2:0];

always@(posedge clk or negedge rst_n)begin //写数据
  if(!rst_n) 
    wr_ptr <= 4'd0;
  else if(wr_en && !full)begin
    data[wr_addr]  <= wdata;
    wr_ptr <= wr_ptr + 4'd1;
  end
end

always@(posedge clk or negedge rst_n)begin //读数据
  if(!rst_n)
    rd_ptr <= 'd0;
  else if(rd_en && !empty)begin
    rdata_reg  <= data[rd_addr];
    rd_ptr <= rd_ptr + 4'd1;
  end
end

assign empty = (wr_ptr == rd_ptr); //读空
assign full  = (wr_ptr == {~rd_ptr[3],rd_ptr[2:0]}); //写满

endmodule

FIFO 模块验证文档

文档概述

本文档描述FIFO的功能,并根据功能给出测试点参考,方便测试的参与者理解测试需求,编写相关测试用例。

术语说明

缩写 全称 定义
FIFO First In First Out 先进先出的数据缓冲队列

功能说明

本次需要验证的是FIFO,一种常见的硬件缓冲模块,在硬件电路中临时存储数据,并按照数据到达的顺序进行处理。

本次需要验证的FIFO每次可写可读8位数据,容量为8。

1. 读FIFO操作

1.1. 常规读取

功能描述:当rd_en=1且empty=0时,在时钟上升沿输出rdata

建议观测点

  • 读指针递增逻辑
  • rdata与预期数据匹配

1.2. 读空栈

功能描述:当empty=1且rd_en=1时,rdata保持无效值

建议观测点

  • empty信号持续为高
  • 读指针无变化

1.3. 无读使能不读

功能描述:当rd_en=0时,无论FIFO状态如何均不更新rdata

建议观测点

  • 连续写入后关闭读使能,验证读指针冻结

2. 写FIFO操作

2.1. 常规写入

功能描述:当wr_en=1且full=0时,在时钟上升沿存储wdata

观测点

  • 写指针递增逻辑
  • 存储阵列数据更新

2.2. FIFO已满无法写入

功能描述:当full=1且wr_en=1时,wdata被丢弃

观测点

  • full信号持续为高
  • 存储阵列内容不变

2.3. 无写使能不写

功能描述:当wr_en=0时,无论FIFO状态如何均不写入数据

观测点

  • 写指针冻结
  • 存储阵列内容保持不变

3. 复位操作

3.1. 复位控制

功能描述:当rst_n=0时,清空FIFO并重置指针

观测点

  • 复位后empty=1且full=0
  • 读写指针归零

常量说明

常量名 常量值 解释
FIFO_DEPTH 8 FIFO存储单元数量
DATA_WIDTH 8 数据总线位宽

接口说明

输入接口

信号名 方向 位宽 描述
clk Input 1 主时钟
rst_n Input 1 异步复位
wr_en Input 1 写使能
wdata Input 8 写入数据
rd_en Input 1 读使能

输出接口

信号名 方向 位宽 描述
rdata Output 8 读出数据
empty Output 1 FIFO空标志(高有效)
full Output 1 FIFO满标志(高有效)

测试点总表

建议各个测试点的覆盖组使用下表描述的功能和测试点名称进行命名。

比如FIFO_READ的测试点NORMAL,其覆盖点建议命名为FIFO_READ_NORMAL

序号 功能名称 测试点名称 描述
1.1 FIFO_READ NORMAL fifo有数据时,设置读使能,可以读出数据
1.2 FIFO_READ EMPTY fifo为空时,设置读使能,无法读出数据
1.3 FIFO_READ NO_EN fifo有数据时,不设置读使能,无法读出数据
2.1 FIFO_WRITE NORMAL fifo未满时,设置写使能,可以写入数据
2.2 FIFO_WRITE FULL fifo已满时,设置写使能,可以写入数据
2.3 FIFO_WRITE NO_EN fifo未满时,不设置写使能,无法写入数据
3.1 FIFO_RESET RESET 重置后,栈为空

3 - 果壳Cache文档案例

本文档将以果壳L1Cache作为案例,展示一个具有相当复杂度的模块的验证说明文档例子(请一定同提交的验证报告区分开来)。

果壳L1Cache验证文档

文档概述

本文档针对NutShell L1Cache的验证需求撰写,通过对其功能进行描述并依据功能给出参考测试点,从而帮助验证人员编制测试用例。

果壳(NutShell)是一款由5位中国科学院大学本科生设计的基于RISC-V RV64开放指令集的顺序单发射处理器(NutShell·Github), 隶属于国科大与计算所“一生一芯”项目。而果壳Cache(NutShell Cache)是其缓存模块,采用可定制化设计(L1 Cache和L2 Cache采用相同的模板生成,只需要调整参数),具体来说,L1 Cache(指令Cache和数据Cache)大小为32KB,L2 Cache大小为128KB, 在整体结构上,果壳Cache采用三级流水的结构。

本次验证的目标是L1 Cache,即一级缓存。

术语说明

名称 定义
MMIO(Memory-Mapped Input/Output) 内存映射IO
写回 Cache需要进行替换时,会将脏替换块写回内存
关键字优先方案 缺失发生时,系统会优先获取CPU所需要的当前指令或数据所对应的字

前置知识

Cache的层次结构

Cache有三种主要的组织方式:直接映射(Direct-Mapped)Cache、组相连(Set-Associative)Cache和全相连(Fully-Associative)Cache。对于物理内存中的一个数据,如果在Cache中只有一个位置可以存放它,这就是直接映射Cache;如果有多个位置可以存放这个数据,这就是组相连Cache;如果Cache中的任何位置都可以存放这个数据,这就是全相连Cache。

直接映射Cache和全相连Cache实际上是组相连Cache的两种特殊情况。现代处理器中的Cache通常属于这三种方式中的一种。例如,翻译后备缓冲区(TLB)和Victim Cache多采用全相连结构,而普通的指令缓存(I-Cache)和数据缓存(D-Cache)则采用组相连结构。当处理器需要执行一个指令时,它会首先查找该指令是否在I-Cache中。如果在,则直接从I-Cache中读取指令并执行;如果不在,则需要从内存中读取指令到I-Cache中,再执行。与I-Cache类似,当处理器需要读取或写入数据时,会首先查找D-Cache。如果数据在D-Cache中,则直接读取或写入;如果不在,则需要从内存中加载数据到D-Cache中。与I-Cache不同的是,D-Cache需要考虑数据的一致性和写回策略。为了保证数据的一致性,当数据在D-Cache中被修改后,需要同步更新到内存中。

composition

Cache的写入

在执行写数据时,如果只是向D-Cache中写入数据而不改变其下级存储器中的数据,就会导致D-Cache和下级存储器对于同一地址的数据不一致(non-consistent)。为了保持一致性,一般Cache在写命中状态下采用两种写入方式: (1)写通(Write Through):数据写入D-Cache的同时也写入其下级存储器。然而,由于下级存储器的访问时间较长,而存储指令的频率较高,频繁地向这种较慢的存储器中写入数据会降低处理器的执行效率。 (2)写回(Write Back):数据写入D-Cache后,只是在Cache line上做一个标记,并不立即将数据写入更下级的存储器。只有当Cache中这个被标记的line要被替换时,才将其写入下级存储器。这种方式能够减少向较慢存储器写入数据的频率,从而获得更好的性能。然而,这种方式会导致D-Cache和下级存储器中许多地址的数据不一致,给存储器的一致性管理带来一定的负担。

D-Cache处理写缺失一般有两种策略:

(1)非写分配(Non-Write Allocate):直接将数据写入下级存储器,而不将其写入D-Cache。这意味着当发生写缺失时,数据会直接写入到下级存储器,而不会经过D-Cache。

(2)写分配(Write Allocate):在发生写缺失时,会先将相应地址的整个数据块从下级存储器中读取到D-Cache中,然后再将要写入的数据合并到这个数据块中,最终将整个数据块写回到D-Cache中。这样做的好处是可以在D-Cache中进行更多的操作,但同时也增加了对内存的访问次数和延迟。 写通(Write Through)和非写分配(Non-Write Allocate)将数据直接写入下级存储器,而写回(Write Back)和写分配(Write Allocate)则会将数据写入到D-Cache中。通常情况下,D-Cache的写策略搭配为写通+非写分配或写回+写分配。

写通示意图

write-through

写通示意图

write-back

写回示意图

替换策略

读写D-Cache发生缺失时,需要从对应的Cache Set中找到一个cache行,来存放从下级存储器中读出的数据,如果此时这个Cache Set内的所有Cache行都已经被占用了,那么就需要替换掉其中一个,如何从这些有效的Cache行找到一个并替换它,这就是替换策略,本节介绍几种最常用的替换策略。

近期最少使用法会选择最近被使用次数最少的Cache行,因此这个算法需要追踪每个Cache行的使用情况,这需要为每个Cache行都设置一个年龄(age)部分,每当一个Cache行被访问时,它对应的年龄部分就会增加,或者减少其他Cache行的年龄值,这样当进行替换时,年龄值最小的那个Cache行就是被使用次数最少的了,会选择它进行替换。

随机替换算法硬件实现简单,这种方法发生缺失的频率会更高一些,但是随着Cache容量的增大,这个差距是越来越小的。在实际的设计中,很难实现严格的随机,一般采用一种称为时钟算法(clock algorithm)的方法实现近似的随机,它的工作原理本质上是一个时钟计数器,计数器的宽度由Cache的路的个数决定,当要替换时,就根据这个计数器选择相应的行进行替换。这种方法硬件复杂度较低,也不会损失较多的性能,因此是一种折中的方法。

整体框图和流水级

以下是L1Cache的整体框图和流水级示意:

Cache

子模块列表

以下是NutShell L1Cache的一些子模块:

子模块 描述
s1 缓存阶段1
s2 缓存阶段2
s3 缓存阶段3
metaArray 以数组形式存储元数据
dataArray 以数组形式存储缓存数据
arb 总线仲裁器

上下游通信总线采用SimpleBus总线,包含了req和resp两个通路,其中req通路的cmd信号表明请求的操作类型,可以通过检查该信号获得访问类型。SimpleBus总线共有七种操作类型,由于NutShell文档未涉及probe和prefetch操作,在验证中只出现五种操作:read、write、readBurst、writeBurst、writeLast,前两种为字读写,后三种为Burst读写,即一次可以操作多个字。

模块功能说明

Cache的功能是降低访存的时间开销,其功能本质上和内存是一致的。也就是说,不论是向Cache存数还是取数,其都应该和直接向内存存取的数是一样的。 因此,Cache的基础读写功能将成为我们的第一个功能点。

进一步,访问Cache的地址空间分为MMIO和内存。其中,访问MMIO的地址空间时,Cache一定会Miss,然后将请求转发到MMIO端口上。而访问内存的地址空间时,Cache则会根据该地址所在的Cache Line是否在Cache中而触发Hit或者Miss。Hit则直接返回响应,Miss则会将请求转发到内存端口。如果被替换的受害者行之前被写过,是dirty的,则要先将受害者行写回(write-back)内存,否则直接从内存加载缺失的Cache Line,重填(refill)回Cache。

1. 内存备份

Cache的功能本质上和内存是一致的,所以,不管向Cache存或取数据,本质上都应该和从内存存取的数一样。

据此,我们为这一功能点安排了一个测试点:即Cache应当为内存的备份。在实际测试过程中,必须同时考虑读写两方面的一致性。

2. MMIO

Cache会根据地址所在的区间,判断是否发生MMIO请求。

2.1. MMIO读写

如果发生MMIO请求则会将请求转发到MMIO的端口上,而不会发生Cache行的读写。此外,MMIO请求不是Burst请求,每次只会写入或读出一个地址的数据,而不是一个Cache行的数据。因此,在MMIO端口上不应当观测到Burst的请求类型。

据此,我们可以设计下述两个测试点:

序号 功能名称 测试点名称 描述
2.1.1 CACHE_MMIO_RW FORWARD Cache接收到MMIO空间的请求时,不应发生读写,而是直接转发给MMIO端口
2.1.2 CACHE_MMIO_RW NO_BURST Cache接收到MMIO空间的请求时,MMIO端口接收到的Cache请求不应为BURST类型

2.2. MMIO阻塞

NutShell手册指出,在检测出MMIO请求后会阻塞流水线。

因此,我们将设计这一测试点:当MMIO请求发出后,应当检查流水线是否阻塞。

3. Cache命中

NutShell的Cache采用写回策略,因此,在写命中时,需要标记脏块,后续发生缓存行替换时再将对应的缓存行写回内存。

同时,因为采用写回方式,所以,即使写命中也不需要同内存进行交互,因此收到回复的周期数更少。

3.1. 写命中

由于果壳Cache采用写回策略,因此,在发生写命中时,需要标记脏位,后续还要写回内存中。据此,可以设置一个测试点。

3.2. 命中时序

命中发生时,即使是写命中,也无需写回或者重填,因此,回复的时间会更短一些。

以下是本功能点的所有测试点:

序号 功能名称 测试点名称 描述
3.2.1 CACHE_HIT WRITE Cache写命中时,应设置脏位
3.2.2 CACHE_HIT SHORTER Cache写命中时,回复的周期应该更少

4. Cache缺失

为了创造Cache Miss的测试环境,首先需要通过一系列的Load操作先将Cache填满。后续需要触发Cache Miss时,只需要访问上述Load覆盖范围之外的地址即可。

4.1. 缺失通用行为

发生Cache Miss时,会阻塞流水线,同时,NutShell Cache重填时采用关键字优先方案,即缺失发生时,系统会优先获取CPU所需要的当前指令或数据所对应的字。因此,Cache向内存请求数据时,发出的首个地址应当是向Cache发出请求时的地址。例如,假设向Cache发出0x1000地址的读请求,此时发生Cache Miss,Cache会向内存发出读请求,这个请求的首地址应当是0x1000。显然Cache缺失时,回复的时间会更长。

从而,我们可以划分如下的测试点:

序号 功能名称 测试点名称 描述
4.1.1 CACHE_MISS_COMMON BLOCK 发生缺失时,也会阻塞流水线
4.1.2 CACHE_MISS_COMMON CRITICAL_WORD Cache缺失时,Cache发出请求的首个地址应当是向Cache请求的地址
4.1.3 CACHE_MISS_COMMON LONGER Cache缺失时,回复的时间会更长

4.2. 脏块写回

当需要替换的Cache块是脏块时,首先会进行写回的操作。

在进行测试时,我们首先需要创建脏块的环境,由于NutShell Cache采用随机替换的策略,因此我们考虑将整个Cache都设置成脏块。操作也是简单的,在上述的Load的基础上,只需要在每个CacheLine的起始地址进行一次Store操作即可。

4.3. 干净块不写回

当需要替换的Cache块是干净的时,不会写回这个Cache块。

常量说明

常量名 常量值 解释
缓存行大小 64 以字节为单位的缓存行大小
L1Cache大小 32 L1Cache的总容量,单位为千字节

接口说明

信号 说明
clock
reset
时钟
复位信号
io_flush
io_empty
io_in_*


请求总线信号(req & resp)
io_out_mem_*
io_mmio_*
io_out_coh_*
victim_way_mask
cache向内存请求的总线信号
cache向MMIO请求的总线信号
一致性相关的信号
受害者相关信号,即被替换的cache块相关信息

测试点总表

实际使用下面的表格时,请用有意义的英文大写的功能名称和测试点名称替换下面表格中的名称

序号 功能名称 测试点名称 描述
1 CACHE_BACKUP BACKUP 对Cache的存取应该同对内存的存取一致
2.1.1 CACHE_MMIO_RW FORWARD Cache接收到MMIO空间的请求时,不应发生读写,而是直接转发给MMIO端口
2.1.2 CACHE_MMIO_RW NO_BURST
2.2 CACHE_MMIO BLOCK MMIO请求发生时,应当阻塞流水线
3.1 CACHE_HIT WRITE Cache写命中时,应设置脏位
3.2 CACHE_HIT SHORTER Cache写命中时,回复的周期应该更少
4.1.1 CACHE_MISS_COMMON BLOCK 发生缺失时,也会阻塞流水线
4.1.2 CACHE_MISS_COMMON CRITICAL_WORD Cache缺失时,Cache发出请求的首个地址应当是向Cache请求的地址
4.1.3 CACHE_MISS_COMMON LONGER Cache缺失时,回复的时间会更长
4.2 CACHE_MISS DIRTY Cache缺失时,Cache发出请求的首个地址应当是向Cache请求的地址
4.3 CACHE_MISS CLEAN Cache缺失时,回复的时间会更长