diff --git a/dir_map.f b/dir_map.f index ad851e2..cf4343d 100644 --- a/dir_map.f +++ b/dir_map.f @@ -12,4 +12,10 @@ frontend_itlb --> TLB --> ut_frontend/itlb/toffee_version frontend_tlb_fa --> TLBFA --> ut_frontend/itlb/submodules/TLBFA frontend_tlb_nonblock --> TLBNonBlock --> ut_frontend/itlb/submodules/TLBNonBlock frontend_tlb_storage_wrapper --> TlbStorageWrapper --> ut_frontend/itlb/submodules/TlbStorageWrapper -frontend_tlbuffer --> TLBuffer --> ut_frontend/itlb/submodules/TLBuffer \ No newline at end of file +frontend_tlbuffer --> TLBuffer --> ut_frontend/itlb/submodules/TLBuffer +frontend_icache_ctrlunit --> CtrlUnit --> ut_frontend/icache/ctrlunit +frontend_icache_iprefetchpipe --> IPrefetchPipe --> ut_frontend/icache/iprefechpipe +frontend_icache_icache --> ICache --> ut_frontend/icache/icache +frontend_icache_mainpipe --> ICacheMainPipe --> ut_frontend/icache/mainpipe +frontend_icache_missunit --> ICacheMissUnit --> ut_frontend/icache/missunit +frontend_icache_waylookup --> WayLookup --> ut_frontend/icache/waylookup \ No newline at end of file diff --git a/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/01_iprefetchpipe.md b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/01_iprefetchpipe.md new file mode 100644 index 0000000..af356a5 --- /dev/null +++ b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/01_iprefetchpipe.md @@ -0,0 +1,424 @@ +--- +title: IPrefetchPipe +linkTitle: IPrefetchPipe +weight: 12 +--- + +
+ +
+ +## IPrefetchPipe + +IPrefetchPipe 为预取的流水线,三级流水设计,负责预取请求的过滤。 + +
+
+ IPrefetchPipe模块结构示意图 +
+ IPrefetchPipe结构示意图 +
+
+ +
+ +1. 接收预取请求(s0 阶段): + +- 从 FTQ 或后端接收预取请求。 +- 发送读请求到 ITLB 和 MetaArray 缓存元数据模块。 + +2. 地址转换和缓存检查(s1 阶段): + +- 接收 ITLB 的地址转换结果,处理可能的缺失和重发。 +- 从缓存元数据中读取标签和有效位,检查是否命中。 +- 进行 PMP 权限检查,合并异常信息。 +- 根据情况决定是否发送请求到 WayLookup 模块。 + +3. 未命中请求处理(s2 阶段): + +- 检查与 missUnit 的交互,更新命中状态。 +- 对于无异常的未命中请求,向 missUnit 发送请求以获取数据。 +- 控制流水线的推进和刷新,处理可能的阻塞和异常。 + +### S0 流水级 + +在 S0 流水级,接收来自 FTQ 的预取请求,向 MetaArray 和 ITLB 发送请求。 + +- 接收预取请求:从 FTQ 或后端接收预取请求,提取预取请求的虚拟地址、FTQ 索引、是否为软件预取、是否跨缓存行信、虚拟组索引(s0_req_vSetIdx)和后端的异常信息。 +- 发送请求到 ITLB:将虚拟地址发送到 ITLB 进行地址转换。 +- 发送请求到缓存元数据(Meta SRAM):将请求发送到缓存的元数据存储器,以便在后续阶段读取缓存标签和有效位。 + +### S1 流水级 + +软件预取 enqway 持续一拍... + +- 接收 ITLB 的响应:从 ITLB 接收地址转换的结果,包括物理地址 paddr、异常类型(`af`/`pf`)和特殊情况(`pbmt.nc`/`pbmt.io`)。 +- 接收缓存元数据的响应并检查缓存命中:从缓存元数据存储器 MetaArray 读取缓存标签 `tag` 和有效位,检查预取地址是否在缓存中已存在,命中结果存入 `waymask` 中。 +- 权限检查:使用 PMP 对物理地址进行权限检查,确保预取操作的合法性。 +- 异常处理和合并:合并来自后端、ITLB、PMP 的异常信息,准备在后续阶段处理。 +- 发送请求到 WayLookup 模块:当条件满足时,将元数据(命中信息 `waymask`、ITLB 信息 `paddr`/`af`/`pf`)发送到 WayLookup 模块,以便进行后续的缓存访问。 +- 状态机转换:根据当前状态和条件,更新下一个状态。 + - 状态机初始状态为 `idle`,当 S1 流水级进入新的请求时,首先判断 ITLB 是否缺失,如果缺失,就进入 `itlbResend`;如果 ITLB 命中但命中信息未入队 WayLookup,就进入 `enqWay`;如果 ITLB 命中且 WayLookup 入队但 S2 请求未处理完,就进入 `enterS2`。 + - 在 `itlbResend` 状态,重发 ITLB 请求,此时占用 ITLB 端口,直至请求回填完成,在回填完成的当拍向 MetaArray 再次发送读请求,回填期间可能发生新的写入,如果 MetaArray 繁忙(正在被写入),就进入`metaResend`,否则进入 `enqWay`。 + - 在 `metaResend` 状态,重发 MetaArray 读请求,发送成功后进入 `enqWay`。 + - 在 `enqWay` 状态,尝试将元数据入队 WayLookup,如果 WayLookup 队列已满,就阻塞至 WayLookup 入队成功,另外在 MSHR 发生新的写入时禁止入队,主要是为了防止写入的信息与命中信息所冲突,需要对命中信息进行更新。当成功入队 WayLookup 或者是软件预取时,如果 S2 空闲,就直接进入 `idle`,否则进入 `enterS2`。 + - 在 `enterS2` 状态,尝试将请求流入下一流水级,流入后进入 `idle`。 + +
+
+ IPrefetchPipe状态机 +
+ IPrefetchPipe S1状态机 +
+
+ +
+ +### S2 流水级 + +- 监控 missUnit 的请求:更新 MSHR 的匹配状态。综合该请求的命中结果、ITLB 异常、PMP 异常、meta 损坏,判断是否需要预取,只有不存在异常时才进行预取。 +- 发送请求到 missUnit:因为同一个预测块可能对应两个 cacheline,所以通过 Arbiter 依次将请求发送至 MissUnit。 + +### 命中信息的更新 + +在 S1 流水级中得到命中信息后,距离命中信息真正在 MainPipe 中被使用要经过两个阶段,分别是在 IPrefetchPipe 中等待入队 WayLookup 阶段和在 WayLookup 中等待出队阶段,在等待期间可能会发生 MSHR 对 Meta/DataArray 的更新,因此需要对 MSHR 的响应进行监听,分为两种情况: + +1. 请求在 MetaArray 中未命中,监听到 MSHR 将该请求对应的 cacheline 写入了 SRAM,需要将命中信息更新为命中状态。 +2. 请求在 MetaArray 中已经命中,监听到同样的位置发生了其它 cacheline 的写入,原有数据被覆盖,需要将命中信息更新为缺失状态。 + +为了防止更新逻辑的延迟引入到 DataArray 的访问路径上,在 MSHR 发生新的写入时禁止入队 WayLookup,在下一拍入队。 + +### 刷新机制 + +在 IPrefetch 中如果收到后端重定向、IFU 预译码、fencei 带来的刷新,就冲刷整个流水线 + +IPrefetchPipe 模块中的刷新信号主要来自以下两个方面: + +1. 全局刷新信号:由系统的其他模块发出的全局刷新信号,如怀疑流水线中存在错误数据或需要清除流水线时触发。 + +- io.flush:模块输入的全局刷新信号。当系统需要清除所有流水线阶段的数据时,该信号被置为高。 + +2. 来自分支预测单元(BPU)的刷新信号:当分支预测错误或需要更新预测信息时,BPU 会发出刷新信号。 + +- io.flushFromBpu:包含来自 BPU 的刷新信息,指示哪些指令需要被刷新。 + +## IPrefetchPipe 的功能点和测试点 + +### 接收预取请求 + +从 FTQ 接收预取请求,请求可能有效( io.req.valid 为高),可能无效; IPrefetchPipe 可能处于空闲( io.req.ready 为高),可能处于非空闲状态。 +只有在请求有效且 IPrefetchPipe 处于空闲状态时,预取请求才会被接收(这里暂不考虑 s0 的刷新信号 s0_flush ,默认其为低)。 +预取请求分为不同类型,包括硬件预取请求 (isSoftPrefetch = false)和软件预取请求 (isSoftPrefetch = true)。 +cacheline 也分为单 cacheline 和双 cacheline。 + +1. 硬件预取请求: + 预取请求为硬件 (isSoftPrefetch = false) + + 1. 预取请求可以继续: + - 当预取请求有效且 IPrefetchPipe 处于空闲状态时,预取请求应该被接收。 + - s0_fire 信号在没有 s0 的刷新信号( s0_flush 为低)时,应该被置为高。 + 2. 预取请求被拒绝--预取请求无效时: + - 当预取请求无效时,预取请求应该被拒绝。 + - s0_fire 信号应该被置为低。 + 3. 预取请求被拒绝--IPrefetchPipe 非空闲时: + - 当 IPrefetchPipe 非空闲时,预取请求应该被拒绝。 + - s0_fire 信号应该被置为低。 + 4. 预取请求被拒绝--预取请求无效且 IPrefetchPipe 非空闲时: + - 当预取请求无效且 IPrefetchPipe 非空闲时,预取请求应该被拒绝。 + - s0_fire 信号应该被置为低。 + 5. 预取请求有效且为单 cacheline 时: + - 当预取请求有效且为单 cacheline 时,预取请求应该被接收。 + - s0_fire 为高,s0_doubleline 应该被置低(false)。 + 6. 预取请求有效且为双 cacheline 时: + - 当预取请求有效且为双 cacheline 时,预取请求应该被接收。 + - s0_fire 为高,s0_doubleline 应该被置高(true)。 + +2. 软件预取请求: + 预取请求为软件 (isSoftPrefetch = true) + + 1. 软件预取请求可以继续: + - 当预取请求有效且 IPrefetchPipe 处于空闲状态时,软件预取请求应该被接收。 + - s0_fire 信号在没有 s0 的刷新信号( s0_flush 为低)时,应该被置为高。 + 2. 软件预取请求被拒绝--预取请求无效时: + - 当预取请求无效时,软件预取请求应该被拒绝。 + - s0_fire 信号应该被置为低。 + 3. 软件预取请求被拒绝--IPrefetchPipe 非空闲时: + - 当 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。 + - s0_fire 信号应该被置为低。 + 4. 软件预取请求被拒绝--预取请求无效且 IPrefetchPipe 非空闲时: + - 当预取请求无效且 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。 + - s0_fire 信号应该被置为低。 + 5. 软件预取请求有效且为单 cacheline 时: + - 当软件预取请求有效且为单 cacheline 时,软件预取请求应该被接收。 + - s0_fire 为高,s0_doubleline 应该被置低(false)。 + 6. 软件预取请求有效且为双 cacheline 时: + - 当软件预取请求有效且为双 cacheline 时,软件预取请求应该被接收。 + - s0_fire 为高,s0_doubleline 应该被置高(true)。 + +### 接收来自 ITLB 的响应并处理结果 + +接收 ITLB 的响应,完成虚拟地址到物理地址的转换。 +当 ITLB 发生缺失(miss)时,保存请求信息,等待 ITLB 完成后再继续处理。 + +1. 地址转换完成: + + - 根据 ITLB 的响应,接收物理地址(paddr),并完成地址转换。 + - 处理 ITLB 响应可能在不同周期到达的情况,管理有效信号和数据保持机制,确保正确使用物理地址。 + + 1. 当 ITLB 正常返回物理地址时: + + - ITLB 在一个周期内成功返回物理地址 paddr,s1_valid 为高。 + - 确认 s1 阶段正确接收到 paddr。 + + 2. 当 ITLB 发生 TLB 缺失,需要重试时: + - fromITLB(PortNumber).bits.miss 为高,表示对应通道的 ITLB 发生了 TLB 缺失,需要重发。 + - 重发完成后,后续步骤继续进行,fromITLB(PortNumber).bits.miss 为低。 + +2. 处理 ITLB 异常: + + - 根据 ITLB 的异常信息,处理可能的异常。pf 缺页、pgf 虚拟机缺页、af 访问错误。 + + 1. 当 ITLB 发生页错误异常时: + - s1_itlb_exception 返回的页错误。 + - iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示页错误 pf。 + 2. 当 ITLB 发生虚拟机页错误异常时: + - s1_itlb_exception 返回的虚拟机页错误。 + - iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示虚拟机页错误 pgf。 + 3. 当 ITLB 发生访问错误异常时: + - s1_itlb_exception 返回的访问错误。 + - iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示访问错误 af。 + +3. 处理虚拟机物理地址(用于虚拟化): + + - 在虚拟化环境下,处理虚拟机物理地址(gpaddr),确定访问是否针对二级虚拟机的非叶子页表项(isForVSnonLeafPTE)。 + + 1. 发生虚拟机页错误异常返回虚拟机物理地址(gpaddr): + - 发生 pgf 后,需要返回对应的 gpaddr。 + - 只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。 + 2. 当访问二级虚拟机的非叶子页表项时: + - 发生 gpf 后,如果是访问二级虚拟机的非叶子页表项时,需要返回对应的 gpaddr。 + - 只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。 + +4. 返回基于页面的内存类型 pbmt 信息: + - TLB 有效时,返回 pbmt 信息。 + +### 接收来自 IMeta(缓存元数据)的响应并检查缓存命中 + +从 Meta SRAM 中读取缓存标签和有效位。 +将物理地址的标签部分与缓存元数据中的标签比较,确定是否命中。 + +1. 缓存标签比较和有效位检查: + + - 从物理地址中提取物理标签(ptag),将其与缓存元数据中的标签进行比较,检查所有缓存路(Way)。检查有效位,确保只考虑有效的缓存行。 + + 1. 缓存未命中(标签不匹配或有效位为假): + - 当标签不匹配或者标签匹配,但是有效位为假时,表示缓存未命中。 + - s1_meta_ptags(PortNumber)(nWays) 不等于 ptags(PortNumber) 或者它们相等,但是对应的 s1_meta_valids 为低时,总之返回的 waymasks 为全 0。 + 2. 单路缓存命中(标签匹配且有效位为真): + - 当标签匹配,且有效位为真时,表示缓存命中。 + - waymasks 对应的位为 1。 + +### PMP(物理内存保护)权限检查 + +对物理地址进行 PMP 权限检查,确保预取操作的合法性。 +处理 PMP 返回的异常和 MMIO 信息 + +1. 访问被允许的内存区域 + - itlb 返回的物理地址在 PMP 允许的范围内。 + - s1_pmp_exception(i) 为 none。 +2. 访问被禁止的内存区域 + - s1_req_paddr(i) 对应的地址在 PMP 禁止的范围内。 + - s1_pmp_exception(i) 为 af。 +3. 访问 MMIO 区域 + - itlb 返回的物理地址在 MMIO 区域。 + - s1_pmp_mmio 为高。 + +### 异常处理和合并 + +backend 优先级最高,merge 方法里的异常越靠前优先级越高 + +合并来自后端、ITLB、PMP 的异常信息,按照优先级确定最终的异常类型。 + +1. 仅 ITLB 产生异常 + - s1_itlb_exception(i) 为非零,s1_pmp_exception(i) 为零。 + - s1_exception_out(i) 正确包含 ITLB 异常。 +2. 仅 PMP 产生异常 + - s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为非零。 + - s1_exception_out(i) 正确包含 PMP 异常。 +3. 仅 后端 产生异常 + - s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为零。 + - s1_exception_out(i) 正确包含 后端 异常。 +4. ITLB 和 PMP 都产生异常 + - s1_itlb_exception(i) 和 s1_pmp_exception(i) 都为非零。 + - s1_exception_out(i) 包含 ITLB 异常(优先级更高)。 +5. ITLB 和 后端 都产生异常 + - s1_itlb_exception(i) 和 s1_backendException(i) 都为非零。 + - s1_exception_out(i) 包含 后端 异常(优先级更高)。 +6. PMP 和 后端 都产生异常 + - s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。 + - s1_exception_out(i) 包含 后端 异常(优先级更高)。 +7. ITLB、PMP 和 后端 都产生异常 + - s1_itlb_exception(i)、s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。 + - s1_exception_out(i) 包含 后端 异常(优先级更高)。 +8. 无任何异常 + - s1_itlb_exception(i)、s1_pmp_exception(i)、s1_backendException(i) 都为零。 + - s1_exception_out(i) 指示无异常。 + +### 发送请求到 WayLookup 模块 + +当条件满足时,将请求发送到 WayLookup 模块,以进行后续的缓存访问。 + +1. 正常发送请求到 WayLookup + - toWayLookup.valid 为高,toWayLookup.ready 为高,s1_isSoftPrefetch 为假。 + - 请求成功发送,包含正确的地址、标签、waymask 和异常信息。 +2. WayLookup 无法接收请求 + - toWayLookup.valid 为高,toWayLookup.ready 为假。 + - 状态机等待 WayLookup 准备好,不会错误地推进。 +3. 软件预取请求不发送到 WayLookup + - s1_isSoftPrefetch 为真。 + - toWayLookup.valid 为假,不会发送预取请求到 WayLookup。 + +### 状态机控制和请求处理流程 + +使用状态机管理 s1 阶段的请求处理流程。 +包括处理 ITLB 重发、Meta 重发、进入 WayLookup、等待 s2 准备等状态 + +1. 初始为 m_idle 状态: + + 1. 正常流程推进,保持 m_idle 状态 + + - s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为真,s2_ready 为真。 + - 状态机保持在 m_idle 状态,s1 阶段顺利推进。 + + 2. ITLB 未完成,需要重发 + + - s1_valid 为高,itlb_finish 为假。 + - 状态机进入 m_itlbResend 状态,等待 ITLB 完成。 + + 3. ITLB 完成,WayLookup 未命中 + + - s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为假。 + - 状态机进入 m_enqWay 状态,等待 WayLookup 入队。 + +2. 初始为 m_itlbResend 状态: + 1. ITLB 命中, MetaArray 空闲,需要 WayLookup 入队 + - itlb_finish 为假,toMeta.ready 为真。 + - 状态机进入 m_enqWay 状态,等待 WayLookup 入队。 + 2. ITLB 命中, MetaArray 繁忙,等待 MetaArray 读请求 + - itlb_finish 为假,toMeta.ready 为假。 + - 状态机进入 m_metaResend 状态,MetaArray 读请求 +3. 初始为 m_metaResend 状态: + + 1. MetaArray 空闲 ,需要 WayLookup 入队 + - toMeta.ready 为真。 + - 状态机进入 m_enqWay 状态,等待 WayLookup 入队。 + +4. 初始为 m_enqWay 状态: + + 1. WayLookup 入队完成或者为软件预取, S2 空闲, 重新进入空闲状态 + - toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为假。 + - 状态机进入空闲状态 m_idle。 + 2. WayLookup 入队完成或者为软件预取, S2 繁忙,需要 enterS2 状态 + - toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为真。 + - 状态机进入 m_enterS2 状态,等待 s2 阶段准备好。 + +5. 初始为 m_enterS2 状态: + 1. s2 阶段准备好,请求进入下流水级,流入后进入 m_idle 状态 + - s2_ready 为真。 + - 状态机进入空闲状态 m_idle。 + +### 监控 missUnit 的请求 + +检查 missUnit 的响应,更新缓存的命中状态和 MSHR 的匹配状态。 + +1. 请求与 MSHR 匹配且有效: + + - s2_req_vSetIdx 和 s2_req_ptags 与 fromMSHR 中的数据匹配,且 fromMSHR.valid 为高,fromMSHR.bits.corrupt 为假。 + - s2_MSHR_match(PortNumber) 为真, s2_MSHR_hits(PortNumber) 应保持为真 + +2. 请求在 SRAM 中命中: + + - s2_waymasks(PortNumber) 中有一位为高,表示在缓存中命中。 + - s2_SRAM_hits(PortNumber) 为真,s2_hits(PortNumber) 应为真。 + +3. 请求未命中 MSHR 和 SRAM: + - 请求未匹配 MSHR,且 s2_waymasks(PortNumber) 为空。 + - s2_MSHR_hits(PortNumber)、s2_SRAM_hits(PortNumber) 均为假, s2_hits(PortNumber) 为假。 + +### 发送请求到 missUnit + +对于未命中的预取请求,向 missUnit 发送请求,以获取缺失的数据。 + +1. 确定需要发送给 missUnit 的请求 + +- 根据命中状态、异常信息、MMIO 信息等,确定哪些请求需要发送到 missUnit(即 s2_miss)。 + + 1. 请求未命中且无异常,需要发送到 missUnit: + + - s2_hits(PortNumber) 为假(未命中缓存),s2_exception 无异常,s2_mmio 为假(不是 MMIO 或不可缓存的内存)。 + - s2_miss(PortNumber) 为真,表示需要发送请求到 missUnit。 + + 2. 请求命中或有异常,不需要发送到 missUnit: + + - s2_hits(i) 为真(已命中)或者 s2_exception 有异常 或者 s2_mmio 为真(MMIO 访问)。 + - s2_miss(i) 为假,不会发送请求到 missUnit。 + + 3. 双行预取时,处理第二个请求的条件: + - s2_doubleline 为真,处理第二个请求。 + - 如果第一个请求有异常或 MMIO,s2_miss(1) 应为假,后续请求被取消或处理。 + +2. 避免发送重复请求,发送请求到 missUnit + +- 使用寄存器 has_send 记录每个端口是否已发送请求,避免重复发送。 +- 将需要发送的请求通过仲裁器 toMSHRArbiter 发送到 missUnit。 + + 1. 在 s1_real_fire 时,复位 has_send: + + - s1_real_fire 为高。 + - has_send(PortNumber) 应被复位为假,表示新的请求周期开始。 + + 2. 当请求成功发送时,更新 has_send: + + - toMSHRArbiter.io.in(PortNumber).fire 为高(请求已发送)。 + - has_send(PortNumber) 被设置为真,表示该端口已发送请求。 + + 3. 避免重复发送请求: + + - 同一请求周期内,has_send(PortNumber) 为真,s2_miss(PortNumber) 为真。 + - toMSHRArbiter.io.in(PortNumber).valid 为假,不会再次发送请求。 + + 4. 正确发送需要的请求到 missUnit: + + - s2_valid 为高,s2_miss(i) 为真,has_send(i) 为假。 + - toMSHRArbiter.io.in(i).valid 为高,请求被成功发送。 + + 5. 仲裁器正确仲裁多个请求: + - 多个端口同时需要发送请求。 + - 仲裁器按照优先级或设计要求选择请求发送到 missUnit,未被选中的请求在下个周期继续尝试发送。 + +### 刷新机制 + +- io.flush: 全局刷新信号,当该信号为高时,所有请求都需要刷新。 +- from_bpu_s0_flush:当请求不是软件预取(!s0_isSoftPrefetch, 软件预取请求是由特定的指令触发的,与指令流中的分支预测无关。因此,在处理刷新信号时,对于软件预取请求,通常不受来自 BPU 的刷新信号影响。),且 BPU 指示需要在 Stage 2 或 Stage 3 刷新的请求,由于该请求尚未进入 s1 阶段,因此在 s0 阶段也需要刷新。 +- s0_flush:综合考虑全局刷新信号、来自 BPU 的刷新信号,以及 s1 阶段的刷新信号 +- from_bpu_s1_flush:当 s1 阶段的请求有效且不是软件预取,且 BPU 指示在 Stage 3 需要刷新,则在 s1 阶段需要刷新。 +- io.itlbFlushPipe:当 s1 阶段需要刷新时,该信号用于通知 ITLB 刷新其流水线,以保持一致性。 +- s1_flush:综合考虑全局刷新信号和来自 BPU 的刷新信号。 +- s2_flush:用于控制 s2 阶段是否需要刷新。 + +1. 发生全局刷新 + - io.flush 为高。 + - s0_flush、s1_flush、s2_flush 分别为高,所有阶段的请求被正确清除。 +2. 来自 BPU 的刷新 + - io.flushFromBpu.shouldFlushByStageX 为真(X 为 2 或 3),且请求不是软件预取。 + - 对应阶段的 from_bpu_sX_flush 为高,sX_flush 为高,阶段请求被刷新。 +3. 刷新时状态机复位 + - s1_flush 为高。 + - 状态机 state 被重置为 m_idle 状态。 +4. ITLB 管道同步刷新 + - s1_flush 为高。 + - io.itlbFlushPipe 为高,ITLB 被同步刷新。 diff --git a/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/02_mainpipe.md b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/02_mainpipe.md new file mode 100644 index 0000000..cd1f2e8 --- /dev/null +++ b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/02_mainpipe.md @@ -0,0 +1,362 @@ +--- +title: MainPipe +linkTitle: MainPipe +weight: 12 +--- + +
+ +
+ +## MainPipe + +MainPipe 为 ICache 的主流水,为三级流水设计,负责从 DataArray 中读取数据,pmp 检查,缺失处理,并且将结果返回给 IFU。 + +
+
+ MainPipe结构示意图 +
+ MainPipe结构示意图 +
+
+ +
+ +1. 从 WayLookup 获取信息,访问 DataArray 单路(S0 阶段) + 在 S0 流水级,从 WayLookup 获取元数据,包括路命中信息和 ITLB 查询结果,访问 DataArray 的单路,如果 DataArray 正在被写或 WayLookup 中没有有效表项,流水线就会阻塞。每次重定向后,FTQ 中同一个请求被同时发送到 MainPipe 和 IPrefetchPipe 中,MainPipe 始终需要等待 IPrefetchPipe 将请求的查询信息写入 WayLookup 后才能向下走,导致了 1 拍重定向延迟,当预取超过取指时,该延迟就会被覆盖。 + +- 接收并解析来自 FTQ 的取指请求,提取必要的请求信息,如虚拟地址、缓存组索引、块内偏移、是否为双行读、后端的异常信息。 +- 从 WayLookup 模块获取缓存命中信息和 TLB 信息,包括 waymask、物理标签、虚拟机物理地址、是否为叶节点、 ITLB 异常、ITLB 的 PBMT 信息、缓存元数据的校验码。 +- 访问 DataArray 的单路,如果 DataArray 正在被写或 WayLookup 中没有有效表项,流水线就会阻塞。 +- 每次重定向后,FTQ 中同一个请求被同时发送到 MainPipe 和 IPrefetchPipe 中,MainPipe 始终需要等待 IPrefetchPipe 将请求的查询信息写入 WayLookup 后才能向下走,导致了 1 拍重定向延迟,当预取超过取指时,该延迟就会被覆盖。 + +2. 接收上一个阶段的信息并进行数据暂存、PMP 检查、从 DataArray 获取读响应异常合并、替换策略更新以及监控 MissUnit(S1 阶段) + +- 寄存并延迟 S0 阶段信息 + - 从 S0 获取的地址、tag、命中方式(waymask)、TLB 异常标志、下一拍要用的数据等,都会在 S1 寄存一拍,保证在流水线停顿时也能维持正确值。 +- Meta ECC 检查 + - 对 S0 读出的 meta 和其校验码(ECC/Parity)进行比对,判断是否发生错误。如果关闭 parity 功能,则跳过该检查。 +- 更新 replacer + - 对确定命中的访问请求,进行“touch”更新,标记最近使用过的 way,以便后续替换算法正确运行。 +- PMP 检查 + - 根据 S0 得到的物理地址(paddr),在 S1 对其进行 PMP 检查,判断是否拥有执行权限、是否为 MMIO 等。在当拍收到响应,将结果寄存到下一流水级进行处理。 + - 需要指出,IPrefetchPipe s1 流水级也会进行 PMP 检查,和此处的检查实际上是完全一样的,分别检查只是为了优化时序(避免 `ITLB(reg) -> ITLB.resp -> PMP.req -> PMP.resp -> WayLookup.write -> bypass -> WayLookup.read -> MainPipe s1(reg)` 的超长组合逻辑路径)。 +- 异常合并 + - 将 ITLB 与 PMP 异常进行优先级合并,产生最终的异常标记。 +- 选择数据来源:MSHR 或 SRAM + - 接收 DataArray 返回的 data 和 code 并寄存,同时监听 MSHR 的响应,当 DataArray 和 MSHR 的响应同时有效时,后者的优先级更高。当 MSHR 已在填充一些数据,如果当前请求与 MSHR 命中,可以在 S1 阶段直接选用 MSHR 的数据,而不必依赖 SRAM 读出的结果。 + +3. 监控 MissUnit,在 ECC 校验、异常处理和缺失处理之后,将最终的数据、异常信息传递给 IFU,完成取指流程(S2 阶段) + +- ECC 校验 + - DataArray ECC 校验,对 S1 流水级寄存的 code 进行校验,生成 data 是否损坏信号 s2_data_corrupt。如果校验出错,就将错误报告给 BEU。 + - MetaArray ECC 校验,IPrefetchPipe 读出 MetaArray 的数据后会直接进行校验,并将校验结果随命中信息一起入队 WayLookup 并随 MainPipe 流水到达 S2 级(meta_corrupt 信号),在此处随 DataArray 的 ECC 校验结果一起报告给 BEU。 +- 监控 MissUnit 响应端口 + - 检查当前 S2 阶段的请求是否与 MSHR 中的条目匹配,命中时寄存 MSHR 响应的数据,为了时序在下一拍才将数据发送到 IFU。 + - 更新 Data 和其是否来自 MSHR 的信息。 + - 更新 s2_hits 和处理异常。 + - 处理 L2 Cache 的 Corrupt 标志。 +- 缺失处理,发送 Miss 请求到 MSHR + - 计算是否需要重新获取(Refetch)。 + - 通过是否命中、ECC 错误、正确跨行、是否异常和是否属于 MMIO 区域来发送 Miss 请求。 + - 设置 Arbiter 合并多个端口的 Miss 请求,确保一次只处理一个请求,同时有避免重复请求的设置。 + - 判断 Fetch 是否完成。 + - 生成 L2 Cache 的异常标记,再将当前 S2 阶段的异常(包括 ITLB、PMP)与 L2 Cache 的异常进行合并。 +- 响应 IFU + - 将最终的数据、异常信息传递给 IFU,完成取指流程。 + - 根据请求是否为跨行,决定如何处理双行数据。 +- 报告 TileLink 的 Corrupt 错误 + - 对于每个端口,如果在当前周期 s2_fire 时检测到来自 L2 Cache 的数据 corrupt 错误,就将错误报告给 BEU。 + +## MainPipe 的功能点和测试点 + +### 访问 DataArray 的单路 + +根据从 WayLookup 获取信息,包括路命中信息和 ITLB 查询结果还有 DataArray 当前的情况,决定是否需要从 DataArray 中读取数据。 + +1. 访问 DataArray 的单路 + - 当 WayLookup 中的信息表明路命中时,ITLB 查询成功,并且 DataArray 当前没有写时,MainPipe 会向 DataArray 发送读取请求,以获取数据。 + - s0_hits 为高(一路命中),s0_itlb_exception 信号为零(ITLB 查询成功),toData.last.ready 为高(DataArray 没有正在进行的写操作)。 + - toData.valid 信号为高,表示 MainPipe 向 DataArray 发出了读取请求。 +2. 不访问 DataArray(Way 未命中) ==会访问,但是返回数据无效== + - 当 WayLookup 中的信息表明路未命中时,MainPipe 不会向 DataArray 发送读取请求。 + - s0_hits 为低表示缓存未命中 + - toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 +3. 不访问 DataArray(ITLB 查询失败)==会访问,但是返回数据无效== + - 当 ITLB 查询失败时,MainPipe 不会向 DataArray 发送读取请求。 + - s0_itlb_exception 信号不为零(ITLB 查询失败)。 + - toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 +4. 不访问 DataArray(DataArray 正在进行写操作) + - 当 DataArray 正在进行写操作时,MainPipe 不会向 DataArray 发送读取请求。 + - toData.last.ready 信号为低,表示 DataArray 正在进行写操作。 + - toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 + +### Meta ECC 校验 + +将物理地址的标签部分与对应的 Meta 进行 ECC 校验,以确保 Meta 的完整性。 + +1. 无 ECC 错误 + +- 当 waymask 全为 0(没有命中),则 hit_num 为 0 或 waymask 有一位为 1(一路命中),hit_num 为 1 且 ECC 对比通过(encodeMetaECC(meta) == code) +- s1_meta_corrupt 为假。 + +2. 单路命中的 ECC 错误 + +- 当 waymask 有一位为 1(一路命中),ECC 对比失败(encodeMetaECC(meta) != code) +- s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 + +3. 多路命中 + > hit multi-way, must be an ECC failure + +- 当 waymask 有两位及以上为 1(多路命中),视为 ECC 错误。 +- s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 + +4. ECC 功能关闭 + +- 当奇偶校验关闭时(ecc_enable 为低),强制清除 s1_meta_corrupt 信号置位。 +- 不管是否发生 ECC 错误,s1_meta_corrupt 都为假。 + +### PMP 检查 + +- 将 S1 的物理地址 s1_req_paddr(i) 和指令 TlbCmd.exec 发往 PMP,判断取指是否合法。 +- 防止非法地址,区分普通内存和 MMIO 内存。 + +1. 没有异常 + - s1_pmp_exception 为全零,表示没有 PMP 异常。 +2. 通道 0 有 PMP 异常 + - s1_pmp_exception(0) 为真,表示通道 0 有 PMP 异常。 +3. 通道 1 有 PMP 异常 + - s1_pmp_exception(1) 为真,表示通道 1 有 PMP 异常。 +4. 通道 0 和通道 1 都有 PMP 异常 + - s1_pmp_exception(0) 和 s1_pmp_exception(1) 都为真,表示通道 0 和通道 1 都有 PMP 异常。 +5. 没有映射到 MMIO 区域 + - s1_pmp_mmio(0) 和 s1_pmp_mergemmio(1) 都为假,表示没有映射到 MMIO 区域。 +6. 通道 0 映射到了 MMIO 区域 + - s1_pmp_mmio(0) 为真,表示映射到了 MMIO 区域。 +7. 通道 1 映射到了 MMIO 区域 + - s1_pmp_mmio(1) 为真,表示映射到了 MMIO 区域。 +8. 通道 0 和通道 1 都映射到了 MMIO 区域 + - s1_pmp_mmio(0) 和 s1_pmp_mmio(1) 都为真,表示通道 0 和通道 1 都映射到了 MMIO 区域。 + +### 异常合并 + +- 将 s1_itlbmergeption 与 s1_pmp_exception 合并生成 s1_exception_out。 +- ITLB 异常通常优先于 PMP 异常。merge + +1. 没有异常 + - s1_exception_out 为全零,表示没有异常。 +2. 只有 ITLB 异常 + - s1_exception_out 和 s1_itlb_exception 一致 +3. 只有 PMP 异常 + - s1_exception_out 和 s1_pmp_exception 一致 +4. ITLB 与 PMP 异常同时出现 + > itlb has the highest priority, pmp next + - s1_exception_out 和 s1_itlb_exception 一致 + +### MSHR 匹配和数据选择 + +- 检查当前的请求是否与 MSHR 中正在处理的缺失请求匹配。 +- 判断 缓存组索引相同(s1_req_vSetIdx(i) == fromMSHR.bits.vSetIdx) ,物理标签相同 (s1_req_ptags(i) == fromMSHR.bits.blkPaddr);若匹配 MSHR 有效且没有错误(fromMSHR.valid && !fromMSHR.bits.corrupt),则优先使用 MSHR 中的数据 +- 避免重复访问 Data SRAM,提升性能;当 MSHR 中已有重填结果时,可立即命中。 + +1. 命中 MSHR + +- MSHR 中已有正确数据时,S1 阶段能直接拿到 +- s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 s1_bankMSHRHit(i),s1_data_is_from_MSHR(i) 为 true + +2. 未命中 MSHR + +- MSHR 中存放的地址与当前请求不同,那么应该读取 SRAM 的数据 +- s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false + +3. MSHR 数据 corrupt + +- fromMSHR.bits.corrupt = true,那么 MSHR 将不匹配,应该读取 SRAM 的数据 +- s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false + +### Data ECC 校验 + +在 S2 阶段,对从 S1 或 MSHR 获得的数据(如 s2_datas)进行 ECC 校验: + +- 若 ECC 校验失败,则标记 s2_data_corrupt(i) = true。 +- 若数据来自 MSHR,则不重复进行 ECC 校验(或忽略 corrupt) + +1. 无 ECC 错误 + +- s2_bank 全部没有损坏,bank 也选对了对应的端口和 bank,数据不来自 MSHR +- s2_data_corrupt(i) 为 false,没有 ECC 错误。 + +2. 单 Bank ECC 错误 + +- s2_bank_corrupt(bank) 有一个为 true ,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR +- s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 + +3. 多 Bank ECC 错误 + +- s2_bank_corrupt(bank) 有两个或以上为 true,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR +- s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 + +4. ECC 功能关闭 + +- 当奇偶校验关闭时(ecc_enable 为低),强制清除 s2_data_corrupt 信号置位。 +- 不管是否发生 ECC 错误,s2_data_corrupt 都为假。 + +### 冲刷 MetaArray + +Meta 或者 Data ECC 校验错误时,会冲刷 MetaArray,为重取做准备。 + +1. 只有 Meta ECC 校验错误 + > if is meta corrupt, clear all way (since waymask may be unreliable) + +- 当 s1_meta_corrupt 为真时,MetaArray 的所有路都会被冲刷。 +- toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。 + +2. 只有 Data ECC 校验错误 + > if is data corrupt, only clear the way that has error + +- 当 s2_data_corrupt 为真时,只有对应路会被冲刷。 +- toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的对应路置位。 + +3. 同时有 Meta ECC 校验错误和 Data ECC 校验错误 + +- 处理 Meta ECC 的优先级更高, 将 MetaArray 的所有路冲刷。 +- toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。 + +### 监控 MSHR 匹配与数据更新 + +- 判断是否命中 MSHR +- 根据 MSHR 是否命中和 s1 阶段是否发射来更新 s2 的数据,s2 的命中状态和 l2 是否损坏 + +1. MSHR 命中(匹配且本阶段有效) + +- MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效 +- s2_MSHR_match,s2_MSHR_hits 为高,s2_bankMSHRHit 对应 bank 为高 +- s1_fire 无效时,s2_datas 更新为 MSHR 的数据,将 s2_data_is_from_MSHR 对应位置位,s2_hits 置位,清除 s2_data_corrupt,l2 的 corrupt 更新为 fromMSHR.bits.corrupt +- s1_fire 有效时,s2_datas 为 s1_datas 的数据,将 s2_data_is_from_MSHR 对应位置为 s1 的 s1_data_is_from_MSHR,s2_hits 置为 s1_hits,清除 s2_data_corrupt,l2 的 corrupt 为 false + +2. MSHR 未命中 + +- MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效,至少有一个未达成 +- s2_MSHR_hits(i) = false,S2 不会更新 s2_datas,继续保持原先 SRAM 数据或进入 Miss 流程。 + +### Miss 请求发送逻辑和合并异常 + +- 通过计算 s2_should_fetch(i) 判断是否需要向 MSHR 发送 Miss 请求: + - 当出现未命中 (!s2_hits(i)) 或 ECC 错误(s2_meta_corrupt(i) || s2_data_corrupt(i)) 时,需要请求重新获取。 + - 若端口存在异常或处于 MMIO 区域,则不发送 Miss 请求。 +- 使用 Arbiter 将多个端口的请求合并后发送至 MSHR。 +- 通过 s2_has_send(i) 避免重复请求。 +- 将 S2 阶段已有的 ITLB/PMP 异常(s2_exception)与 L2 Cache 报告的 s2_l2_corrupt(i)(封装后为 s2_l2_exception(i))进行合并。 + +1. 未发生 Miss + +- 当 s2_hits(i) 为高(s2 已经命中),s2 的 meta 和 data 都没有错误,s2 异常,处于 mmio 区域 +- 以上条件至少满足一个时,s2_should_fetch(i) 为低,表示不发送 Miss 请求。 + +2. 单口 Miss + +- 当出现未命中 (!s2_hits(i)) 或 ECC 错误(s2_meta_corrupt(i) || s2_data_corrupt(i)),端口不存在异常且未处于 MMIO 区域时,会向 MSHR 发送 Miss 请求。 +- toMSHRArbiter.io.in(i).valid = true ,Arbiter 只发送一条 Miss 请求。 + +3. 双口都需要 Miss + +- 同上,但是两个端口都满足 s2_should_fetch 为高的条件。 +- toMSHRArbiter.io.in(0).valid、toMSHRArbiter.io.in(1).valid 均为 true,Arbiter 根据仲裁顺序依次发出请求。 + +4. 重复请求屏蔽 + +- 当 s1_fire 为高,表示可以进入 s2 阶段,那么 s2 还没有发送 s2_has_send(i) := false.B +- 如果已经有请求发送了,那么对应的 toMSHRArbiter.io.in(i).fire 为高,表示对应的请求可以发送,s2_has_send(i) := true。 +- 此时再次发送,toMSHRArbiter.io.in(i).valid 为低,表示发送失败。 + +5. 仅 ITLB/PMP 异常 + +- S1 阶段已记录了 ITLB 或 PMP 异常,L2 corrupt = false。 +- 2_exception_out 仅保留 ITLB/PMP 异常标记,无新增 AF 异常。 + +6. 仅 L2 异常 + +- S2 阶段 s2_l2_corrupt(i) = true,且无 ITLB/PMP 异常。 +- s2_exception_out(i) 表示 L2 访问错误(AF)。 + +7. ITLB + L2 同时出现 + +- 同时触发 ITLB 异常和 L2 corrupt。 +- s2_exception_out 优先保留 ITLB 异常类型,不被 L2 覆盖。 + +8. s2 阶段取指完成 + +- s2_should_fetch 的所有端口都为低,表示需要取指,那么取指完成 +- s2_fetch_finish 为高 + +### 响应 IFU + +- 若当前周期 S2 成功发射(s2_fire = true)且数据获取完毕(s2_fetch_finish),则把数据、异常信息、物理地址等打包到 toIFU.bits 输出。 +- 若为双行请求(s2_doubleline = true),也会向 IFU 发送第二路的信息(地址、异常)。 + +1. 正常命中并返回 + +- 不存在任何异常或 Miss,s2 命中,s2 阶段取指完成,外部的 respStall 停止信号也为低 。 +- toIFU.valid = true,toIFU.bits.data 为正确的 Cacheline 数据,toIFU.bits.exception、pmp_mmio、itlb_pbmt = none。 + +2. 异常返回 + +- 设置 ITLB、PMP、或 L2 corrupt 异常。 +- toIFU.bits.exception(i) = 对应异常类型,pmp_mmio、itlb_pbmt 根据是否有对应的异常设置为 true。 + +3. 跨行取指 + +- s2_doubleline = true,同时检查第一路、第二路返回情况。 +- toIFU.bits.doubleline = true。 +- 若第二路正常,toIFU.bits.exception(1) = none;若第二路异常,则 exception(1) 标记相应类型。 +- pmp_mmio、itlb_pbmt 类似。 + +4. RespStall + +- 外部 io.respStall = true,导致 S2 阶段无法发射到 IFU。 +- s2_fire = false,toIFU.valid 也不拉高,S2 保持原状态等待下一拍(或直到 respStall 解除)。 + +### L2 Corrupt 报告 + +- 当检测到 L2 Cache 返回的 corrupt 标记时(s2_l2_corrupt(i) = true),在 S2 完成发射后额外向外部错误接口 io.errors(i) 报告。 +- 与 Data ECC 或 Meta ECC 不同,L2 corrupt 由 L2 自己报告给 BEU,这里不需要再次报告给 beu。 + +1. L2 Corrupt 单路 + +- s2 阶段准备完成可以发射(s2_fire 为高),s2_MSHR_hits(0)和 fromMSHR.bits.corrupt 为高 +- s2_l2_corrupt(0) = true,io.errors(0).valid = true,io.errors(0).bits.source.l2 = true。 + +2. 双路同时 corrupt + +- 端口 0 和端口 1 都从 L2 corrupt 数据中获取。 +- s2_l2_corrupt 均为 true,发射后分别报告到 io.errors(0) 和 io.errors(1)。 + +### 刷新机制 + +- io.flush:外部的全局刷新信号,它用于指示整个流水线需要被冲刷(清空)。 +- s0_flush: S0 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S0 阶段的刷新操作。 +- s1_flush: S1 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S1 阶段的刷新操作。 +- s2_flush: S2 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S2 阶段的刷新操作。 + +1. 全局刷新 + +- io.flush 被激活时,流水线的各个阶段(S0, S1 和 S2)都能正确响应并执行刷新操作。 +- io.flush = true。 +- s0_flush, s1_flush, s2_flush = true。 + +2. S0 阶段刷新 + +- s0_flush = true。 +- s0_fire = false。 + +3. S1 阶段刷新 + +- s1_flush = true。 +- s1_valid, s1_fire = false。 + +4. S2 阶段刷新 + +- s2_flush = true。 +- s2_valid, toMSHRArbiter.io.in(i).valid , s2_fire = false \ No newline at end of file diff --git a/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/03_waylookup.md b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/03_waylookup.md new file mode 100644 index 0000000..1c04c6b --- /dev/null +++ b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/03_waylookup.md @@ -0,0 +1,202 @@ +--- +title: WayLookup +linkTitle: WayLookup +weight: 12 +--- + +
+ +
+ +## WayLookup + +
+
+ WayLookup 读写结构 +
+ WayLookup 读写结构 +
+
+ +
+ +
+
+ WayLookup 更新结构 +
+ WayLookup 更新结构 +
+
+ +
+ +- 内部是 FIFO 环形队列结构。暂存 IPrefetchPipe 查询 MetaArray 和 ITLB 得到的元数据,以备 MainPipe 使用。同时监听 MSHR 写入 SRAM 的 cacheline,对命中信息进行更新。 +- 通过 readPtr 和 writePtr 来管理读写位置。当有 flush 信号时,读写指针都会被重置。当写入数据时,写指针递增;读取时,读指针递增。需要处理队列的空和满的情况,empty 是读指针等于写指针,而 full 则是两者的值相同且标志位不同。 +- 处理 GPF 的部分,有一个 gpf_entry 寄存器,存储 GPF 的相关信息。当写入的数据包含 GPF 异常时,需要将信息存入 gpf_entry,并记录当前的写指针位置到 gpfPtr。当读取的时候,如果当前读指针的位置与 gpfPtr 匹配,并且 gpf_entry 有效,那么就将 GPF 信息一并输出。 +- IPrefetchPipe 向其写入 WayLookupInfo 信息(包括 vSetIdx,waymask,ptag,itlb_exception,itlb_pbmt,meta_codes,gpaddr,isForVSnonLeafPTE)。 + - 写入前,需要考虑队列是否已满,以及是否有 GPF 阻塞。如果有 GPF 信息待读取且未被处理,则写入需要等待,防止覆盖 GPF 信息。写入时,如果数据中包含 GPF 异常,就将信息存入 gpf_entry,并更新 gpfPtr。 +- MainPipe 从其读出 WayLookupInfo 信息。 + - 在读取上,有两种情况:当队列为空但有写请求时,可以直接将写的数据旁路(bypass)给读端口;否则就从 entries 数组中读取对应读指针的数据。同时,如果当前读的位置存在 GPF 信息,就将 GPF 信息一起输出,并在读取后清除有效位。 +- 允许 bypass(当队列为空但有写请求时,可以直接将写的数据旁路给读端口),为了不将更新逻辑的延迟引入到 DataArray 的访问路径上,在 MSHR 有新的写入时禁止出队,MainPipe 的 S0 流水级也需要访问 DataArray,当 MSHR 有新的写入时无法向下走,所以该措施并不会带来额外影响。 +- MissUnit 向其写入命中信息。 + - 若是命中则将 waymask 更新 ICacheMissResp 信息(包括 blkPaddr,vSetIdx,waymask,data,corrupt)且 meta_codes 也更新,否则 waymask 清零。更新逻辑与 IPrefetchPipe 中相同,见 [IPrefetchPipe 子模块文档中的“命中信息的更新”](./01_iprefetchpipe.md#命中信息的更新)一节。 + +### GPaddr 省面积机制 + +由于 `gpaddr` 仅在 guest page fault 发生时有用,并且每次发生 gpf 后前端实际上工作在错误路径上,后端保证会送一个 redirect(WayLookup flush)到前端(无论是发生 gpf 前就已经预测错误/发生异常中断导致的;还是 gpf 本身导致的),因此在 WayLookup 中只需存储 reset/flush 后第一个 gpf 有效时的 gpaddr。对双行请求,只需存储第一个有 gpf 的行的 `gpaddr。` + +在实现上,把 gpf 相关信号(目前只有 `gpaddr`)与其它信号(`paddr`,etc.)拆成两个 bundle,其它信号实例化 nWayLookupSize 个,gpf 相关只实例化一个寄存器。同时另用一个 `gpfPtr` 指针。总计可以节省$(\text{nWayLookupSize}\times2-1)\times \text{GPAddrBits} - \log_2{(\text{nWayLookupSize})} - 1$bit 的寄存器。 +当 prefetch 向 WayLookup 写入时,若有 gpf 发生,且 WayLookup 中没有已经存在的 gpf,则将 gpf/gpaddr 写入 `gpf_entry` 寄存器,同时将 `gpfPtr` 设置为此时的 `writePtr。` +当 MainPipe 从 WayLookup 读取时,若 bypass,则仍然直接将 prefetch 入队的数据出队;否则,若 `readPtr === gpfPtr`,则读出 gpf_entry;否则读出全 0。 +需要指出: + +1. 考虑双行请求,`gpaddr` 只需要存一份(若第一行发生 gpf,则第二行肯定也在错误路径上,不必存储),但 gpf 信号本身仍然需要存两份,因为 ifu 需要判断是否是跨行异常。 +2. `readPtr===gpfPtr` 这一条件可能导致 flush 来的比较慢时 `readPtr` 转了一圈再次与 `gpfPtr` 相等,从而错误地再次读出 gpf,但如前所述,此时工作在错误路径上,因此即使再次读出 gpf 也无所谓。 +3. 需要注意一个特殊情况:一个跨页的取指块,其 32B 在前一页且无异常,后 2B 在后一页且发生 gpf,若前 32B 正好是 16 条 RVC 压缩指令,则 IFU 会将后 2B 及对应的异常信息丢弃,此时可能导致下一个取指块的 `gpaddr` 丢失。需要在 WayLookup 中已有一个未被 MainPipe 取走的 gpf 及相关信息时阻塞 WayLookup 的入队(即 IPrefetchPipe s1 流水级),见 PR#3719。 + +## WayLookup 的功能点和测试点 + +### 刷新操作 + +- 接收到全局刷新刷新信号 io.flush 后,读、写指针和 GPF 信息都被重置。 + +1. 刷新读指针 + +- io.flush 为高时,重置读指针。 +- readPtr.value 为 0, readPtr.flag 为 false。 + +2. 刷新写指针 + +- io.flush 为高时,重置写指针。 +- writePtr.value 为 0, writePtr.flag 为 false。 + +3. 刷新 GPF 信息 + +- io.flush 为高时,重置 GPF 信息。 +- gpf_entry.valid 为 0, gpf_entry.bits 为 0。 + +### 读写指针更新 + +- 读写信号握手完毕之后(io.read.fire/io.write.fire 为高),对应指针加一。 +- 因为是在环形队列上,所以超过队列大小后,指针会回到队列头部。 + +1. 读指针更新 + +- 当 io.read.fire 为高时,读指针加一。 +- readPtr.value 加一。 +- 如果 readPtr.value 超过环形队列的大小,readPtr.flag 会翻转。 + +2. 写指针更新 + +- 当 io.write.fire 为高时,写指针加一。 +- writePtr.value 加一。 +- 如果 writePtr.value 超过环形队列的大小,writePtr.flag 会翻转。 + +### 更新操作 + +- MissUnit 处理完 Cache miss 后,向 WayLookup 写入命中信息,也就是 update 操作。 +- 情况分为两种: + - 命中:更新 waymask 和 meta_codes。 + - 未命中:重置 waymask。 + +1. 命中更新 + +- MissUnit 返回的更新信息和 WayLookup 的信息相同时,更新 waymask 和 meta_codes。 +- vset_same 和 ptag_same 为真。 +- waymask 和 meta_codes 更新。 +- hits 对应位为高。 + +2. 未命中更新 + +- vset_same 和 way_same 为真。 +- waymask 清零。 +- hit 对应位为高。 + +3. 不更新 + +- 其他情况下不更新。 +- vset_same 为假或者 ptag_same 和 way_same 都为假。 +- hits 对应位为低。 + +### 读操作 + +- 读操作会根据读指针从环形队列中读取信息。 +- 如果达成了绕过条件,优先绕过。 + +1. Bypass 读 + +- 队列为空,并且 io.write.valid 写有效时,可以直接读取,而不经过队列。 +- empty 和 io.write.valid 都为真。 +- io.read.bits = io.write.bits + +2. 读信号无效 + +- 队列为空(readPtr === writePtr)且写信号 io.write.valid 为低。 +- io.read.valid 为低,读信号无效。 + +3. 正常读 + +- 未达成绕过条件(empty 和 io.write.valid 至少有一个为假)且 io.read.valid 为高。 +- 从环形队列中读取信息。 +- io.read.bits.entry = entries(readPtr.value) + +4. gpf 命中 + +- io.read.valid 为高,可以读。 +- 当 gpf_hits 为高时,从 GPF 队列中读取信息。 +- io.read.bits.gpf = gpf_entry.bits + +5. gpf 命中且被读取 + +- io.read.valid 为高,可以读。 + + > also clear gpf_entry.valid when it's read + +- 当 gpf 命中且被读取其时(io.read.fire 为高),gpf_entry.valid 会被置为 0。 + +6. gpf 未命中 + +- io.read.valid 为高,可以读。 +- io.read.bits.gpf 清零。 + +### 写操作 + +- 写操作会根据写指针从环形队列中读取信息。 +- 如果有 gpf 停止,就会停止写。 + +1. gpf 停止 + > if there is a valid gpf to be read, we should stall write + +- gpf 队列数据有效,并且没有被读取或者没有命中,就会产生 gpf 停止,此时写操作会被停止。 +- gpf_entry.valid && !(io.read.fire && gpf_hit) 为高时,写操作会被停止(io.write.ready 为低)。 + +2. 写就绪无效 + +- 当队列为满((readPtr.value === writePtr.value) && (readPtr.flag ^ writePtr.flag))或者 gpf 停止时,写操作会被停止。 +- (io.write.ready 为低) + +3. 正常写 + +- 当 io.write.valid 为高时(没满且没有 gpf 停止),写操作会被执行。 +- 正常握手完毕 io.write.fire 为高。 +- 写信息会被写入环形队列。 +- entries(writePtr.value) = io.write.bits.entry。 + +有 ITLB 异常的写 + +- 前面与正常写相同,只不过当写信息中存在 ITLB 异常时,会更新 gpf 队列和 gpf 指针。 +- 此时如果已经被绕过直接读取了,那么就不需要存储它了。 + - 4. 被绕过直接读取了 + - can_bypass 和 io.read.fire 都为高。 + - gpf_entry.valid 为 false。 + - gpf_entry.bits = io.write.bits.gpf + - gpfPtr = writePtr + - 5. 没有被绕过直接读取 + - can_bypass 为低。 + - gpf_entry.valid 为 true。 + - gpf_entry.bits = io.write.bits.gpf + - gpfPtr = writePtr diff --git a/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/04_missunit.md b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/04_missunit.md new file mode 100644 index 0000000..1db64cb --- /dev/null +++ b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/04_missunit.md @@ -0,0 +1,268 @@ +--- +title: MissUnit +linkTitle: MissUnit +weight: 12 +--- + +
+ +
+ +## 子模块:FIFO + +- 一个先入先出的循环队列,目前仅在 MissUnit 中有使用,作为优先队列 priorityFIFO。 +- 按照在 MissUnit 中的实例化,pipe 是默认值 false,hasflush 是 true。 +- 队列的指针都是环形的,分为入队指针(写指针,ent_ptr)和出队指针(读指针,deq_ptr),记录读和写的位置。 +- 两个指针都有对应的 flag 位,当指针超过队列大小时,flag 位会翻转,用以判断是否已经循环。 +- 在入队、出队对应的 fire(valid && ready) 信号有效时,移动对应的指针。 + +## FIFO 的功能点和测试点 + +### 入队操作 + +1. 队未满,正常入队 + +- 当队列未满,且空位不小于一时,可以正常入队,如果从零号位开始入队到最大容量,入队指针的 flag 不会翻转。 +- io.enq.fire 为高有效,regFiles(enq_ptr.value) = io.enq.bits,enq_ptr.value+1 入队指针移动,入队指针标记位不翻转。 +- 重复以上操作至队满。 + +2. 队未满,入队后标记位翻转 + +- 当队未满,但是空位却是靠近队尾时,入队一位后就到达了队头,入队指针的 flag 会翻转。 +- 队列的容量为 10,入队指针指向 9,队未满。此时如果 io.enq.fire 为高,则 regFiles(9) = io.enq.bits,enq_ptr.value+1(循环队列,加完后 enq_ptr.value=0)入队指针移动,入队指针标记位翻转。 + +3. 队满,入队就绪信号为低,无法入队 + +- 当队满时,(enq_ptr.value === deq_ptr.value) && (enq_ptr.flag ^ deq_ptr.flag) 为高,io.enq.ready 为低,io.enq.fire 为低无效。 +- 此时入队,入队指针的 value 和 flag 不变。 + +### 出队操作 + +1. 队非空,正常出队 + +- 当队列非空时,可以正常出队,如果出队指针不经过最大容量位置,出队指针的 flag 不会翻转。 +- io.deq.fire 为高有效,io.deq.bits = regFiles(deq_ptr.value),deq_ptr.value+1 出队指针移动,出队指针标记位不翻转。 + +2. 队非空,出队后标记位翻转 + +- 当队非空,但是出队指针是靠近队尾时,出队一位后就到达了队头,出队指针的 flag 会翻转。 +- 队列的容量为 10,出队指针指向 9,队非空。此时如果 io.deq.fire 为高,则 io.deq.bits = regFiles(9),deq_ptr.value+1(循环队列,加完后 deq_ptr.value=0)出队指针移动,出队指针标记位翻转。 + +3. 队空,出队有效信号为低,无法出队 + +- 当队空时,enq_ptr === deq_ptr 为高,io.deq.valid 为低,io.deq.fire 为低无效。 +- 此时出队,出队指针的 value 和 flag 不变。 + +### 刷新清空操作 + +1. flush 清空 + +- 当刷新信号有效时,重置出队和入队的指针和标记位,清空队列。 +- 当 flush 为高时,deq_ptr.value=0,enq_ptr.value=0,deq_ptr.flag=false,enq_ptr.flag=false,empty=true,full=false。 + +## MissUnit + +
+
+ MissUnit 结构 +
+ MissUnit 结构 +
+
+ +
+ +- 接收并管理多个 Miss 请求 + - 处理来自 Fetch 和 Prefetch 的 Miss 请求。 + - 将这些请求分派给适当数量的 MSHR 进行排队和状态管理。 +- 管理 MSHR + - ICacheMissUnit 使用多个 MSHR 来跟踪和管理未完成的缓存未命中请求。为了防止 flush 时取指 MSHR 不能完全释放,设置取指 MSHR 的数量为 4,预取 MSHR 的数量为 10。采用数据和地址分离的设计方法,所有的 MSHR 共用一组数据寄存器,在 MSHR 只存储请求的地址信息、状态等信息。 + - 接收来自 MainPipe 的取指请求和来自 IPrfetchPipe 的预取请求,取指请求只能被分配到 fetchMSHR,预取请求只能分配到 prefetchMSHR,入队时采用低 index 优先的分配方式。 + - 在入队的同时对 MSHR 进行查询,如果请求已经在 MSHR 中存在,就丢弃该请求,对外接口仍表现 fire,只是不入队到 MSHR 中。==在入队时向 Replacer 请求写入 waymask==。当请求完成后,MSHR 会被释放,以便处理新的请求。 +- 通过 TileLink 协议与 L2 缓存进行通信,发送获取缓存块的请求(mem_acquire),并接收 L2 缓存的响应(mem_grant)。 + - 当到 L2 的总线空闲时,选择 MSHR 表现进行处理,整体 fetchMSHR 的优先级高于 prefetchMSHR,只有没有需要处理的 fetchMSHR,才会处理 prefetchMSHR。 + - 对于 fetchMSHR,采用低 index 优先的优先级策略,因为同时最多只有两个请求需要处理,并且只有当两个请求都处理完成时才能向下走,所有 fetchMSHR 之间的优先级并不重要。 + - 对于 prefetchMSHR,考虑到预取请求之间具有时间顺序,采用先到先得的优先级策略,在入队时通过一个 FIFO 记录入队顺序,处理时按照入队顺序进行处理。 + - 通过状态机与 Tilelink 的 D 通道进行交互,到 L2 的带宽为 32byte,需要分 2 次传输,并且不同的请求不会发生交织,所以只需要一组寄存器来存储数据。 + - 当一次传输完成时,根据传输的 id 选出对应的 MSHR,从 MSHR 中读取地址、掩码等信息,将相关信息写入 SRAM,同时将 MSHR 释放。 +- 向 MetaArray 和 DataArray 发送写请求,向 MainPipe 发送响应 + - 当数据传回后,MissUnit 根据相应的替换策略信息(victim way),将新数据写回 ICache 的 SRAM(Meta/Data) 。 + - 同时向取指端(或预取端)返回“Miss 已完成”的响应,包括:写入了哪一路(way)、实际数据以及可能的校验信息(如 corrupt 标记等)。 +- 处理特殊情况(如 flush、fencei、数据损坏等) + - 遇到 Flush 或 fence.i 等指令时,MissUnit 可以终止或跳过某些 Miss 请求的写回,从而保证不在无效或过期的情况下写入缓存。 + - 数据若出现 corrupt(部分拍损坏),也会在写回或发给前端时进行特殊处理或标记。 + +过程: + +1. fetch_req 和 prefetch_req 分别先经过 DeMultiplexer (Demux),把请求分发给对应数量的 MSHR。fetch 的 MSHR 和 prefetch 的 MSHR 分成两组,分别处理取指和预取请求。 +2. 每个 MSHR 内部会记录当前 Miss 请求的地址、索引、是否已经发出 acquire 等状态。当有其它相同的 miss 请求进来时,可以直接 “ hit MSHR ” 而不用重复创建新的请求。 +3. 对于 fetchMSHR,采用低 index 优先的优先级策略;对于 prefetchMSHR,采用先到先得的优先级策略,在入队 prefetchMSHR 前通过一个 priorityFIFO.记录入队顺序,处理时按照入队顺序进行处理。 +4. fetchMSHR 发出的请求与 prefetchArb 选出的 prefetchMSHR 通过 acquireArb 合并后,通过 mem_acquire 发送给下一级或外部存储。 +5. mem_grant 表示对这一条 Miss 请求的返回数据。需要分多个 beat 收集,直到收满一个 Cacheline。 +6. 收集完 Cacheline 数据后,会根据对应 MSHR 的信息向 metaArray 和 dataArray 发起写操作 (meta_write, data_write),同时向取指端 (fetch_resp) 发送补全后的数据和标记 (waymask 等)。 +7. 如果发生 flush 或 fencei,在未发出请求前,请求会被无效化;请求被发出后,会持续阻止新请求进入,已经发出的访问最终会将返回过程走完,但收到的响应并不会回复给 MainPipe 和 IPrefetchPipe,也不会写给 MetaArray 和 DataArray。 + +Demultiplexer 类 +grant:选择第一个 ready(能写)的 mshr,写进去(第 0 到 n 个端口,前面有 ready 的。比如 grant=seq(false,true),grant(1)为 true,表示 1 端口前面有一个 ready 的端口(0 端口)) +io.out(i).valid:前 i-1 个 mshr 没有 ready 的,输入的写有效。 +io.in.ready := grant.last || io.out.last.ready,给 MissUnit 的 ready 信号有一个有效,那么 MissUnit 给 MSHR 的 ready 信号就有效。 + +## MissUnit 的功能点和测试点 + +### 处理取指缺失请求 + +处理来自 MainPipe 的取指单元的缓存缺失请求,将缺失请求分发到多个 Fetch MSHR 中的一个,避免重复请求。 +低索引的请求优先处理。 + +1. 接受新的取指请求 + + - 当新的 fetch miss 与 MSHR 中的已有请求不重复时(通过 io.fetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Fetch MSHR 中。 + - 当有新的取指缺失请求到达时(io.fetch_req.valid 为高),且没有命中已有的 MSHR(fetchHit 为低),io.fetch_req.ready 应为高,表示可以接受请求。 + - io.fetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。 + +2. 处理已有的取指请求 + + - 当已有取指缺失请求到达时(io.fetch_req.valid 为高),且命中已有的 MSHR(fetchHit 为高),io.fetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。 + - fetchDemux.io.in.valid 应为低,fetchDemux.io.in.fire 为低,表示没有新的请求被分发到 MSHR。 + +3. 低索引的请求优先进入 MSHR + - Fetch 的请求会通过 fetchDemux 分配到多个 Fetch MSHR,fetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。 + - 当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。 + +### 处理预取缺失请求 + +与 Fetch Miss 类似,但走另一些 MSHR(Prefetch MSHR)。 + +1. 接受新的预取请求 + + - 当新的 prefetch miss 与 MSHR 中的已有请求不重复时(通过 io.prefetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Prefetch MSHR 中。 + - 当有新的预取缺失请求到达时(io.prefetch_req.valid 为高),且没有命中已有的 MSHR(prefetchHit 为低),io.prefetch_req.ready 应为高,表示可以接受请求。 + - io.prefetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。 + +2. 处理已有的预取请求 + + - 当已有预取缺失请求到达时(io.prefetch_req.valid 为高),且命中已有的 MSHR(prefetchHit 为高),io.prefetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。 + - prefetchDemux.io.in.valid 应为低,prefetchDemux.io.in.fire 为低,表示请求被接受但未分发到新的 MSHR。 + +3. 低索引的请求优先进入 MSHR + + - Prefetch 的请求会通过 prefetchDemux 分配到多个 Prefetch MSHR,prefetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。 + - 当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。 + +4. 先进入 MSHR 的优先进入 prefetchArb + - 从 prefetchDemux 离开后,请求的编号会进入 priorityFIFO,priorityFIFO 会根据进入队列的顺序排序,先进入队列的请求会先进入 prefetchArb。 + - prefetchDemux.io.in.fire 为高,并且 prefetchDemux.io.chosen 有数据时,将其编号写入 priorityFIFO。 + - 在 priorityFIFO 中有多个编号时,出队的顺序和入队顺序一致。 + - 检查 priorityFIFO.io.deq.bit 中的数据即可。 + +### MSHR 管理与查找 + +1. MSHR 查找命中逻辑 + + - 当新的请求到来时,能够正确查找所有 MSHR,判断请求是否命中已有 MSHR。 + - 当新的请求(取指或预取)到来时,系统遍历所有 MSHR,根据所有 MSHR 的查找信号 allMSHRs(i).io.lookUps(j).hit,检查请求是否已经存在于某个 MSHR 中。 + - 如果命中,则对应的 fetchHit 或 prefetchHit 为高。 + - 对于 prefetchHit 为高,还有一种情况:预取的物理块地址和组索引与取指的相等((io.prefetch_req.bits.blkPaddr === io.fetch_req.bits.blkPaddr) && (io.prefetch_req.bits.vSetIdx === io.fetch_req.bits.vSetIdx))并且有取指请求 io.fetch_req.valid 有效时,也算命中 + +2. MSHR 状态的更新与释放 + + - 当请求完成后,也就是来自内存总线的响应完成(D 通道接收完所有节拍),MSHR 能够正确地释放(清除其有效位),以便接收新的请求。 + - TileLink D 通道返回的 source ID ,即 io.mem_grant.bits.source。 + - 无效化信号 allMSHRs(i).io.invalid 为高,对应的 MSHR 的有效位 allMSHRs(i).valid 变为低 + +### acquireArb 仲裁 + +预取和取指的 acquire 都会发送给 acquireArb,acquireArb 会选择一个 acquire 发送给 mem_acquire。 +acquireArb 使用 chisel 自带的 Arbiter 实现,Arbiter 使用固定优先级仲裁,优先级从编号 0 开始,编号越小优先级越高。 + +1. acquireArb 仲裁 + - acquireArb 会选择一个 acquire 发送给 mem_acquire。 + - 当有多个 MSHR 同时发出请求时,acquireArb 会根据优先级进行仲裁,选择优先级最高的 MSHR 发送请求。 + - 取指请求总是在 0-3 号,预取请求直接在最后一号,所以取指请求优先级高于预取请求。 + - 当取指 acquire 和预取 acquire 同时发出时,fetchMSHRs(i).io.acquire 和 prefetchMSHRs(i).io.acquire 都有效,仲裁结果 acquireArb.io.out 应该和 fetchMSHRs(i).io.acquire 一致。 + +### Grant 数据接收与 Refill + +在收到 TileLink D 通道数据时收集整行 + +- 累计 beat 数(readBeatCnt),直到完成一整行 (last_fire) +- 记录 corrupt 标志 +- 将完成的请求映射回对应的 MSHR (id_r = mem_grant.bits.source) + +1. 正常完整 Grant 流程,readBeatCnt 为 0 时 + - readBeatCnt 初始为 0,refillCycles - 1 也为 0。 + - io.mem_grant.valid 为高(因为 io.mem_grant.ready 默认为高,所以 io.mem_grant.fire 为高只需要 io.mem_grant.valid 为高)且 io.mem_grant.bits.opcpde(0)为高。 + - 此时 respDataReg(0)= io.mem_grant.bits.data + - readBeatCnt 加一为 1。 +2. 正常完整 Grant 流程,readBeatCnt 为 1 时 + - io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高。 + - 此时 respDataReg(1)= io.mem_grant.bits.data + - readBeatCnt 重置回 0。 + - last_fire 为高。 + - 下一拍 last_fire_r 为高,id_r=io.mem_grant.bits.source。 +3. 正常完整 Grant 流程,last_fire_r 为高 + - last_fire_r 为高,并且 id_r 为 0-13 中的一个。 + - 对应的 fetchMSHRs 或者 prefetchMSHRs 会被无效,也就是 fetchMSHRs_i 或 prefetchMSHRs_i-4 的 io_invalid 会被置高。 +4. Grant 带有 corrupt 标志 + - io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高,则 corrupt_r 应为高。 + - 如果 io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高中有一个不满足,且此时 last_fire_r 为高,则 corrupt_r 重置为低。 + +### 替换策略更新 (Replacer) + +MissUnit 在发出 Acquire 请求时,还会将本次选中的 victim way 对应的索引告诉 io.victim,让替换策略更新其记录(替换策略采用 PLRU) +只有当 Acquire 真正“fire”时,才说明成功替换,replacer 需要更新状态 + +1. 正常替换更新 + - 当 io.mem.acquire.ready & acquireArb.io.out.valid 同时为高,也就是 acquireArb.io.out.fir 为高时,io.victim.vSetIdx.valid 也为高。 + - io.victim.vSetIdx.bits = 当前 MSHR 请求的 acquireArb.io.out.bits.vSetIdx。 +2. 生成 waymask + - 根据从 L2 返回的 mshr_resp 中 mshr_resp.bits.way 生成 waymask 信息。 + - 返回的 mshr_resp.bits.way 有 16 位,通过独热码生成一位掩码信息,waymask 表示其中哪一路被替换。 + - 生成的 waymask 应该和 mshr_resp.bits.way 一致。 + +### 写回 SRAM (Meta / Data) + +在一条 Miss Request refill 完成时,将新得到的 Cache line 写到 ICache。 +生成 io.meta_write 和 io.data_write 的请求,带上 waymask, tag, idx, data 。 +生成 io.meta_write.valid 和 io.data_write.valid 信号。 + +1. 生成 io.meta_write.valid 和 io.data_write.valid 信号 + - 当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。 + - 并且此时没有硬件刷新信号和软件刷新信号,也就是 io.flush 和 io.fencei 为低。 在等待 l2 响应的过程中,没有刷新信号 + - 也没有数据 corrupt,即 corrupt_r 为低。 + - 那么 io.meta_write.valid 和 io.data_write.valid 均为高。 +2. 正常写 SRAM + - io.meta_write.bits 的 virIdx、phyTag、waymask、bankIdx、poison 应该正常更新 + - io.data_write.bits 的 virIdx、data、waymask、bankIdx、poison 应该正常更新 + +### 向 mainPipe/prefetchPipe 发出 Miss 完成响应(fetch_resp) + +在完成 refill 后,无论是否要真正写阵列,都会向取指端发送“Miss 请求完成” +更新 io.fetch_resp.valid 和 fetch_resp.bits。 + +1. 正常 Miss 完成响应 + - 当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。 + - 无论此时是否有硬件刷新信号和软件刷新信号, io.fetch_resp.valid 都为高,说明可向取指端发送响应。 + - io.fetch_resp.bits 中的数据更新: + - io.fetch_resp.bits.blkPaddr = mshr_resp.bits.blkPaddr + - io.fetch_resp.bits.vSetIdx = mshr_resp.bits.vSetIdx + - io.fetch_resp.bits.waymask = waymask + - io.fetch_resp.bits.data = respDataReg.asUInt + - io.fetch_resp.bits.corrupt = corrupt_r + +### 处理 flush / fencei + +一旦收到 io.flush 或 io.fencei 时,对未发射的请求可立即取消,对已经发射的请求在拿到数据后也不写 SRAM。 + +1. MSHR 未发射前 fencei + - 如果 MSHR 还没有通过 io.acquire.fire 发出请求,就应立即取消该 MSHR(mshr_resp.valid= false),既不发出请求,也不要写 SRAM。 + - 当 io.fencei 为高时,fetchMSHRs 和 prefetchMSHRs 的 io.req.ready 和 io.acquire.valid 均为低,表示请求不发射。 +2. MSHR 未发射前 flush + - 由于 fetchMSHRs 的 io.flush 被直接设置为 false,所以 io.flush 对 fetchMSHRs 无效,但是对 prefetchMSHRs 有效。 + - 当 io.flush 为高时,只能发射 fetchMSHRs 的请求。 +3. MSHR 已发射后 flush/fencei + - 已经发射了请求,之后再有刷新信号,那么等数据回来了但不写 SRAM。 + - 在发射后,io.flush/io.fencei 为高时,等待数据回来,但是写 SRAM 的信号,write_sram_valid、io.meta_write.valid 和 io.data_write.valid 均为低,表示不写 SRAM。 + - 对于 response fetch 无影响。 \ No newline at end of file diff --git a/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/05_ctrlunit.md b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/05_ctrlunit.md new file mode 100644 index 0000000..22f7380 --- /dev/null +++ b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/05_ctrlunit.md @@ -0,0 +1,162 @@ +--- +title: CtrlUnit +linkTitle: CtrlUnit +weight: 12 +--- + +
+ +
+ +## CtrlUnit + +目前 CtrlUnit 主要负责 ECC 校验使能/错误注入等功能。 +RegField 案例类和伴生对象的作用,RegReadFn 和 RegWriteFn 案例类和伴生对象的作用。 + +通过两个控制寄存器 CSR:eccctrl 和 ecciaddr,来实现错误注入。 +在 eccctrlBundle 中,定义 eccctrl 的 ierror、istatus、itarget、inject、enable 域的初始值。 +在 ecciaddrBundle 中,定义 ecciaddr 的 paddr 域的初始值。 + +### mmio-mapped CSR + +CtrlUnit 内实现了一组 mmio-mapped CSR,连接在 tilelink 总线上,地址可由参数 `cacheCtrlAddressOpt` 配置,默认地址为`0x38022080`。总大小为 128B。 + +当参数 `cacheCtrlAddressOpt` 为 `None` 时,CtrlUnit **不会实例化**。此时 ECC 校验使能**默认开启**,软件不可控制关闭;软件不可控制错误注入。 + +目前实现的 CSR 如下: + +```plain + 64 10 7 4 2 1 0 +0x00 eccctrl | WARL | ierror | istatus | itarget | inject | enable | + + 64 PAddrBits-1 0 +0x08 ecciaddr | WARL | paddr | +``` + +| CSR | field | desp | +| ------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| eccctrl | enable | ECC 错误校验使能,原 sfetchctl(0) 。
注:即使不使能,在 icache 重填时仍会计算 parity,可能会有额外功耗;但如果不计算,则在未使能转换成使能时需要冲刷 icache(否则读出的 parity 有 50%概率是错的)。 | +| eccctrl | inject | ECC 错误注入使能,写 1 即使能,读恒 0 | +| eccctrl | itarget | ECC 错误注入目标
0: metaArray
1: rsvd
2: dataArray
3: rsvd | +| eccctrl | istatus | ECC 错误注入状态(read-only)
0: idle:注入控制器闲置
1: working:收到注入请求,注入中
2: injected:注入完成,等待触发
3: rsvd
4: rsvd
5: rsvd
6: rsvd
7: error:注入出错 | +| eccctrl | ierror | ECC 错误原因(read-only)
0: ECC 未使能 (i.e. !eccctrl.enable)
1: inject 目标 SRAM 无效 (i.e. eccctrl.itarget==rsvd)
2: inject 目标地址 (i.e. ecciaddr.paddr) 不在 ICache 中
3: rsvd
4: rsvd
5: rsvd
6: rsvd
7: rsvd | +| ecciaddr | paddr | ECC 错误注入物理地址 | +| RERI standard | | RERI 手册还要求了错误计数等寄存器,用于软件获取 RAS controller 状态,参考手册,可能需要与 dcache、L2cache 统一在后端实现,icache 像现在给 BEU 送 error 一样送给后端。
即:暂时不需要在 icache 实现,但要把错误计数等机制所需的接口准备出来 | + +### 错误校验使能 + +CtrlUnit 的 `eccctrl.enable` 位直接连接到 MainPipe,控制 ECC 校验使能。当该位为 0 时,ICache 不会进行 ECC 校验。但仍会在重填时计算校验码并存储,这可能会有少量的额外功耗;如果不计算,则在未使能转换成使能时需要冲刷 ICache(否则读出的 parity code 可能是错的)。 + +### 错误注入使能 + +CtrlUnit 内部使用一个状态机控制错误注入过程,其 status (注意:与 `eccctrl.istatus` 不同)有: + +- idle:注入控制器闲置 +- readMetaReq:发送读取 metaArray 请求 +- readMetaResp:接收读取 metaArray 响应 +- writeMeta:写入 metaArray +- writeData:写入 dataArray + +当软件向 `eccctrl.inject` 写入 1 时,进行以下简单检查,检查通过时状态机进入 `readMetaReq` 状态: + +- 若 `eccctrl.enable` 为 0,报错 `eccctrl.ierror=0` +- 若 `eccctrl.itarget` 为 rsvd(1/3),报错 `eccctrl.ierror=1` + +在 `readMetaReq` 状态下,CtrlUnit 向 MetaArray 发送 `ecciaddr.paddr` 地址对应的 set 读取的请求,等待握手。握手后转移到 `readMetaResp` 状态。 + +在 `readMetaResp` 状态下,CtrlUnit 接收到 MetaArray 的响应,检查 `ecciaddr.paddr` 地址对应的 ptag 是否命中,若未命中则报错 `eccctrl.ierror=2`。否则,根据 `eccctrl.itarget` 进入 `writeMeta` 或 `writeData` 状态。 + +在 `writeMeta` 或 `writeData` 状态下,CtrlUnit 向 MetaArray/DataArray 写入任意数据,同时拉高 `poison` 位,写入完成后状态机进入 `idle` 状态。 + +ICache 顶层中实现了一个 Mux,当 CtrlUnit 的状态机不为 `idle` 时,将 MetaArray/DataArray 的读写口连接到 CtrlUnit,而非 MainPipe/IPrefetchPipe/MissUnit。当状态机 `idle` 时反之。 + +状态机和错误注入流程 + +- `is_idle`:模块处于空闲状态,等待错误注入的触发。只有当 `eccctrl.istatus` 为 `working` 时,状态机才会转移到 `is_readMetaReq` 状态,准备读取元数据。 +- `is_readMetaReq`:发送读取元数据请求。通过接口 `metaRead` 向缓存发送读取请求。当握手成功后状态会转移到 `is_readMetaResp`。 +- `is_readMetaResp`:接收元数据响应并验证。如果未命中,则会设置错误状态。没命中会转移状态到`is_idle`,并设置错误错误注入状态和错误原因;如果找到有效的缓存行并且标签匹配,根据错误注入目标来判断是向元数据还是数据阵列写入错误。 +- `is_writeMeta`:写入带 poison 标记的数据完成注入。当握手成功后,错误注入状态设置为 injected(注入完成,等待触发),状态转移到`is_idle`。 +- `is_writeData`:写入带 poison 标记的数据完成注入。当握手成功后,向数据阵列写入错误数据,错误注入状态设置为 injected(注入完成,等待触发),状态转移到`is_idle`。 + +寄存器和接口映射 + +- eccctrl:控制 ECC 启用、错误注入状态等。寄存器通过 eccctrlRegField 进行映射。 +- ecciaddr:指定错误注入的物理地址。通过 ecciaddrRegField 映射。 +- 通过寄存器描述符(RegFieldDesc)和寄存器字段(RegField),定义了寄存器的这些是寄存器的描述信息和读写逻辑。 +- 通过 node.regmap,这两个寄存器被映射到指定的地址偏移。eccctrl 寄存器被映射到 params.eccctrlOffset 地址,ecciaddr 寄存器被映射到 params.ecciaddrOffset 地址。 +- `node.regmap` 使得这两个寄存器可以通过外部的 TileLink 接口进行访问,外部模块可以读写这些寄存器以控制 ECC 和错误注入功能。 + +## CtrlUnit 的功能点和测试点 + +### ECC 启用/禁用 + +控制 eccctrl.enable 字段来启用或禁用 ECC 功能。外部系统可以通过写寄存器 eccctrl 来控制 ECC 是否启用。 + +- 通过寄存器写入控制信号 enable,当 enable 为 true 时,ECC 功能启用;为 false 时,ECC 功能禁用。 + +1. 启用 ECC + +- 向 eccctrl.enable 寄存器写入 true,验证模块内部 eccctrl.enable 设置为 true,并确保后续的错误注入操作能够成功进行。此测试确保 eccctrl.enable 写操作被执行。 +- 确保 eccctrl.enable 被正确设置为 true,并触发 eccctrlRegWriteFn 中的写操作逻辑。 + +2. 禁用 ECC + +- 向 eccctrl.enable 寄存器写入 false,验证模块内部 eccctrl.enable 设置为 false,并确保在后续的错误注入过程中,ECC 功能被禁用,不允许进行错误注入。此测试确保 eccctrl.enable 写操作被正确设置为 false。 +- 验证禁用 ECC 时 eccctrl.enable 为 false,并触发 eccctrlRegWriteFn 中的错误处理分支。x.istatus = eccctrlInjStatus.error 和 x.ierror = eccctrlInjError.notEnabled + +### 状态机转换 + +根据状态机的状态,验证错误注入的流程是否正确。 + +1. is_idle 状态 + +- 初始为 is_idle 状态。 +- 当 eccctrl.istatus 为 working 时,验证此时的状态为 is_readMetaReq。 + +2. is_readMetaReq 状态 + +- 当握手成功后(io.metaRead.ready 和 io.metaRead.valid 都为高),验证此时的状态为 is_readMetaResp。 + +3. is_readMetaResp 状态 + +- 未命中 + - 当 waymask 全零的时候,表示没有命中,会进入 is_idle 状态,并且设置错误错误注入状态和错误原因。 + - 验证此时的状态为 is_idle, eccctrl.istatus = error 和 eccctrl.ierror = notFound。 +- 命中 + - 当 waymask 不全零的时候,表示命中,会根据错误注入目标来判断是向元数据还是数据阵列写入错误。 + - 当 eccctrl.itarget=metaArray 时,验证此时的状态为 is_writeMeta ;当 eccctrl.itarget!=metaArray 时,验证此时的状态为 is_writeData。 + +4. is_writeMeta 状态 + +- RegWriteFn + - 此状态进入后,io.dataWrite.valid 会为高 + - x.itarget = req.itarget + - 当 req.inject 为高并且 x.istatus = idle 时: + - 如果 ecc 的 req.enable = false,则验证 x.istatus = error 且 x.ierror = notEnabled + - 否则,如果 req.itarget != metaArray 和 dataArray,则验证 x.istatus = error 且 x.ierror = targetInvalid + - 如果都不满足,则验证 x.istatus = working +- 状态转换 + - 当 io.metaWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。 + +5. is_writeData 状态 + +- RegWriteFn + - 此状态进入后,io.dataWrite.valid 会为高 + - res.inject = false + - 当 ready 为高,且 x.istatus = injected 或 x.istatus = error 时,验证 x.istatus = idle 和 x.ierror = notEnabled +- 状态转换 + - 当 io.dataWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。 + +### 寄存器映射和外部访问 + +通过 TileLink 总线将寄存器映射到特定地址,使外部模块可以读写 ECC 控制寄存器和注入地址寄存器。 + +- 使用 TLRegisterNode 实现寄存器的映射,使得外部系统可以通过地址访问寄存器。寄存器的读写操作通过 TileLink 协议进行。 + +1. 外部读取和写入 ECC 控制寄存器 + +- 验证外部模块可以通过 TileLink 协议正确读取和写入 eccctrl 和 ecciaddr 寄存器,并对模块内部的状态产生影响,确保读写操作完全覆盖。 + +2. 外部模块触发错误注入 + +- 通过外部模块经 TileLink 总线向 eccctrl.inject 寄存器写入 true,触发错误注入,验证内部状态是否按 RegWriteFn 内部过程执行。 \ No newline at end of file diff --git a/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/06_icache.md b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/06_icache.md new file mode 100644 index 0000000..fed7204 --- /dev/null +++ b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/06_icache.md @@ -0,0 +1,178 @@ +--- +title: ICache +linkTitle: ICache +weight: 12 +--- + +
+ +
+ +## ICache + +各种组合数据的宽度以 system verilog/verilog 中的为准。 + + +- IPrefetchPipe 接收来自 FTQ 的预取请求,然后向 MetaArray 和 ITLB 发送请求,再从 ITLB 的响应得到 paddr,之后与 MetaArray 返回的 tag 进行比较得到命中信息,把命中信息、MetaArray ECC 校验信息和 ITLB 信息一并写入 WayLookup,同时进行 PMP 检查。未命中就将信息发送给 MissUnit 处理,MissUnit 通过 TileLink 总线向 L2Cache 发起请求,获取数据后返回给 MetaArray 和 IPrefetchPipe。之后会判断是否 Miss,如果 Miss 则把预取请求发送到 MissUnit,它会通过 TileLink 向 L2 做预取指。 +- MainPipe 接收来自 FTQ 的取指请求,然后从 WayLookup 获取路命中信息和 ITLB 查询结果,再访问 DataArray。命中后向 replacer 发送 touch 请求,replacer 采用 PLRU 替换策略,接收到 MainPipe 的命中更新,向 MissUnit 提供写入的 waymask。同时进行 PMP 检查,接收 DataArry 返回的数据。对 DataArray 做 ECC 校验,根据 DataArry 和 MetaArry 的校验结果(MetaArray 的校验结果来自 Waylookup)判断是否将错误报告给总线(beu)。之后,如果 DataArry 没有命中,将信息发往 MissUnit 处理。MissUnit 通过 TileLink 总线向 L2Cache 发起请求,获取数据后返回给 DataArray 和 MainPipe。之后就可以将数据返回给 IFU。 +- MetaArray 存储缓存行的标签(Tag)和 ECC 校验码 + - 使用双 Bank SRAM 结构,支持双线访问(Double-Line),每个 Bank 存储部分元数据。 + - 标签包含物理地址的高位,用于地址匹配。 + - 支持标签 ECC 校验,检测和纠正存储错误。 + - valid_array 记录每个 Way 的有效状态,Flush 操作会清零 +- DataArray 存储实际的指令数据块。 + - 数据按 Bank 划分为八个,每个 Bank 宽度为 64 位,支持多 Bank 并行访问。 + - 数据 ECC 校验,分段生成校验码,增强错误检测能力。 + - 支持双线访问,根据地址偏移选择 Bank,单周期可读取 32 字节数据。 +- 冲刷信号有三种:ftqPrefetch.flushFromBpu,itlbFlushPipe,模块外部的 fencei 和 flush 信号。 + - ftqPrefetch.flushFromBpu:通过 FTQ 来自的 BPU 刷新信号,用于控制预取请求的冲刷。 + - itlbFlushPipe:ITLB 的冲刷信号,itlb 在收到该信号时会冲刷 gpf 缓存。 + - fencei:刷新 MetaArray,清除所有路的 valid_array 清零;missUnit 中所有 MSHR 置无效。 + - flush:mainPipe 和 prefetchPipe 所有流水级直接置无效,wayLookup 读写指针复位,gpf_entry 直接置无效,missUnit 中所有 MSHR 置无效。 + +### Replacer + +采用 PLRU 更新算法,考虑到每次取指可能访问连续的 doubleline,对于奇地址和偶地址设置两个 replacer,在进行 touch 和 victim 时根据地址的奇偶分别更新 replacer。 + +
+
+ PLRU 算法示意 +
+ PLRU 算法示意 +
+
+ +
+ +#### touch + +Replacer 具有两个 touch 端口,用以支持双行,根据 touch 的地址奇偶分配到对应的 replacer 进行更新。 + +#### victim + +Replacer 只有一个 victim 端口,因为同时只有一个 MSHR 会写入 SRAM,同样根据地址的奇偶从对应的 replacer 获取 waymask。并且在下一拍再进行 touch 操作更新 replacer。 + +## ICache 的功能点和测试点 + +### FTQ 预取请求处理 + +接收来自 FTQ 的预取请求,经 IPrefetchPipe 请求过滤(查询 ITLB 地址,是否命中 MetaArry,PMP 检查),(有异常则由 MissUnit 处理)后进入 WayLookup。 + +1. 预取地址命中,无异常 + +- io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查通过。 +- 如果没有监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该命中的取指数据。 +- 如果监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该是未命中的取指数据。 + +2. 预取地址未命中,无异常 + +- io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 不匹配,pmp 检查通过。 +- 如果监听到 MSHR 将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该命中的取指数据。 +- 如果监听到 MSHR 没有将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该未命中的取指数据。 + +3. 预取地址 TLB 异常,无其他异常 + +- io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 异常。 +- 验证 wayLookup.io.write 的 itlb_exception 内容中,其有对应的异常类型编号(pf:01;gpf:10;af:11)。 + +4. 预取地址 PMP 异常,无其他异常 + +- io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查未通过。 +- 验证 wayLookup.io.write 的 tlb_pbmt 内容中,其有对应的异常类型编号(nc:01;io:10)。 + +### FTQ 取指请求处理 + +io.fetch.resp <> mainPipe.io.fetch.resp 发送回 IFU 的数据是在 io.fetch.resp。 + +接收来自 FTQ 的取指请求,从 WayLookup 获取路命中信息和 ITLB 查询结果,再访问 DataArray,监控 MSHR 的响应。更新 replacer,做 pmp 检查。后做 DataArray 和 MetaArray 的 ECC 校验。最后将数据发送给 IFU。 + +1. 取指请求命中,无异常 + +- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。 +- 验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。 +- 验证 io.fetch.resp 的数据应该是取指的数据。 + +2. 取指请求未命中,MSHR 返回的响应命中,无异常 + +- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,未命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。 +- 请求在 MSHR 返回的响应命中。 +- 验证 missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。 +- 验证 io.fetch.resp 的数据应该是取指的数据。 + +3. 取指请求命中,ECC 校验错误,无其他异常 + +- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 或 MetaArray 的 ECC 校验错误。 +- 验证 io.error.valid 为高,且 io.error.bits 内容为对应的错误源和错误类型。 +- 先刷 MetaArray 的 ValidArray,给 MissUnit 发请求,由其在 L2 重填,阻塞至数据返回。 +- 验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。 +- 验证 io.fetch.resp 的数据应该是取指的数据。 + +4. 取指请求未命中,但是 exception 非 0(af、gpf、pf),无其他异常 + +- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查未通过,DataArray 和 MetaArray 的 ECC 校验正常。 +- 验证 io.fetch.resp 为对应的错误源和错误类型。 +- 验证 io.fetch.resp 的数据无效,里面有异常类型。 + +5. 取指请求未命中,通过 WayLookup 中读取到的预取过来的 itlb 中返回 pbmt。 + +- 有 itlb_pbmt 和 pmp_mmio 时,他们合成 s1_mmio,传递到 s2_mmio,生成 s2_miss,有特殊情况就不会取指。 +- io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查通过,DataArray 和 MetaArray 的 ECC 校验正常。 +- 验证 io.fetch.resp 为对应的错误源和错误类型。 +- 验证 io.fetch.resp 的数据无效,里面有特殊情况类型类型。 + +6. 取指请求未命中,pmp 返回 mmio ,处理同 5。 + +### MetaArray 功能 + +在 IPrefetchPipe 的 S0,接收来自 IPrefetchPipe 的读请求 read,返回对应路和组的响应 readResp。 +在 miss 的时候,MissUnit 会将会应的数据写入 write 到 MetaArray。 +MetaArray 主要存储了每个 Cache 行的标签和 ECC 校验码。 + +1. 元数据写入操作(对应的 Set 已满): ICacheMetaArray 应当能够正确地将元数据(标签和有效位)写入到指定的 Set 和 Way 。 + +- 从 MissUnit 返回的请求都是未命中的请求(已命中不会向 MissUnit 请求,那么 MissUnit 自然也不会向 MetaArray 写入)。 +- 发送一个写请求 write 到 ICacheMetaArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最后指定 virIdx、phyTag、waymask、bankIdx、poison。 +- 写入操作后,发起一个对相同虚拟索引的读请求。验证 readResp 的 metas 和 codes 分别包含写入的 ptag 和 ecc code,并且对于写入的路,readResp.entryValid 信号被置为有效。 + +2. 元数据读取操作 (命中): 当一个读请求在 ICacheMetaArray 中命中时(存在有效的条目),它应该返回正确的元数据(标签和有效位)。 + +- 首先,向特定的虚拟索引(组和路)写入元数据(参照上面的写入操作)。然后,向相同的虚拟索引发送一个读请求。 +- 验证 readResp.metas 包含之前写入的物理标签,并且对于相应的路,readResp.entryValid 信号被置为有效。 + +3. 元数据读取操作 (未命中): 当读取一个尚未被写入的地址时,ICacheMetaArray 应当指示未命中(条目无效)。 + +- 向 ICacheMetaArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。 +- 验证对于任何路,readResp.entryValid 信号都没有被置为有效。 对应的 readResp.metas 和 codes 的内容是 DontCare 也就是 0。 + +4. 独立的缓存组刷新:在第 i 个端口是有效的刷新请求,并且该请求的 waymask 指定了当前正在处理的第 w 路时,应该使第 i 个端口的条目无效。 + +- 先向 ICacheMetaArray 写入指定一个或多个端口的元数据,然后再给对应的端口的路发送刷新请求 io.flush,其包含虚拟索引 virIdx 和路掩码 waymask。 +- 验证 valid_array 对应的路中的 virIdx 被置为无效,io.readResp.entryValid 对应路的对应端口为无效。 + +5. 全部刷新操作: ICacheMetaArray 应当能够在接收到全部刷新请求时,使所有条目无效。 + +- 先向多个不同的虚拟索引写入元数据。然后置位 io.flushAll 信号。 +- 验证步骤: 在 io.flushAll 信号置位后,发起对所有之前写入过的虚拟索引的读请求。验证在所有的读取响应中,对于任何路,readResp.entryValid 信号都没有被置为有效。 + +### DataArray 功能 + +与 MetaArray 类似,在 MainPipe 的 S0,接收来自 MainPipe 的读请求 read,返回对应路和组的响应 readResp。 +在 miss 的时候,MissUnit 会将会应的数据写入 write 到 DataArray。 +DataArray 主要存储了每个 Cache 行的标签和 ECC 校验码。 + +1. 数据写入操作(对应的 Set 已满): ICacheDataArray 应当能够正确地将数据写入到指定的 Set (组)、Way (路) 和数据 Bank (存储体)。 + +- 发送一个写请求 write 到 ICacheDataArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最终指定虚拟索引、数据、路掩码、存储体索引 bankIdx 和毒化位。写入的数据模式应跨越多个数据存储体。 +- 写入操作后,发起一个对相同虚拟索引和块偏移量的读请求。验证 readResp.datas 与写入的数据相匹配。 + +2. 数据读取操作 (命中): 当一个读请求命中时(相应的元数据有效),它应该从相应的组、路和数据存储体返回正确的数据。 + +- 首先,向特定的虚拟索引和块偏移量写入数据。然后,向相同的虚拟索引和块偏移量发送一个读请求。使用不同的块偏移量进行测试,以覆盖存储体的选择逻辑。 +- 验证 readResp.datas 包含之前写入的数据。 + +3. 数据读取操作 (未命中): 当读取一个尚未被写入的地址时,ICacheDataArray 的输出应该是默认值或无关值。 + +- 向 ICacheDataArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。 +- 验证 readResp.datas 为 0。 diff --git a/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/_index.md b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/_index.md new file mode 100644 index 0000000..776902b --- /dev/null +++ b/documents/content/zh-cn/docs/98_ut/01_frontend/04_icache/_index.md @@ -0,0 +1,2163 @@ +--- +title: ICache +linkTitle: ICache +weight: 12 +--- + +**本文档参考[香山 IFU 设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/blob/master/docs/frontend/ICache/index.md)写成** + +本次验证对象是昆明湖前端指令缓存(ICache)的模块。验证的代码版本为[XiangShan_20250307_4b2c87ba](https://github.com/OpenXiangShan/XiangShan/tree/4b2c87ba1d7965f6f2b0a396be707a6e2f6fb345) + +请注意,本文档撰写的测试点仅供参考,部分测试点需要修改,如能补充或修改测试点,最终获得的奖励可能更高! + +# **ICache 说明文档** + +
+ +## 文档概述 + +本文档描述 ICache 的模块列表、设计规格、参数列表、功能概述和详述。 +
+功能概述部分提供 ICache 整体数据流向图和过程。 + +## 术语说明 + +| 缩写 | 全称 | 中文名称 | 说明 | +| ---------- | ---------------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| ICache | Instruction Cache | L1 指令缓存 | 用于存储最近访问过的指令,以减少对主内存的访问次数,从而提高处理速度。 | +| DCache | Data Cache | L1 数据缓存 | 用于存储最近访问过的数据,以减少对主内存的访问次数,从而提高处理速度。 | +| L2 Cache | Level Two Cache | L2 缓存 | 从主内存中预取指令和数据,作为 iCache 和主内存之间的缓冲区。 | +| ITLB | Instruction TLB | 指令地址转换缓冲 | 用于将虚拟地址转换为物理地址的缓冲。 | +| BPU | Branch Prediction Unit | 分支预测单元 | 预测程序中的条件分支,以便提前获取和解码后续指令,这样可以减少等待分支结果的时间。 | +| FTQ | Fetch Target Queue | 取指目标队列 | 暂存 BPU 预测的取指目标,并根据这些取指目标给 IFU 发送取指请求。 | +| IFU | Instruction Fetch Unit | 取指单元 | 进行取指,预译码,分支预测检查,指令扩展和非法检查。 | +| BEU | Bus Error Unit | 总线错误单元 | 总线错误可以使用总线错误单元 (BEU) 对 hart 产生中断。 | +| PF | Page Fault | 缺页异常 | 当 CPU 访问的内存地址不在物理内存中时,会触发缺页异常。 | +| GPF | Guest Page Fault | 虚拟机缺页异常 | 是在虚拟化环境中,客户机(guest)操作系统尝试访问的虚拟地址未能成功映射到物理地址时产生的异常。 | +| AF | Access Fault | 访问错误 | 当 CPU 试图访问一个不允许的物理地址时,会触发访问错误。 | +| PMP | Physical Memory Protection | 物理内存保护模块 | RISC-V 架构中提供的一种硬件机制,用于控制不同内存区域的读、写和执行权限,主要目的是提供对物理内存的保护和隔离。 | +| PMA | Physical Memory Attribute | 物理内存属性模块(PMP 的一部分) | RISC-V 系统中,机器物理地址空间的每个区域的这些属性和能力称为物理内存属性。 | +| PBMT | Page-Base Memory Type | 基于页面的内存类型 | 一种内存管理机制,它使用分页(paging)技术来管理虚拟内存。见特权手册 Svpbmt 扩展。 | +| MMIO | Memory-Mapped Input/Output | 内存映射输入/输出 | 在 MMIO 中,外设的寄存器和内存被映射到同一个地址空间。 | +| cacheline | cacheline | 缓存行 | 缓存中的最小存储单位。 | +| MetaArray | MetaArray | 元数据数组 | 用于存储指令的元数据信息,包括指令的地址、访问权限、是否有效等。 | +| DataArray | DataArray | 数据数组 | 用于存储指令数据的数组。 | +| SRAM | Static Random Access Memory | 静态随机存取存储器 | 一种用于存储数据的存储器,具有随机访问特性。 | +| MSHR | Miss Status Holding Register | 缺失状态保持寄存器 | 用来处理非阻塞缓存(non-blocking cache)中的缓存未命中(cache miss)情况 。 | +| SATP | Supervisor Address Translation and Protection | 监管者地址转换和保护单元 | 管理地址转换和保护机制,它决定了虚拟地址到物理地址的转换方式以及访问权限的控制。 | +| VS | Virtual Supervisor | 虚拟监管者 | 是 H 扩展引入的一种特权模式,用于运行虚拟机中的操作系统。包括两级地址翻译,虚拟 CSR 和异常和中断虚拟化等机制。 | +| hartID | hardware thread ID | 硬件线程标识 | RISC-V 硬件线程 ID。在 RISC-V 架构中,每个处理器核心都有一个唯一的 hart ID,用于区分同一处理器中运行的多个硬件线程。 | +| SFENCE.VMA | Supervisor Memory-Management Fence Instruction | 监管者内存管理屏障指令 | 同步对内存中内存管理数据结构的更新与当前执行的指令。 | +| fence.i | fence.i | 屏障指令 | 用于同步指令流与数据流,确保在指令之前的存储操作对后续取指可见。 | + +## ICache 和 DCache 区别 + +### 功能用途 + +ICache 专门用于存储指令。当 CPU 从内存中读取指令时,这些指令会先被存储在 ICache 中。这样,当 CPU 需要再次执行相同的指令时,可以直接从 ICache 中读取,而无需再次访问速度较慢的内存,从而提高指令的读取速度,加快程序的执行效率。 + +DCache 用于存储数据。程序运行过程中,CPU 需要频繁地读取和写入数据。DCache 的作用就是缓存这些数据,使得 CPU 对数据的访问速度更快。当 CPU 访问内存中的数据时,如果数据已经在 DCache 中,就可以直接从 DCache 中读取或写入,减少了访问内存的次数,提高了数据访问的效率。 + +### 数据一致性问题 + +ICache 通常不需要考虑数据一致性问题。因为**指令是只读的**,一旦程序开始运行,指令的内容一般不会改变。所以,ICache 中的指令可以一直使用,直到程序结束或者新的指令被加载进来。即使内存中的指令被修改了,也不会影响 ICache 中已经缓存的指令的执行。 + +DCache 数据一致性是一个重要的问题。因为数据可能会被多个处理器或者设备访问和修改。如果一个处理器修改了 DCache 中的数据,而其他处理器或者设备仍然使用旧的数据,就会导致数据不一致的问题。为了保证数据一致性,需要采用一些机制,如缓存一致性协议(如 MESI 协议等),来确保所有处理器和设备看到的数据是一致的。 + +为什么我们需要区分数据和指令呢?原因之一是出于性能的考量。CPU 在执行程序时,可以同时获取指令和数据,做到硬件上的并行,提升性能。另外,指令和数据有很大的不同。例如,指令一般不会被修改,所以 iCache 在硬件设计上是可以是只读的,这在一定程度上降低硬件设计的成本。所以硬件设计上,系统中一般存在 L1 dCache 和 L1 iCache,L2 Cache 和 L3 Cache。 + +### 区分原因 + +指令访问模式是:通常按照程序的顺序依次执行,具有较高的局部性。并且在程序运行过程中通常是只读的,不会被修改。 +数据访问模式是:数据的访问通常较为随机,可能涉及频繁的读写操作。数据需要支持读写操作,这意味着 DCache 需要处理数据的写入和一致性问题。 + +将数据和指令分开存储到 DCache 和 ICache,有利于提高命中率和减少冲突,提升性能(CPU 在执行程序时,可以同时获取指令和数据,做到硬件上的并行),简化设计(ICache 可以专注读指令,而 DCache 需要数据的读写操作,还需要考虑数据一致性问题)。 + +## 模块列表 + +| 子模块 | 描述 | +| --------------------------------- | --------------------------------------------- | +| [MainPipe](02_mainpipe) | 主流水线 | +| [IPrefetchPipe](01_iprefetchpipe) | 预取流水线 | +| [WayLookup](03_waylookup) | 元数据缓冲队列 | +| [MetaArray](06_icache) | 元数据 SRAM | +| [DataArray](06_icache) | 数据 SRAM | +| [MissUnit](04_missunit) | 缺失处理单元 | +| [Replacer](06_icache) | 替换策略单元 | +| [CtrlUnit](05_ctrlunit) | 控制单元,目前仅用于控制错误校验/错误注入功能 | +| [ICache](06_icache) | ICache 顶层模块 | +| [FIFO](04_missunit) | 先入先出循环队列,在 MissUnit 中有使用 | + +## 设计规格 + +- 缓存指令数据 +- 缺失时通过 tilelink 总线向 L2 请求数据 +- 软件维护 L1 I/D Cache 一致性(`fence.i`) +- 支持跨 cacheline (预)取指请求 +- 支持冲刷(bpu redirect、backend redirect、`fence.i`) +- 支持预取指请求 + - 硬件预取为 FDIP 预取算法 + - 软件预取为 Zicbop 扩展`prefetch.i`指令 +- 支持可配置的替换算法 +- 支持可配置的缺失状态寄存器数量 +- 支持检查地址翻译错误、物理内存保护错误 +- 支持错误检查 & 错误恢复 & 错误注入[^ecc] + - 默认采用 parity code + - 通过从 L2 重取实现错误恢复 + - 软件可通过 MMIO 空间访问的错误注入控制寄存器 +- DataArray 支持分 bank 存储,细存储粒度实现低功耗 + +[^ecc]: 本文档也将错误检查 & 错误恢复 & 错误注入相关功能称为 ECC,见 [ECC](#ecc) 一节开始的说明。 + +## 参数列表 + +若和 chisel 代码不一致,以 chisel 代码为准。 + +| 参数 | 默认值 | 描述 | 要求 | +| ------------------- | ------ | ----------------------------------------------------- | ------------------------------------ | +| nSets | 256 | SRAM set 数量 | 2 的幂次 | +| nWays | 4 | SRAM way 数量 | | +| nFetchMshr | 4 | 取指 MSHR 的数量 | | +| nPrefetchMshr | 10 | 预取 MSHR 的数量 | | +| nWayLookupSize | 32 | WayLookup 深度,同时可以反压限制预取最大距离 | | +| DataCodeUnit | 64 | 校验单元大小,单位为 bit,每 64bit 对应 1bit 的校验位 | | +| ICacheDataBanks | 8 | cacheline 划分 bank 数量 | | +| ICacheDataSRAMWidth | 66 | DataArray 基本 SRAM 的宽度 | 大于每 bank 的 data 和 code 宽度之和 | + +## 功能概述 + +
+
+ FTQ 指针示意 +
+ FTQ 指针示意 +
+
+ +
+ +FTQ 中存储着 BPU 生成的预测块,fetchPtr 指向取指预测块,prefetchPtr 指向预取预测块,当复位时 prefetchPtr 与 fetchPtr 相同,每成功发送一次取指请求时 fetchPtr++,每成功发送一次预取请求时 prefetchPtr++。详细说明见[FTQ 设计文档](https://github.com/OpenXiangShan/XiangShan-Design-Doc/blob/master/docs/frontend/FTQ/index.md)。 + +
+
+ ICache整体数据流向图 +
+ ICache整体数据流向图 +
+
+ +
+ +ICache 结构如上图所示,有 MainPipe 和 IPrefetchPipe 两个流水线,MainPipe 接收来自 FTQ 的取指请求,IPrefetchPipe 接收来自 FTQ/MemBlock 的硬/软件预取请求。 + +- 对于预取请求,IPrefetch 对 MetaArray 进行查询,将元数据(在哪一路命中、ECC 校验码、是否发生异常等)存储到 WayLookup 中,如果该请求缺失,就发送至 MissUnit 进行预取。 +- 对于取指请求,MainPipe 首先从 WayLookup 中读取命中信息,如果 WayLookup 中没有可用信息,MainPipe 就会阻塞,直至 IPrefetchPipe 将信息写入 WayLookup 中,该方案将 MetaArray 和 DataArray 的访问分离,一次只访问 DataArray 单路,代价是产生了一个周期的重定向延迟。 + +- MissUnit 处理来自 MainPipe 的取指请求和来自 IPrefetchPipe 的预取请求,通过 MSHR 进行管理,所有 MSHR 公用一组数据寄存器以减少面积。 + +- CtrlUnit 主要负责 ECC 校验使能/错误注入等功能。从 MetaArray 读取元数据,之后向 MetaArray 写入毒化的标签,向 DataArray 写入毒化的数据。Tilelink 用于外部配置和状态监控,通过 eccctrl 和 ecciaddr 寄存器实现读写交互。 + +- Replacer 为替换器,采用 PLRU 替换策略,接收来自 MainPipe 的命中更新,向 MissUnit 提供写入的 waymask。 + +- MetaArray 分为奇偶两个 bank,用于支持跨 cacheline 的双行访问。 + +- DataArray 中的 cacheline 分为 8 个 bank 存储,每个 bank 中存储的有效数据为 64bit,另外对于每 64bit 还需要 1bit 的校验位,由于 65bit 宽度的 SRAM 表现不好,所以选用深度 256\*宽度 66bit 的 SRAM 作为基本单元,一共有 32 个这样的基本单元。一次访问需要 34Byte 的指令数据,每次需要访问 5 个 bank($8\times 5 > 34$),根据起始地址进行选择。 + +## 功能详述 + +### 取指/预取指请求 + +
+
+ ICache 两条流水线的关系 +
+ ICache 两条流水线的关系 +
+
+ +
+ +FTQ 分别把取指/预取指请求发送到对应的取指/预取指流水线进行处理。如前所述,由 IPrefetch 对 MetaArray 和 ITLB 进行查询,将元数据(在哪一路命中、ECC 校验码、是否发生异常等)在 IPrefetchPipe s1 流水级存储到 WayLookup 中,以供 MainPipe s0 流水级读取。 + +在上电解复位/重定向时,由于 WayLookup 为空,而 FTQ 的 prefetchPtr、fetchPtr 复位到同一位置,MainPipe s0 流水级不得不阻塞等待 IPrefetchPipe s1 流水级的写入,这引入了一拍的额外重定向延迟。但随着 BPU 向 FTQ 填充预测块的进行和 MainPipe/IFU 因各种原因阻塞(e.g. miss、IBuffer 满),IPrefetchPipe 将工作在 MainPipe 前(`prefetchPtr > fetchPtr`),而 WayLookup 中也会有足够的元数据,此时 MainPipe s0 级和 IPrefetchPipe s0 级的工作将是并行的。 + +详细的取指过程见[MainPipe](02_mainpipe)、[IPrefetchPipe](01_iprefetchpipe)和[WayLookup](03_waylookup)。 + +#### 硬件预取与软件预取 + +V2R2 后,ICache 可能接受两个来源的预取请求: + +1. 来自 Ftq 的硬件预取请求,基于 FDIP 算法。 +2. 来自 Memblock 中 LoadUint 的软件预取请求,其本质是 Zicbop 扩展中的 prefetch.i 指令,请参考 RISC-V CMO 手册。 + +然而,PrefetchPipe 每周期仅可以处理一个预取请求,故需要进行仲裁。ICache 顶层负责缓存软件预取请求,并与来自 Ftq 的硬件预取请求二选一送往 PrefetchPipe,软件预取请求的优先级高于硬件预取请求。 + +逻辑上来说,每个 LoadUnit 都有可能发出软件预取请求,因此每周期至多会有 LoadUnit 数量(目前默认参数为 LduCnt=3)个软件预取请求。但出于实现成本和性能收益考量,ICache 每周期至多仅接收并处理一个,多余的会被丢弃,端口下标最小的优先。此外,若 PrefetchPipe 阻塞,而 ICache 内已经缓存了一个软件预取请求,那么原先的软件预取请求将被覆盖。 + +
+
+ ICache 预取请求接收与仲裁 +
+ ICache 预取请求接收与仲裁 +
+
+ +
+发送到 PrefetchPipe 后,软件预取请求的处理和硬件预取请求的处理几乎是一致的,除了: + +- 软件预取请求不会影响控制流,即**不会**发送到 MainPipe(和后续的 Ifu、IBuffer 等环节),仅做:1) 判断是否 miss 或异常;2) 若 miss 且无异常,发送到 MissUnit 做预取指并重填 SRAM。 + +关于 PrefetchPipe 的细节请查看子模块文档。 + +### 异常传递/特殊情况处理 + +ICache 负责对取指请求的地址进行权限检查(通过 ITLB 和 PMP),可能的异常和特殊情况有: + +| 来源 | 异常 | 描述 | 处理 | +| -------- | ---------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| ITLB | af | 虚拟地址翻译过程出现访问错误 | 禁止取指,标记取指块为 af,经 IFU、IBuffer 发送到后端处理 | +| ITLB | gpf | 客户机页错误 | 禁止取指,标记取指块为 gpf,经 IFU、IBuffer 发送到后端处理,将有效的 `gpaddr` 和 `isForNonLeafPTE`发送到后端的 GPAMem 以备使用 | +| ITLB | pf | 页错误 | 禁止取指,标记取指块为 pf,经 IFU、IBuffer 发送到后端处理 | +| backend | af/pf/gpf | 同 ITLB 的 af/pf/gpf | 同 ITLB 的 af/pf/gpf | +| PMP | af | 物理地址无权限访问 | 同 ITLB af | +| MissUnit | L2 corrupt | L2 cache 响应 corrupt | 标记取指块为 af,经 IFU、IBuffer 发送到后端处理 | + +需要指出,对于一般的取指流程来说,并不存在 backend 异常这一项。但 XiangShan 出于节省硬件资源的考虑,在前端传递的 pc 只有 41 / 50 bit(Sv39*4 / Sv48*4),而对于 `jr`、`jalr` 等指令,跳转目标来源于 64 bit 寄存器。根据 RISC-V 规范,高位非全 0 或全 1 时的地址不合法,需要引发异常,这一检查只能由后端完成,并随同后端重定向信号一起发送到 Ftq,进而随同取指请求一起发送到 ICache。其本质是一种 ITLB 异常,因此解释描述和处理方式与 ITLB 相同。 + +另外,L2 cache 通过 tilelink 总线响应 corrupt 可能是 L2 ECC 错误(`d.corrupt`),亦可能是无权限访问总线地址空间导致拒绝访问(`d.denied`)。tilelink 手册规定,当拉高 `d.denied` 时必须同时拉高 `d.corrupt`。而这两种情况都需要将取指块标记为 access fault,因此目前在 ICache 中无需区分这两种情况(即无需关注 `d.denied`,其可能被 chisel 自动优化掉而导致 verilog 中看不到)。 + +这些异常间存在优先级:backend 异常 > ITLB 异常 > PMP 异常 > MissUnit 异常。这是自然的: + +1. 当出现 backend 异常时,发送到前端的 vaddr 不完整且不合法,故 ITLB 地址翻译过程无意义,检查出的异常无效; +2. 当出现 ITLB 异常时,翻译得到的 paddr 无效,故 PMP 检查过程无意义,检查出的异常无效; +3. 当出现 PMP 异常时,paddr 无权限访问,不会发送(预)取指请求,故不会从 MissUnit 得到响应。 + +而对于 backend 的三种异常、ITLB 的三种异常,由 backend 和 ITLB 内部进行有优先级的选择,保证同时至多只有一种拉高。 + +此外,一些机制还会引发一些特殊情况,在旧版文档/代码中也称为异常,但其实际上并不引发 RISC-V 手册定义的 `exception`,为了避免混淆,此后将称为特殊情况: + +| 来源 | 特殊情况 | 描述 | 处理 | +| -------- | --------- | ------------------------------------------- | -------------------------------------------------------- | +| PMP | mmio | 物理地址为 mmio 空间 | 禁止取指,标记取指块为 mmio,由 IFU 进行**非推测性**取指 | +| ITLB | pbmt.NC | 页属性为不可缓存、幂等 | 禁止取指,由 IFU 进行**推测性**取指 | +| ITLB | pbmt.IO | 页属性为不可缓存、非幂等 | 同 pmp mmio | +| MainPipe | ECC error | 主流水检查发现 MetaArray/DataArray ECC 错误 | 见[ECC 一节](#ecc),旧版同 ITLB af,新版做自动重取 | + +### DataArray 分 bank 的低功耗设计 + +目前,ICache 中每个 cacheline 分为 8 个 bank,bank0-7。一个取指块需要 34B 指令数据,故一次访问连续的 5 个 bank。存在两种情况: + +1. 这 5 个 bank 位于单个 cacheline 中(起始地址位于 bank0-3)。假设起始地址位于 bank2,则所需数据位于 bank2-6。如下图 a。 +2. 跨 cacheline(起始地址位于 bank4-7)。假设起始地址位于 bank6,则数据位于 cacheline0 的 bank6-7、cacheline1 的 bank0-2。有些类似于环形缓冲区。如下图 b。 + +
+
+ DataArray 分 bank 示意图 +
+ DataArray 分 bank 示意图 +
+
+ +
+ +当从 SRAM 或 MSHR 中获取 cacheline 时,根据地址将数据放入对应的 bank。 + +由于每次访问只需要 5 个 bank 的数据,因此 ICache 到 IFU 的端口实际上只需要一个 64B 的端口,将两个 cacheline 各自的 bank 选择出来并拼接在一起返回给 IFU(在 DataArray 模块内完成);IFU 将这一个 64B 的数据复制一份拼接在一起,即可直接根据取指块起始地址选择出取指块的数据。不跨行/跨行两种情况的示意图如下: + +
+
+ DataArray 单行数据返回示意图 +
+ DataArray 单行数据返回示意图 +
+
+ +
+ +
+
+ DataArray 双行数据返回示意图 +
+ DataArray 双行数据返回示意图 +
+
+ +
+ +亦可参考 [IFU.scala 中的注释](https://github.com/OpenXiangShan/XiangShan/blob/fad7803d97ed4a987a743036cec42d1c07b48e2e/src/main/scala/xiangshan/frontend/IFU.scala#L474-L502)。 + +```markdown +NOTE: the following `Cat(_data, _data)` _is_ intentional. +Explanation: +In the old design, IFU is responsible for selecting requested data from two adjacent cachelines, +so IFU has to receive 2*64B (2cacheline * 64B) data from ICache, and do `Cat(_data(1), _data(0))` here. +However, a fetch block is 34B at max, sending 2*64B is quiet a waste of power. +In current design (2024.06~), ICacheDataArray is responsible for selecting data from two adjacent cachelines, +so IFU only need to receive 40B (5bank * 8B) valid data, and use only one port is enough. +For example, when pc falls on the 6th bank in cacheline0(so this is a doubleline request): +MSB LSB +cacheline 1 || 1-7 | 1-6 | 1-5 | 1-4 | 1-3 | 1-2 | 1-1 | 1-0 || +cacheline 0 || 0-7 | 0-6 | 0-5 | 0-4 | 0-3 | 0-2 | 0-1 | 0-0 || +and ICacheDataArray will respond: +fromICache.bits.data || 0-7 | 0-6 | xxx | xxx | xxx | 1-2 | 1-1 | 1-0 || +therefore simply make a copy of the response and `Cat` together, and obtain the requested data from centre: +f2_data_2_cacheline || 0-7 | 0-6 | xxx | xxx | xxx | 1-2 | 1-1 | 1-0 | 0-7 | 0-6 | xxx | xxx | xxx | 1-2 | 1-1 | 1-0 || +requested data: ^-----------------------------^ +For another example, pc falls on the 1st bank in cacheline 0, we have: +fromICache.bits.data || xxx | xxx | 0-5 | 0-4 | 0-3 | 0-2 | 0-1 | xxx || +f2_data_2_cacheline || xxx | xxx | 0-5 | 0-4 | 0-3 | 0-2 | 0-1 | xxx | xxx | xxx | 0-5 | 0-4 | 0-3 | 0-2 | 0-1 | xxx || +requested data: ^-----------------------------^ +Each "| x-y |" block is a 8B bank from cacheline(x).bank(y) +Please also refer to: + +- DataArray selects data: + https://github.com/OpenXiangShan/XiangShan/blob/d4078d6edbfb4611ba58c8b0d1d8236c9115dbfc/src/main/scala/xiangshan/frontend/icache/ICache.scala#L355-L381 + https://github.com/OpenXiangShan/XiangShan/blob/d4078d6edbfb4611ba58c8b0d1d8236c9115dbfc/src/main/scala/xiangshan/frontend/icache/ICache.scala#L149-L161 +- ICache respond to IFU: + https://github.com/OpenXiangShan/XiangShan/blob/d4078d6edbfb4611ba58c8b0d1d8236c9115dbfc/src/main/scala/xiangshan/frontend/icache/ICacheMainPipe.scala#L473 +``` + +### 冲刷 + +在后端/IFU 重定向、BPU 重定向、`fence.i` 指令执行时,需要视情况对 ICache 内的存储结构和流水级进行冲刷。可能的冲刷目标/动作有: + +1. MainPipe、IPrefetchPipe 所有流水级 + - 冲刷时直接将 `s0/1/2_valid` 置为 `false.B` 即可 +2. MetaArray 中的 valid + - 冲刷时直接将 `valid` 置为 `false.B` 即可 + - `tag`、`code`不需要冲刷,因为它们的有效性由 `valid` 控制 + - DataArray 中的数据不需要冲刷,因为它们的有效性由 MetaArray 中的 `valid` 控制 +3. WayLookup + - 读写指针复位 + - `gpf_entry.valid` 置为 `false.B` +4. MissUnit 中所有 MSHR + - 若 MSHR 尚未向总线发出请求,请求和预取请求直接置无效(`valid === false.B`) + - 若 MSHR 已经向总线发出请求,记录待冲刷(`flush === true.B` 或 `fencei === true.B`),等到 d 通道收到 grant 响应时再置无效,同时不把 grant 的数据回复给 MainPipe/PrefetchPipe,也不写入 SRAM +     - 需要留意,当 d 通道收到 grant 响应的同时收到冲刷(`io.flush === true.B` 或 `io.fencei === true.B`)时,MissUnit 同样不写入 SRAM,但**会**将数据回复给 MainPipe/PrefetchPipe,避免将端口的延时引入响应逻辑中,此时 MainPipe/PrefetchPipe 也同步收到了冲刷请求,因此会将数据丢弃 + +每种冲刷原因需要执行的冲刷目标: + +| 冲刷原因 | 1 | 2 | 3 | 4 | +| --------------- | ----------------------- | --- | ----------------------- | --- | +| 后端/IFU 重定向 | Y | | Y | Y | +| BPU 重定向 | Y[^redirect_tab_bpu] | | | | +| `fence.i` | Y[^redirect_tab_fencei] | Y | Y[^redirect_tab_fencei] | Y | + +[^redirect_tab_bpu]: + BPU 精确预测器(BPU s2/s3 给出结果)可能覆盖简单预测器(BPU s0 给出结果)的预测,显然其重定向请求最晚在预取请求的 1- 2 拍之后就到达 ICache,因此仅需要: + + BPU s2 redirect:冲刷 IPrefetchPipe s0 + + BPU s3 redirect:冲刷 IPrefetchPipe s0/1 + + 当 IPrefetchPipe 的对应流水级中的请求来自于软件预取时 `isSoftPrefetch === true.B`,不需要进行冲刷 + + 当 IprefetchPipe 的对应流水级中的请求来自于硬件预取,但 `ftqIdx` 与冲刷请求不匹配时,不需要进行冲刷 + +[^redirect_tab_fencei]: `fence.i` 在逻辑上需要冲刷 MainPipe 和 IPrefetchPipe(因为此时流水级中的数据可能无效),但实际上`io.fencei`拉高必然伴随一个后端重定向,因此目前的实现中没有冲刷 MainPipe 和 IPrefetchPipe 的必要。 + +ICache 进行冲刷时不接收取指/预取请求(`io.req.ready === false.B`) + +#### 对 ITLB 的冲刷 + +ITLB 的冲刷比较特殊,其缓存的页表项仅需要在执行 `sfence.vma` 指令时冲刷,而这条冲刷通路由后端负责,因此前端或 ICache 一般不需要管理 ITLB 的冲刷。只有一个特例:目前 ITLB 为了节省资源,不会存储 `gpaddr`,而是在 `gpf` 发生时去 L2TLB 重取,重取状态由一个 `gpf` 缓存控制,这要求 ICache 在收到 `ITLB.resp.excp.gpf_instr` 时保证下面两个条件之一: + +1. 重发相同的 `ITLB.req.vaddr`,直到 `ITLB.resp.miss` 拉低(此时`gpf`、`gpaddr`均有效,正常发往后端处理即可),ITLB 此时会冲刷 `gpf` 缓存。 +2. 给 `ITLB.flushPipe`,ITLB 在收到该信号时会冲刷 `gpf` 缓存。 + +若 ITLB 的 `gpf` 缓存未被冲刷,就收到了不同 `ITLB.req.vaddr` 的请求,且再次发生 `gpf`,将导致核卡死。 + +因此,每当冲刷 IPrefetchPipe 的 s1 流水级时,无论冲刷原因为何,都需要同步冲刷 ITLB 的 `gpf` 缓存(即拉高 `ITLB.flushPipe`)。 + +### ECC + +首先需要指出,ICache 在默认参数下使用 parity code,其仅具备 1 bit 错误检测能力,不具备错误恢复能力,严格意义上不能算是 ECC(Error Correction Code)。但一方面,其可以配置为使用 secded code;另一方面,我们在代码中大量使用 ECC 来命名错误检测与错误恢复相关的功能(`ecc_error`、`ecc_inject`等)。因此本文档仍将使用 ECC 一词来指代错误检测、错误恢复、错误注入相关功能以保证与代码的一致性。 + +ICache 支持错误检测、错误恢复、错误注入功能,是 RAS[^ras] 能力的一部分,可以参考 RISC-V RERI[^reri] 手册,由 CtrlUnit 进行控制。 + +[^ras]: 此 RAS(Reliability, Availability, and Serviceability)非彼 RAS(Return Address Stack)。 +[^reri]: RERI(RAS Error-record Register Interface),参考 [RISC-V RERI 手册](https://github.com/riscv-non-isa/riscv-ras-eri)。 + +#### 错误检测 + +在 MissUnit 向 MetaArray 和 DataArray 重填数据时,会计算 meta 和 data 的校验码,前者和 meta 一起存储在 Meta SRAM 中,后者存储在单独的 Data Code SRAM 中。 + +当取指请求读取 SRAM 时,会同步读取出校验码,在 MainPipe 的 s1/s2 流水级中分别对 meta/data 进行校验。软件可以通过向 mmio-mapped CSR 中相应位置写入特定的值来使能/关闭这一功能,详见 [CtrlUnit 文档](05_ctrlunit)。 + +在校验码设计方面,ICache 使用的校验码可由参数控制,默认使用的是 parity code,即校验码为对数据做规约异或 $code = \oplus data$。检查时只需将校验码和数据一起做规约异或 $error = (\oplus data) \oplus code$,结果为 1 则发生错误,反之**认为没有**错误(可能出现偶数个错误,但此处检查不出来)。 + +ICache 支持错误注入,这要求 ICache 支持向 MetaArray/DataArray 写入错误的校验码。因此实现了一个`poison`位,当其拉高时,翻转写入的 code,即 $code = (\oplus data) \oplus poison$。 + +为了减少检查不出的情况,目前将 data 划分成 DataCodeUnit(默认为 64bit)的单元分别进行奇偶校验,因此对每个 64B 的缓存行,总计会计算 $8(data) + 1(meta) = 9$ 个校验码。 + +当 MainPipe 的 s1/s2 流水级检查到错误时,会进行以下处理: + +在 6 月至 11 月的版本中: + +1. 错误处理:引起 access fault 异常,由软件处理。 +2. 错误报告:向 BEU 报告错误,后者会引起中断向软件报告错误。 +3. 取消请求:当 MetaArray 被检查出错误时,其读出的 ptag 不可靠,进而对 hit 与否的判断不可靠,因此无论是否 hit 都不向 L2 Cache 发送请求,而是直接将异常传递到 IFU、进而传递到后端处理。 + +在后续版本([#3899](https://github.com/OpenXiangShan/XiangShan/pull/3899) 后)实现了错误自动恢复机制,故只需进行以下处理: + +1. 错误处理:从 L2 Cache 重新取指,见[错误自动恢复](#错误自动恢复)。 +2. 错误报告:拉高 erros.valid 向顶层报告错误。 + +#### 错误自动恢复 + +注意到,ICache 与 DCache 不同,是只读的,因此其数据必然不是 dirty 的,这意味着我们总是可以从下级存储结构(L2/3 Cache、memory)中重新获取正确的数据。因此,ICache 可以通过向 L2 Cache 重新发起 miss 请求来实现错误自动恢复。 + +实现重取功能本身只需要复用现有的 miss 取指路径,走 MainPipe -> MissUnit -> MSHR --tilelink-> L2 Cache 的请求路径。MissUnit 向 SRAM 重填数据时会自然地计算新的校验码并存储,因此在重取后会回到无错误的状态而不需要额外的处理。 + +6-11 月和后续代码行为差异的伪代码示意如下: + +```diff +- exception = itlb_exception || pmp_exception || ecc_error ++ exception = itlb_exception || pmp_exception + +- should_fetch = !hit && !exception ++ should_fetch = (!hit || ecc_error) && !exception +``` + +需要留意的是:为了避免重取后出现 multi-hit(即,同一个 set 内存在多个 way 的 ptag 相同),需要在重取前将 metaArray 对应位置的 valid 清空: + +- 若 MetaArray 错误:meta 保存的 ptag 本身可能出错,命中结果(one-hot 的 waymask)不可靠,“对应位置”指该 set 的所有 way +- 若 DataArray 错误:命中结果可靠,“对应位置”指该 set 中 waymask 拉高的那一 way + +#### 错误注入 + +根据 RERI 手册[^reri]的说明,为了使软件能够测试 ECC 功能,进而更好地判断硬件功能是否正常,需要提供错误注入功能,即主动地触发 ECC 错误。 + +ICache 的错误注入功能由 CtrlUnit 控制,通过向 mmio-mapped CSR 中相应位置写入特定的值来触发。详见 [CtrlUnit 文档](05_ctrlunit)。 + +目前 ICache 支持: + +- 向特定 paddr 注入,当请求注入的 paddr 未命中时,注入失败 +- 向 MetaArray 或 DataArray 注入 +- 当 ECC 校验功能本身未使能时,注入失败 + +软件注入流程示意如下: + +```asm +inject_target: + # maybe do something + ret + +test: + la t0, $BASE_ADDR # 载入 mmio-mapped CSR 基地址 + la t1, inject_target # 载入注入目标地址 + jalr ra, 0(t1) # 跳转到注入目标以保证其加载到 ICache + sd t1, 8(t0) # 向 CSR 写入注入目标地址 + la t2, ($TARGET << 2 | 1 << 1 | 1 << 0) # 设置注入目标、注入使能、校验使能 + sd t1, 0(t0) # 向 CSR 写入注入请求 +loop: + ld t1, 0(t0) # 读取 CSR + andi t1, t1, (0b11 << (4+1)) # 读取注入状态 + beqz t1, loop # 如果注入未完成,继续等待 + + addi t1, t1, -1 + bnez t1, error # 如果注入失败,跳转到错误处理 + + jalr ra, 0(t1) # 注入成功,跳转到注入目标地址以触发错误 + j finish # 结束 + +error: + # handle error +finish: + # finish +``` + +我们编写了一个测试用例,见[此仓库](https://github.com/OpenXiangShan/nexus-am/pull/48),其测试了如下情况: + +1. 正常注入 MetaArray +2. 正常注入 DataArray +3. 注入无效的目标 +4. 注入但 ECC 校验未使能 +5. 注入未命中的地址 +6. 尝试写入只读的 CSR 域 + +## 参考文献 + +1. Glenn Reinman, Brad Calder, and Todd Austin. "[Fetch directed instruction prefetching.](https://doi.org/10.1109/MICRO.1999.809439)" 32nd Annual ACM/IEEE International Symposium on Microarchitecture (MICRO). 1999. + + + +## ICache 模块功能说明 + +--- + +以下是**IPrefetchPipe**模块的功能 + +### 1. 接收预取请求 + +从 FTQ 接收预取请求,请求可能有效( io.req.valid 为高),可能无效; IPrefetchPipe 可能处于空闲( io.req.ready 为高),可能处于非空闲状态。 +只有在请求有效且 IPrefetchPipe 处于空闲状态时,预取请求才会被接收(这里暂不考虑 s0 的刷新信号 s0_flush ,默认其为低)。 +预取请求分为不同类型,包括硬件预取请求 (isSoftPrefetch = false)和软件预取请求 (isSoftPrefetch = true)。 +cacheline 也分为单 cacheline 和双 cacheline。 + +#### 1.1 硬件预取请求: + +预取请求为硬件 (isSoftPrefetch = false) + +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| 1.1.1 | 预取请求可以继续 | 当预取请求有效且 IPrefetchPipe 处于空闲状态时,预取请求应该被接收。
s0_fire 信号在没有 s0 的刷新信号( s0_flush 为低)时,应该被置为高。 | +| 1.1.2 | 预取请求被拒绝--预取请求无效 | 当预取请求无效时,预取请求应该被拒绝。
s0_fire 信号应该被置为低。 | +| 1.1.3 | 预取请求被拒绝--IPrefetchPipe 非空闲 | 当 IPrefetchPipe 非空闲时,预取请求应该被拒绝。
s0_fire 信号应该被置为低。 | +| 1.1.4 | 预取请求被拒绝--预取请求无效且 IPrefetchPipe 非空闲 | 当预取请求无效且 IPrefetchPipe 非空闲时,预取请求应该被拒绝。
s0_fire 信号应该被置为低。 | +| 1.1.5 | 预取请求有效且为单 cacheline | 当预取请求有效且为单 cacheline 时,预取请求应该被接收。
s0_fire 为高,s0_doubleline 应该被置低(false)。 | +| 1.1.6 | 预取请求有效且为双 cacheline | 当预取请求有效且为双 cacheline 时,预取请求应该被接收。
s0_fire 为高,s0_doubleline 应该被置高(true)。 | + +#### 1.2 软件预取请求: + +预取请求为软件 (isSoftPrefetch = true) + +| 序号 | 名称 | 描述 | +| ----- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1.2.1 | 软件预取请求可以继续 | 当预取请求有效且 IPrefetchPipe 处于空闲状态时,软件预取请求应该被接收。
当预取请求有效且 IPrefetchPipe 处于空闲状态时,软件预取请求应该被接收。 | +| 1.2.2 | 软件预取请求被拒绝--预取请求无效 | 当预取请求无效时,软件预取请求应该被拒绝。
s0_fire 信号应该被置为低。 | +| 1.2.3 | 软件预取请求被拒绝--IPrefetchPipe 非空闲 | 当 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。
s0_fire 信号应该被置为低。 | +| 1.2.4 | 软件预取请求被拒绝--预取请求无效且 IPrefetchPipe 非空闲 | 当预取请求无效且 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。
s0_fire 信号应该被置为低。 | +| 1.2.5 | 软件预取请求有效且为单 cacheline | 当软件预取请求有效且为单 cacheline 时,软件预取请求应该被接收。
s0_fire 为高,s0_doubleline 应该被置低(false)。 | +| 1.2.6 | 软件预取请求有效且为双 cacheline | 当软件预取请求有效且为双 cacheline 时,软件预取请求应该被接收。
s0_fire 为高,s0_doubleline 应该被置高(true)。 | + +### 2. 接收来自 ITLB 的响应并处理结果 + +接收 ITLB 的响应,完成虚拟地址到物理地址的转换。 +当 ITLB 发生缺失(miss)时,保存请求信息,等待 ITLB 完成后再继续处理。 + +#### 2.1 地址转换完成 + +- 根据 ITLB 的响应,接收物理地址(paddr),并完成地址转换。 +- 处理 ITLB 响应可能在不同周期到达的情况,管理有效信号和数据保持机制,确保正确使用物理地址。 + +| 序号 | 名称 | 描述 | +| ----- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 2.1.1 | ITLB 正常返回物理地址 | ITLB 在一个周期内成功返回物理地址 paddr,s1_valid 为高。
确认 s1 阶段正确接收到 paddr。 | +| 2.1.2 | ITLB 发生 TLB 缺失,需要重试 | fromITLB(PortNumber).bits.miss 为高,表示对应通道的 ITLB 发生了 TLB 缺失,需要重发。
重发完成后,后续步骤继续进行,fromITLB(PortNumber).bits.miss 为低。 | + +#### 2.2 处理 ITLB 异常 + +- 根据 ITLB 的异常信息,处理可能的异常。pf 缺页、pgf 虚拟机缺页、af 访问错误。 + +| 序号 | 名称 | 描述 | +| ----- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| 2.2.1 | ITLB 发生页错误异常 | s1_itlb_exception 返回的页错误。
iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示页错误 pf。 | +| 2.2.2 | ITLB 发生虚拟机页错误异常 | s1_itlb_exception 返回的虚拟机页错误。
iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示虚拟机页错误 pgf。 | +| 2.2.3 | ITLB 发生访问错误异常 | s1_itlb_exception 返回的访问错误。
iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示访问错误 af。 | + +#### 2.3 处理虚拟机物理地址(用于虚拟化) + +- 在虚拟化环境下,处理虚拟机物理地址(gpaddr),确定访问是否针对二级虚拟机的非叶子页表项(isForVSnonLeafPTE)。 + +| 序号 | 名称 | 描述 | +| ----- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 2.3.1 | 发生虚拟机页错误异常返回虚拟机物理地址(gpaddr) | 发生 pgf 后,需要返回对应的 gpaddr。
只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。 | +| 2.3.2 | ITLB 发生虚拟机页错误异常 | 发生 gpf 后,如果是访问二级虚拟机的非叶子页表项时,需要返回对应的 gpaddr。
只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。 | + +#### 2.4 返回基于页面的内存类型 pbmt 信息 + +- TLB 有效时,返回 pbmt 信息。 + +### 3. 接收来自 IMeta(缓存元数据)的响应并检查缓存命中 + +从 Meta SRAM 中读取缓存标签和有效位。 +将物理地址的标签部分与缓存元数据中的标签比较,确定是否命中。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|3.1|缓存标签比较和有效位检查: |从物理地址中提取物理标签(ptag),将其与缓存元数据中的标签进行比较,检查所有缓存路(Way)。检查有效位,确保只考虑有效的缓存行。 | +|3.1|缓存未命中(标签不匹配或有效位为假): |当标签不匹配或者标签匹配,但是有效位为假时,表示缓存未命中。
s1_meta_ptags(PortNumber)(nWays) 不等于 ptags(PortNumber) 或者它们相等,但是对应的 s1_meta_valids 为低时,总之返回的 waymasks 为全 0。 | +|3.2|单路缓存命中(标签匹配且有效位为真): |当标签匹配,且有效位为真时,表示缓存命中。
waymasks 对应的位为 1。 | + +### 4. PMP(物理内存保护)权限检查 + +对物理地址进行 PMP 权限检查,确保预取操作的合法性。 +处理 PMP 返回的异常和 MMIO 信息 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|4.1|访问被允许的内存区域 |itlb 返回的物理地址在 PMP 允许的范围内。
s1_pmp_exception(i) 为 none。 | +|4.2|访问被禁止的内存区域 |s1_req_paddr(i) 对应的地址在 PMP 禁止的范围内。
s1_pmp_exception(i) 为 af。 | +|4.3|访问 MMIO 区域 |itlb 返回的物理地址在 MMIO 区域。
s1_pmp_mmio 为高。 | + +### 5. 异常处理和合并 + +backend 优先级最高,merge 方法里的异常越靠前优先级越高 +合并来自后端、ITLB、PMP 的异常信息,按照优先级确定最终的异常类型。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|5.1|仅 ITLB 产生异常 |s1_itlb_exception(i) 为非零,s1_pmp_exception(i) 为零。
s1_exception_out(i) 正确包含 ITLB 异常。 | +|5.2|仅 PMP 产生异常 |s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为非零。
s1_exception_out(i) 正确包含 PMP 异常。 | +|5.3|仅 后端 产生异常 |s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为零。
s1_exception_out(i) 正确包含 后端 异常。 | +|5.4|ITLB 和 PMP 都产生异常 |s1_itlb_exception(i) 和 s1_pmp_exception(i) 都为非零。
s1_exception_out(i) 包含 ITLB 异常(优先级更高)。 | +|5.5|ITLB 和 后端 都产生异常 |s1_itlb_exception(i) 和 s1_backendException(i) 都为非零。
s1_exception_out(i) 包含 后端 异常(优先级更高)。 | +|5.6|PMP 和 后端 都产生异常 |s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。
s1_exception_out(i) 包含 后端 异常(优先级更高)。 | +|5.7|ITLB、PMP 和 后端 都产生异常 |s1_itlb_exception(i)、s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。
s1_exception_out(i) 包含 后端 异常(优先级更高)。 | +|5.8|无任何异常 |s1_itlb_exception(i)、s1_pmp_exception(i)、s1_backendException(i) 都为零。
s1_exception_out(i) 指示无异常。 | + +### 6. 发送请求到 WayLookup 模块 + +当条件满足时,将请求发送到 WayLookup 模块,以进行后续的缓存访问。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|6.1|正常发送请求到 WayLookup |toWayLookup.valid 为高,toWayLookup.ready 为高,s1_isSoftPrefetch 为假。
请求成功发送,包含正确的地址、标签、waymask 和异常信息。 | +|6.2|WayLookup 无法接收请求 |toWayLookup.valid 为高,toWayLookup.ready 为假。
状态机等待 WayLookup 准备好,不会错误地推进。 | +|6.3|软件预取请求不发送到 WayLookup |s1_isSoftPrefetch 为真。
toWayLookup.valid 为假,不会发送预取请求到 WayLookup。 | + +### 7. 状态机控制和请求处理流程 + +使用状态机管理 s1 阶段的请求处理流程。 +包括处理 ITLB 重发、Meta 重发、进入 WayLookup、等待 s2 准备等状态 + +#### 7.1 初始为 m_idle 状态 + +| 序号 | 名称 | 描述 | +| ----- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | +| 7.1.1 | 正常流程推进,保持 m_idle 状态 | s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为真,s2_ready 为真。
状态机保持在 m_idle 状态,s1 阶段顺利推进。 | +| 7.1.2 | ITLB 未完成,需要重发 | s1_valid 为高,itlb_finish 为假。
状态机进入 m_itlbResend 状态,等待 ITLB 完成。 | +| 7.1.3 | ITLB 完成,WayLookup 未命中 | s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为假。
状态机进入 m_enqWay 状态,等待 WayLookup 入队。 | + +#### 7.2 初始为 m_itlbResend 状态 + +| 序号 | 名称 | 描述 | +| ----- | ------------------------------------------------ | ----------------------------------------------------------------------------------------- | +| 7.2.1 | ITLB 命中, MetaArray 空闲,需要 WayLookup 入队 | itlb_finish 为假,toMeta.ready 为真。
状态机进入 m_enqWay 状态,等待 WayLookup 入队。 | +| 7.2.2 | ITLB 命中, MetaArray 繁忙,等待 MetaArray 读请求 | itlb_finish 为假,toMeta.ready 为假。
状态机进入 m_metaResend 状态,MetaArray 读请求 | + +#### 7.3 初始为 m_metaResend 状态 + +| 序号 | 名称 | 描述 | +| ---- | ------------------------------------ | ----------------------------------------------------------------------- | +| 7.3 | MetaArray 空闲 ,需要 WayLookup 入队 | toMeta.ready 为真。
状态机进入 m_enqWay 状态,等待 WayLookup 入队。 | + +#### 7.4 初始为 m_enqWay 状态 + +| 序号 | 名称 | 描述 | +| ----- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | +| 7.4.1 | WayLookup 入队完成或者为软件预取, S2 空闲, 重新进入空闲状态 | toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为假。
状态机进入空闲状态 m_idle。 | +| 7.4.2 | WayLookup 入队完成或者为软件预取, S2 繁忙,需要 enterS2 状态 | toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为真。
状态机进入 m_enterS2 状态,等待 s2 阶段准备好。 | + +#### 7.5 初始为 m_enterS2 状态 + +| 序号 | 名称 | 描述 | +| ---- | ------------------------------------------------------- | ----------------------------------------------- | +| 7.5 | s2 阶段准备好,请求进入下流水级,流入后进入 m_idle 状态 | s2_ready 为真。
状态机进入空闲状态 m_idle。 | + +### 8. 监控 missUnit 的请求 + +检查 missUnit 的响应,更新缓存的命中状态和 MSHR 的匹配状态。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|8.1|请求与 MSHR 匹配且有效: |s2_req_vSetIdx 和 s2_req_ptags 与 fromMSHR 中的数据匹配,且 fromMSHR.valid 为高,fromMSHR.bits.corrupt 为假。
s2_MSHR_match(PortNumber) 为真, s2_MSHR_hits(PortNumber) 应保持为真 | +|8.2|请求在 SRAM 中命中: |s2_waymasks(PortNumber) 中有一位为高,表示在缓存中命中。
s2_SRAM_hits(PortNumber) 为真,s2_hits(PortNumber) 应为真。 | +|8.3|请求未命中 MSHR 和 SRAM: |请求未匹配 MSHR,且 s2_waymasks(PortNumber) 为空。
s2_MSHR_hits(PortNumber)、s2_SRAM_hits(PortNumber) 均为假, s2_hits(PortNumber) 为假。 | + +### 9. 发送请求到 missUnit + +对于未命中的预取请求,向 missUnit 发送请求,以获取缺失的数据。 + +#### 9.1 确定需要发送给 missUnit 的请求 + +根据命中状态、异常信息、MMIO 信息等,确定哪些请求需要发送到 missUnit(即 s2_miss)。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|9.1.1|请求未命中且无异常,需要发送到 missUnit: |s2_hits(PortNumber) 为假(未命中缓存),s2_exception 无异常,s2_mmio 为假(不是 MMIO 或不可缓存的内存)。
s2_miss(PortNumber) 为真,表示需要发送请求到 missUnit。 | +|9.1.2|请求命中或有异常,不需要发送到 missUnit: |s2_hits(i) 为真(已命中)或者 s2_exception 有异常 或者 s2_mmio 为真(MMIO 访问)。
s2_miss(i) 为假,不会发送请求到 missUnit。 | +|9.1.3|双行预取时,处理第二个请求的条件: |s2_doubleline 为真,处理第二个请求。
如果第一个请求有异常或 MMIO,s2_miss(1) 应为假,后续请求被取消或处理。 | + +#### 9.2 避免发送重复请求,发送请求到 missUnit + +- 使用寄存器 has_send 记录每个端口是否已发送请求,避免重复发送。 +- 将需要发送的请求通过仲裁器 toMSHRArbiter 发送到 missUnit。 + +| 序号 | 名称 | 描述 | +| ----- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| 9.2.1 | 在 s1_real_fire 时,复位 has_send: | s1_real_fire 为高。
has_send(PortNumber) 应被复位为假,表示新的请求周期开始。 | +| 9.2.2 | 当请求成功发送时,更新 has_send: | toMSHRArbiter.io.in(PortNumber).fire 为高(请求已发送)。
has_send(PortNumber) 被设置为真,表示该端口已发送请求。 | +| 9.2.3 | 避免重复发送请求: | 同一请求周期内,has_send(PortNumber) 为真,s2_miss(PortNumber) 为真。
toMSHRArbiter.io.in(PortNumber).valid 为假,不会再次发送请求。 | +| 9.2.4 | 正确发送需要的请求到 missUnit: | s2_valid 为高,s2_miss(i) 为真,has_send(i) 为假。
toMSHRArbiter.io.in(i).valid 为高,请求被成功发送。 | +| 9.2.5 | 仲裁器正确仲裁多个请求: | 多个端口同时需要发送请求。
仲裁器按照优先级或设计要求选择请求发送到 missUnit,未被选中的请求在下个周期继续尝试发送。 | + +### 10. 刷新机制 + +- io.flush: 全局刷新信号,当该信号为高时,所有请求都需要刷新。 +- from_bpu_s0_flush:当请求不是软件预取(!s0_isSoftPrefetch, 软件预取请求是由特定的指令触发的,与指令流中的分支预测无关。因此,在处理刷新信号时,对于软件预取请求,通常不受来自 BPU 的刷新信号影响。),且 BPU 指示需要在 Stage 2 或 Stage 3 刷新的请求,由于该请求尚未进入 s1 阶段,因此在 s0 阶段也需要刷新。 +- s0_flush:综合考虑全局刷新信号、来自 BPU 的刷新信号,以及 s1 阶段的刷新信号 +- from_bpu_s1_flush:当 s1 阶段的请求有效且不是软件预取,且 BPU 指示在 Stage 3 需要刷新,则在 s1 阶段需要刷新。 +- io.itlbFlushPipe:当 s1 阶段需要刷新时,该信号用于通知 ITLB 刷新其流水线,以保持一致性。 +- s1_flush:综合考虑全局刷新信号和来自 BPU 的刷新信号。 +- s2_flush:用于控制 s2 阶段是否需要刷新。 + +| 序号 | 名称 | 描述 | +| ---- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| 10.1 | 发生全局刷新 | io.flush 为高。
s0_flush、s1_flush、s2_flush 分别为高,所有阶段的请求被正确清除。 | +| 10.2 | 来自 BPU 的刷新 | io.flushFromBpu.shouldFlushByStageX 为真(X 为 2 或 3),且请求不是软件预取。
对应阶段的 from_bpu_sX_flush 为高,sX_flush 为高,阶段请求被刷新。 | +| 10.3 | 刷新时状态机复位 | s1_flush 为高。
状态机 state 被重置为 m_idle 状态。 | +| 10.4 | ITLB 管道同步刷新 | s1_flush 为高。
io.itlbFlushPipe 为高,ITLB 被同步刷新。 | + +--- + +以下是**MainPipe**模块的功能 + +### 11. 访问 DataArray 的单路 + +根据从 WayLookup 获取信息,包括路命中信息和 ITLB 查询结果还有 DataArray 当前的情况,决定是否需要从 DataArray 中读取数据。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|11.1|访问 DataArray 的单路 |当 WayLookup 中的信息表明路命中时,ITLB 查询成功,并且 DataArray 当前没有写时,MainPipe 会向 DataArray 发送读取请求,以获取数据。
s0_hits 为高(一路命中),s0_itlb_exception 信号为零(ITLB 查询成功),toData.last.ready 为高(DataArray 没有正在进行的写操作)。
toData.valid 信号为高,表示 MainPipe 向 DataArray 发出了读取请求。 | +|11.2|不访问 DataArray(Way 未命中) ==会访问,但是返回数据无效== |当 WayLookup 中的信息表明路未命中时,MainPipe 不会向 DataArray 发送读取请求。
s0_hits 为低表示缓存未命中
toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 | +|11.3|不访问 DataArray(ITLB 查询失败)==会访问,但是返回数据无效== |当 ITLB 查询失败时,MainPipe 不会向 DataArray 发送读取请求。
s0_itlb_exception 信号不为零(ITLB 查询失败)。
toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 | +|11.4|不访问 DataArray(DataArray 正在进行写操作) |当 DataArray 正在进行写操作时,MainPipe 不会向 DataArray 发送读取请求。
toData.last.ready 信号为低,表示 DataArray 正在进行写操作。
toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 | + +### 12. Meta ECC 校验 + +将物理地址的标签部分与对应的 Meta 进行 ECC 校验,以确保 Meta 的完整性。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|12.1|无 ECC 错误 |当 waymask 全为 0(没有命中),则 hit_num 为 0 或 waymask 有一位为 1(一路命中),hit_num 为 1 且 ECC 对比通过(encodeMetaECC(meta) == code)
s1_meta_corrupt 为假。 | +|12.2|单路命中的 ECC 错误 |当 waymask 有一位为 1(一路命中),ECC 对比失败(encodeMetaECC(meta) != code)
s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 | +|12.3|多路命中 |> hit multi-way, must be an ECC failure
当 waymask 有两位及以上为 1(多路命中),视为 ECC 错误。
s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 | +|12.4|ECC 功能关闭 |当奇偶校验关闭时(ecc_enable 为低),强制清除 s1_meta_corrupt 信号置位。
不管是否发生 ECC 错误,s1_meta_corrupt 都为假。 | + +### 13. PMP 检查 + +- 将 S1 的物理地址 s1_req_paddr(i) 和指令 TlbCmd.exec 发往 PMP,判断取指是否合法。 +- 防止非法地址,区分普通内存和 MMIO 内存。 + +| 序号 | 名称 | 描述 | +| ---- | ------------------------------------ | --------------------------------------------------------------------------------------- | +| 13.1 | 没有异常 | s1_pmp_exception 为全零,表示没有 PMP 异常。 | +| 13.2 | 通道 0 有 PMP 异常 | s1_pmp_exception(0) 为真,表示通道 0 有 PMP 异常。 | +| 13.3 | 通道 1 有 PMP 异常 | s1_pmp_exception(1) 为真,表示通道 1 有 PMP 异常。 | +| 13.4 | 通道 0 和通道 1 都有 PMP 异常 | s1_pmp_exception(0) 和 s1_pmp_exception(1) 都为真,表示通道 0 和通道 1 都有 PMP 异常。 | +| 13.5 | 没有映射到 MMIO 区域 | s1_pmp_mmio(0) 和 s1_pmp_mergemmio(1) 都为假,表示没有映射到 MMIO 区域。 | +| 13.6 | 通道 0 映射到了 MMIO 区域 | s1_pmp_mmio(0) 为真,表示映射到了 MMIO 区域。 | +| 13.7 | 通道 1 映射到了 MMIO 区域 | s1_pmp_mmio(1) 为真,表示映射到了 MMIO 区域。 | +| 13.8 | 通道 0 和通道 1 都映射到了 MMIO 区域 | s1_pmp_mmio(0) 和 s1_pmp_mmio(1) 都为真,表示通道 0 和通道 1 都映射到了 MMIO 区域。 | + +### 14. 异常合并 + +- 将 s1_itlbmergeption 与 s1_pmp_exception 合并生成 s1_exception_out。 +- ITLB 异常通常优先于 PMP 异常。merge + +| 序号 | 名称 | 描述 | +| ---- | ------------------------ | ---------------------------------------------------------------------------------------- | +| 14.1 | 没有异常 | s1_exception_out 为全零,表示没有异常。 | +| 14.2 | 只有 ITLB 异常 | s1_exception_out 和 s1_itlb_exception 一致 | +| 14.3 | 只有 PMP 异常 | s1_exception_out 和 s1_pmp_exception 一致 | +| 14.4 | ITLB 与 PMP 异常同时出现 | > itlb has the highest priority, pmp next
s1_exception_out 和 s1_itlb_exception 一致 | + +### 15. MSHR 匹配和数据选择 + +- 检查当前的请求是否与 MSHR 中正在处理的缺失请求匹配。 +- 判断 缓存组索引相同(s1_req_vSetIdx(i) == fromMSHR.bits.vSetIdx) ,物理标签相同 (s1_req_ptags(i) == fromMSHR.bits.blkPaddr);若匹配 MSHR 有效且没有错误(fromMSHR.valid && !fromMSHR.bits.corrupt),则优先使用 MSHR 中的数据 +- 避免重复访问 Data SRAM,提升性能;当 MSHR 中已有重填结果时,可立即命中。 + +| 序号 | 名称 | 描述 | +| ---- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 15.1 | 命中 MSHR | MSHR 中已有正确数据时,S1 阶段能直接拿到
s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 s1_bankMSHRHit(i),s1_data_is_from_MSHR(i) 为 true | +| 15.2 | 未命中 MSHR | MSHR 中存放的地址与当前请求不同,那么应该读取 SRAM 的数据
s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false | +| 15.3 | MSHR 数据 corrupt | fromMSHR.bits.corrupt = true,那么 MSHR 将不匹配,应该读取 SRAM 的数据
s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false | + +### 16. Data ECC 校验 + +在 S2 阶段,对从 S1 或 MSHR 获得的数据(如 s2_datas)进行 ECC 校验: + +- 若 ECC 校验失败,则标记 s2_data_corrupt(i) = true。 +- 若数据来自 MSHR,则不重复进行 ECC 校验(或忽略 corrupt) + +| 序号 | 名称 | 描述 | +| ---- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 16.1 | 无 ECC 错误 | s2_bank 全部没有损坏,bank 也选对了对应的端口和 bank,数据不来自 MSHR
s2_data_corrupt(i) 为 false,没有 ECC 错误。 | +| 16.2 | 单 Bank ECC 错误 | s2_bank_corrupt(bank) 有一个为 true ,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR
s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 | +| 16.3 | 多 Bank ECC 错误 | s2_bank_corrupt(bank) 有两个或以上为 true,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR
s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 | +| 16.4 | ECC 功能关闭 | 当奇偶校验关闭时(ecc_enable 为低),强制清除 s2_data_corrupt 信号置位。
不管是否发生 ECC 错误,s2_data_corrupt 都为假。 | + +### 17. 冲刷 MetaArray + +Meta 或者 Data ECC 校验错误时,会冲刷 MetaArray,为重取做准备。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|17.1|只有 Meta ECC 校验错误 |> if is meta corrupt, clear all way (since waymask may be unreliable)
当 s1_meta_corrupt 为真时,MetaArray 的所有路都会被冲刷。
toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。 | +|17.2|只有 Data ECC 校验错误 |> if is data corrupt, only clear the way that has error
当 s2_data_corrupt 为真时,只有对应路会被冲刷。
toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的对应路置位。 | +|17.3|同时有 Meta ECC 校验错误和 Data ECC 校验错误 |处理 Meta ECC 的优先级更高, 将 MetaArray 的所有路冲刷。
toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。 | + +### 18. 监控 MSHR 匹配与数据更新 + +- 判断是否命中 MSHR +- 根据 MSHR 是否命中和 s1 阶段是否发射来更新 s2 的数据,s2 的命中状态和 l2 是否损坏 + +| 序号 | 名称 | 描述 | +| ---- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 18.1 | MSHR 命中(匹配且本阶段有效) | MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效
s2_MSHR_match,s2_MSHR_hits 为高,s2_bankMSHRHit 对应 bank 为高
s1_fire 无效时,s2_datas 更新为 MSHR 的数据,将 s2_data_is_from_MSHR 对应位置位,s2_hits 置位,清除 s2_data_corrupt,l2 的 corrupt 更新为 fromMSHR.bits.corrupt
s1_fire 有效时,s2_datas 为 s1_datas 的数据,将 s2_data_is_from_MSHR 对应位置为 s1 的 s1_data_is_from_MSHR,s2_hits 置为 s1_hits,清除 s2_data_corrupt,l2 的 corrupt 为 false | +| 18.2 | MSHR 未命中 | MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效,至少有一个未达成
s2_MSHR_hits(i) = false,S2 不会更新 s2_datas,继续保持原先 SRAM 数据或进入 Miss 流程。 | + +### 19. Miss 请求发送逻辑和合并异常 + +- 通过计算 s2_should_fetch(i) 判断是否需要向 MSHR 发送 Miss 请求: + - 当出现未命中 (!s2_hits(i)) 或 ECC 错误(s2_meta_corrupt(i) || s2_data_corrupt(i)) 时,需要请求重新获取。 + - 若端口存在异常或处于 MMIO 区域,则不发送 Miss 请求。 +- 使用 Arbiter 将多个端口的请求合并后发送至 MSHR。 +- 通过 s2_has_send(i) 避免重复请求。 +- 将 S2 阶段已有的 ITLB/PMP 异常(s2_exception)与 L2 Cache 报告的 s2_l2_corrupt(i)(封装后为 s2_l2_exception(i))进行合并。 + +| 序号 | 名称 | 描述 | +| ---- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 19.1 | 未发生 Miss | 当 s2_hits(i) 为高(s2 已经命中),s2 的 meta 和 data 都没有错误,s2 异常,处于 mmio 区域
以上条件至少满足一个时,s2_should_fetch(i) 为低,表示不发送 Miss 请求。 | +| 19.2 | 单口 Miss | 当出现未命中 (!s2_hits(i)) 或 ECC 错误(s2_meta_corrupt(i)
s2_data_corrupt(i)),端口不存在异常且未处于 MMIO 区域时,会向 MSHR 发送 Miss 请求。
toMSHRArbiter.io.in(i).valid = true ,Arbiter 只发送一条 Miss 请求。 | +| 19.3 | 双口都需要 Miss | 同上,但是两个端口都满足 s2_should_fetch 为高的条件。
toMSHRArbiter.io.in(0).valid、toMSHRArbiter.io.in(1).valid 均为 true,Arbiter 根据仲裁顺序依次发出请求。 | +| 19.4 | 重复请求屏蔽 | 当 s1_fire 为高,表示可以进入 s2 阶段,那么 s2 还没有发送 s2_has_send(i) := false.B
如果已经有请求发送了,那么对应的 toMSHRArbiter.io.in(i).fire 为高,表示对应的请求可以发送,s2_has_send(i) := true。
此时再次发送,toMSHRArbiter.io.in(i).valid 为低,表示发送失败。 | +| 19.5 | 仅 ITLB/PMP 异常 | S1 阶段已记录了 ITLB 或 PMP 异常,L2 corrupt = false。
2_exception_out 仅保留 ITLB/PMP 异常标记,无新增 AF 异常。 | +| 19.6 | 仅 L2 异常 | S2 阶段 s2_l2_corrupt(i) = true,且无 ITLB/PMP 异常。
s2_exception_out(i) 表示 L2 访问错误(AF)。 | +| 19.7 | ITLB + L2 同时出现 | 同时触发 ITLB 异常和 L2 corrupt。
s2_exception_out 优先保留 ITLB 异常类型,不被 L2 覆盖。 | +| 19.8 | s2 阶段取指完成 | s2_should_fetch 的所有端口都为低,表示需要取指,那么取指完成
s2_fetch_finish 为高 | + +### 20. 响应 IFU + +- 若当前周期 S2 成功发射(s2_fire = true)且数据获取完毕(s2_fetch_finish),则把数据、异常信息、物理地址等打包到 toIFU.bits 输出。 +- 若为双行请求(s2_doubleline = true),也会向 IFU 发送第二路的信息(地址、异常)。 + +| 序号 | 名称 | 描述 | +| ---- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 20.1 | 正常命中并返回 | 不存在任何异常或 Miss,s2 命中,s2 阶段取指完成,外部的 respStall 停止信号也为低 。
toIFU.valid = true,toIFU.bits.data 为正确的 Cacheline 数据,toIFU.bits.exception、pmp_mmio、itlb_pbmt = none。 | +| 20.2 | 异常返回 | 设置 ITLB、PMP、或 L2 corrupt 异常。
toIFU.bits.exception(i) = 对应异常类型,pmp_mmio、itlb_pbmt 根据是否有对应的异常设置为 true。 | +| 20.3 | 跨行取指 | s2_doubleline = true,同时检查第一路、第二路返回情况。
toIFU.bits.doubleline = true。
若第二路正常,toIFU.bits.exception(1) = none;若第二路异常,则 exception(1) 标记相应类型。
pmp_mmio、itlb_pbmt 类似。 | +| 20.4 | RespStall | 外部 io.respStall = true,导致 S2 阶段无法发射到 IFU。
s2_fire = false,toIFU.valid 也不拉高,S2 保持原状态等待下一拍(或直到 respStall 解除)。 | + +### 21. L2 Corrupt 报告 + +- 当检测到 L2 Cache 返回的 corrupt 标记时(s2_l2_corrupt(i) = true),在 S2 完成发射后额外向外部错误接口 io.errors(i) 报告。 +- 与 Data ECC 或 Meta ECC 不同,L2 corrupt 由 L2 自己报告给 BEU,这里不需要再次报告给 beu。 + +| 序号 | 名称 | 描述 | +| ---- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 21.1 | L2 Corrupt 单路 | s2 阶段准备完成可以发射(s2_fire 为高),s2_MSHR_hits(0)和 fromMSHR.bits.corrupt 为高
s2_l2_corrupt(0) = true,io.errors(0).valid = true,io.errors(0).bits.source.l2 = true。 | +| 21.2 | 双路同时 corrupt | 端口 0 和端口 1 都从 L2 corrupt 数据中获取。
s2_l2_corrupt 均为 true,发射后分别报告到 io.errors(0) 和 io.errors(1)。 | + +### 22. 刷新机制 + +- io.flush:外部的全局刷新信号,它用于指示整个流水线需要被冲刷(清空)。 +- s0_flush: S0 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S0 阶段的刷新操作。 +- s1_flush: S1 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S1 阶段的刷新操作。 +- s2_flush: S2 阶段内部的刷新信号,它由 io.flush 传递而来,用于控制 S2 阶段的刷新操作。 + +| 序号 | 名称 | 描述 | +| ---- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| 22.1 | 全局刷新 | io.flush 被激活时,流水线的各个阶段(S0, S1 和 S2)都能正确响应并执行刷新操作。
io.flush = true。
s0_flush, s1_flush, s2_flush = true。 | +| 22.2 | S0 阶段刷新 | s0_flush = true。
s0_fire = false。 | +| 22.3 | S1 阶段刷新 | s1_flush = true。
s1_valid, s1_fire = false。 | +| 22.4 | S2 阶段刷新 | s2_flush = true。
s2_valid, toMSHRArbiter.io.in(i).valid , s2_fire = false | + +--- + +以下是**WayLookup**模块的功能 + +### 23. 刷新操作 + +- 接收到全局刷新刷新信号 io.flush 后,读、写指针和 GPF 信息都被重置。 + +| 序号 | 名称 | 描述 | +| ---- | ------------- | --------------------------------------------------------------------------------- | +| 23.1 | 刷新读指针 | io.flush 为高时,重置读指针。
readPtr.value 为 0, readPtr.flag 为 false。 | +| 23.2 | 刷新写指针 | io.flush 为高时,重置写指针。
writePtr.value 为 0, writePtr.flag 为 false。 | +| 23.3 | 刷新 GPF 信息 | io.flush 为高时,重置 GPF 信息。
gpf_entry.valid 为 0, gpf_entry.bits 为 0。 | + +### 24. 读写指针更新 + +- 读写信号握手完毕之后(io.read.fire/io.write.fire 为高),对应指针加一。 +- 因为是在环形队列上,所以超过队列大小后,指针会回到队列头部。 + +| 序号 | 名称 | 描述 | +| ---- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| 24.1 | 读指针更新 | 当 io.read.fire 为高时,读指针加一。
readPtr.value 加一。
如果 readPtr.value 超过环形队列的大小,readPtr.flag 会翻转。 | +| 24.2 | 写指针更新 | 当 io.write.fire 为高时,写指针加一。
writePtr.value 加一。
如果 writePtr.value 超过环形队列的大小,writePtr.flag 会翻转。 | + +### 25. 更新操作 + +- MissUnit 处理完 Cache miss 后,向 WayLookup 写入命中信息,也就是 update 操作。 +- 情况分为两种: + - 命中:更新 waymask 和 meta_codes。 + - 未命中:重置 waymask。 + +| 序号 | 名称 | 描述 | +| ---- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 25.1 | 命中更新 | MissUnit 返回的更新信息和 WayLookup 的信息相同时,更新 waymask 和 meta_codes。
vset_same 和 ptag_same 为真。
waymask 和 meta_codes 更新。
hits 对应位为高。 | +| 25.2 | 未命中更新 | vset_same 和 way_same 为真。
waymask 清零。
hit 对应位为高。 | +| 25.3 | 不更新 | 其他情况下不更新。
vset_same 为假或者 ptag_same 和 way_same 都为假。
hits 对应位为低。 | + +### 26. 读操作 + +- 读操作会根据读指针从环形队列中读取信息。 +- 如果达成了绕过条件,优先绕过。 + +| 序号 | 名称 | 描述 | +| ---- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 26.1 | Bypass 读 | 队列为空,并且 io.write.valid 写有效时,可以直接读取,而不经过队列。
empty 和 io.write.valid 都为真。
io.read.bits = io.write.bits | +| 26.2 | 读信号无效 | 队列为空(readPtr === writePtr)且写信号 io.write.valid 为低。
io.read.valid 为低,读信号无效。 | +| 26.3 | 正常读 | 未达成绕过条件(empty 和 io.write.valid 至少有一个为假)且 io.read.valid 为高。
从环形队列中读取信息。
io.read.bits.entry = entries(readPtr.value) | +| 26.4 | gpf 命中 | io.read.valid 为高,可以读。
当 gpf_hits 为高时,从 GPF 队列中读取信息。
io.read.bits.gpf = gpf_entry.bits | +| 26.5 | gpf 命中且被读取 | io.read.valid 为高,可以读。
> also clear gpf_entry.valid when it's read
当 gpf 命中且被读取其时(io.read.fire 为高),gpf_entry.valid 会被置为 0。 | +| 26.6 | gpf 未命中 | io.read.valid 为高,可以读。
io.read.bits.gpf 清零。 | + +### 27. 写操作 + +- 写操作会根据写指针从环形队列中读取信息。 +- 如果有 gpf 停止,就会停止写。 + +| 序号 | 名称 | 描述 | +| ---- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 27.1 | gpf 停止 | > if there is a valid gpf to be read, we should stall write
gpf 队列数据有效,并且没有被读取或者没有命中,就会产生 gpf 停止,此时写操作会被停止。
gpf_entry.valid && !(io.read.fire && gpf_hit) 为高时,写操作会被停止(io.write.ready 为低)。 | +| 27.2 | 写就绪无效 | 当队列为满((readPtr.value === writePtr.value) && (readPtr.flag ^ writePtr.flag))或者 gpf 停止时,写操作会被停止。
(io.write.ready 为低) | +| 27.3 | 正常写 | 当 io.write.valid 为高时(没满且没有 gpf 停止),写操作会被执行。
正常握手完毕 io.write.fire 为高。
写信息会被写入环形队列。
entries(writePtr.value) = io.write.bits.entry。 | + +#### 27.4 有 ITLB 异常的写 + +- 前面与正常写相同,只不过当写信息中存在 ITLB 异常时,会更新 gpf 队列和 gpf 指针。 +- 此时如果已经被绕过直接读取了,那么就不需要存储它了。 + +| 序号 | 名称 | 描述 | +| ------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | +| 27.4.1 | 被绕过直接读取了 | can_bypass 和 io.read.fire 都为高。
gpf_entry.valid 为 false。
gpf_entry.bits = io.write.bits.gpf
gpfPtr = writePtr
| +| 27.4.2 | 没有被绕过直接读取 | can_bypass 为低。
gpf_entry.valid 为 true。
gpf_entry.bits = io.write.bits.gpf
gpfPtr = writePtr | + +--- + +以下是**FIFO**模块的功能 + +### 28. 入队操作 + +| 序号 | 名称 | 描述 | +| ---- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 28.1 | 队未满,正常入队 | 当队列未满,且空位不小于一时,可以正常入队,如果从零号位开始入队到最大容量,入队指针的 flag 不会翻转。
io.enq.fire 为高有效,regFiles(enq_ptr.value) = io.enq.bits,enq_ptr.value+1 入队指针移动,入队指针标记位不翻转。
重复以上操作至队满。 | +| 28.2 | 队未满,入队后标记位翻转 | 当队未满,但是空位却是靠近队尾时,入队一位后就到达了队头,入队指针的 flag 会翻转。
队列的容量为 10,入队指针指向 9,队未满。此时如果 io.enq.fire 为高,则 regFiles(9) = io.enq.bits,enq_ptr.value+1(循环队列,加完后 enq_ptr.value=0)入队指针移动,入队指针标记位翻转。 | +| 28.3 | 队满,入队就绪信号为低,无法入队 | 当队满时,(enq_ptr.value === deq_ptr.value) && (enq_ptr.flag ^ deq_ptr.flag) 为高,io.enq.ready 为低,io.enq.fire 为低无效。
此时入队,入队指针的 value 和 flag 不变。 | + +### 29. 出队操作 + +| 序号 | 名称 | 描述 | +| ---- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 29.1 | 队非空,正常出队 | 当队列非空时,可以正常出队,如果出队指针不经过最大容量位置,出队指针的 flag 不会翻转。
io.deq.fire 为高有效,io.deq.bits = regFiles(deq_ptr.value),deq_ptr.value+1 出队指针移动,出队指针标记位不翻转。 | +| 29.2 | 队非空,出队后标记位翻转 | 当队非空,但是出队指针是靠近队尾时,出队一位后就到达了队头,出队指针的 flag 会翻转。
队列的容量为 10,出队指针指向 9,队非空。此时如果 io.deq.fire 为高,则 io.deq.bits = regFiles(9),deq_ptr.value+1(循环队列,加完后 deq_ptr.value=0)出队指针移动,出队指针标记位翻转。 | +| 29.3 | 队空,出队有效信号为低,无法出队 | 当队空时,enq_ptr === deq_ptr 为高,io.deq.valid 为低,io.deq.fire 为低无效。
此时出队,出队指针的 value 和 flag 不变。 | + +### 30. 刷新清空操作 + +| 序号 | 名称 | 描述 | +| ---- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 30.1 | flush 清空 | 当刷新信号有效时,重置出队和入队的指针和标记位,清空队列。
当 flush 为高时,deq_ptr.value=0,enq_ptr.value=0,deq_ptr.flag=false,enq_ptr.flag=false,empty=true,full=false。 | + +--- + +以下是**MissUnit**模块的功能 + +### 31. 处理取指缺失请求 + +处理来自 MainPipe 的取指单元的缓存缺失请求,将缺失请求分发到多个 Fetch MSHR 中的一个,避免重复请求。 +低索引的请求优先处理。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|31.1|接受新的取指请求 |当新的 fetch miss 与 MSHR 中的已有请求不重复时(通过 io.fetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Fetch MSHR 中。
当有新的取指缺失请求到达时(io.fetch_req.valid 为高),且没有命中已有的 MSHR(fetchHit 为低),io.fetch_req.ready 应为高,表示可以接受请求。
io.fetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。 | +|31.2|处理已有的取指请求 |当已有取指缺失请求到达时(io.fetch_req.valid 为高),且命中已有的 MSHR(fetchHit 为高),io.fetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。
fetchDemux.io.in.valid 应为低,fetchDemux.io.in.fire 为低,表示没有新的请求被分发到 MSHR。 | +|31.3|低索引的请求优先进入 MSHR |Fetch 的请求会通过 fetchDemux 分配到多个 Fetch MSHR,fetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。
当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。 | + +### 32. 处理预取缺失请求 + +与 Fetch Miss 类似,但走另一些 MSHR(Prefetch MSHR)。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|32.1|接受新的预取请求 |当新的 prefetch miss 与 MSHR 中的已有请求不重复时(通过 io.prefetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Prefetch MSHR 中。
当有新的预取缺失请求到达时(io.prefetch_req.valid 为高),且没有命中已有的 MSHR(prefetchHit 为低),io.prefetch_req.ready 应为高,表示可以接受请求。
io.prefetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。 | +|32.2|处理已有的预取请求 |当已有预取缺失请求到达时(io.prefetch_req.valid 为高),且命中已有的 MSHR(prefetchHit 为高),io.prefetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。
prefetchDemux.io.in.valid 应为低,prefetchDemux.io.in.fire 为低,表示请求被接受但未分发到新的 MSHR。 | +|32.3|低索引的请求优先进入 MSHR |Prefetch 的请求会通过 prefetchDemux 分配到多个 Prefetch MSHR,prefetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。
当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。 | +|32.4|先进入 MSHR 的优先进入 prefetchArb |从 prefetchDemux 离开后,请求的编号会进入 priorityFIFO,priorityFIFO 会根据进入队列的顺序排序,先进入队列的请求会先进入 prefetchArb。
prefetchDemux.io.in.fire 为高,并且 prefetchDemux.io.chosen 有数据时,将其编号写入 priorityFIFO。
在 priorityFIFO 中有多个编号时,出队的顺序和入队顺序一致。
检查 priorityFIFO.io.deq.bit 中的数据即可。 | + +### 33. MSHR 管理与查找 + +| 序号 | 名称 | 描述 | +| ---- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 33.1 | MSHR 查找命中逻辑 | 当新的请求到来时,能够正确查找所有 MSHR,判断请求是否命中已有 MSHR。
当新的请求(取指或预取)到来时,系统遍历所有 MSHR,根据所有 MSHR 的查找信号 allMSHRs(i).io.lookUps(j).hit,检查请求是否已经存在于某个 MSHR 中。
如果命中,则对应的 fetchHit 或 prefetchHit 为高。
对于 prefetchHit 为高,还有一种情况:预取的物理块地址和组索引与取指的相等((io.prefetch_req.bits.blkPaddr === io.fetch_req.bits.blkPaddr) && (io.prefetch_req.bits.vSetIdx === io.fetch_req.bits.vSetIdx))并且有取指请求 io.fetch_req.valid 有效时,也算命中 | +| 33.2 | MSHR 状态的更新与释放 | 当请求完成后,也就是来自内存总线的响应完成(D 通道接收完所有节拍),MSHR 能够正确地释放(清除其有效位),以便接收新的请求。
TileLink D 通道返回的 source ID ,即 io.mem_grant.bits.source。
无效化信号 allMSHRs(i).io.invalid 为高,对应的 MSHR 的有效位 allMSHRs(i).valid 变为低 | + +### 34. acquireArb 仲裁 + +预取和取指的 acquire 都会发送给 acquireArb,acquireArb 会选择一个 acquire 发送给 mem_acquire。 +acquireArb 使用 chisel 自带的 Arbiter 实现,Arbiter 使用固定优先级仲裁,优先级从编号 0 开始,编号越小优先级越高。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|34.1|acquireArb 仲裁 |acquireArb 会选择一个 acquire 发送给 mem_acquire。
当有多个 MSHR 同时发出请求时,acquireArb 会根据优先级进行仲裁,选择优先级最高的 MSHR 发送请求。
取指请求总是在 0-3 号,预取请求直接在最后一号,所以取指请求优先级高于预取请求。
当取指 acquire 和预取 acquire 同时发出时,fetchMSHRs(i).io.acquire 和 prefetchMSHRs(i).io.acquire 都有效,仲裁结果 acquireArb.io.out 应该和 fetchMSHRs(i).io.acquire 一致。 | + +### 35. Grant 数据接收与 Refill + +在收到 TileLink D 通道数据时收集整行 + +- 累计 beat 数(readBeatCnt),直到完成一整行 (last_fire) +- 记录 corrupt 标志 +- 将完成的请求映射回对应的 MSHR (id_r = mem_grant.bits.source) + +| 序号 | 名称 | 描述 | +| ---- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 35.1 | 正常完整 Grant 流程,readBeatCnt 为 0 时 | readBeatCnt 初始为 0,refillCycles - 1 也为 0。
io.mem_grant.valid 为高(因为 io.mem_grant.ready 默认为高,所以 io.mem_grant.fire 为高只需要 io.mem_grant.valid 为高)且 io.mem_grant.bits.opcpde(0)为高。
此时 respDataReg(0)= io.mem_grant.bits.data
readBeatCnt 加一为 1。 | +| 35.2 | 正常完整 Grant 流程,readBeatCnt 为 1 时 | io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高。
此时 respDataReg(1)= io.mem_grant.bits.data
readBeatCnt 重置回 0。
last_fire 为高。
下一拍 last_fire_r 为高,id_r=io.mem_grant.bits.source。 | +| 35.3 | 正常完整 Grant 流程,last_fire_r 为高 | last_fire_r 为高,并且 id_r 为 0-13 中的一个。
对应的 fetchMSHRs 或者 prefetchMSHRs 会被无效,也就是 fetchMSHRs_i 或 prefetchMSHRs_i-4 的 io_invalid 会被置高。 | +| 35.4 | Grant 带有 corrupt 标志 | io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高,则 corrupt_r 应为高。
如果 io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高中有一个不满足,且此时 last_fire_r 为高,则 corrupt_r 重置为低。 | + +### 36. 替换策略更新 (Replacer) + +MissUnit 在发出 Acquire 请求时,还会将本次选中的 victim way 对应的索引告诉 io.victim,让替换策略更新其记录(替换策略采用 PLRU) +只有当 Acquire 真正“fire”时,才说明成功替换,replacer 需要更新状态 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|36.1|正常替换更新 |当 io.mem.acquire.ready & acquireArb.io.out.valid 同时为高,也就是 acquireArb.io.out.fir 为高时,io.victim.vSetIdx.valid 也为高。
io.victim.vSetIdx.bits = 当前 MSHR 请求的 acquireArb.io.out.bits.vSetIdx。 | +|36.2|生成 waymask |根据从 L2 返回的 mshr_resp 中 mshr_resp.bits.way 生成 waymask 信息。
返回的 mshr_resp.bits.way 有 16 位,通过独热码生成一位掩码信息,waymask 表示其中哪一路被替换。
生成的 waymask 应该和 mshr_resp.bits.way 一致。 | + +### 37. 写回 SRAM (Meta / Data) + +在一条 Miss Request refill 完成时,将新得到的 Cache line 写到 ICache。 +生成 io.meta_write 和 io.data_write 的请求,带上 waymask, tag, idx, data 。 +生成 io.meta_write.valid 和 io.data_write.valid 信号。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|37.1|生成 io.meta_write.valid 和 io.data_write.valid 信号 |当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。
并且此时没有硬件刷新信号和软件刷新信号,也就是 io.flush 和 io.fencei 为低。 在等待 l2 响应的过程中,没有刷新信号
也没有数据 corrupt,即 corrupt_r 为低。
那么 io.meta_write.valid 和 io.data_write.valid 均为高。 | +|37.2|正常写 SRAM |io.meta_write.bits 的 virIdx、phyTag、waymask、bankIdx、poison 应该正常更新
io.data_write.bits 的 virIdx、data、waymask、bankIdx、poison 应该正常更新 | + +### 38. 向 mainPipe/prefetchPipe 发出 Miss 完成响应(fetch_resp) + +在完成 refill 后,无论是否要真正写阵列,都会向取指端发送“Miss 请求完成” +更新 io.fetch_resp.valid 和 fetch_resp.bits。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|38.1|正常 Miss 完成响应 |当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。
无论此时是否有硬件刷新信号和软件刷新信号, io.fetch_resp.valid 都为高,说明可向取指端发送响应。
io.fetch_resp.bits 中的数据更新:
io.fetch_resp.bits.blkPaddr = mshr_resp.bits.blkPaddr
io.fetch_resp.bits.vSetIdx = mshr_resp.bits.vSetIdx
io.fetch_resp.bits.waymask = waymask
io.fetch_resp.bits.data = respDataReg.asUInt
io.fetch_resp.bits.corrupt = corrupt_r | + +### 39. 处理 flush / fencei + +一旦收到 io.flush 或 io.fencei 时,对未发射的请求可立即取消,对已经发射的请求在拿到数据后也不写 SRAM。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|39.1|MSHR 未发射前 fencei |如果 MSHR 还没有通过 io.acquire.fire 发出请求,就应立即取消该 MSHR(mshr_resp.valid= false),既不发出请求,也不要写 SRAM。
当 io.fencei 为高时,fetchMSHRs 和 prefetchMSHRs 的 io.req.ready 和 io.acquire.valid 均为低,表示请求不发射。 | +|39.2|MSHR 未发射前 flush |由于 fetchMSHRs 的 io.flush 被直接设置为 false,所以 io.flush 对 fetchMSHRs 无效,但是对 prefetchMSHRs 有效。
当 io.flush 为高时,只能发射 fetchMSHRs 的请求。 | +|39.3|MSHR 已发射后 flush/fencei |已经发射了请求,之后再有刷新信号,那么等数据回来了但不写 SRAM。
在发射后,io.flush/io.fencei 为高时,等待数据回来,但是写 SRAM 的信号,write_sram_valid、io.meta_write.valid 和 io.data_write.valid 均为低,表示不写 SRAM。
对于 response fetch 无影响。 | + +--- + +以下是**CtrlUnit**模块的功能 + +### 40. ECC 启用/禁用 + +控制 eccctrl.enable 字段来启用或禁用 ECC 功能。外部系统可以通过写寄存器 eccctrl 来控制 ECC 是否启用。 + +- 通过寄存器写入控制信号 enable,当 enable 为 true 时,ECC 功能启用;为 false 时,ECC 功能禁用。 + +| 序号 | 名称 | 描述 | +| ---- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 40.1 | 启用 ECC | 向 eccctrl.enable 寄存器写入 true,验证模块内部 eccctrl.enable 设置为 true,并确保后续的错误注入操作能够成功进行。此测试确保 eccctrl.enable 写操作被执行。
确保 eccctrl.enable 被正确设置为 true,并触发 eccctrlRegWriteFn 中的写操作逻辑。 | +| 40.2 | 禁用 ECC | 向 eccctrl.enable 寄存器写入 false,验证模块内部 eccctrl.enable 设置为 false,并确保在后续的错误注入过程中,ECC 功能被禁用,不允许进行错误注入。此测试确保 eccctrl.enable 写操作被正确设置为 false。
验证禁用 ECC 时 eccctrl.enable 为 false,并触发 eccctrlRegWriteFn 中的错误处理分支。x.istatus = eccctrlInjStatus.error 和 x.ierror = eccctrlInjError.notEnabled | + +### 41. 状态机转换 + +根据状态机的状态,验证错误注入的流程是否正确。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|41.1|is_idle 状态 |初始为 is_idle 状态。
当 eccctrl.istatus 为 working 时,验证此时的状态为 is_readMetaReq。 | +|41.2|is_readMetaReq 状态 |当握手成功后(io.metaRead.ready 和 io.metaRead.valid 都为高),验证此时的状态为 is_readMetaResp。 | + +#### 41.3 is_readMetaResp 状态 + +| 序号 | 名称 | 描述 | +| ------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 41.3.1 | 未命中 | 当 waymask 全零的时候,表示没有命中,会进入 is_idle 状态,并且设置错误错误注入状态和错误原因。
验证此时的状态为 is_idle, eccctrl.istatus = error 和 eccctrl.ierror = notFound。 | +| 41.3.2 | 命中 | 当 waymask 不全零的时候,表示命中,会根据错误注入目标来判断是向元数据还是数据阵列写入错误。
当 eccctrl.itarget=metaArray 时,验证此时的状态为 is_writeMeta ;当 eccctrl.itarget!=metaArray 时,验证此时的状态为 is_writeData。 | + +#### 41.4 is_writeMeta 状态 + +| 序号 | 名称 | 描述 | +| ------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 41.4.1 | RegWriteFn | 此状态进入后,io.dataWrite.valid 会为高
x.itarget = req.itarget
当 req.inject 为高并且 x.istatus = idle 时:
1. 如果 ecc 的 req.enable = false,则验证 x.istatus = error 且 x.ierror = notEnabled
2. 否则,如果 req.itarget != metaArray 和 dataArray,则验证 x.istatus = error 且 x.ierror = targetInvalid
3. 如果都不满足,则验证 x.istatus = working | +| 41.4.2 | 状态转换 | 当 io.metaWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。 | + +#### 41.5 is_writeData 状态 + +| 序号 | 名称 | 描述 | +| ------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 41.5.1 | RegWriteFn | 此状态进入后,io.dataWrite.valid 会为高
res.inject = false
当 ready 为高,且 x.istatus = injected 或 x.istatus = error 时,验证 x.istatus = idle 和 x.ierror = notEnabled | +| 41.5.2 | 状态转换 | 当 io.dataWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。 | + +### 42. 寄存器映射和外部访问 + +通过 TileLink 总线将寄存器映射到特定地址,使外部模块可以读写 ECC 控制寄存器和注入地址寄存器。 + +- 使用 TLRegisterNode 实现寄存器的映射,使得外部系统可以通过地址访问寄存器。寄存器的读写操作通过 TileLink 协议进行。 + +| 序号 | 名称 | 描述 | +| ---- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| 42.1 | 外部读取和写入 ECC 控制寄存器 | 验证外部模块可以通过 TileLink 协议正确读取和写入 eccctrl 和 ecciaddr 寄存器,并对模块内部的状态产生影响,确保读写操作完全覆盖。 | +| 42.2 | 外部模块触发错误注入 | 通过外部模块经 TileLink 总线向 eccctrl.inject 寄存器写入 true,触发错误注入,验证内部状态是否按 RegWriteFn 内部过程执行。 | + +--- + +以下是**ICache 顶层**模块的功能 + +### 43. FTQ 预取请求处理 + +接收来自 FTQ 的预取请求,经 IPrefetchPipe 请求过滤(查询 ITLB 地址,是否命中 MetaArry,PMP 检查),(有异常则由 MissUnit 处理)后进入 WayLookup。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|43.1|预取地址命中,无异常 |io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查通过。
如果没有监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该命中的取指数据。
如果监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该是未命中的取指数据。 | +|43.2|预取地址未命中,无异常 |io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 不匹配,pmp 检查通过。
如果监听到 MSHR 将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该命中的取指数据。
如果监听到 MSHR 没有将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该未命中的取指数据。 | +|43.3|预取地址 TLB 异常,无其他异常 |io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 异常。
验证 wayLookup.io.write 的 itlb_exception 内容中,其有对应的异常类型编号(pf:01;gpf:10;af:11)。 | +|43.4|预取地址 PMP 异常,无其他异常 |io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查未通过。
验证 wayLookup.io.write 的 tlb_pbmt 内容中,其有对应的异常类型编号(nc:01;io:10)。 | + +### 44. FTQ 取指请求处理 + +io.fetch.resp <> mainPipe.io.fetch.resp 发送回 IFU 的数据是在 io.fetch.resp。 +接收来自 FTQ 的取指请求,从 WayLookup 获取路命中信息和 ITLB 查询结果,再访问 DataArray,监控 MSHR 的响应。更新 replacer,做 pmp 检查。后做 DataArray 和 MetaArray 的 ECC 校验。最后将数据发送给 IFU。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|44.1|取指请求命中,无异常 |io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。
验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。
验证 io.fetch.resp 的数据应该是取指的数据。 | +|44.2|取指请求未命中,MSHR 返回的响应命中,无异常 |io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,未命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。
请求在 MSHR 返回的响应命中。
验证 missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。
验证 io.fetch.resp 的数据应该是取指的数据。 | +|44.3|取指请求命中,ECC 校验错误,无其他异常 |io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 或 MetaArray 的 ECC 校验错误。
验证 io.error.valid 为高,且 io.error.bits 内容为对应的错误源和错误类型。
先刷 MetaArray 的 ValidArray,给 MissUnit 发请求,由其在 L2 重填,阻塞至数据返回。
验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。
验证 io.fetch.resp 的数据应该是取指的数据。 | +|44.4|取指请求未命中,但是 exception 非 0(af、gpf、pf),无其他异常 |io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查未通过,DataArray 和 MetaArray 的 ECC 校验正常。
验证 io.fetch.resp 为对应的错误源和错误类型。
验证 io.fetch.resp 的数据无效,里面有异常类型。 | +|44.5|取指请求未命中,通过 WayLookup 中读取到的预取过来的 itlb 中返回 pbmt。 |有 itlb_pbmt 和 pmp_mmio 时,他们合成 s1_mmio,传递到 s2_mmio,生成 s2_miss,有特殊情况就不会取指。
io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查通过,DataArray 和 MetaArray 的 ECC 校验正常。
验证 io.fetch.resp 为对应的错误源和错误类型。
验证 io.fetch.resp 的数据无效,里面有特殊情况类型类型。 | +|44.6|取指请求未命中,pmp 返回 mmio 。 | 处理同 5。 | + +### 45. MetaArray 功能 + +在 IPrefetchPipe 的 S0,接收来自 IPrefetchPipe 的读请求 read,返回对应路和组的响应 readResp。 +在 miss 的时候,MissUnit 会将会应的数据写入 write 到 MetaArray。 +MetaArray 主要存储了每个 Cache 行的标签和 ECC 校验码。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|45.1|元数据写入操作(对应的 Set 已满): ICacheMetaArray 应当能够正确地将元数据(标签和有效位)写入到指定的 Set 和 Way 。 |从 MissUnit 返回的请求都是未命中的请求(已命中不会向 MissUnit 请求,那么 MissUnit 自然也不会向 MetaArray 写入)。
发送一个写请求 write 到 ICacheMetaArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最后指定 virIdx、phyTag、waymask、bankIdx、poison。
写入操作后,发起一个对相同虚拟索引的读请求。验证 readResp 的 metas 和 codes 分别包含写入的 ptag 和 ecc code,并且对于写入的路,readResp.entryValid 信号被置为有效。 | +|45.2|元数据读取操作 (命中): 当一个读请求在 ICacheMetaArray 中命中时(存在有效的条目),它应该返回正确的元数据(标签和有效位)。 |首先,向特定的虚拟索引(组和路)写入元数据(参照上面的写入操作)。然后,向相同的虚拟索引发送一个读请求。
验证 readResp.metas 包含之前写入的物理标签,并且对于相应的路,readResp.entryValid 信号被置为有效。 | +|45.3|元数据读取操作 (未命中): 当读取一个尚未被写入的地址时,ICacheMetaArray 应当指示未命中(条目无效)。 |向 ICacheMetaArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。
验证对于任何路,readResp.entryValid 信号都没有被置为有效。 对应的 readResp.metas 和 codes 的内容是 DontCare 也就是 0。 | +|45.4|独立的缓存组刷新:在第 i 个端口是有效的刷新请求,并且该请求的 waymask 指定了当前正在处理的第 w 路时,应该使第 i 个端口的条目无效。 |先向 ICacheMetaArray 写入指定一个或多个端口的元数据,然后再给对应的端口的路发送刷新请求 io.flush,其包含虚拟索引 virIdx 和路掩码 waymask。
验证 valid_array 对应的路中的 virIdx 被置为无效,io.readResp.entryValid 对应路的对应端口为无效。 | +|45.5|全部刷新操作: ICacheMetaArray 应当能够在接收到全部刷新请求时,使所有条目无效。 |先向多个不同的虚拟索引写入元数据。然后置位 io.flushAll 信号。
验证步骤: 在 io.flushAll 信号置位后,发起对所有之前写入过的虚拟索引的读请求。验证在所有的读取响应中,对于任何路,readResp.entryValid 信号都没有被置为有效。 | + +### 46. DataArray 功能 + +与 MetaArray 类似,在 MainPipe 的 S0,接收来自 MainPipe 的读请求 read,返回对应路和组的响应 readResp。 +在 miss 的时候,MissUnit 会将会应的数据写入 write 到 DataArray。 +DataArray 主要存储了每个 Cache 行的标签和 ECC 校验码。 +| 序号 | 名称 | 描述 | +| ----- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +|46.1|数据写入操作(对应的 Set 已满): ICacheDataArray 应当能够正确地将数据写入到指定的 Set (组)、Way (路) 和数据 Bank (存储体)。 |发送一个写请求 write 到 ICacheDataArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最终指定虚拟索引、数据、路掩码、存储体索引 bankIdx 和毒化位。写入的数据模式应跨越多个数据存储体。
写入操作后,发起一个对相同虚拟索引和块偏移量的读请求。验证 readResp.datas 与写入的数据相匹配。 | +|46.2|数据读取操作 (命中): 当一个读请求命中时(相应的元数据有效),它应该从相应的组、路和数据存储体返回正确的数据。 |首先,向特定的虚拟索引和块偏移量写入数据。然后,向相同的虚拟索引和块偏移量发送一个读请求。使用不同的块偏移量进行测试,以覆盖存储体的选择逻辑。
验证 readResp.datas 包含之前写入的数据。 | +|46.3|数据读取操作 (未命中): 当读取一个尚未被写入的地址时,ICacheDataArray 的输出应该是默认值或无关值。 |向 ICacheDataArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。
验证 readResp.datas 为 0。 | + +--- + +
+ +## ICache 接口说明 + +为方便测试开展,需要对 ICache 的接口进行进一步的说明,以明确各个接口的含义。 + +\*注意:源文件编译成 verilog/system verilog 后,部分接口会被优化,实际接口以编译后的为准。 + +### IPrefetch 模块接口 + +#### 控制接口 + +| 接口名 | 解释 | +| ------------- | ----------------------------------- | +| csr_pf_enable | 控制 s1_real_fire,软件控制预取开关 | +| ecc_enable | 编译后被优化 ,控制 ecc 开启 | +| flush | 刷新信号 | + +#### req:FTQ 预取请求 + +由于 BPU 基本无阻塞,它经常能走到 IFU 的前面,于是 BPU 提供的这些还没发到 IFU 的取指请求就可以用作指令预取,FTQ 中实现了这部分逻辑,直接给 ICache 发送预取请求。 +预取请求来自 FTQ,在 S0 流水级传入。 +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|ready||指示 s0 能否继续| +|valid|指示软件预取或者硬件预取是否有效。| +|startAddr | 预测块起始地址。 | +|nextlineStart | 预测块下一个缓存行的起始地址。 | +|ftqIdx| 指示当前预测块在 FTQ 中的位置,包含 flag 和 value 两个量。| +|isSoftPrefetch|是否为软件预取(来自 Memblock 中 LoadUint 的软件预取请求)。| +|backendException| ICache 向 IFU 报告后端存在的异常类型。| + +#### flushFromBpu:来自 BPU 的刷新信息 + +由 FTQ 传递而来的 BPU 刷新信息,在 S0 流水级传入。 +这是预测错误引起的,包括 s2 和 s3 两个同构成员,指示是否在 BPU 的 s2 和 s3 流水级发现了问题。 +s2 的详细结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|valid|指示 s2 是否有效。| +|ftqIdx|指示 s2 流水级请求冲刷的预测块在 FTQ 中的位置,包含 flag 和 value 两个量。| + +#### itlb:请求和响应 itlb 的信息 + +在 s0 流水级,发送 itlb_req;在 s1 流水级,如果 itlb 命中则直接接收 itlb_resp,否则重发 itlb_req。 + +req 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|valid|指示 req 请求是否有效。| +|Tlbreq|有多个子结构,这里我们只用上了 vaddr,即 req 请求的虚拟地址| + +resp 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|paddr|指令物理地址。| +|gpaddr|客户页地址。| +|pbmt|基于页面的内存类型。| +|miss|指示 itlb 是否未命中。| +|isForVSnonLeafPTE|指示是否为非叶 PTE。| +|excp|ITLB 可能产生的异常,包括:访问异常指令 af_instr、客户页错误指令 gpf_instr、页错误指令 pf_instr。见[异常传递/特殊情况处理](#异常传递特殊情况处理)| + +#### itlbFlushPipe:itlb 刷新信号 + +在 itlb 中,如果出现 gpf 的取指请求处于推测路径上,且发现出现错误的推测,则会通过 flushPipe 信号进行刷新(包括后端 redirect、或前端多级分支预测器出现后级预测器的预测结果更新前级预测器的预测结果等)。 +当 iprefetchpipe 的 s1 被刷新时,itlb 也应该被刷新,该信号会在 s1 流水被置位。 + +#### pmp: 物理内存保护相关的信息 + +在 s1 流水级做 pmp 检查。 +pmp 包含 req 和 resp 两个子结构。 + +req 的结构如下(编译后): +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|addr|pmp 检查的地址。| + +resp 的结构如下(编译后): +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|instr|指示物理地址是否有权限访问,没有则会引起 pmp 的 af 异常。| +|mmio|地址在 mmio 空间。| + +#### metaRead: 和 MetaArray 交互的读请求和读响应 + +在 s1 流水级读 meta。 + +metaRead 包含 toIMeta 和 fromIMeta 两个子结构,即读请求和读响应。 + +toIMeta 的结构如下(编译后): +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|vSetIdx|虚拟地址的缓存组索引(Virtual Set Index)。| +|isDoubleLine|预测块是否跨缓存行。| + +fromIMeta 的结构如下(编译后): +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|metas|MetaArray 的本身,包含 tag 量。tag,即 cache 的标签。| +|codes|ptag 的 ecc 校验码。| +|entryValid|指示 meta 是否有效。| + +#### MSHRReq: MSHR 请求 + +预取逻辑检测到未命中时,在 s2 流水级,向 MissUnit 发送请求。 + +| 接口名 | 解释 | +| -------- | -------------------------------------- | +| blkPaddr | 要从 tilelink 获取的缓存行的物理地址。 | +| vSetIdx | 虚拟地址的缓存组索引。 | + +#### MSHRResp: MSHR 响应 + +用于在 s1 流水级更新 waymasks 和 meta_codes 以及 s2 流水级判断返回的响应是否命中。 + +| 接口名 | 解释 | +| -------- | --------------------------------------------------------------------------------- | +| blkPaddr | 已从 tilelink 获取的缓存行的物理地址。 | +| vSetIdx | 虚拟地址的缓存组索引。 | +| waymask | 标识由 MSHR 处理的缺失(miss)请求完成后,返回的数据块应该写入到哪个路(way)中。 | +| corrupt | 返回的数据块是否损坏。 | + +#### wayLookupWrite: 向 waylookup 写数据 + +在 s1 流水级,向 waylookup 写数据。 +包含 entry(WayLookupEntry)和 gpf(WayLookupGPFEntry)两个子结构。 + +entry 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|vSetIdx|虚拟地址的缓存组索引。| +|waymask|来自 MSHR 的 waymask。| +|ptag|物理地址标签。| +|itlb_exception|指示 itlb 是否产生了异常 pf/gpf/af| +|itlb_pbmt|指示 itlb 是否产生 pbmt。| +|meta_codes|meta 的 ecc 校验码。| + +gpf 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|gpaddr|客户页地址。| +|isForVSnonLeafPTE|指示是否为非叶 PTE。| + +### MainPipe 模块接口 + +#### 不需要关注的接口 + +`hartId`硬件线程 ID,difftest 使用,不需要关注。 + +`perfInfo`性能信息,不需要关注。 + +#### dataArray:和 DataArray 交互的读请求和读响应 + +在 s0 流水级读请求。 + +dataArray 包含 toData 和 fromData 两个子结构,即读请求和读响应。 + +toData 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|vSetIdx|虚拟地址的缓存组索引。| +|waymask|标识由 MSHR 处理的缺失(miss)请求完成后,返回的数据块应该写入到哪个路(way)中。通过 MissUnit 写给 prefetch,prefetch 写入 waylookup,mainpipe 从 waylookup 中读出。| +|blkOffset|指令在块中的偏移。| +|isDoubleLine|预测块是否跨缓存行。| + +fromData 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|datas|DataArray 本身的数据。| +|codes|data 的 ecc 校验码。| + +#### metaArrayFlush: 刷新 metaArray + +在 s2 流水级,向 metaArray 发送刷新请求, 为重新取指做准备。 + +| 接口名 | 解释 | +| ------- | ------------------------ | +| virIdx | 需要刷新的虚拟地址索引。 | +| waymask | 需要刷新的路。 | + +#### touch: 更新 replacer + +在 s1 流水级,更新 replacer,向 replacer 发送 touch 请求。 +把一次访问告诉 replacer,让它更新 plru 状态。 +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|vSetIdx|被访问的缓存行的虚拟组索引。| +|way|被访问的缓存行在集合中的路。| + +#### wayLookupRead: 读取预取流水写入 waylookup 的信息 + +在 s0 流水级,从 waylookup 获取元信息。 +包含 entry(WayLookupEntry)和 gpf(WayLookupGPFEntry)两个子结构。 + +entry 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|waymask|来自 MSHR 的 waymask。| +|ptag|物理地址标签。| +|itlb_exception|指示 itlb 是否产生了异常 pf/gpf/af| +|itlb_pbmt|指示 itlb 是否产生 pbmt。| +|meta_codes|meta 的 ecc 校验码。| + +gpf 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|gpaddr|客户页地址。| +|isForVSnonLeafPTE|指示是否为非叶 PTE。| + +#### mshr: 对 MissUnit 中的 mshr 的请求和响应 + +在 s1 流水级,监听 MSHR 的响应。 +在 s2 流水级,缺失时将请求发送至 MissUnit,同时对 MSHR 的响应进行监听,命中时寄存 MSHR 响应的数据。 + +包含 req 和 resp 两个子结构。 + +req 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|blkPaddr|要从 tilelink 获取的缓存行的物理地址。| +|vSetIdx|虚拟地址的缓存组索引。| +resp 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|blkPaddr|已从 tilelink 获取的缓存行的物理地址。| +|vSetIdx|虚拟地址的缓存组索引。| +|waymask|标识由 MSHR 处理的缺失(miss)请求完成后,返回的数据块应该写入到哪个路(way)中。| +|data|返回的数据块。| +|corrupt|返回的数据块是否损坏。| + +#### fetch: 与 FTQ 交互和 IFU 交互接口 + +包含 req 和 resp 两个子结构。 + +##### req: FTQ 取指请求 + +在 s0 流水级,接收 FTQ 的取指请求。 +包含 pcMemRead,readValid 和 backendException 三个子结构。 + +其中 pcMemRead 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|startAddr|预测块起始地址。| +|nextlineStart|预测块下一个缓存行的起始地址。| + +readValid:读取请求的有效性。 + +backendException:是否有来自后端的 Exception。 + +##### resp: IFU 取指响应 + +在 s2 流水级,向 IFU 发送取指响应。 + +| 接口名 | 解释 | +| ------ | ---- | + +|doubleLine| 指示预测块是否跨缓存行。| +|vaddr |指令块起始虚拟地址、下一个缓存行的虚拟地址。| +|data |要传送的缓存行。| +|paddr |指令块的起始物理地址| +|exception| 向 IFU 报告每个缓存行上的异常情况,方便 ICache 生成每个指令的异常向量。| +|pmp_mmio| 指示当前指令块是否在 MMIO 空间。| +|itlb_pbmt |ITLB 基于客户页的内存类型,对 MMIO 状态有用。| +|backendException| 后端是否存在异常。| +|gpaddr| 客户页地址。| +|isForVSnonLeafPTE| 是否为非叶的 PTE,来自 itlb。| + +#### flush:全局刷新信号 + +来自 FTQ。 + +#### pmp: 物理内存保护相关的信息 + +在 s1 流水级做 pmp 检查。 +pmp 包含 req 和 resp 两个子结构。 + +req 的结构如下(编译后): +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|addr|需要检查的地址| + +resp 的结构如下(编译后): +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|instr|指示物理地址是否有权限访问,没有则会引起 pmp 的 af 异常。| +|mmio|地址在 mmio 空间。| + +#### respStall + +IFU 的 f3_ready 为低时会被置位,表示 IFU 没有准备好接收数据,此时需要 stall。 + +#### errors: 向 BEU 报告指令缓存中检测到的错误 + +在 s2 流水级,综合 data 的 ECC 校验加上从 s1 传来的 meta 的 ECC 校验结果,决定是否向 BEU 报告错误。 + +编译后: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|valid|指示 errors 是否有效。| +|bits|有两个量。paddr 表示错误的物理地址,report_to_beu 表示是否向 beu 报告错误| + +#### perfInfo: 性能相关信息,不关注 + +### WayLookup 模块接口 + +#### flush:全局刷新信号 + +来自 FTQ。 + +#### read:Mainpipe 的读接口 + +包含 entry(WayLookupEntry)和 gpf(WayLookupGPFEntry)两个子结构。 + +entry 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|waymask|来自 MSHR 的 waymask。| +|ptag|物理地址标签。| +|itlb_exception|指示 itlb 是否产生了异常 pf/gpf/af| +|itlb_pbmt|指示 itlb 是否产生 pbmt。| +|meta_codes|meta 的 ecc 校验码。| + +gpf 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|gpaddr|客户页地址。| +|isForVSnonLeafPTE|指示是否为非叶 PTE。| + +#### write:IprefetchPipe 的写接口 + +包含 entry(WayLookupEntry)和 gpf(WayLookupGPFEntry)两个子结构。 + +entry 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|vSetIdx|虚拟地址的缓存组索引。| +|waymask|来自 MSHR 的 waymask。| +|ptag|物理地址标签。| +|itlb_exception|指示 itlb 是否产生了异常 pf/gpf/af| +|itlb_pbmt|指示 itlb 是否产生 pbmt。| +|meta_codes|meta 的 ecc 校验码。| + +gpf 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|gpaddr|客户页地址。| +|isForVSnonLeafPTE|指示是否为非叶 PTE。| + +#### update:MissUnit 的更新接口 + +在 IPrefetchPipe 中等待入队 WayLookup 阶段和在 WayLookup 中等待出队阶段,可能会发生 MSHR 对 Meta/DataArray 的更新。也就是命中状态可能在出入队 WayLookup 时不同。 + +| 接口名 | 解释 | +| -------- | --------------------------------------------------------------------------------- | +| blkPaddr | 已从 tilelink 获取的缓存行的物理地址。 | +| vSetIdx | 虚拟地址的缓存组索引。 | +| waymask | 标识由 MSHR 处理的缺失(miss)请求完成后,返回的数据块应该写入到哪个路(way)中。 | +| corrupt | 返回的数据块是否损坏。 | + +### MissUnit 模块接口 + +#### fencei: 软件刷新信号 + +#### flush:全局刷新信号 + +来自 FTQ。 + +#### fetch_req:MainPipe 的取指请求缺失时的请求 + +| 接口名 | 解释 | +| -------- | -------------------------------------- | +| blkPaddr | 要从 tilelink 获取的缓存行的物理地址。 | +| vSetIdx | 虚拟地址的缓存组索引。 | + +#### fetch_respf: MainPipe 的取指响应缺失时的响应 + +| 接口名 | 解释 | +| -------- | --------------------------------------------------------------------------------- | +| blkPaddr | 已从 tilelink 获取的缓存行的物理地址。 | +| vSetIdx | 虚拟地址的缓存组索引。 | +| waymask | 标识由 MSHR 处理的缺失(miss)请求完成后,返回的数据块应该写入到哪个路(way)中。 | +| data | 返回的数据块。 | +| corrupt | 返回的数据块是否损坏。 | + +#### prefetch_req: IPrefetchPipe 的预取请求缺失时的请求 + +| 接口名 | 解释 | +| -------- | -------------------------------------- | +| blkPaddr | 要从 tilelink 获取的缓存行的物理地址。 | +| vSetIdx | 虚拟地址的缓存组索引。 | + +#### meta_write: MetaArray 的写请求接口 + +| 接口名 | 解释 | +| ------- | -------------------------- | +| virIdx | 需要写入的虚拟地址索引。 | +| phyTag | 需要写入的物理地址标签。 | +| waymask | 指示写入哪一路。 | +| bankIdx | 指示写入哪一个存储体索引。 | + +#### data_write: DataArray 的写请求接口 + +| 接口名 | 解释 | +| ------- | ------------------------ | +| virIdx | 需要写入的虚拟地址索引。 | +| data | 需要写入的数据块。 | +| waymask | 需要写入的路。 | + +#### victim:与缓存的替换器(replacer)交互,获取需要被替换的缓存路(way)的信息 + +| 接口名 | 解释 | +| ------- | ---------------------- | +| vSetIdx | 虚拟地址的缓存组索引。 | +| way | 被替换的路。 | + +#### mem_acquire:Tilelink a 通道发送请求 + +L2 的总线空闲时,发送请求。 + +| 接口名 | 解释 | +| ------- | ---------------------------- | +| source | 标识发起此请求的源。 | +| address | 要访问的内存的起始物理地址。 | + +#### mem_grant:Tilelink d 通道返回数据 + +| 接口名 | 解释 | +| ------- | -------------------------------------------------------------------------------------------------- | +| opcode | 标识响应消息类型的关键字段,它指示了响应的性质和意图。针对 acquire 请求的响应是 GrantData (5,授予) | +| source | 请求的源标识。 | +| data | 返回的数据块。 | +| corrupt | 返回的数据块是否损坏。 | + +### FIFO 模块接口 + +#### enq: 入队信号 + +| 接口名 | 解释 | +| ------ | ------------------- | +| valid | 指示 enq 是否有效。 | +| bits | 要入队的数据。 | + +#### deq: 出队信号 + +| 接口名 | 解释 | +| ------ | ------------------- | +| ready | 指示 deq 是否就绪。 | +| bits | 要出队的数据。 | + +### Replacer 模块接口 + +#### touch: 更新 replacer + +| 接口名 | 解释 | +| ------- | ---------------------------- | +| vSetIdx | 被访问的缓存行的虚拟组索引。 | +| way | 被访问的缓存行在集合中的路。 | + +#### victim: 与缓存的替换器(replacer)交互,获取需要被替换的缓存路(way)的信息 + +| 接口名 | 解释 | +| ------- | ---------------------- | +| vSetIdx | 虚拟地址的缓存组索引。 | +| way | 被替换的路。 | + +### MetaArray 模块接口 + +#### write: MetaArray 的写请求接口 + +写请求来自 MissUnit 或者 CtrlUnit。 + +| 接口名 | 解释 | +| ------- | -------------------------- | +| virIdx | 需要写入的虚拟地址索引。 | +| phyTag | 需要写入的物理地址标签。 | +| waymask | 指示写入哪一路。 | +| bankIdx | 指示写入哪一个存储体索引。 | +| poison | 指示是否为毒化位。 | + +#### read: MetaArray 的读请求接口 + +| 接口名 | 解释 | +| ------------ | ------------------------------------------- | +| vSetIdx | 虚拟地址的缓存组索引(Virtual Set Index)。 | +| isDoubleLine | 预测块是否跨缓存行。 | + +#### readResp: MetaArray 的读响应接口 + +fromIMeta 的结构如下(编译后): +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|metas|MetaArray 的本身,包含 tag 量。tag,即 cache 的标签。| +|codes|ptag 的 ecc 校验码。| +|entryValid|指示 meta 是否有效。| + +#### flush:双端刷新信号 + +来自 MainPipe 的刷新信号。可以只刷新指定的某一个端口,也可以都刷新。 + +| 接口名 | 解释 | +| ------- | ------------------------ | +| virIdx | 需要刷新的虚拟地址索引。 | +| waymask | 需要刷新的路。 | + +#### flushAll:刷新所有 MetaArray + +来自软件刷新信号 fencei。 + +### DataArray 模块接口 + +#### write:DataArray 的写请求接口 + +来自 MissUnit 或者 CtrlUnit 的写请求。 + +| 接口名 | 解释 | +| ------- | ------------------------ | +| virIdx | 需要写入的虚拟地址索引。 | +| data | 需要写入的数据块。 | +| waymask | 需要写入的路。 | +| poison | 指示是否为毒化位。 | + +#### read:DataArray 的读请求接口 + +来自 MainPipe 的读请求。 + +| 接口名 | 解释 | +| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| vSetIdx | 虚拟地址的缓存组索引。 | +| waymask | 标识由 MSHR 处理的缺失(miss)请求完成后,返回的数据块应该写入到哪个路(way)中。通过 MissUnit 写给 prefetch,prefetch 写入 waylookup,mainpipe 从 waylookup 中读出。 | +| blkOffset | 指令在块中的偏移。 | + +#### readResp:DataArray 的读响应接口 + +| 接口名 | 解释 | +| ------ | ---------------------- | +| datas | DataArray 本身的数据。 | +| codes | data 的 ecc 校验码。 | + +### CtrlUnit 模块接口 + +#### auto_in: Tilelink 相关接口 + +CtrlUnit 和 Tilelink 交互,分为 a 通道和 d 通道。 + +a 通道: +| 接口名 | 解释 | +| ------- | ---------------------------- | +|opcode|标识携带消息类型。| +|size|传输的数据大小对数,表示操作的字节数为$2^n$。| +|source| 标识发起此请求的源,主设备源标识符。 | +|address|传输的目标字节地址。| +|mask|要读的字节通道。| +|data|忽略。| + +d 通道: +| 接口名 | 解释 | +| ------- | ---------------------------- | +|opcode|标识携带消息类型。| +|size|传输的数据大小对数,表示操作的字节数为$2^n$。| +|source| 标识发起此请求的源,主设备源标识符。 | +|data|数据载荷。| + +#### ecc_enable: ecc 控制信号 + +指示 ecc 是否开启。 + +#### injecting: ecc 注入信号 + +指示 eccctrl 的 istatus 域是否处于 working 状态,即收到注入请求,注入中 + +#### metaRead: 对 MetaArray 的读请求 + +在对应读状态机 is_readMetaReq 中,对 MetaArray 发起读请求。 +| 接口名 | 解释 | +| ------------ | ------------------------------------------- | +| vSetIdx | 要读取的拟地址的缓存组索引。 | + +#### metaReadResp: 对 MetaArray 的读响应 + +在状态机 is_readMetaResp 中,接收 MetaArray 的读响应。 + +| 接口名 | 解释 | +| ---------- | ----------------------------------------------------- | +| metas | MetaArray 的本身,包含 tag 量。tag,即 cache 的标签。 | +| entryValid | 指示 meta 是否有效。 | + +#### metaWrite: 对 MetaArray 的写 + +在状态机 is_writeMeta 中,对 MetaArray 发起写。 + +| 接口名 | 解释 | +| ------- | -------------------------- | +| virIdx | 需要写入的虚拟地址索引。 | +| phyTag | 需要写入的物理地址标签。 | +| waymask | 指示写入哪一路。 | +| bankIdx | 指示写入哪一个存储体索引。 | + +#### dataWrite: 对 DataArray 的写请求 + +在状态机 is_writeData 中,对 DataArray 发起写请求。 + +| 接口名 | 解释 | +| ------- | ------------------------ | +| virIdx | 需要写入的虚拟地址索引。 | +| waymask | 需要写入的路。 | + +### ICache 顶层模块接口 + +在 scala 代码中,顶层模块除了包含对外的接口,实际上还包括了 MetaArray、DataArray 和 Replacer。在编译成 verilog 后,这三个模块会被编译成三个独立的模块,然后再通过顶层模块的接口连接起来。 + +#### 不需要关注的接口 + +`hartId`硬件线程 ID,difftest 使用,不需要关注。 +`perfInfo`性能信息,不需要关注。 + +#### auto_ctrlUnitOpt_in:CtrlUnit 和 Tilelink 交互的接口 + +CtrlUnit 和 Tilelink 交互,分为 a 通道和 d 通道。 + +a 通道: +| 接口名 | 解释 | +| ------- | ---------------------------- | +|opcode|标识携带消息类型。| +|size|传输的数据大小对数,表示操作的字节数为$2^n$。| +|source| 标识发起此请求的源,主设备源标识符。 | +|address|传输的目标字节地址。| +|mask|要读的字节通道。| +|data|忽略。| + +d 通道: +| 接口名 | 解释 | +| ------- | ---------------------------- | +|opcode|标识携带消息类型。| +|size|传输的数据大小对数,表示操作的字节数为$2^n$。| +|source| 标识发起此请求的源,主设备源标识符。 | +|data|数据载荷。| + +#### auto_client_out: MissUnit 和 Tilelink 交互的接口 + +MissUnit 和 Tilelink 交互,分为 a 通道和 d 通道。 +a 通道: +| 接口名 | 解释 | +| ------- | ---------------------------- | +| source | 标识发起此请求的源。 | +| address | 要访问的内存的起始物理地址。 | + +d 通道: + +| 接口名 | 解释 | +| ------- | -------------------------------------------------------------------------------------------------- | +| opcode | 标识响应消息类型的关键字段,它指示了响应的性质和意图。针对 acquire 请求的响应是 GrantData (5,授予) | +| source | 请求的源标识。 | +| data | 返回的数据块。 | +| corrupt | 返回的数据块是否损坏。 | + +#### fetch: 与 FTQ 交互和 IFU 交互接口 + +包含 req 和 resp 两个子结构。 + +##### req: FTQ 取指请求 + +在 s0 流水级,接收 FTQ 的取指请求。 +包含 pcMemRead,readValid 和 backendException 三个子结构。 + +其中 pcMemRead 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|startAddr|预测块起始地址。| +|nextlineStart|预测块下一个缓存行的起始地址。| + +readValid:读取请求的有效性。 + +backendException:是否有来自后端的 Exception。 + +##### resp: IFU 取指响应 + +在 s2 流水级,向 IFU 发送取指响应。 + +| 接口名 | 解释 | +| ----------------- | ----------------------------------------------------------------------- | +| doubleLine | 指示预测块是否跨缓存行。 | +| vaddr | 指令块起始虚拟地址、下一个缓存行的虚拟地址。 | +| data | 要传送的缓存行。 | +| paddr | 指令块的起始物理地址 | +| exception | 向 IFU 报告每个缓存行上的异常情况,方便 ICache 生成每个指令的异常向量。 | +| pmp_mmio | 指示当前指令块是否在 MMIO 空间。 | +| itlb_pbmt | ITLB 基于客户页的内存类型,对 MMIO 状态有用。 | +| backendException | 后端是否存在异常。 | +| gpaddr | 客户页地址。 | +| isForVSnonLeafPTE | 是否为非叶的 PTE,来自 itlb。 | + +#### ftqPrefetch:FTQ 预取相关信息 + +包含三个子结构: +req: 来自 FTQ 的预取请求 +flushFromBpu:来自 BPU 的刷新信息 +bakckendException:来自后端的异常信息 + +##### req: 来自 FTQ 的预取请求 + +由于 BPU 基本无阻塞,它经常能走到 IFU 的前面,于是 BPU 提供的这些还没发到 IFU 的取指请求就可以用作指令预取,FTQ 中实现了这部分逻辑,直接给 ICache 发送预取请求。 +预取请求来自 FTQ,在 MainPipe 的 S0 流水级传入。 +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|ready||指示 s0 能否继续| +|valid|指示软件预取或者硬件预取是否有效。| +|startAddr | 预测块起始地址。 | +|nextlineStart | 预测块下一个缓存行的起始地址。 | +|ftqIdx| 指示当前预测块在 FTQ 中的位置,包含 flag 和 value 两个量。| + +##### flushFromBpu:来自 BPU 的刷新信息 + +由 FTQ 传递而来的 BPU 刷新信息,在 MainPipe 的 S0 流水级传入。 +这是预测错误引起的,包括 s2 和 s3 两个同构成员,指示是否在 BPU 的 s2 和 s3 流水级发现了问题。 +s2 的详细结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|valid|指示 s2 是否有效。| +|ftqIdx|指示 s2 流水级请求冲刷的预测块在 FTQ 中的位置,包含 flag 和 value 两个量。| + +##### backendException: 后端异常信息 + +ICache 向 IFU 报告后端存在的异常类型 + +#### softPrefetch: 来自 Memblock 的软件预取信息 + +| 接口名 | 解释 | +| ------ | -------------------- | +| vaddr | 软件预取的虚拟地址。 | + +#### stop: IFU 发送到 ICache 的停止信号 + +IFU 在 F3 流水级之前出现了问题,通知 ICache 停下。 + +#### ToIFU: 发送给 I FU 的就绪信号 + +由 MainPipe 的 s0 流水级 s0_can_go 生成。该信号用于提醒 IFU,Icache 的流水可用,可以发送换存行了。 + +#### pmp: MainPipe 和 PrefetchPipe 的 pmp 信息 + +0,1 通道为 MainPipe 的 pmp 信息,2,3 通道为 PrefetchPipe 的 pmp 信息。 +pmp 包含 req 和 resp 两个子结构。 + +req 的结构如下(编译后): +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|addr|需要检查的地址| + +resp 的结构如下(编译后): +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|instr|指示物理地址是否有权限访问,没有则会引起 pmp 的 af 异常。| +|mmio|地址在 mmio 空间。| + +#### itlb:PrefetchPipe 的 itlb 信息 + +在 PrefetchPipe 的 s0 流水级,发送 itlb_req;在 PrefetchPipe 的 s1 流水级,如果 itlb 命中则直接接收 itlb_resp,否则重发 itlb_req。 + +itlb 包含 req 和 resp 两个子结构。 + +req 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|valid|指示 req 请求是否有效。| +|Tlbreq|有多个子结构,这里我们只用上了 vaddr,即 req 请求的虚拟地址| + +resp 的结构如下: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|paddr|指令物理地址。| +|gpaddr|客户页地址。| +|pbmt|基于页面的内存类型。| +|miss|指示 itlb 是否未命中。| +|isForVSnonLeafPTE|指示是否为非叶 PTE。| +|excp|ITLB 可能产生的异常,包括:访问异常指令 af_instr、客户页错误指令 gpf_instr、页错误指令 pf_instr。见[异常传递/特殊情况处理](#异常传递特殊情况处理)| + +#### itlbFlushPipe: itlb 刷新信号 + +在 itlb 中,如果出现 gpf 的取指请求处于推测路径上,且发现出现错误的推测,则会通过 flushPipe 信号进行刷新(包括后端 redirect、或前端多级分支预测器出现后级预测器的预测结果更新前级预测器的预测结果等)。 +当 iprefetchpipe 的 s1 被刷新时,itlb 也应该被刷新,该信号会在 iprefetchpipe 的 s1 流水被置位。 + +#### error:向 BEU 报告指令缓存中检测到的错误 + +将 MainPipe 中收集到的 errors 多个错误信息,使用优先级选择器选择索引最小且有效的错误信息,然后通过 error 信号发送给 BEU。接口结构和 MainPipe 中相同,区别在于 MainPipe 中有两个端口,所以有两个 errors,而这里要发送的只有一个。 + +编译后: +| 接口名 | 解释 | +| ---------- | ------------------------------------------------------ | +|valid|指示 errors 是否有效。| +|bits|有两个量。paddr 表示错误的物理地址,report_to_beu 表示是否向 beu 报告错误| + +#### csr_pf_enable + +控制 s1_real_fire,软件控制预取开关 + +#### fencei: 软件刷新信号 + +#### flush: 全局刷新信号 + +在 FTQ 中,有后端重新定向或者 IFU 重定向时,会将其 icacheFlush 信号拉高,触发 icache 的刷新。 + +## **测试点汇总 ** + +再次声明,本测试点仅供参考,如果有其他测试点需要补充可以告知我们。 + +建议覆盖点采用`功能名称`\_`测试点名称`命名。 + + + +| 序号 | 功能名称 | 测试点名称 | 描述 | +| ------ | ---------------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 1.1.1 | [ICACHE_PREFETCH_HARDPREFETCH](#11-硬件预取请求) | RECEIVE | 当预取请求有效且 IPrefetchPipe 处于空闲状态时,预取请求应该被接收。
s0_fire 信号在没有 s0 的刷新信号( s0_flush 为低)时,应该被置为高。 | +| 1.1.2 | ICACHE_PREFETCH_HARDPREFETCH | INVALID_PREFETCH | 当预取请求无效时,预取请求应该被拒绝。
s0_fire 信号应该被置为低。 | +| 1.1.3 | ICACHE_PREFETCH_HARDPREFETCH | PREFETCHPIPE_BUSY | 当 IPrefetchPipe 非空闲时,预取请求应该被拒绝。
s0_fire 信号应该被置为低。 | +| 1.1.4 | ICACHE_PREFETCH_HARDPREFETCH | INVALID_PREFETCH_AND_PREFETCHPIPE_BUSY | 当预取请求无效且 IPrefetchPipe 非空闲时,预取请求应该被拒绝。
s0_fire 信号应该被置为低。 | +| 1.1.5 | ICACHE_PREFETCH_HARDPREFETCH | SINGLE_CACHELINE | 当预取请求有效且为单 cacheline 时,预取请求应该被接收。
s0_fire 为高,s0_doubleline 应该被置低(false)。 | +| 1.1.6 | ICACHE_PREFETCH_HARDPREFETCH | DOUBLE_CACHELINE | 当预取请求有效且为双 cacheline 时,预取请求应该被接收。
s0_fire 为高,s0_doubleline 应该被置高(true)。 | | +| 1.2.1 | [ICACHE_PREFETCH_SOFTPREFETCH](#12-软件预取请求) | RECEIVE | 当预取请求有效且 IPrefetchPipe 处于空闲状态时,软件预取请求应该被接收。
当预取请求有效且 IPrefetchPipe 处于空闲状态时,软件预取请求应该被接收。 | +| 1.2.2 | ICACHE_PREFETCH_SOFTPREFETCH | INVALID_PREFETCH | 当预取请求无效时,软件预取请求应该被拒绝。
s0_fire 信号应该被置为低。 | +| 1.2.3 | ICACHE_PREFETCH_SOFTPREFETCH | PREFETCHPIPE_BUSY | 当 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。
s0_fire 信号应该被置为低。 | +| 1.2.4 | ICACHE_PREFETCH_SOFTPREFETCH | INVALID_PREFETCH_AND_PREFETCHPIPE_BUSY | 当预取请求无效且 IPrefetchPipe 非空闲时,软件预取请求应该被拒绝。
s0_fire 信号应该被置为低。 | +| 1.2.5 | ICACHE_PREFETCH_SOFTPREFETCH | SINGLE_CACHELINE | 当软件预取请求有效且为单 cacheline 时,软件预取请求应该被接收。
s0_fire 为高,s0_doubleline 应该被置低(false)。 | +| 1.2.6 | ICACHE_PREFETCH_SOFTPREFETCH | DOUBLE_CACHELINE | 当软件预取请求有效且为双 cacheline 时,软件预取请求应该被接收。
s0_fire 为高,s0_doubleline 应该被置高(true)。 | +| 2.1.1 | [ICACHE_PREFETCH_ITLB_ADDR](#21-地址转换完成) | RETURN_PADDR | ITLB 在一个周期内成功返回物理地址 paddr,s1_valid 为高。
确认 s1 阶段正确接收到 paddr。 | +| 2.1.2 | ICACHE_PREFETCH_ITLB_ADDR | TLB_MISS | fromITLB(PortNumber).bits.miss 为高,表示对应通道的 ITLB 发生了 TLB 缺失,需要重发。
重发完成后,后续步骤继续进行,fromITLB(PortNumber).bits.miss 为低。 | +| 2.2.1 | [ICACHE_PREFETCH_ITLB_EXCEPTION](#22-处理-itlb-异常) | PF | s1_itlb_exception 返回的页错误。
iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示页错误 pf。 | +| 2.2.2 | ICACHE_PREFETCH_ITLB_EXCEPTION | PGF | s1_itlb_exception 返回的虚拟机页错误。
iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示虚拟机页错误 pgf。 | +| 2.2.3 | ICACHE_PREFETCH_ITLB_EXCEPTION | AF | s1_itlb_exception 返回的访问错误。
iTLB 返回的物理地址有效(fromITLB(PortNumber).bits.miss 为低),s1_itlb_exception 指示访问错误 af。 | +| 2.3.1 | [ICACHE_PREFETCH_ITLB_GPADDR](#23-处理虚拟机物理地址用于虚拟化) | PGF | 发生 pgf 后,需要返回对应的 gpaddr。
只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。 | +| 2.3.2 | ICACHE_PREFETCH_ITLB_GPADDR | VS_NONLEAF_PTE | 发生 gpf 后,如果是访问二级虚拟机的非叶子页表项时,需要返回对应的 gpaddr。
只有一个通道发生 pgf 时,返回对应通道的 gpaddr 即可;多个通道发生 pgf 时,返回第一个通道的 gpaddr。 | +| 2.4 | [ICACHE_PREFETCH_ITLB_PBMT](#24-返回基于页面的内存类型-pbmt-信息) | PBMT | TLB 有效时,返回 pbmt 信息。 | +| 3.1 | [ICACHE_PREFETCH_IMETA](#3-接收来自-imeta缓存元数据的响应并检查缓存命中) | PTAG_AND_VALID | 从物理地址中提取物理标签(ptag),将其与缓存元数据中的标签进行比较,检查所有缓存路(Way)。检查有效位,确保只考虑有效的缓存行。 | +| 3.1 | ICACHE_PREFETCH_IMETA | MISS | 当标签不匹配或者标签匹配,但是有效位为假时,表示缓存未命中。
s1_meta_ptags(PortNumber)(nWays) 不等于 ptags(PortNumber) 或者它们相等,但是对应的 s1_meta_valids 为低时,总之返回的 waymasks 为全 0。 | +| 3.2 | ICACHE_PREFETCH_IMETA | SINGLE_HIT | 当标签匹配,且有效位为真时,表示缓存命中。
waymasks 对应的位为 1。 | +| 4.1 | [ICACHE_PREFETCH_PMP](#4-pmp物理内存保护权限检查) | NONE_EXCEPTION | itlb 返回的物理地址在 PMP 允许的范围内。
s1_pmp_exception(i) 为 none。 | +| 4.2 | ICACHE_PREFETCH_PMP | AF_EXCEPTION | s1_req_paddr(i) 对应的地址在 PMP 禁止的范围内。
s1_pmp_exception(i) 为 af。 | +| 4.3 | ICACHE_PREFETCH_PMP | MMIO | itlb 返回的物理地址在 MMIO 区域。
s1_pmp_mmio 为高。 | +| 5.1 | [ICACHE_PREFETCH_EXCEPTION_MERGE](#5-异常处理和合并) | ONLY_ITLB_EXCEPTION | s1_itlb_exception(i) 为非零,s1_pmp_exception(i) 为零。
s1_exception_out(i) 正确包含 ITLB 异常。 | +| 5.2 | ICACHE_PREFETCH_EXCEPTION_MERGE | ONLY_PMP_EXCEPTION | s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为非零。
s1_exception_out(i) 正确包含 PMP 异常。 | +| 5.3 | ICACHE_PREFETCH_EXCEPTION_MERGE | ONLY_BACKEND_EXCEPTION | s1_itlb_exception(i) 为零,s1_pmp_exception(i) 为零。
s1_exception_out(i) 正确包含 后端 异常。 | +| 5.4 | ICACHE_PREFETCH_EXCEPTION_MERGE | ITLB_AND_PMP_EXCEPTION | s1_itlb_exception(i) 和 s1_pmp_exception(i) 都为非零。
s1_exception_out(i) 包含 ITLB 异常(优先级更高)。 | +| 5.5 | ICACHE_PREFETCH_EXCEPTION_MERGE | ITLB_AND_BACKEND_EXCEPTION | s1_itlb_exception(i) 和 s1_backendException(i) 都为非零。
s1_exception_out(i) 包含 后端 异常(优先级更高)。 | +| 5.6 | ICACHE_PREFETCH_EXCEPTION_MERGE | PMP_AND_BACKEND_EXCEPTION | s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。
s1_exception_out(i) 包含 后端 异常(优先级更高)。 | +| 5.7 | ICACHE_PREFETCH_EXCEPTION_MERGE | ALL_EXCEPTION | s1_itlb_exception(i)、s1_pmp_exception(i) 和 s1_backendException(i) 都为非零。
s1_exception_out(i) 包含 后端 异常(优先级更高)。 | +| 5.8 | ICACHE_PREFETCH_EXCEPTION_MERGE | NONE_EXCEPTION | s1_itlb_exception(i)、s1_pmp_exception(i)、s1_backendException(i) 都为零。
s1_exception_out(i) 指示无异常。 | +| 6.1 | [ICACHE_PREFETCH_WAYLOOKUP](#6-发送请求到-waylookup-模块) | NORMAL | toWayLookup.valid 为高,toWayLookup.ready 为高,s1_isSoftPrefetch 为假。
请求成功发送,包含正确的地址、标签、waymask 和异常信息。 | +| 6.2 | ICACHE_PREFETCH_WAYLOOKUP | WAYLOOKUP_REJECT | toWayLookup.valid 为高,toWayLookup.ready 为假。
状态机等待 WayLookup 准备好,不会错误地推进。 | +| 6.3 | ICACHE_PREFETCH_WAYLOOKUP | SOFTPREFETCH_NOT_GO_WAYLOOKUP | s1_isSoftPrefetch 为真。
toWayLookup.valid 为假,不会发送预取请求到 WayLookup。 | +| 7.1.1 | [ICACHE_PREFETCH_FSM_IDLE](#71-初始为-m_idle-状态) | NORMAL_IMPEL_KEEP_IDLE | s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为真,s2_ready 为真。
状态机保持在 m_idle 状态,s1 阶段顺利推进。 | +| 7.1.2 | ICACHE_PREFETCH_FSM_IDLE | ITLB_UNFINISH_RESEND | s1_valid 为高,itlb_finish 为假。
状态机进入 m_itlbResend 状态,等待 ITLB 完成。 | +| 7.1.3 | ICACHE_PREFETCH_FSM_IDLE | ITLB_FINISH_WAYLOOKUP_MISS | s1_valid 为高,itlb_finish 为真,toWayLookup.fire 为假。
状态机进入 m_enqWay 状态,等待 WayLookup 入队。 | +| 7.2.1 | ICACHE_PREFETCH_FSM_ITLBRESEND | WAIT_WAYLOOKUP_ENQ | itlb_finish 为假,toMeta.ready 为真。
状态机进入 m_enqWay 状态,等待 WayLookup 入队。 | +| 7.2.2 | ICACHE_PREFETCH_FSM_ITLBRESEND | WAIT_META_READ_REQ | itlb_finish 为假,toMeta.ready 为假。
状态机进入 m_metaResend 状态,MetaArray 读请求 | +| 7.3 | ICACHE_PREFETCH_FSM_METARESEND | WAIT_WAYLOOKUP_ENQ | toMeta.ready 为真。
状态机进入 m_enqWay 状态,等待 WayLookup 入队。 | +| 7.4.1 | ICACHE_PREFETCH_FSM_ENQWAY | ENTER_IDLE | toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为假。
状态机进入空闲状态 m_idle。 | +| 7.4.2 | ICACHE_PREFETCH_FSM_ENQWAY | WAIT_ENTERS2 | toWayLookup.fire 或 s1_isSoftPrefetch 为真,s2_ready 为真。
状态机进入 m_enterS2 状态,等待 s2 阶段准备好。 | +| 7.5 | ICACHE_PREFETCH_FSM_ENTERS2 | ENTER_IDLE | s2_ready 为真。
状态机进入空闲状态 m_idle。 | +| 8.1 | [ICACHE_PREFETCH_MISSUNIT_MONITER](#8-监控-missunit-的请求) | MATCH_AND_VALID | s2_req_vSetIdx 和 s2_req_ptags 与 fromMSHR 中的数据匹配,且 fromMSHR.valid 为高,fromMSHR.bits.corrupt 为假。
s2_MSHR_match(PortNumber) 为真, s2_MSHR_hits(PortNumber) 应保持为真 | +| 8.2 | ICACHE_PREFETCH_MISSUNIT_MONITER | SRAM_HIT | s2_waymasks(PortNumber) 中有一位为高,表示在缓存中命中。
s2_SRAM_hits(PortNumber) 为真,s2_hits(PortNumber) 应为真。 | +| 8.3 | ICACHE_PREFETCH_MISSUNIT_MONITER | MISS | 请求未匹配 MSHR,且 s2_waymasks(PortNumber) 为空。
s2_MSHR_hits(PortNumber)、s2_SRAM_hits(PortNumber) 均为假, s2_hits(PortNumber) 为假。 | +| 9.1.1 | [ICACHE_PREFETCH_MISSUNIT_REQ](#91-确定需要发送给-missunit-的请求) | SEND_TO_MISSUNIT | s2_hits(PortNumber) 为假(未命中缓存),s2_exception 无异常,s2_mmio 为假(不是 MMIO 或不可缓存的内存)。
s2_miss(PortNumber) 为真,表示需要发送请求到 missUnit。 | +| 9.1.2 | ICACHE_PREFETCH_MISSUNIT_REQ | NO_SEND_TO_MISSUNIT | s2_hits(i) 为真(已命中)或者 s2_exception 有异常 或者 s2_mmio 为真(MMIO 访问)。
s2_miss(i) 为假,不会发送请求到 missUnit。 | +| 9.1.3 | ICACHE_PREFETCH_MISSUNIT_REQ | DOUBLE_PREFETCH | s2_doubleline 为真,处理第二个请求。
如果第一个请求有异常或 MMIO,s2_miss(1) 应为假,后续请求被取消或处理。 | +| 9.2.1 | [ICACHE_PREFETCH_MISSUNIT_UNIQUE](#92-避免发送重复请求发送请求到-missunit) | RESET_HAS_SEND | s1_real_fire 为高。
has_send(PortNumber) 应被复位为假,表示新的请求周期开始。 | +| 9.2.2 | ICACHE_PREFETCH_MISSUNIT_UNIQUE | UPDATE_HAS_SEND | toMSHRArbiter.io.in(PortNumber).fire 为高(请求已发送)。
has_send(PortNumber) 被设置为真,表示该端口已发送请求。 | +| 9.2.3 | ICACHE_PREFETCH_MISSUNIT_UNIQUE | UNIQUE_REQ | 同一请求周期内,has_send(PortNumber) 为真,s2_miss(PortNumber) 为真。
toMSHRArbiter.io.in(PortNumber).valid 为假,不会再次发送请求。 | +| 9.2.4 | ICACHE_PREFETCH_MISSUNIT_UNIQUE | RIGHTLY_SEND | s2_valid 为高,s2_miss(i) 为真,has_send(i) 为假。
toMSHRArbiter.io.in(i).valid 为高,请求被成功发送。 | +| 9.2.5 | ICACHE_PREFETCH_MISSUNIT_UNIQUE | ARBITRATE_REQ | 多个端口同时需要发送请求。
仲裁器按照优先级或设计要求选择请求发送到 missUnit,未被选中的请求在下个周期继续尝试发送。 | +| 10.1 | [ICACHE_PREFETCH_FLUSH](#10-刷新机制) | GLOBAL_FLUSH | io.flush 为高。
s0_flush、s1_flush、s2_flush 分别为高,所有阶段的请求被正确清除。 | +| 10.2 | ICACHE_PREFETCH_FLUSH | FLUSH_FROM_BPU | io.flushFromBpu.shouldFlushByStageX 为真(X 为 2 或 3),且请求不是软件预取。
对应阶段的 from_bpu_sX_flush 为高,sX_flush 为高,阶段请求被刷新。 | +| 10.3 | ICACHE_PREFETCH_FLUSH | RESET_FSM | s1_flush 为高。
状态机 state 被重置为 m_idle 状态。 | +| 10.4 | ICACHE_PREFETCH_FLUSH | ITLB_FLUSH_PIPE | s1_flush 为高。
io.itlbFlushPipe 为高,ITLB 被同步刷新。 | +| 11.1 | [ICACHE_MAINPIPE_DATA_ARRAY](#11-访问-dataarray-的单路) | WAY_HIT_AND_ITLB_SUCCESS | 当 WayLookup 中的信息表明路命中时,ITLB 查询成功,并且 DataArray 当前没有写时,MainPipe 会向 DataArray 发送读取请求,以获取数据。
s0_hits 为高(一路命中),s0_itlb_exception 信号为零(ITLB 查询成功),toData.last.ready 为高(DataArray 没有正在进行的写操作)。
toData.valid 信号为高,表示 MainPipe 向 DataArray 发出了读取请求。 | +| 11.2 | ICACHE_MAINPIPE_DATA_ARRAY | WAY_MISS | 当 WayLookup 中的信息表明路未命中时,MainPipe 不会向 DataArray 发送读取请求。
s0_hits 为低表示缓存未命中
toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 | +| 11.3 | ICACHE_MAINPIPE_DATA_ARRAY | ITLB_FAIL | 当 ITLB 查询失败时,MainPipe 不会向 DataArray 发送读取请求。
s0_itlb_exception 信号不为零(ITLB 查询失败)。
toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 | +| 11.4 | ICACHE_MAINPIPE_DATA_ARRAY | DATA_ARRAY_WRITING | 当 DataArray 正在进行写操作时,MainPipe 不会向 DataArray 发送读取请求。
toData.last.ready 信号为低,表示 DataArray 正在进行写操作。
toData.valid 信号为低,表示 MainPipe 未向 DataArray 发出读取请求。 | +| 12.1 | [ICACHE_MAINPIPE_META_ECC](#12-meta-ecc-校验) | NO_ECC_ERROR | 当 waymask 全为 0(没有命中),则 hit_num 为 0 或 waymask 有一位为 1(一路命中),hit_num 为 1 且 ECC 对比通过(encodeMetaECC(meta) == code)
s1_meta_corrupt 为假。 | +| 12.2 | ICACHE_MAINPIPE_META_ECC | SINGLE_ECC_ERROR | 当 waymask 有一位为 1(一路命中),ECC 对比失败(encodeMetaECC(meta) != code)
s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 | +| 12.3 | ICACHE_MAINPIPE_META_ECC | MULTI_WAY_HIT | > hit multi-way, must be an ECC failure
当 waymask 有两位及以上为 1(多路命中),视为 ECC 错误。
s1_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 | +| 12.4 | ICACHE_MAINPIPE_META_ECC | ECC_CLOSE | 当奇偶校验关闭时(ecc_enable 为低),强制清除 s1_meta_corrupt 信号置位。
不管是否发生 ECC 错误,s1_meta_corrupt 都为假。 | +| 13.1 | [ICACHE_MAINPIPE_PMP](#13-pmp-检查) | NONE_EXCEPTION | s1_pmp_exception 为全零,表示没有 PMP 异常。 | +| 13.2 | ICACHE_MAINPIPE_PMP | CHANNEL_0_EXCEPTION | s1_pmp_exception(0) 为真,表示通道 0 有 PMP 异常。 | +| 13.3 | ICACHE_MAINPIPE_PMP | CHANNEL_1_EXCEPTION | s1_pmp_exception(1) 为真,表示通道 1 有 PMP 异常。 | +| 13.4 | ICACHE_MAINPIPE_PMP | CHANNEL_0_AND_1_EXCEPTION | s1_pmp_exception(0) 和 s1_pmp_exception(1) 都为真,表示通道 0 和通道 1 都有 PMP 异常。 | +| 13.5 | ICACHE_MAINPIPE_PMP | NONE_MMIO | s1_pmp_mmio(0) 和 s1_pmp_mergemmio(1) 都为假,表示没有映射到 MMIO 区域。 | +| 13.6 | ICACHE_MAINPIPE_PMP | CHANNEL_0_MMIO | s1_pmp_mmio(0) 为真,表示映射到了 MMIO 区域。 | +| 13.7 | ICACHE_MAINPIPE_PMP | CHANNEL_1_MMIO | s1_pmp_mmio(1) 为真,表示映射到了 MMIO 区域。 | +| 13.8 | ICACHE_MAINPIPE_PMP | CHANNEL_0_AND_1_MMIO | s1_pmp_mmio(0) 和 s1_pmp_mmio(1) 都为真,表示通道 0 和通道 1 都映射到了 MMIO 区域。 | +| 14.1 | [ICACHE_MAINPIPE_EXCEPTION_MERGE](#14-异常合并) | NONE_EXCEPTION | s1_exception_out 为全零,表示没有异常。 | +| 14.2 | ICACHE_MAINPIPE_EXCEPTION_MERGE | ITLB_EXCEPTION | s1_exception_out 和 s1_itlb_exception 一致 | +| 14.3 | ICACHE_MAINPIPE_EXCEPTION_MERGE | PMP_EXCEPTION | s1_exception_out 和 s1_pmp_exception 一致 | +| 14.4 | ICACHE_MAINPIPE_EXCEPTION_MERGE | ITLB_AND_PMP_EXCEPTION | > itlb has the highest priority, pmp next
s1_exception_out 和 s1_itlb_exception 一致 | +| 15.1 | [ICACHE_MAINPIPE_MSHR_MATCH](#15-mshr-匹配和数据选择) | MSHR_HIT | MSHR 中已有正确数据时,S1 阶段能直接拿到
s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 s1_bankMSHRHit(i),s1_data_is_from_MSHR(i) 为 true | +| 15.2 | ICACHE_MAINPIPE_MSHR_MATCH | MSHR_MISS | MSHR 中存放的地址与当前请求不同,那么应该读取 SRAM 的数据
s1_MSHR_hits(i) 为 true 时,s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false | +| 15.3 | ICACHE_MAINPIPE_MSHR_MATCH | MSHR_DATA_CORRUPT | fromMSHR.bits.corrupt = true,那么 MSHR 将不匹配,应该读取 SRAM 的数据
s1_datas(i) 为 fromData.datas(i),s1_data_is_from_MSHR(i) 为 false | +| 16.1 | [ICACHE_MAINPIPE_DATA_ECC](#16-data-ecc-校验) | NO_ECC_ERROR | s2_bank 全部没有损坏,bank 也选对了对应的端口和 bank,数据不来自 MSHR
s2_data_corrupt(i) 为 false,没有 ECC 错误。 | +| 16.2 | ICACHE_MAINPIPE_DATA_ECC | SINGLE_Bank_ECC_ERROR | s2_bank_corrupt(bank) 有一个为 true ,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR
s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 | +| 16.3 | ICACHE_MAINPIPE_DATA_ECC | MULTIPLE_Bank_ECC_ERROR | s2_bank_corrupt(bank) 有两个或以上为 true,即对应的 bank 有损坏;同时 bank 也选对了对应的端口和 bank,数据不来自 MSHR
s2_data_corrupt(i), io.errors(i).valid, io.errors(i).bits.report_to_beu, io.errors(i).bits.source.data 为 true。 | +| 16.4 | ICACHE_MAINPIPE_DATA_ECC | ECC_CLOSE | 当奇偶校验关闭时(ecc_enable 为低),强制清除 s2_data_corrupt 信号置位。
不管是否发生 ECC 错误,s2_data_corrupt 都为假。 | +| 17.1 | [ICACHE_MAINPIPE_META_FLUSH](#17-冲刷-metaarray) | FLUSH_META_ARRAY | > if is meta corrupt, clear all way (since waymask may be unreliable)
当 s1_meta_corrupt 为真时,MetaArray 的所有路都会被冲刷。
toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。 | +| 17.2 | ICACHE_MAINPIPE_META_FLUSH | FLUSH_DATA_ARRAY | > if is data corrupt, only clear the way that has error
当 s2_data_corrupt 为真时,只有对应路会被冲刷。
toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的对应路置位。 | +| 17.3 | ICACHE_MAINPIPE_META_FLUSH | FLUSH_META_ARRAY_AND_DATA_ARRAY | 处理 Meta ECC 的优先级更高, 将 MetaArray 的所有路冲刷。
toMetaFlush(i).valid 为真,toMetaFlush(i).bits.waymask 对应端口的所有路置位。 | +| 18.1 | [ICACHE_MAINPIPE_MSHR_MONITER](#18-监控-mshr-匹配与数据更新) | MSHR_HIT | MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效
s2_MSHR_match,s2_MSHR_hits 为高,s2_bankMSHRHit 对应 bank 为高
s1_fire 无效时,s2_datas 更新为 MSHR 的数据,将 s2_data_is_from_MSHR 对应位置位,s2_hits 置位,清除 s2_data_corrupt,l2 的 corrupt 更新为 fromMSHR.bits.corrupt
s1_fire 有效时,s2_datas 为 s1_datas 的数据,将 s2_data_is_from_MSHR 对应位置为 s1 的 s1_data_is_from_MSHR,s2_hits 置为 s1_hits,清除 s2_data_corrupt,l2 的 corrupt 为 false | +| 18.2 | ICACHE_MAINPIPE_MSHR_MONITER | MSHR_MISS | MSHR 的 vSetIdx / blkPaddr 与 S2 请求一致, fromMSHR.valid 有效,s2_valid 也有效,至少有一个未达成
s2_MSHR_hits(i) = false,S2 不会更新 s2_datas,继续保持原先 SRAM 数据或进入 Miss 流程。 | +| 19.1 | [ICACHE_MAINPIPE_MISS_REQ](#19-miss-请求发送逻辑和合并异常) | NONE_MISS | 当 s2_hits(i) 为高(s2 已经命中),s2 的 meta 和 data 都没有错误,s2 异常,处于 mmio 区域
以上条件至少满足一个时,s2_should_fetch(i) 为低,表示不发送 Miss 请求。 | +| 19.2 | ICACHE_MAINPIPE_MISS_REQ | SINGLE_MISS | 当出现未命中 (!s2_hits(i)) 或 ECC 错误(s2_meta_corrupt(i) | | s2_data_corrupt(i)),端口不存在异常且未处于 MMIO 区域时,会向 MSHR 发送 Miss 请求。
toMSHRArbiter.io.in(i).valid = true ,Arbiter 只发送一条 Miss 请求。 | +| 19.3 | ICACHE_MAINPIPE_MISS_REQ | DOUBLE_MISS | 同上,但是两个端口都满足 s2_should_fetch 为高的条件。
toMSHRArbiter.io.in(0).valid、toMSHRArbiter.io.in(1).valid 均为 true,Arbiter 根据仲裁顺序依次发出请求。 | +| 19.4 | ICACHE_MAINPIPE_MISS_REQ | BLOCK_REPETITION | 当 s1_fire 为高,表示可以进入 s2 阶段,那么 s2 还没有发送 s2_has_send(i) := false.B
如果已经有请求发送了,那么对应的 toMSHRArbiter.io.in(i).fire 为高,表示对应的请求可以发送,s2_has_send(i) := true。
此时再次发送,toMSHRArbiter.io.in(i).valid 为低,表示发送失败。 | +| 19.5 | ICACHE_MAINPIPE_MISS_REQ | ONLY_ITLB_OR_PMP_EXCEPTION | S1 阶段已记录了 ITLB 或 PMP 异常,L2 corrupt = false。
2_exception_out 仅保留 ITLB/PMP 异常标记,无新增 AF 异常。 | +| 19.6 | ICACHE_MAINPIPE_MISS_REQ | ONLY_L2_EXCEPTION | S2 阶段 s2_l2_corrupt(i) = true,且无 ITLB/PMP 异常。
s2_exception_out(i) 表示 L2 访问错误(AF)。 | +| 19.7 | ICACHE_MAINPIPE_MISS_REQ | ITLB_AND_L2_EXCEPTION | 同时触发 ITLB 异常和 L2 corrupt。
s2_exception_out 优先保留 ITLB 异常类型,不被 L2 覆盖。 | +| 19.8 | ICACHE_MAINPIPE_MISS_REQ | S2_FETCH_FINISH | s2_should_fetch 的所有端口都为低,表示需要取指,那么取指完成
s2_fetch_finish 为高 | +| 20.1 | [ICACHE_MAINPIPE_IFU](#20-响应-ifu) | HIT_AND_RETURN | 不存在任何异常或 Miss,s2 命中,s2 阶段取指完成,外部的 respStall 停止信号也为低 。
toIFU.valid = true,toIFU.bits.data 为正确的 Cacheline 数据,toIFU.bits.exception、pmp_mmio、itlb_pbmt = none。 | +| 20.2 | ICACHE_MAINPIPE_IFU | ABNORMAL_RETURN | 设置 ITLB、PMP、或 L2 corrupt 异常。
toIFU.bits.exception(i) = 对应异常类型,pmp_mmio、itlb_pbmt 根据是否有对应的异常设置为 true。 | +| 20.3 | ICACHE_MAINPIPE_IFU | CROSSLINE_FETCH | s2_doubleline = true,同时检查第一路、第二路返回情况。
toIFU.bits.doubleline = true。
若第二路正常,toIFU.bits.exception(1) = none;若第二路异常,则 exception(1) 标记相应类型。
pmp_mmio、itlb_pbmt 类似。 | +| 20.4 | ICACHE_MAINPIPE_IFU | RESPSTALL | 外部 io.respStall = true,导致 S2 阶段无法发射到 IFU。
s2_fire = false,toIFU.valid 也不拉高,S2 保持原状态等待下一拍(或直到 respStall 解除)。 | +| 21.1 | [ICACHE_MAINPIPE_L2_CORRUPT](#21-l2-corrupt-报告) | SINGLE_L2_CORRUPT | s2 阶段准备完成可以发射(s2_fire 为高),s2_MSHR_hits(0)和 fromMSHR.bits.corrupt 为高
s2_l2_corrupt(0) = true,io.errors(0).valid = true,io.errors(0).bits.source.l2 = true。 | +| 21.2 | ICACHE_MAINPIPE_L2_CORRUPT | DOUBLE_L2_CORRUPT | 端口 0 和端口 1 都从 L2 corrupt 数据中获取。
s2_l2_corrupt 均为 true,发射后分别报告到 io.errors(0) 和 io.errors(1)。 | +| 22.1 | [ICACHE_MAINPIPE_FLUSH](#22-刷新机制) | GLOBAL_FLUSH | io.flush 被激活时,流水线的各个阶段(S0, S1 和 S2)都能正确响应并执行刷新操作。
io.flush = true。
s0_flush, s1_flush, s2_flush = true。 | +| 22.2 | ICACHE_MAINPIPE_FLUSH | S0_FLUSH | s0_flush = true。
s0_fire = false。 | +| 22.3 | ICACHE_MAINPIPE_FLUSH | S1_FLUSH | s1_flush = true。
s1_valid, s1_fire = false。 | +| 22.4 | ICACHE_MAINPIPE_FLUSH | S2_FLUSH | s2_flush = true。
s2_valid, toMSHRArbiter.io.in(i).valid , s2_fire = false | +| 23.1 | [ICACHE_WAYLOOKUP_FLUSH](#22-刷新机制) | READ_POINTER | io.flush 为高时,重置读指针。
readPtr.value 为 0, readPtr.flag 为 false。 | +| 23.2 | ICACHE_WAYLOOKUP_FLUSH | WRITE_POINTER | io.flush 为高时,重置写指针。
writePtr.value 为 0, writePtr.flag 为 false。 | +| 23.3 | ICACHE_WAYLOOKUP_FLUSH | GPF | io.flush 为高时,重置 GPF 信息。
gpf_entry.valid 为 0, gpf_entry.bits 为 0。 | +| 24.1 | [ICACHE_WAYLOOKUP_UPDATE_POINTER](#24-读写指针更新) | READ_POINTER | 当 io.read.fire 为高时,读指针加一。
readPtr.value 加一。
如果 readPtr.value 超过环形队列的大小,readPtr.flag 会翻转。 | +| 24.2 | ICACHE_WAYLOOKUP_UPDATE_POINTER | WRITE_POINTER | 当 io.write.fire 为高时,写指针加一。
writePtr.value 加一。
如果 writePtr.value 超过环形队列的大小,writePtr.flag 会翻转。 | +| 25.1 | [ICACHE_WAYLOOKUP_UPDATE](#25-更新操作) | HIT | MissUnit 返回的更新信息和 WayLookup 的信息相同时,更新 waymask 和 meta_codes。
vset_same 和 ptag_same 为真。
waymask 和 meta_codes 更新。
hits 对应位为高。 | +| 25.2 | ICACHE_WAYLOOKUP_UPDATE | MISS | vset_same 和 way_same 为真。
waymask 清零。
hit 对应位为高。 | +| 25.3 | ICACHE_WAYLOOKUP_UPDATE | NONE_UPDATE | 其他情况下不更新。
vset_same 为假或者 ptag_same 和 way_same 都为假。
hits 对应位为低。 | +| 26.1 | [ICACHE_WAYLOOKUP_READ](#26-读操作) | BYPASS_READ | 队列为空,并且 io.write.valid 写有效时,可以直接读取,而不经过队列。
empty 和 io.write.valid 都为真。
io.read.bits = io.write.bits | +| 26.2 | ICACHE_WAYLOOKUP_READ | READ_INVALID | 队列为空(readPtr === writePtr)且写信号 io.write.valid 为低。
io.read.valid 为低,读信号无效。 | +| 26.3 | ICACHE_WAYLOOKUP_READ | NORMAL_READ | 未达成绕过条件(empty 和 io.write.valid 至少有一个为假)且 io.read.valid 为高。
从环形队列中读取信息。
io.read.bits.entry = entries(readPtr.value) | +| 26.4 | ICACHE_WAYLOOKUP_READ | GPF_HIT | io.read.valid 为高,可以读。
当 gpf_hits 为高时,从 GPF 队列中读取信息。
io.read.bits.gpf = gpf_entry.bits | +| 26.5 | ICACHE_WAYLOOKUP_READ | GPF_HIT_AND_READ | io.read.valid 为高,可以读。
> also clear gpf_entry.valid when it's read
当 gpf 命中且被读取其时(io.read.fire 为高),gpf_entry.valid 会被置为 0。 | +| 26.6 | ICACHE_WAYLOOKUP_READ | GPF_MISS | io.read.valid 为高,可以读。
io.read.bits.gpf 清零。 | +| 27.1 | [ICACHE_WAYLOOKUP_WRITE](#27-写操作) | GPF_STOP | > if there is a valid gpf to be read, we should stall write
gpf 队列数据有效,并且没有被读取或者没有命中,就会产生 gpf 停止,此时写操作会被停止。
gpf_entry.valid && !(io.read.fire && gpf_hit) 为高时,写操作会被停止(io.write.ready 为低)。 | +| 27.2 | ICACHE_WAYLOOKUP_WRITE | WRITE_READY_INVALID | 当队列为满((readPtr.value === writePtr.value) && (readPtr.flag ^ writePtr.flag))或者 gpf 停止时,写操作会被停止。
(io.write.ready 为低) | +| 27.3 | ICACHE_WAYLOOKUP_WRITE | NORMAL_WRITE | 当 io.write.valid 为高时(没满且没有 gpf 停止),写操作会被执行。
正常握手完毕 io.write.fire 为高。
写信息会被写入环形队列。
entries(writePtr.value) = io.write.bits.entry。 | +| 27.4.1 | [ICACHE_WAYLOOKUP_WRITE_WITH_ITLB_EXCEPTION](#274-有-itlb-异常的写) | BYPASS | can_bypass 和 io.read.fire 都为高。
gpf_entry.valid 为 false。
gpf_entry.bits = io.write.bits.gpf
gpfPtr = writePtr
| +| 27.4.2 | ICACHE_WAYLOOKUP_WRITE_WITH_ITLB_EXCEPTION | NO_BYPASS | can_bypass 为低。
gpf_entry.valid 为 true。
gpf_entry.bits = io.write.bits.gpf
gpfPtr = writePtr | +| 28.1 | [ICACHE_WAYLOOKUP_ENQ](#28-入队操作) | NORMAL_ENQ | 当队列未满,且空位不小于一时,可以正常入队,如果从零号位开始入队到最大容量,入队指针的 flag 不会翻转。
io.enq.fire 为高有效,regFiles(enq_ptr.value) = io.enq.bits,enq_ptr.value+1 入队指针移动,入队指针标记位不翻转。
重复以上操作至队满。 | +| 28.2 | ICACHE_WAYLOOKUP_ENQ | REVERSE_FLAG | 当队未满,但是空位却是靠近队尾时,入队一位后就到达了队头,入队指针的 flag 会翻转。
队列的容量为 10,入队指针指向 9,队未满。此时如果 io.enq.fire 为高,则 regFiles(9) = io.enq.bits,enq_ptr.value+1(循环队列,加完后 enq_ptr.value=0)入队指针移动,入队指针标记位翻转。 | +| 28.3 | ICACHE_WAYLOOKUP_ENQ | FULL | 当队满时,(enq_ptr.value === deq_ptr.value) && (enq_ptr.flag ^ deq_ptr.flag) 为高,io.enq.ready 为低,io.enq.fire 为低无效。
此时入队,入队指针的 value 和 flag 不变。 | +| 29.1 | [ICACHE_WAYLOOKUP_DEQ](#29-出队操作) | NORMAL_DEQ | 当队列非空时,可以正常出队,如果出队指针不经过最大容量位置,出队指针的 flag 不会翻转。
io.deq.fire 为高有效,io.deq.bits = regFiles(deq_ptr.value),deq_ptr.value+1 出队指针移动,出队指针标记位不翻转。 | +| 29.2 | ICACHE_WAYLOOKUP_DEQ | REVERSE_FLAG | 当队非空,但是出队指针是靠近队尾时,出队一位后就到达了队头,出队指针的 flag 会翻转。
队列的容量为 10,出队指针指向 9,队非空。此时如果 io.deq.fire 为高,则 io.deq.bits = regFiles(9),deq_ptr.value+1(循环队列,加完后 deq_ptr.value=0)出队指针移动,出队指针标记位翻转。 | +| 29.3 | ICACHE_WAYLOOKUP_DEQ | EMPTY | 当队空时,enq_ptr === deq_ptr 为高,io.deq.valid 为低,io.deq.fire 为低无效。
此时出队,出队指针的 value 和 flag 不变。 | +| 30.1 | [ICACHE_WAYLOOKUP_FLUSH](#30-刷新清空操作) | FLUSH | 当刷新信号有效时,重置出队和入队的指针和标记位,清空队列。
当 flush 为高时,deq_ptr.value=0,enq_ptr.value=0,deq_ptr.flag=false,enq_ptr.flag=false,empty=true,full=false。 | +| 31.1 | [ICACHE_MISSUNIT_HANDLE_FETCH](#31-处理取指缺失请求) | RECEIVE_NEW_REQ | 当新的 fetch miss 与 MSHR 中的已有请求不重复时(通过 io.fetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Fetch MSHR 中。
当有新的取指缺失请求到达时(io.fetch_req.valid 为高),且没有命中已有的 MSHR(fetchHit 为低),io.fetch_req.ready 应为高,表示可以接受请求。
io.fetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。 | +| 31.2 | ICACHE_MISSUNIT_HANDLE_FETCH | ALREADY_REQ | 当已有取指缺失请求到达时(io.fetch_req.valid 为高),且命中已有的 MSHR(fetchHit 为高),io.fetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。
fetchDemux.io.in.valid 应为低,fetchDemux.io.in.fire 为低,表示没有新的请求被分发到 MSHR。 | +| 31.3 | ICACHE_MISSUNIT_HANDLE_FETCH | MSHR_PRIORITY | Fetch 的请求会通过 fetchDemux 分配到多个 Fetch MSHR,fetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。
当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。 | +| 32.1 | [ICACHE_MISSUNIT_HANDLE_PREFETCH](#32-处理预取缺失请求) | RECEIVE_NEW_REQ | 当新的 prefetch miss 与 MSHR 中的已有请求不重复时(通过 io.prefetch_req.bits.blkPaddr / vSetIdx 给出具体地址),MissUnit 会将请求分配到一个空闲的 Prefetch MSHR 中。
当有新的预取缺失请求到达时(io.prefetch_req.valid 为高),且没有命中已有的 MSHR(prefetchHit 为低),io.prefetch_req.ready 应为高,表示可以接受请求。
io.prefetch_req.fire 成功握手后,该 MSHR 处于 valid = true 状态,并记录地址。 | +| 32.2 | ICACHE_MISSUNIT_HANDLE_PREFETCH | ALREADY_REQ | 当已有预取缺失请求到达时(io.prefetch_req.valid 为高),且命中已有的 MSHR(prefetchHit 为高),io.prefetch_req.ready 应为高,虽然不接受请求,但是表现出来为已经接收请求。
prefetchDemux.io.in.valid 应为低,prefetchDemux.io.in.fire 为低,表示请求被接受但未分发到新的 MSHR。 | +| 32.3 | ICACHE_MISSUNIT_HANDLE_PREFETCH | MSHR_PRIORITY | Prefetch 的请求会通过 prefetchDemux 分配到多个 Prefetch MSHR,prefetchDemux 的实现中,低索引的 MSHR 会优先被分配请求。
当取指请求有多个 io.out(i).read 时,选择其中的第一个,也就是低索引的写入 MSHR,io.chose 为对应的索引。 | +| 32.4 | ICACHE_MISSUNIT_HANDLE_PREFETCH | FIFO_PRIORITY | 从 prefetchDemux 离开后,请求的编号会进入 priorityFIFO,priorityFIFO 会根据进入队列的顺序排序,先进入队列的请求会先进入 prefetchArb。
prefetchDemux.io.in.fire 为高,并且 prefetchDemux.io.chosen 有数据时,将其编号写入 priorityFIFO。
在 priorityFIFO 中有多个编号时,出队的顺序和入队顺序一致。
检查 priorityFIFO.io.deq.bit 中的数据即可。 | +| 33.1 | [ICACHE_MISSUNIT_MSHR](#33-mshr-管理与查找) | HIT | 当新的请求到来时,能够正确查找所有 MSHR,判断请求是否命中已有 MSHR。
当新的请求(取指或预取)到来时,系统遍历所有 MSHR,根据所有 MSHR 的查找信号 allMSHRs(i).io.lookUps(j).hit,检查请求是否已经存在于某个 MSHR 中。
如果命中,则对应的 fetchHit 或 prefetchHit 为高。
对于 prefetchHit 为高,还有一种情况:预取的物理块地址和组索引与取指的相等((io.prefetch_req.bits.blkPaddr === io.fetch_req.bits.blkPaddr) && (io.prefetch_req.bits.vSetIdx === io.fetch_req.bits.vSetIdx))并且有取指请求 io.fetch_req.valid 有效时,也算命中 | +| 33.2 | ICACHE_MISSUNIT_MSHR | UPDATE_AND RELEASE | 当请求完成后,也就是来自内存总线的响应完成(D 通道接收完所有节拍),MSHR 能够正确地释放(清除其有效位),以便接收新的请求。
TileLink D 通道返回的 source ID ,即 io.mem_grant.bits.source。
无效化信号 allMSHRs(i).io.invalid 为高,对应的 MSHR 的有效位 allMSHRs(i).valid 变为低 | +| 34.1 | [ICACHE_MISSUNIT_ACQUIREARB](#34-acquirearb-仲裁) | ARBITRATE | acquireArb 会选择一个 acquire 发送给 mem_acquire。
当有多个 MSHR 同时发出请求时,acquireArb 会根据优先级进行仲裁,选择优先级最高的 MSHR 发送请求。
取指请求总是在 0-3 号,预取请求直接在最后一号,所以取指请求优先级高于预取请求。
当取指 acquire 和预取 acquire 同时发出时,fetchMSHRs(i).io.acquire 和 prefetchMSHRs(i).io.acquire 都有效,仲裁结果 acquireArb.io.out 应该和 fetchMSHRs(i).io.acquire 一致。 | +| 35.1 | [ICACHE_MISSUNIT_GRANT](#35-grant-数据接收与-refill) | READBEATCNT_EQUAL_0 | readBeatCnt 初始为 0,refillCycles - 1 也为 0。
io.mem_grant.valid 为高(因为 io.mem_grant.ready 默认为高,所以 io.mem_grant.fire 为高只需要 io.mem_grant.valid 为高)且 io.mem_grant.bits.opcpde(0)为高。
此时 respDataReg(0)= io.mem_grant.bits.data
readBeatCnt 加一为 1。 | +| 35.2 | ICACHE_MISSUNIT_GRANT | READBEATCNT_EQUAL_1 | io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高。
此时 respDataReg(1)= io.mem_grant.bits.data
readBeatCnt 重置回 0。
last_fire 为高。
下一拍 last_fire_r 为高,id_r=io.mem_grant.bits.source。 | +| 35.3 | ICACHE_MISSUNIT_GRANT | LAST_FIRE_R_EQUAL_HIGH | last_fire_r 为高,并且 id_r 为 0-13 中的一个。
对应的 fetchMSHRs 或者 prefetchMSHRs 会被无效,也就是 fetchMSHRs_i 或 prefetchMSHRs_i-4 的 io_invalid 会被置高。 | +| 35.4 | ICACHE_MISSUNIT_GRANT | GRANT_WITH_CORRUPT | io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高,则 corrupt_r 应为高。
如果 io.mem_grant.valid 为高且 io.mem_grant.bits.opcpde(0)为高,io.mem_grant.bits.corrupt 为高中有一个不满足,且此时 last_fire_r 为高,则 corrupt_r 重置为低。 | +| 36.1 | [ICACHE_MISSUNIT_REPLACER](#36-替换策略更新-replacer) | UPDATE | 当 io.mem.acquire.ready & acquireArb.io.out.valid 同时为高,也就是 acquireArb.io.out.fir 为高时,io.victim.vSetIdx.valid 也为高。
io.victim.vSetIdx.bits = 当前 MSHR 请求的 acquireArb.io.out.bits.vSetIdx。 | +| 36.2 | ICACHE_MISSUNIT_REPLACER | GENERATE_WAYMASK | 根据从 L2 返回的 mshr_resp 中 mshr_resp.bits.way 生成 waymask 信息。
返回的 mshr_resp.bits.way 有 16 位,通过独热码生成一位掩码信息,waymask 表示其中哪一路被替换。
生成的 waymask 应该和 mshr_resp.bits.way 一致。 | +| 37.1 | [ICACHE_MISSUNIT_WRITE_SRAM](#37-写回-sram-meta--data) | GENERATE_META_AND_DATA_WRITE_VALID | 当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。
并且此时没有硬件刷新信号和软件刷新信号,也就是 io.flush 和 io.fencei 为低。 在等待 l2 响应的过程中,没有刷新信号
也没有数据 corrupt,即 corrupt_r 为低。
那么 io.meta_write.valid 和 io.data_write.valid 均为高。 | +| 37.2 | ICACHE_MISSUNIT_WRITE_SRAM | WRITE_SRAM | io.meta_write.bits 的 virIdx、phyTag、waymask、bankIdx、poison 应该正常更新
io.data_write.bits 的 virIdx、data、waymask、bankIdx、poison 应该正常更新 | +| 38.1 | [ICACHE_MISSUNIT_FINISH](#38-向-mainpipeprefetchpipe-发出-miss-完成响应fetch_resp) | MISS_REQ_FINISH | 当 grant 传输完成后,经过一拍后,即 last_fire_r 为高,且从 TileLink 返回的 mshr_resp 中的 mshr_resp.valid 为高。
无论此时是否有硬件刷新信号和软件刷新信号, io.fetch_resp.valid 都为高,说明可向取指端发送响应。
io.fetch_resp.bits 中的数据更新:
io.fetch_resp.bits.blkPaddr = mshr_resp.bits.blkPaddr
io.fetch_resp.bits.vSetIdx = mshr_resp.bits.vSetIdx
io.fetch_resp.bits.waymask = waymask
io.fetch_resp.bits.data = respDataReg.asUInt
io.fetch_resp.bits.corrupt = corrupt_r | +| 39.1 | [ICACHE_MISSUNIT_FLUSH_OR_FENCEI](#39-处理-flush--fencei) | FENCEI_BEFORE_MSHR_ISSUE | 如果 MSHR 还没有通过 io.acquire.fire 发出请求,就应立即取消该 MSHR(mshr_resp.valid= false),既不发出请求,也不要写 SRAM。
当 io.fencei 为高时,fetchMSHRs 和 prefetchMSHRs 的 io.req.ready 和 io.acquire.valid 均为低,表示请求不发射。 | +| 39.2 | ICACHE_MISSUNIT_FLUSH_OR_FENCEI | FLUSH_BEFORE_MSHR_ISSUE | 由于 fetchMSHRs 的 io.flush 被直接设置为 false,所以 io.flush 对 fetchMSHRs 无效,但是对 prefetchMSHRs 有效。
当 io.flush 为高时,只能发射 fetchMSHRs 的请求。 | +| 39.3 | ICACHE_MISSUNIT_FLUSH_OR_FENCEI | FLUSH_OR_FENCEI_AFTER_MSHR_ISSUE | 已经发射了请求,之后再有刷新信号,那么等数据回来了但不写 SRAM。
在发射后,io.flush/io.fencei 为高时,等待数据回来,但是写 SRAM 的信号,write_sram_valid、io.meta_write.valid 和 io.data_write.valid 均为低,表示不写 SRAM。
对于 response fetch 无影响。 | +| 40.1 | [ICACHE_CTRLUNIT_ECCCTRL](#40-ecc-启用禁用) | ENABLE_ECC | 向 eccctrl.enable 寄存器写入 true,验证模块内部 eccctrl.enable 设置为 true,并确保后续的错误注入操作能够成功进行。此测试确保 eccctrl.enable 写操作被执行。
确保 eccctrl.enable 被正确设置为 true,并触发 eccctrlRegWriteFn 中的写操作逻辑。 | +| 40.2 | ICACHE_CTRLUNIT_ECCCTRL | DISANLE_ECC | 向 eccctrl.enable 寄存器写入 false,验证模块内部 eccctrl.enable 设置为 false,并确保在后续的错误注入过程中,ECC 功能被禁用,不允许进行错误注入。此测试确保 eccctrl.enable 写操作被正确设置为 false。
验证禁用 ECC 时 eccctrl.enable 为 false,并触发 eccctrlRegWriteFn 中的错误处理分支。x.istatus = eccctrlInjStatus.error 和 x.ierror = eccctrlInjError.notEnabled | +| 41.1 | [ICACHE_CTRLUNIT_FSM](#41-状态机转换) | IDLE | 初始为 is_idle 状态。
当 eccctrl.istatus 为 working 时,验证此时的状态为 is_readMetaReq。 | +| 41.2 | ICACHE_CTRLUNIT_FSM | READMETAREQ | 当握手成功后(io.metaRead.ready 和 io.metaRead.valid 都为高),验证此时的状态为 is_readMetaResp。 | +| 41.3.1 | [ICACHE_CTRLUNIT_FSM_READMETARESP](#413-is_readmetaresp-状态) | MISS | 当 waymask 全零的时候,表示没有命中,会进入 is_idle 状态,并且设置错误错误注入状态和错误原因。
验证此时的状态为 is_idle, eccctrl.istatus = error 和 eccctrl.ierror = notFound。 | +| 41.3.2 | ICACHE_CTRLUNIT_FSM_READMETARESP | HIT | 当 waymask 不全零的时候,表示命中,会根据错误注入目标来判断是向元数据还是数据阵列写入错误。
当 eccctrl.itarget=metaArray 时,验证此时的状态为 is_writeMeta ;当 eccctrl.itarget!=metaArray 时,验证此时的状态为 is_writeData。 | +| 41.4.1 | [ICACHE_CTRLUNIT_FSM_WRITEMETA](#414-is_writemeta-状态) | REGWRITEFN | 此状态进入后,io.dataWrite.valid 会为高
x.itarget = req.itarget
当 req.inject 为高并且 x.istatus = idle 时:
1. 如果 ecc 的 req.enable = false,则验证 x.istatus = error 且 x.ierror = notEnabled
2. 否则,如果 req.itarget != metaArray 和 dataArray,则验证 x.istatus = error 且 x.ierror = targetInvalid
3. 如果都不满足,则验证 x.istatus = working | +| 41.4.2 | ICACHE_CTRLUNIT_FSM_WRITEMETA | STATE_TRANSFER | 当 io.metaWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。 | +| 41.5.1 | [ICACHE_CTRLUNIT_FSM_WRITEDATA](#415-is_writedata-状态) | REGWRITEFN | 此状态进入后,io.dataWrite.valid 会为高
res.inject = false
当 ready 为高,且 x.istatus = injected 或 x.istatus = error 时,验证 x.istatus = idle 和 x.ierror = notEnabled | +| 41.5.2 | ICACHE_CTRLUNIT_FSM_WRITEDATA | STATE_TRANSFER | 当 io.dataWrite.fire 为高, 验证下一个状态为 is_idle,并且 eccctrl.istatus = injected。 | +| 42.1 | [ICACHE_CTRLUNIT_EXTERNAL](#42-寄存器映射和外部访问) | READ_OR_WRITE_ECC_REGISTER | 验证外部模块可以通过 TileLink 协议正确读取和写入 eccctrl 和 ecciaddr 寄存器,并对模块内部的状态产生影响,确保读写操作完全覆盖。 | +| 42.2 | ICACHE_CTRLUNIT_EXTERNAL | EXTERNAL_MODULE_TRIGGER_ERROR_INJECT | 通过外部模块经 TileLink 总线向 eccctrl.inject 寄存器写入 true,触发错误注入,验证内部状态是否按 RegWriteFn 内部过程执行。 | +| 43.1 | [ICACHE_ICACHE_PREFETCH](#43-ftq-预取请求处理) | PREFETCH_HIT_WITHOUT_EXCEPTION | io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查通过。
如果没有监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该命中的取指数据。
如果监听到 MSHR 同样的位置发生了其它 cacheline 的写入,那么验证 wayLookup.io.write 的内容应该是未命中的取指数据。 | +| 43.2 | ICACHE_ICACHE_PREFETCH | PREFETCH_MISS_WITHOUT_EXCEPTION | io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 不匹配,pmp 检查通过。
如果监听到 MSHR 将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该命中的取指数据。
如果监听到 MSHR 没有将该请求对应的 cacheline 写入了 SRAM,那么验证 wayLookup.io.write 的内容应该未命中的取指数据。 | +| 43.3 | ICACHE_ICACHE_PREFETCH | ONLY_TLB_EXCEPTION | io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 异常。
验证 wayLookup.io.write 的 itlb_exception 内容中,其有对应的异常类型编号(pf:01;gpf:10;af:11)。 | +| 43.4 | ICACHE_ICACHE_PREFETCH | ONLY_TPMP_EXCEPTION | io.ftqPrefetch.req.bits 的 startAddr 和 nextlineStart 在正常地址范围内,itlb 命中无异常,itlb 查询到的地址与 MetaArry 的 ptag 匹配,pmp 检查未通过。
验证 wayLookup.io.write 的 tlb_pbmt 内容中,其有对应的异常类型编号(nc:01;io:10)。 | +| 44.1 | [ICACHE_ICACHE_FETCH](#44-ftq-取指请求处理) | FETCH_HIT_WITHOUT_EXCEPTION | io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。
验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。
验证 io.fetch.resp 的数据应该是取指的数据。 | +| 44.2 | ICACHE_ICACHE_FETCH | FETCH_MISS_MSHR_HIT_WITHOUT_EXCEPTION | io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,未命中,pmp 检查正常,DataArray 和 MetaArray 的 ECC 校验正常。
请求在 MSHR 返回的响应命中。
验证 missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。
验证 io.fetch.resp 的数据应该是取指的数据。 | +| 44.3 | ICACHE_ICACHE_FETCH | FETCH_MISS_AND_ECC_ERROR_WITHOUT_ELSE_EXCEPTION | io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查正常,DataArray 或 MetaArray 的 ECC 校验错误。
验证 io.error.valid 为高,且 io.error.bits 内容为对应的错误源和错误类型。
先刷 MetaArray 的 ValidArray,给 MissUnit 发请求,由其在 L2 重填,阻塞至数据返回。
验证 replacer.io.touch 的 vSetIdx 和 way 和 ftq 的 fetch 一致,missUnit.io.victim 的 vSetIdx 和 way 是按照制定的算法生成的。
验证 io.fetch.resp 的数据应该是取指的数据。 | +| 44.4 | ICACHE_ICACHE_FETCH | FETCH_MISS_AND_SOME_EXCEPTION_WITHOUT_ELSE_EXCEPTION | io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查未通过,DataArray 和 MetaArray 的 ECC 校验正常。
验证 io.fetch.resp 为对应的错误源和错误类型。
验证 io.fetch.resp 的数据无效,里面有异常类型。 | +| 44.5 | ICACHE_ICACHE_FETCH | FETCH_MISS_AND_ITLB_PBMT | 有 itlb_pbmt 和 pmp_mmio 时,他们合成 s1_mmio,传递到 s2_mmio,生成 s2_miss,有特殊情况就不会取指。
io.fetch.req.bits.pcMemRead 的 0-4 的 startAddr 和 nextlineStart 在正常地址范围内,从 WayLookup 获取信息,命中,pmp 检查通过,DataArray 和 MetaArray 的 ECC 校验正常。
验证 io.fetch.resp 为对应的错误源和错误类型。
验证 io.fetch.resp 的数据无效,里面有特殊情况类型类型。 | +| 44.6 | ICACHE_ICACHE_FETCH | FETCH_MISS_AND_PMP_MMIO | 处理同 5。 | +| 45.1 | [ICACHE_ICACHE_METAARRAY](#45-metaarray-功能) | WRITE_META | 从 MissUnit 返回的请求都是未命中的请求(已命中不会向 MissUnit 请求,那么 MissUnit 自然也不会向 MetaArray 写入)。
发送一个写请求 write 到 ICacheMetaArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最后指定 virIdx、phyTag、waymask、bankIdx、poison。
写入操作后,发起一个对相同虚拟索引的读请求。验证 readResp 的 metas 和 codes 分别包含写入的 ptag 和 ecc code,并且对于写入的路,readResp.entryValid 信号被置为有效。 | +| 45.2 | ICACHE_ICACHE_METAARRAY | READ_META_HIT | 首先,向特定的虚拟索引(组和路)写入元数据(参照上面的写入操作)。然后,向相同的虚拟索引发送一个读请求。
验证 readResp.metas 包含之前写入的物理标签,并且对于相应的路,readResp.entryValid 信号被置为有效。 | +| 45.3 | ICACHE_ICACHE_METAARRAY | READ_META_MISS | 向 ICacheMetaArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。
验证对于任何路,readResp.entryValid 信号都没有被置为有效。 对应的 readResp.metas 和 codes 的内容是 DontCare 也就是 0。 | +| 45.4 | ICACHE_ICACHE_METAARRAY | SINGLE_CACHELINE_FLUSH | 先向 ICacheMetaArray 写入指定一个或多个端口的元数据,然后再给对应的端口的路发送刷新请求 io.flush,其包含虚拟索引 virIdx 和路掩码 waymask。
验证 valid_array 对应的路中的 virIdx 被置为无效,io.readResp.entryValid 对应路的对应端口为无效。 | +| 45.5 | ICACHE_ICACHE_METAARRAY | FLUSH_ALL | 先向多个不同的虚拟索引写入元数据。然后置位 io.flushAll 信号。
验证步骤: 在 io.flushAll 信号置位后,发起对所有之前写入过的虚拟索引的读请求。验证在所有的读取响应中,对于任何路,readResp.entryValid 信号都没有被置为有效。 | +| 46.1 | [ICACHE_ICACHE_DATAARRAY](#46-dataarray-功能) | WRITE_DATA | 发送一个写请求 write 到 ICacheDataArray,ICacheReplacer 根据 PLRU 替换策略指定 way,替换路被写入 waymask,最终指定虚拟索引、数据、路掩码、存储体索引 bankIdx 和毒化位。写入的数据模式应跨越多个数据存储体。
写入操作后,发起一个对相同虚拟索引和块偏移量的读请求。验证 readResp.datas 与写入的数据相匹配。 | +| 46.2 | ICACHE_ICACHE_DATAARRAY | READ_DATA_HIT | 首先,向特定的虚拟索引和块偏移量写入数据。然后,向相同的虚拟索引和块偏移量发送一个读请求。使用不同的块偏移量进行测试,以覆盖存储体的选择逻辑。
验证 readResp.datas 包含之前写入的数据。 | +| 46.3 | ICACHE_ICACHE_DATAARRAY | READ_DATA_MISS | 向 ICacheDataArray 发送一个读请求,请求的虚拟索引在复位后从未被写入过。
验证 readResp.datas 为 0。 | + +
+ +
diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/dataarray_bank.png b/documents/static/docs/98_ut/01_frontend/04_icache/dataarray_bank.png new file mode 100644 index 0000000..beb2f85 Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/dataarray_bank.png differ diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/dataarray_bank_read_multiline.png b/documents/static/docs/98_ut/01_frontend/04_icache/dataarray_bank_read_multiline.png new file mode 100644 index 0000000..26f4a00 Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/dataarray_bank_read_multiline.png differ diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/dataarray_bank_read_singleline.png b/documents/static/docs/98_ut/01_frontend/04_icache/dataarray_bank_read_singleline.png new file mode 100644 index 0000000..c950f30 Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/dataarray_bank_read_singleline.png differ diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/ftq_pointer.png b/documents/static/docs/98_ut/01_frontend/04_icache/ftq_pointer.png new file mode 100644 index 0000000..534e62e Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/ftq_pointer.png differ diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/icache_stages.png b/documents/static/docs/98_ut/01_frontend/04_icache/icache_stages.png new file mode 100644 index 0000000..9eaa8e7 Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/icache_stages.png differ diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/icache_structure.png b/documents/static/docs/98_ut/01_frontend/04_icache/icache_structure.png new file mode 100644 index 0000000..806b247 Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/icache_structure.png differ diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/iprefetchpipe_fsm.png b/documents/static/docs/98_ut/01_frontend/04_icache/iprefetchpipe_fsm.png new file mode 100644 index 0000000..35ce09b Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/iprefetchpipe_fsm.png differ diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/iprefetchpipe_structure.png b/documents/static/docs/98_ut/01_frontend/04_icache/iprefetchpipe_structure.png new file mode 100644 index 0000000..a5b150e Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/iprefetchpipe_structure.png differ diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/mainpipe_structure.png b/documents/static/docs/98_ut/01_frontend/04_icache/mainpipe_structure.png new file mode 100644 index 0000000..875453b Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/mainpipe_structure.png differ diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/missunit_structure.png b/documents/static/docs/98_ut/01_frontend/04_icache/missunit_structure.png new file mode 100644 index 0000000..a2b478b Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/missunit_structure.png differ diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/plru.png b/documents/static/docs/98_ut/01_frontend/04_icache/plru.png new file mode 100644 index 0000000..0e41a70 Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/plru.png differ diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/prefetch_mux.png b/documents/static/docs/98_ut/01_frontend/04_icache/prefetch_mux.png new file mode 100644 index 0000000..d989da3 Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/prefetch_mux.png differ diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/waylookup_structure_rw.png b/documents/static/docs/98_ut/01_frontend/04_icache/waylookup_structure_rw.png new file mode 100644 index 0000000..e7bcaa7 Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/waylookup_structure_rw.png differ diff --git a/documents/static/docs/98_ut/01_frontend/04_icache/waylookup_structure_update.png b/documents/static/docs/98_ut/01_frontend/04_icache/waylookup_structure_update.png new file mode 100644 index 0000000..8fcbdbb Binary files /dev/null and b/documents/static/docs/98_ut/01_frontend/04_icache/waylookup_structure_update.png differ diff --git a/scripts/build_ut_frontend_icache.py b/scripts/build_ut_frontend_icache.py deleted file mode 100644 index fc42f90..0000000 --- a/scripts/build_ut_frontend_icache.py +++ /dev/null @@ -1,24 +0,0 @@ -#coding=utf8 -#*************************************************************************************** -# This project is licensed under Mulan PSL v2. -# You can use this software according to the terms and conditions of the Mulan PSL v2. -# You may obtain a copy of Mulan PSL v2 at: -# http://license.coscl.org.cn/MulanPSL2 -# -# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, -# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, -# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -# -# See the Mulan PSL v2 for more details. -#**************************************************************************************/ - - -import os - - -def build(cfg): - return False - - -def line_coverage_files(cfg): - return [] diff --git a/scripts/build_ut_frontend_icache_ctrlunit.py b/scripts/build_ut_frontend_icache_ctrlunit.py new file mode 100644 index 0000000..75fab70 --- /dev/null +++ b/scripts/build_ut_frontend_icache_ctrlunit.py @@ -0,0 +1,41 @@ +import os +from comm import warning, info, get_all_rtl_files + + +def build(cfg): + from tempfile import NamedTemporaryFile + from toffee_test.markers import match_version + from comm import error, info, get_root_dir, exe_cmd + + # check version + if not match_version(cfg.rtl.version, "openxiangshan-kmh-*"): + error(f"frontend_icache_ctrlunit: Unsupported RTL version {cfg.rtl.version}") + return False + + # find source files for ICacheCtrlUnit + rtl_files = get_all_rtl_files("ICacheCtrlUnit", cfg=cfg) + info(f"rtl_files: {rtl_files}") + assert rtl_files, "Cannot find RTL files of Frontend.ICacheCtrlUnit" + + # additional internal signal files + internal_signals_path = os.path.join( + get_root_dir("scripts/icache_related/icache_ctrlunit_internals.yaml") + ) + # assert os.path.exists(internal_signals_path), "Cannot find internal signal files" + + # export ICacheCtrlUnit.sv + if not os.path.exists(get_root_dir("dut/ICacheCtrlUnit")): + info("Exporting ICacheCtrlUnit.sv") + with NamedTemporaryFile("w+", encoding="utf-8", suffix=".txt") as filelist: + filelist.write("\n".join(rtl_files)) + filelist.flush() + s, _, err = exe_cmd( + f"picker export --cp_lib false {rtl_files[0]} --fs {filelist.name} --lang python --tdir " + f"{get_root_dir('dut')}/ -w ICacheCtrlUnit.fst -c --internal={internal_signals_path}" + ) + assert s, err + return True + + +def line_coverage_files(cfg): + return ["ICacheCtrlUnit.v"] diff --git a/scripts/build_ut_frontend_icache_icache.py b/scripts/build_ut_frontend_icache_icache.py new file mode 100644 index 0000000..8b8ba60 --- /dev/null +++ b/scripts/build_ut_frontend_icache_icache.py @@ -0,0 +1,41 @@ +import os +from comm import warning, info, get_all_rtl_files + + +def build(cfg): + from tempfile import NamedTemporaryFile + from toffee_test.markers import match_version + from comm import error, info, get_root_dir, exe_cmd + + # check version + if not match_version(cfg.rtl.version, "openxiangshan-kmh-*"): + error(f"frontend_icache: Unsupported RTL version {cfg.rtl.version}") + return False + + # find source files for ICache + rtl_files = get_all_rtl_files("ICache", cfg=cfg) + info(f"rtl_files: {rtl_files}") + assert rtl_files, "Cannot find RTL files of Frontend.ICache" + + # additional internal signal files + internal_signals_path = os.path.join( + get_root_dir("scripts/icache_related/icache_icache_internals.yaml") + ) + # assert os.path.exists(internal_signals_path), "Cannot find internal signal files" + + # export ICache.sv + if not os.path.exists(get_root_dir("dut/ICache")): + info("Exporting ICache.sv") + with NamedTemporaryFile("w+", encoding="utf-8", suffix=".txt") as filelist: + filelist.write("\n".join(rtl_files)) + filelist.flush() + s, _, err = exe_cmd( + f"picker export --cp_lib false {rtl_files[0]} --fs {filelist.name} --lang python --tdir " + f"{get_root_dir('dut')}/ -w ICache.fst -c --internal={internal_signals_path}" + ) + assert s, err + return True + + +def line_coverage_files(cfg): + return ["ICache.v"] diff --git a/scripts/build_ut_frontend_icache_iprefetchpipe.py b/scripts/build_ut_frontend_icache_iprefetchpipe.py new file mode 100644 index 0000000..e21a1f5 --- /dev/null +++ b/scripts/build_ut_frontend_icache_iprefetchpipe.py @@ -0,0 +1,39 @@ +import os +from comm import warning, info, get_all_rtl_files + + +def build(cfg): + from tempfile import NamedTemporaryFile + from toffee_test.markers import match_version + from comm import error, info, get_root_dir, exe_cmd + + # check version + if not match_version(cfg.rtl.version, "openxiangshan-kmh-*"): + error(f"frontend_icache_iprefetchpipe: Unsupported RTL version {cfg.rtl.version}") + return False + + # find source files for IPrefetchPipe + rtl_files = get_all_rtl_files("IPrefetchPipe", cfg=cfg) + info(f"rtl_files: {rtl_files}") + assert rtl_files, "Cannot find RTL files of Frontend.IPrefetchPipe" + + # additional internal signal files + internal_signals_path = os.path.join(get_root_dir("scripts/icache_related/icache_iprefetchpipe_internals.yaml")) + # assert os.path.exists(internal_signals_path), "Cannot find internal signal files" + + # export IPrefetchPipe.sv + if not os.path.exists(get_root_dir("dut/IPrefetchPipe")): + info("Exporting IPrefetchPipe.sv") + with NamedTemporaryFile("w+", encoding="utf-8", suffix=".txt") as filelist: + filelist.write("\n".join(rtl_files)) + filelist.flush() + s, _, err = exe_cmd( + f"picker export --cp_lib false {rtl_files[0]} --fs {filelist.name} --lang python --tdir " + f"{get_root_dir('dut')}/ -w IPrefetchPipe.fst -c --internal={internal_signals_path}" + ) + assert s, err + return True + + +def line_coverage_files(cfg): + return ["IPrefetchPipe.v"] diff --git a/scripts/build_ut_frontend_icache_mainpipe.py b/scripts/build_ut_frontend_icache_mainpipe.py new file mode 100644 index 0000000..329c048 --- /dev/null +++ b/scripts/build_ut_frontend_icache_mainpipe.py @@ -0,0 +1,42 @@ +import os +from comm import warning, info, get_all_rtl_files + + +def build(cfg): + from tempfile import NamedTemporaryFile + from toffee_test.markers import match_version + from comm import error, info, get_root_dir, exe_cmd + + # check version + if not match_version(cfg.rtl.version, "openxiangshan-kmh-*"): + error(f"frontend_icache_mainpipe: Unsupported RTL version {cfg.rtl.version}") + return False + + # find source files for ICacheMainPipe + rtl_files = get_all_rtl_files("ICacheMainPipe", cfg=cfg) + info(f"rtl_files: {rtl_files}") + assert rtl_files, "Cannot find RTL files of Frontend.ICacheMainPipe" + + # additional internal signal files + internal_signals_path = os.path.join( + get_root_dir("scripts/icache_related/icache_mainpipe_internals.yaml") + ) + # assert os.path.exists(internal_signals_path), "Cannot find internal signal files" + + # export ICacheMainPipe.sv + if not os.path.exists(get_root_dir("dut/ICacheMainPipe")): + info("Exporting ICacheMainPipe.sv") + with NamedTemporaryFile("w+", encoding="utf-8", suffix=".txt") as filelist: + filelist.write("\n".join(rtl_files)) + filelist.flush() + s, _, err = exe_cmd( + f"picker export --cp_lib false {rtl_files[0]} --fs {filelist.name} --lang python --tdir " + f"{get_root_dir('dut')}/ -w ICacheMainPipe.fst -c --internal={internal_signals_path}" + ) + + assert s, err + return True + + +def line_coverage_files(cfg): + return ["ICacheMainPipe.v"] diff --git a/scripts/build_ut_frontend_icache_missunit.py b/scripts/build_ut_frontend_icache_missunit.py new file mode 100644 index 0000000..3e193be --- /dev/null +++ b/scripts/build_ut_frontend_icache_missunit.py @@ -0,0 +1,41 @@ +import os +from comm import warning, info, get_all_rtl_files + + +def build(cfg): + from tempfile import NamedTemporaryFile + from toffee_test.markers import match_version + from comm import error, info, get_root_dir, exe_cmd + + # check version + if not match_version(cfg.rtl.version, "openxiangshan-kmh-*"): + error(f"frontend_icache_missunit: Unsupported RTL version {cfg.rtl.version}") + return False + + # find source files for ICacheMissUnit + rtl_files = get_all_rtl_files("ICacheMissUnit", cfg=cfg) + info(f"rtl_files: {rtl_files}") + assert rtl_files, "Cannot find RTL files of Frontend.ICacheMissUnit" + + # additional internal signal files + internal_signals_path = os.path.join( + get_root_dir("scripts/icache_related/icache_missunit_internals.yaml") + ) + # assert os.path.exists(internal_signals_path), "Cannot find internal signal files" + + # export ICacheMissUnit.sv + if not os.path.exists(get_root_dir("dut/ICacheMissUnit")): + info("Exporting ICacheMissUnit.sv") + with NamedTemporaryFile("w+", encoding="utf-8", suffix=".txt") as filelist: + filelist.write("\n".join(rtl_files)) + filelist.flush() + s, _, err = exe_cmd( + f"picker export --cp_lib false {rtl_files[0]} --fs {filelist.name} --lang python --tdir " + f"{get_root_dir('dut')}/ -w ICacheMissUnit.fst -c --internal={internal_signals_path}" + ) + assert s, err + return True + + +def line_coverage_files(cfg): + return ["ICacheMissUnit.v"] diff --git a/scripts/build_ut_frontend_icache_waylookup.py b/scripts/build_ut_frontend_icache_waylookup.py new file mode 100644 index 0000000..9b250a9 --- /dev/null +++ b/scripts/build_ut_frontend_icache_waylookup.py @@ -0,0 +1,42 @@ +import os +from comm import warning, info, get_all_rtl_files + + +def build(cfg): + from tempfile import NamedTemporaryFile + from toffee_test.markers import match_version + from comm import error, info, get_root_dir, exe_cmd + + # check version + if not match_version(cfg.rtl.version, "openxiangshan-kmh-*"): + error(f"frontend_icache_waylookup: Unsupported RTL version {cfg.rtl.version}") + return False + + # find source files for WayLookup + rtl_files = get_all_rtl_files("WayLookup", cfg=cfg) + info(f"rtl_files: {rtl_files}") + assert rtl_files, "Cannot find RTL files of Frontend.WayLookup" + + # additional internal signal files + internal_signals_path = os.path.join( + get_root_dir("scripts/icache_related/icache_waylookup_internals.yaml") + ) + # assert os.path.exists(internal_signals_path), "Cannot find internal signal files" + + # export WayLookup.sv + if not os.path.exists(get_root_dir("dut/WayLookup")): + info("Exporting WayLookup.sv") + with NamedTemporaryFile("w+", encoding="utf-8", suffix=".txt") as filelist: + filelist.write("\n".join(rtl_files)) + filelist.flush() + s, _, err = exe_cmd( + f"picker export --cp_lib false {rtl_files[0]} --fs {filelist.name} --lang python --tdir " + f"{get_root_dir('dut')}/ -w WayLookup.fst -c --internal={internal_signals_path}" + # f"{get_root_dir('dut')}/ -w WayLookup.fst -c " + ) + assert s, err + return True + + +def line_coverage_files(cfg): + return ["WayLookup.v"] diff --git a/scripts/icache_related/icache_ctrlunit_internals.yaml b/scripts/icache_related/icache_ctrlunit_internals.yaml new file mode 100644 index 0000000..52b001c --- /dev/null +++ b/scripts/icache_related/icache_ctrlunit_internals.yaml @@ -0,0 +1,3 @@ +ICacheCtrlUnit: + - "wire io_injecting_0" + - "wire eccctrl_istatus" diff --git a/scripts/icache_related/icache_icache_internals.yaml b/scripts/icache_related/icache_icache_internals.yaml new file mode 100644 index 0000000..3715dfc --- /dev/null +++ b/scripts/icache_related/icache_icache_internals.yaml @@ -0,0 +1,2 @@ +ICache: + - "wire _metaArray_io_read_ready" diff --git a/scripts/icache_related/icache_iprefetchpipe_internals.yaml b/scripts/icache_related/icache_iprefetchpipe_internals.yaml new file mode 100644 index 0000000..9511aa6 --- /dev/null +++ b/scripts/icache_related/icache_iprefetchpipe_internals.yaml @@ -0,0 +1,2 @@ +IPrefetchPipe: + - "wire s1_flush" diff --git a/scripts/icache_related/icache_mainpipe_internals.yaml b/scripts/icache_related/icache_mainpipe_internals.yaml new file mode 100644 index 0000000..efdcce6 --- /dev/null +++ b/scripts/icache_related/icache_mainpipe_internals.yaml @@ -0,0 +1,5 @@ +ICacheMainPipe: + - "wire s2_fire" + - "wire _toMSHRArbiter_io_in_0_valid_T_4" + - "wire _toMSHRArbiter_io_in_1_valid_T_4" + - "reg s2_valid" diff --git a/scripts/icache_related/icache_missunit_internals.yaml b/scripts/icache_related/icache_missunit_internals.yaml new file mode 100644 index 0000000..bf1a1dd --- /dev/null +++ b/scripts/icache_related/icache_missunit_internals.yaml @@ -0,0 +1,30 @@ +ICacheMissUnit: + - "wire _prefetchMSHRs_9_io_req_ready" + - "wire _prefetchMSHRs_9_io_acquire_valid" + - "wire _prefetchMSHRs_8_io_req_ready" + - "wire _prefetchMSHRs_8_io_acquire_valid" + - "wire _prefetchMSHRs_7_io_req_ready" + - "wire _prefetchMSHRs_7_io_acquire_valid" + - "wire _prefetchMSHRs_6_io_req_ready" + - "wire _prefetchMSHRs_6_io_acquire_valid" + - "wire _prefetchMSHRs_5_io_req_ready" + - "wire _prefetchMSHRs_5_io_acquire_valid" + - "wire _prefetchMSHRs_4_io_req_ready" + - "wire _prefetchMSHRs_4_io_acquire_valid" + - "wire _prefetchMSHRs_3_io_req_ready" + - "wire _prefetchMSHRs_3_io_acquire_valid" + - "wire _prefetchMSHRs_2_io_req_ready" + - "wire _prefetchMSHRs_2_io_acquire_valid" + - "wire _prefetchMSHRs_1_io_req_ready" + - "wire _prefetchMSHRs_1_io_acquire_valid" + - "wire _prefetchMSHRs_0_io_req_ready" + - "wire _prefetchMSHRs_0_io_acquire_valid" + + - "wire _fetchMSHRs_3_io_req_ready" + - "wire _fetchMSHRs_3_io_acquire_valid" + - "wire _fetchMSHRs_2_io_req_ready" + - "wire _fetchMSHRs_2_io_acquire_valid" + - "wire _fetchMSHRs_1_io_req_ready" + - "wire _fetchMSHRs_1_io_acquire_valid" + - "wire _fetchMSHRs_0_io_req_ready" + - "wire _fetchMSHRs_0_io_acquire_valid" \ No newline at end of file diff --git a/scripts/icache_related/icache_waylookup_internals.yaml b/scripts/icache_related/icache_waylookup_internals.yaml new file mode 100644 index 0000000..58265f1 --- /dev/null +++ b/scripts/icache_related/icache_waylookup_internals.yaml @@ -0,0 +1,7 @@ +WayLookup: + - "reg readPtr_flag" + - "reg readPtr_value" + - "reg writePtr_flag" + - "reg writePtr_value" + - "wire io_write_ready_0" + - "reg entries_30_waymask_0" diff --git a/ut_frontend/icache/ctrlunit/__init__.py b/ut_frontend/icache/ctrlunit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ut_frontend/icache/ctrlunit/agent/__init__.py b/ut_frontend/icache/ctrlunit/agent/__init__.py new file mode 100644 index 0000000..ac88d4b --- /dev/null +++ b/ut_frontend/icache/ctrlunit/agent/__init__.py @@ -0,0 +1 @@ +from .ctrlunit_agent import CtrlUnitAgent \ No newline at end of file diff --git a/ut_frontend/icache/ctrlunit/agent/ctrlunit_agent.py b/ut_frontend/icache/ctrlunit/agent/ctrlunit_agent.py new file mode 100644 index 0000000..e517be4 --- /dev/null +++ b/ut_frontend/icache/ctrlunit/agent/ctrlunit_agent.py @@ -0,0 +1,20 @@ +from toffee import Agent +from..bundle import CtrlUnitBundle + +class CtrlUnitAgent(Agent): + def __init__(self, bundle: CtrlUnitBundle): + super().__init__(bundle) + bundle.set_all(0) + self.bundle = bundle + + async def set_opcode(self, set_value): + self.bundle.reset.value = 1 + await self.bundle.step() + self.bundle.reset.value = 0 + await self.bundle.step() + self.bundle.auto_in._a._valid.value = 1 + print("\nBefore setting, auto_in_a_bits_opcode is: ",self.bundle.auto_in._a._bits._opcode.value) + self.bundle.auto_in._a._bits._opcode.value = set_value + print("After setting, auto_in_a_bits_opcode is :",self.bundle.auto_in._a._bits._opcode.value) + await self.bundle.step(10) + print("Output auto_in_d_bits_opcode is :",self.bundle.auto_in._d._bits._opcode.value) \ No newline at end of file diff --git a/ut_frontend/icache/ctrlunit/bundle/__init__.py b/ut_frontend/icache/ctrlunit/bundle/__init__.py new file mode 100644 index 0000000..f7bf33d --- /dev/null +++ b/ut_frontend/icache/ctrlunit/bundle/__init__.py @@ -0,0 +1 @@ +from .ctrlunit_bundle import CtrlUnitBundle \ No newline at end of file diff --git a/ut_frontend/icache/ctrlunit/bundle/ctrlunit_bundle.py b/ut_frontend/icache/ctrlunit/bundle/ctrlunit_bundle.py new file mode 100644 index 0000000..237a6de --- /dev/null +++ b/ut_frontend/icache/ctrlunit/bundle/ctrlunit_bundle.py @@ -0,0 +1,82 @@ +from toffee import Bundle, Signals, Signal + +class _0Bundle(Bundle): + _0 = Signal() + +class _1Bundle(Bundle): + _io_injecting = _0Bundle.from_prefix("_io_injecting") + _eccctrl_istatus = Signal() + +class _2Bundle(Bundle): + _address, _opcode, _mask, _data, _size, _source = Signals(6) + +class _3Bundle(Bundle): + _bits = _2Bundle.from_prefix("_bits") + _ready, _valid = Signals(2) + +class _4Bundle(Bundle): + _opcode, _size, _data, _source = Signals(4) + +class _5Bundle(Bundle): + _bits = _4Bundle.from_prefix("_bits") + _ready, _valid = Signals(2) + +class _6Bundle(Bundle): + _a = _3Bundle.from_prefix("_a") + _d = _5Bundle.from_prefix("_d") + +class _7Bundle(Bundle): + _waymask, _virIdx = Signals(2) + +class _8Bundle(Bundle): + _bits = _7Bundle.from_prefix("_bits") + _ready, _valid = Signals(2) + +class _9Bundle(Bundle): + _1, _0 = Signals(2) + +class _10Bundle(Bundle): + _bits_vSetIdx = _9Bundle.from_prefix("_bits_vSetIdx") + _ready, _valid = Signals(2) + +class _11Bundle(Bundle): + _1, _2, _0, _3 = Signals(4) + +class _12Bundle(Bundle): + _0 = _11Bundle.from_prefix("_0") + +class _13Bundle(Bundle): + _tag = Signal() + +class _14Bundle(Bundle): + _1 = _13Bundle.from_prefix("_1") + _2 = _13Bundle.from_prefix("_2") + _3 = _13Bundle.from_prefix("_3") + _0 = _13Bundle.from_prefix("_0") + +class _15Bundle(Bundle): + _0 = _14Bundle.from_prefix("_0") + +class _16Bundle(Bundle): + _entryValid = _12Bundle.from_prefix("_entryValid") + _metas = _15Bundle.from_prefix("_metas") + +class _17Bundle(Bundle): + _phyTag, _waymask, _virIdx, _bankIdx = Signals(4) + +class _18Bundle(Bundle): + _bits = _17Bundle.from_prefix("_bits") + _ready, _valid = Signals(2) + +class _19Bundle(Bundle): + _dataWrite = _8Bundle.from_prefix("_dataWrite") + _metaRead = _10Bundle.from_prefix("_metaRead") + _metaReadResp = _16Bundle.from_prefix("_metaReadResp") + _metaWrite = _18Bundle.from_prefix("_metaWrite") + _ecc_enable, _injecting = Signals(2) + +class CtrlUnitBundle(Bundle): + io = _19Bundle.from_prefix("io") + ICacheCtrlUnit = _1Bundle.from_prefix("ICacheCtrlUnit") + auto_in = _6Bundle.from_prefix("auto_in") + clock, reset = Signals(2) diff --git a/ut_frontend/icache/ctrlunit/env/__init__.py b/ut_frontend/icache/ctrlunit/env/__init__.py new file mode 100644 index 0000000..db9d1bc --- /dev/null +++ b/ut_frontend/icache/ctrlunit/env/__init__.py @@ -0,0 +1 @@ +from .ctrlunit_env import CtrlUnitEnv \ No newline at end of file diff --git a/ut_frontend/icache/ctrlunit/env/ctrlunit_env.py b/ut_frontend/icache/ctrlunit/env/ctrlunit_env.py new file mode 100644 index 0000000..0e42a72 --- /dev/null +++ b/ut_frontend/icache/ctrlunit/env/ctrlunit_env.py @@ -0,0 +1,13 @@ +from toffee import Env +from dut.ICacheCtrlUnit import DUTICacheCtrlUnit +from ..agent import CtrlUnitAgent +from ..bundle import CtrlUnitBundle + + +class CtrlUnitEnv(Env): + def __init__(self, dut: DUTICacheCtrlUnit): + super().__init__() + self.dut = dut + self.bundle = CtrlUnitBundle.from_prefix("").bind(dut) + self.agent = CtrlUnitAgent(self.bundle) + self.bundle.set_all(0) diff --git a/ut_frontend/icache/ctrlunit/test/__init__.py b/ut_frontend/icache/ctrlunit/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ut_frontend/icache/ctrlunit/test/ctrlunit_fixture.py b/ut_frontend/icache/ctrlunit/test/ctrlunit_fixture.py new file mode 100644 index 0000000..b9750d1 --- /dev/null +++ b/ut_frontend/icache/ctrlunit/test/ctrlunit_fixture.py @@ -0,0 +1,25 @@ +import asyncio +import toffee +import toffee_test +from toffee import start_clock +from dut.ICacheCtrlUnit import DUTICacheCtrlUnit +from ..env import CtrlUnitEnv + + +@toffee_test.fixture +async def ctrlunit_env(toffee_request: toffee_test.ToffeeRequest): + toffee.setup_logging(toffee.ERROR) + dut = toffee_request.create_dut(DUTICacheCtrlUnit) + dut.InitClock("clock") + start_clock(dut) + ctrlunit_env = CtrlUnitEnv(dut) + yield ctrlunit_env + + cur_loop = asyncio.get_event_loop() + for task in asyncio.all_tasks(cur_loop): + if task.get_name() == "__clock_loop": + task.cancel() + try: + await task + except asyncio.CancelledError: + break diff --git a/ut_frontend/icache/ctrlunit/test/ctrlunit_test.py b/ut_frontend/icache/ctrlunit/test/ctrlunit_test.py new file mode 100644 index 0000000..47f3485 --- /dev/null +++ b/ut_frontend/icache/ctrlunit/test/ctrlunit_test.py @@ -0,0 +1,10 @@ +from .ctrlunit_fixture import ctrlunit_env +from ..env import CtrlUnitEnv +import toffee_test + + +@toffee_test.testcase +async def test_smoke(ctrlunit_env: CtrlUnitEnv): + + + await ctrlunit_env.agent.set_opcode(4) diff --git a/ut_frontend/icache/icache/__init__.py b/ut_frontend/icache/icache/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ut_frontend/icache/icache/agent/__init__.py b/ut_frontend/icache/icache/agent/__init__.py new file mode 100644 index 0000000..8d45360 --- /dev/null +++ b/ut_frontend/icache/icache/agent/__init__.py @@ -0,0 +1 @@ +from .icache_agent import ICacheAgent diff --git a/ut_frontend/icache/icache/agent/icache_agent.py b/ut_frontend/icache/icache/agent/icache_agent.py new file mode 100644 index 0000000..71e3fcd --- /dev/null +++ b/ut_frontend/icache/icache/agent/icache_agent.py @@ -0,0 +1,23 @@ +from toffee import Agent +from ..bundle import ICacheBundle + + +class ICacheAgent(Agent): + def __init__(self, bundle: ICacheBundle): + super().__init__(bundle) + self.bundle = bundle + + async def fencei_meta_array_func(self, value): + + print( + f"\nBefore setting fencei: ICacheMetaArray.io_read_ready.value is:", + self.bundle.ICache__metaArray_io_read_ready.value, + ) + + self.bundle.io._fencei.value = value + await self.bundle.step() + + print( + f"\nAfter setting fencei = {value}: ICacheMetaArray.io_read_ready.value is:", + self.bundle.ICache__metaArray_io_read_ready.value, + ) diff --git a/ut_frontend/icache/icache/bundle/__init__.py b/ut_frontend/icache/icache/bundle/__init__.py new file mode 100644 index 0000000..e00bd26 --- /dev/null +++ b/ut_frontend/icache/icache/bundle/__init__.py @@ -0,0 +1 @@ +from .icache_bundle import ICacheBundle diff --git a/ut_frontend/icache/icache/bundle/icache_bundle.py b/ut_frontend/icache/icache/bundle/icache_bundle.py new file mode 100644 index 0000000..ce5fe85 --- /dev/null +++ b/ut_frontend/icache/icache/bundle/icache_bundle.py @@ -0,0 +1,209 @@ +from toffee import Bundle, Signals, Signal + +class _0Bundle(Bundle): + _address, _source = Signals(2) + +class _1Bundle(Bundle): + _bits = _0Bundle.from_prefix("_bits") + _valid, _ready = Signals(2) + +class _2Bundle(Bundle): + _size, _data, _source, _opcode, _corrupt = Signals(5) + +class _3Bundle(Bundle): + _bits = _2Bundle.from_prefix("_bits") + _valid = Signal() + +class _4Bundle(Bundle): + _a = _1Bundle.from_prefix("_a") + _d = _3Bundle.from_prefix("_d") + +class _5Bundle(Bundle): + _size, _mask, _data, _source, _opcode, _address = Signals(6) + +class _6Bundle(Bundle): + _bits = _5Bundle.from_prefix("_bits") + _valid, _ready = Signals(2) + +class _7Bundle(Bundle): + _opcode, _source, _size, _data = Signals(4) + +class _8Bundle(Bundle): + _bits = _7Bundle.from_prefix("_bits") + _ready, _valid = Signals(2) + +class _9Bundle(Bundle): + _d = _8Bundle.from_prefix("_d") + _a = _6Bundle.from_prefix("_a") + +class _10Bundle(Bundle): + _ctrlUnitOpt_in = _9Bundle.from_prefix("_ctrlUnitOpt_in") + _client_out = _4Bundle.from_prefix("_client_out") + +class _11Bundle(Bundle): + _addr_rd, _indata, _writeen, _ack, _readen, _be, _all, _outdata, _array, _req = Signals(10) + +class _12Bundle(Bundle): + _1 = _11Bundle.from_prefix("_1") + _3 = _11Bundle.from_prefix("_3") + _2 = _11Bundle.from_prefix("_2") + _4 = _11Bundle.from_prefix("_4") + _addr_rd, _indata, _writeen, _ack, _readen, _be, _all, _outdata, _array, _req = Signals(10) + +class _13Bundle(Bundle): + _report_to_beu, _paddr = Signals(2) + +class _14Bundle(Bundle): + _bits = _13Bundle.from_prefix("_bits") + _valid = Signal() + +class _15Bundle(Bundle): + _startAddr, _nextlineStart = Signals(2) + +class _16Bundle(Bundle): + _3 = _15Bundle.from_prefix("_3") + _2 = _15Bundle.from_prefix("_2") + _1 = _15Bundle.from_prefix("_1") + _4 = _15Bundle.from_prefix("_4") + _0 = _15Bundle.from_prefix("_0") + +class _17Bundle(Bundle): + _0, _3, _1, _4, _2 = Signals(5) + +class _18Bundle(Bundle): + _pcMemRead = _16Bundle.from_prefix("_pcMemRead") + _readValid = _17Bundle.from_prefix("_readValid") + _backendException = Signal() + +class _19Bundle(Bundle): + _bits = _18Bundle.from_prefix("_bits") + _valid, _ready = Signals(2) + +class _20Bundle(Bundle): + _0, _1 = Signals(2) + +class _21Bundle(Bundle): + _0 = Signal() + +class _22Bundle(Bundle): + _exception = _20Bundle.from_prefix("_exception") + _itlb_pbmt = _20Bundle.from_prefix("_itlb_pbmt") + _pmp_mmio = _20Bundle.from_prefix("_pmp_mmio") + _vaddr = _20Bundle.from_prefix("_vaddr") + _paddr = _21Bundle.from_prefix("_paddr") + _data, _gpaddr, _backendException, _isForVSnonLeafPTE, _doubleline = Signals(5) + +class _23Bundle(Bundle): + _bits = _22Bundle.from_prefix("_bits") + _valid = Signal() + +class _24Bundle(Bundle): + _req = _19Bundle.from_prefix("_req") + _resp = _23Bundle.from_prefix("_resp") + _topdownItlbMiss, _topdownIcacheMiss = Signals(2) + +class _25Bundle(Bundle): + _value, _flag = Signals(2) + +class _26Bundle(Bundle): + _bits = _25Bundle.from_prefix("_bits") + _valid = Signal() + +class _27Bundle(Bundle): + _s3 = _26Bundle.from_prefix("_s3") + _s2 = _26Bundle.from_prefix("_s2") + +class _28Bundle(Bundle): + _ftqIdx = _25Bundle.from_prefix("_ftqIdx") + _startAddr, _nextlineStart = Signals(2) + +class _29Bundle(Bundle): + _bits = _28Bundle.from_prefix("_bits") + _valid, _ready = Signals(2) + +class _30Bundle(Bundle): + _req = _29Bundle.from_prefix("_req") + _flushFromBpu = _27Bundle.from_prefix("_flushFromBpu") + _backendException = Signal() + +class _31Bundle(Bundle): + _valid, _bits_vaddr = Signals(2) + +class _32Bundle(Bundle): + _gpf_instr, _pf_instr, _af_instr = Signals(3) + +class _33Bundle(Bundle): + _0 = _32Bundle.from_prefix("_0") + +class _34Bundle(Bundle): + _pbmt = _21Bundle.from_prefix("_pbmt") + _paddr = _21Bundle.from_prefix("_paddr") + _gpaddr = _21Bundle.from_prefix("_gpaddr") + _excp = _33Bundle.from_prefix("_excp") + _miss, _isForVSnonLeafPTE = Signals(2) + +class _35Bundle(Bundle): + _req = _31Bundle.from_prefix("_req") + _resp_bits = _34Bundle.from_prefix("_resp_bits") + +class _36Bundle(Bundle): + _1 = _35Bundle.from_prefix("_1") + _0 = _35Bundle.from_prefix("_0") + +class _37Bundle(Bundle): + _1 = Signal() + +class _38Bundle(Bundle): + _miss = _37Bundle.from_prefix("_miss") + _except = _37Bundle.from_prefix("_except") + _hit = _37Bundle.from_prefix("_hit") + +class _39Bundle(Bundle): + _0 = _38Bundle.from_prefix("_0") + +class _40Bundle(Bundle): + _hit, _miss = Signals(2) + +class _41Bundle(Bundle): + _0 = _40Bundle.from_prefix("_0") + +class _42Bundle(Bundle): + _bank_hit = _20Bundle.from_prefix("_bank_hit") + _only = _41Bundle.from_prefix("_only") + _miss = _39Bundle.from_prefix("_miss") + _except = _21Bundle.from_prefix("_except") + _hit = Signal() + +class _43Bundle(Bundle): + _instr, _mmio = Signals(2) + +class _44Bundle(Bundle): + _resp = _43Bundle.from_prefix("_resp") + _req_bits_addr = Signal() + +class _45Bundle(Bundle): + _1 = _44Bundle.from_prefix("_1") + _3 = _44Bundle.from_prefix("_3") + _2 = _44Bundle.from_prefix("_2") + _0 = _44Bundle.from_prefix("_0") + +class _46Bundle(Bundle): + _1 = _31Bundle.from_prefix("_1") + _2 = _31Bundle.from_prefix("_2") + _0 = _31Bundle.from_prefix("_0") + +class _47Bundle(Bundle): + _error = _14Bundle.from_prefix("_error") + _pmp = _45Bundle.from_prefix("_pmp") + _itlb = _36Bundle.from_prefix("_itlb") + _perfInfo = _42Bundle.from_prefix("_perfInfo") + _fetch = _24Bundle.from_prefix("_fetch") + _softPrefetch = _46Bundle.from_prefix("_softPrefetch") + _ftqPrefetch = _30Bundle.from_prefix("_ftqPrefetch") + _fencei, _flush, _toIFU, _hartId, _stop, _csr_pf_enable, _itlbFlushPipe = Signals(7) + +class ICacheBundle(Bundle): + io = _47Bundle.from_prefix("io") + boreChildrenBd_bore = _12Bundle.from_prefix("boreChildrenBd_bore") + auto = _10Bundle.from_prefix("auto") + reset, ICache__metaArray_io_read_ready, clock = Signals(3) diff --git a/ut_frontend/icache/icache/env/__init__.py b/ut_frontend/icache/icache/env/__init__.py new file mode 100644 index 0000000..91d0427 --- /dev/null +++ b/ut_frontend/icache/icache/env/__init__.py @@ -0,0 +1 @@ +from .icache_env import ICacheEnv diff --git a/ut_frontend/icache/icache/env/icache_env.py b/ut_frontend/icache/icache/env/icache_env.py new file mode 100644 index 0000000..fd63b54 --- /dev/null +++ b/ut_frontend/icache/icache/env/icache_env.py @@ -0,0 +1,13 @@ +from toffee import Env +from dut.ICache import DUTICache +from ..agent import ICacheAgent +from ..bundle import ICacheBundle + + +class ICacheEnv(Env): + def __init__(self, dut: DUTICache): + super().__init__() + self.dut = dut + self.bundle = ICacheBundle.from_prefix("").bind(dut) + self.agent = ICacheAgent(self.bundle) + self.bundle.set_all(0) diff --git a/ut_frontend/icache/icache/test/__init__.py b/ut_frontend/icache/icache/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ut_frontend/icache/icache/test/icache_fixture.py b/ut_frontend/icache/icache/test/icache_fixture.py new file mode 100644 index 0000000..2c7ac00 --- /dev/null +++ b/ut_frontend/icache/icache/test/icache_fixture.py @@ -0,0 +1,25 @@ +import asyncio +import toffee +import toffee_test +from toffee import start_clock +from dut.ICache import DUTICache +from ..env import ICacheEnv + + +@toffee_test.fixture +async def icache_env(toffee_request: toffee_test.ToffeeRequest): + toffee.setup_logging(toffee.ERROR) + dut = toffee_request.create_dut(DUTICache) + dut.InitClock("clock") + start_clock(dut) + icache_env = ICacheEnv(dut) + yield icache_env + + cur_loop = asyncio.get_event_loop() + for task in asyncio.all_tasks(cur_loop): + if task.get_name() == "__clock_loop": + task.cancel() + try: + await task + except asyncio.CancelledError: + break diff --git a/ut_frontend/icache/icache/test/icache_test.py b/ut_frontend/icache/icache/test/icache_test.py new file mode 100644 index 0000000..0da6770 --- /dev/null +++ b/ut_frontend/icache/icache/test/icache_test.py @@ -0,0 +1,8 @@ +from .icache_fixture import icache_env +from ..env import ICacheEnv +import toffee_test + + +@toffee_test.testcase +async def test_smoke(icache_env: ICacheEnv): + await icache_env.agent.fencei_meta_array_func(1) diff --git a/ut_frontend/icache/iprefetchpipe/__init__.py b/ut_frontend/icache/iprefetchpipe/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ut_frontend/icache/iprefetchpipe/agent/__init__.py b/ut_frontend/icache/iprefetchpipe/agent/__init__.py new file mode 100644 index 0000000..38390e7 --- /dev/null +++ b/ut_frontend/icache/iprefetchpipe/agent/__init__.py @@ -0,0 +1 @@ +from .iprefetchpipe_agent import IPrefetchPipeAgent \ No newline at end of file diff --git a/ut_frontend/icache/iprefetchpipe/agent/iprefetchpipe_agent.py b/ut_frontend/icache/iprefetchpipe/agent/iprefetchpipe_agent.py new file mode 100644 index 0000000..77c24f8 --- /dev/null +++ b/ut_frontend/icache/iprefetchpipe/agent/iprefetchpipe_agent.py @@ -0,0 +1,22 @@ +from toffee import Agent +from ..bundle import IPrefetchPipeBundle + + +class IPrefetchPipeAgent(Agent): + def __inti__(self, bundle: IPrefetchPipeBundle): + super().__init__(bundle) + bundle.set_all(0) + self.bundle = bundle + + async def set_s1_flush(self): + self.bundle.reset.value = 1 + await self.bundle.step() + self.bundle.reset.value = 0 + await self.bundle.step() + + print( + "\nBefore setting, s1_flush is: ", self.bundle.IPrefetchPipe_s1_flush.value + ) + self.bundle.io._flush.value = 1 + await self.bundle.step() + print("After setting, s1_flush is: ", self.bundle.IPrefetchPipe_s1_flush.value) diff --git a/ut_frontend/icache/iprefetchpipe/bundle/__init__.py b/ut_frontend/icache/iprefetchpipe/bundle/__init__.py new file mode 100644 index 0000000..77c624e --- /dev/null +++ b/ut_frontend/icache/iprefetchpipe/bundle/__init__.py @@ -0,0 +1 @@ +from .iprefetchpipe_bundle import IPrefetchPipeBundle diff --git a/ut_frontend/icache/iprefetchpipe/bundle/iprefetchpipe_bundle.py b/ut_frontend/icache/iprefetchpipe/bundle/iprefetchpipe_bundle.py new file mode 100644 index 0000000..736e306 --- /dev/null +++ b/ut_frontend/icache/iprefetchpipe/bundle/iprefetchpipe_bundle.py @@ -0,0 +1,185 @@ +from toffee import Bundle, Signals, Signal + + +class _0Bundle(Bundle): + _vSetIdx, _blkPaddr = Signals(2) + + +class _1Bundle(Bundle): + _bits = _0Bundle.from_prefix("_bits") + _valid, _ready = Signals(2) + + +class _2Bundle(Bundle): + _vSetIdx, _corrupt, _blkPaddr, _waymask = Signals(4) + + +class _3Bundle(Bundle): + _bits = _2Bundle.from_prefix("_bits") + _valid = Signal() + + +class _4Bundle(Bundle): + _flag, _value = Signals(2) + + +class _5Bundle(Bundle): + _bits = _4Bundle.from_prefix("_bits") + _valid = Signal() + + +class _6Bundle(Bundle): + _s3 = _5Bundle.from_prefix("_s3") + _s2 = _5Bundle.from_prefix("_s2") + + +class _7Bundle(Bundle): + _bits_vaddr, _valid = Signals(2) + + +class _8Bundle(Bundle): + _pf_instr, _af_instr, _gpf_instr = Signals(3) + + +class _9Bundle(Bundle): + _0 = _8Bundle.from_prefix("_0") + + +class _10Bundle(Bundle): + _0 = Signal() + + +class _11Bundle(Bundle): + _pbmt = _10Bundle.from_prefix("_pbmt") + _gpaddr = _10Bundle.from_prefix("_gpaddr") + _paddr = _10Bundle.from_prefix("_paddr") + _excp = _9Bundle.from_prefix("_excp") + _isForVSnonLeafPTE, _miss = Signals(2) + + +class _12Bundle(Bundle): + _resp_bits = _11Bundle.from_prefix("_resp_bits") + _req = _7Bundle.from_prefix("_req") + + +class _13Bundle(Bundle): + _1 = _12Bundle.from_prefix("_1") + _0 = _12Bundle.from_prefix("_0") + + +class _14Bundle(Bundle): + _0, _1, _3, _2 = Signals(4) + + +class _15Bundle(Bundle): + _0 = _14Bundle.from_prefix("_0") + _1 = _14Bundle.from_prefix("_1") + + +class _16Bundle(Bundle): + _tag = Signal() + + +class _17Bundle(Bundle): + _1 = _16Bundle.from_prefix("_1") + _0 = _16Bundle.from_prefix("_0") + _2 = _16Bundle.from_prefix("_2") + _3 = _16Bundle.from_prefix("_3") + + +class _18Bundle(Bundle): + _1 = _17Bundle.from_prefix("_1") + _0 = _17Bundle.from_prefix("_0") + + +class _19Bundle(Bundle): + _entryValid = _15Bundle.from_prefix("_entryValid") + _codes = _15Bundle.from_prefix("_codes") + _metas = _18Bundle.from_prefix("_metas") + + +class _20Bundle(Bundle): + _0, _1 = Signals(2) + + +class _21Bundle(Bundle): + _vSetIdx = _20Bundle.from_prefix("_vSetIdx") + _isDoubleLine = Signal() + + +class _22Bundle(Bundle): + _bits = _21Bundle.from_prefix("_bits") + _valid, _ready = Signals(2) + + +class _23Bundle(Bundle): + _fromIMeta = _19Bundle.from_prefix("_fromIMeta") + _toIMeta = _22Bundle.from_prefix("_toIMeta") + + +class _24Bundle(Bundle): + _mmio, _instr = Signals(2) + + +class _25Bundle(Bundle): + _resp = _24Bundle.from_prefix("_resp") + _req_bits_addr = Signal() + + +class _26Bundle(Bundle): + _1 = _25Bundle.from_prefix("_1") + _0 = _25Bundle.from_prefix("_0") + + +class _27Bundle(Bundle): + _ftqIdx = _4Bundle.from_prefix("_ftqIdx") + _backendException, _startAddr, _isSoftPrefetch, _nextlineStart = Signals(4) + + +class _28Bundle(Bundle): + _bits = _27Bundle.from_prefix("_bits") + _valid, _ready = Signals(2) + + +class _29Bundle(Bundle): + _pbmt = _20Bundle.from_prefix("_pbmt") + _exception = _20Bundle.from_prefix("_exception") + + +class _30Bundle(Bundle): + _waymask = _20Bundle.from_prefix("_waymask") + _meta_codes = _20Bundle.from_prefix("_meta_codes") + _vSetIdx = _20Bundle.from_prefix("_vSetIdx") + _ptag = _20Bundle.from_prefix("_ptag") + _itlb = _29Bundle.from_prefix("_itlb") + + +class _31Bundle(Bundle): + _gpaddr, _isForVSnonLeafPTE = Signals(2) + + +class _32Bundle(Bundle): + _gpf = _31Bundle.from_prefix("_gpf") + _entry = _30Bundle.from_prefix("_entry") + + +class _33Bundle(Bundle): + _bits = _32Bundle.from_prefix("_bits") + _valid, _ready = Signals(2) + + +class _34Bundle(Bundle): + _MSHRReq = _1Bundle.from_prefix("_MSHRReq") + _itlb = _13Bundle.from_prefix("_itlb") + _flushFromBpu = _6Bundle.from_prefix("_flushFromBpu") + _metaRead = _23Bundle.from_prefix("_metaRead") + _wayLookupWrite = _33Bundle.from_prefix("_wayLookupWrite") + _req = _28Bundle.from_prefix("_req") + _pmp = _26Bundle.from_prefix("_pmp") + _MSHRResp = _3Bundle.from_prefix("_MSHRResp") + _csr_pf_enable, _itlbFlushPipe, _flush = Signals(3) + + +class IPrefetchPipeBundle(Bundle): + io = _34Bundle.from_prefix("io") + clock, IPrefetchPipe_s1_flush, reset = Signals(3) diff --git a/ut_frontend/icache/iprefetchpipe/env/__init__.py b/ut_frontend/icache/iprefetchpipe/env/__init__.py new file mode 100644 index 0000000..39226bf --- /dev/null +++ b/ut_frontend/icache/iprefetchpipe/env/__init__.py @@ -0,0 +1 @@ +from .iprefetchpipe_env import IPrefetchPipeEnv \ No newline at end of file diff --git a/ut_frontend/icache/iprefetchpipe/env/iprefetchpipe_env.py b/ut_frontend/icache/iprefetchpipe/env/iprefetchpipe_env.py new file mode 100644 index 0000000..eb57034 --- /dev/null +++ b/ut_frontend/icache/iprefetchpipe/env/iprefetchpipe_env.py @@ -0,0 +1,13 @@ +from toffee import Env +from dut.IPrefetchPipe import DUTIPrefetchPipe +from ..agent import IPrefetchPipeAgent +from ..bundle import IPrefetchPipeBundle + + +class IPrefetchPipeEnv(Env): + def __init__(self, dut: DUTIPrefetchPipe): + super().__init__() + self.dut = dut + self.bundle = IPrefetchPipeBundle.from_prefix("").bind(dut) + self.agent = IPrefetchPipeAgent(self.bundle) + self.bundle.set_all(0) diff --git a/ut_frontend/icache/iprefetchpipe/test/__init__.py b/ut_frontend/icache/iprefetchpipe/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ut_frontend/icache/iprefetchpipe/test/iprefetchpipe_fixture.py b/ut_frontend/icache/iprefetchpipe/test/iprefetchpipe_fixture.py new file mode 100644 index 0000000..16dede1 --- /dev/null +++ b/ut_frontend/icache/iprefetchpipe/test/iprefetchpipe_fixture.py @@ -0,0 +1,24 @@ +import asyncio +import toffee +import toffee_test +from toffee import start_clock +from dut.IPrefetchPipe import DUTIPrefetchPipe +from ..env import IPrefetchPipeEnv + +@toffee_test.fixture +async def iprefetchpipe_env(toffee_request: toffee_test.ToffeeRequest): + toffee.setup_logging(toffee.ERROR) + dut = toffee_request.create_dut(DUTIPrefetchPipe) + dut.InitClock("clock") + start_clock(dut) + iprefetchpipe_env = IPrefetchPipeEnv(dut) + yield iprefetchpipe_env + + cur = asyncio.get_event_loop() + for task in asyncio.all_tasks(cur): + if task.get_name() == "__clock_loop": + task.cancel() + try: + await task + except asyncio.CancelledError: + break \ No newline at end of file diff --git a/ut_frontend/icache/iprefetchpipe/test/iprefetchpipe_test.py b/ut_frontend/icache/iprefetchpipe/test/iprefetchpipe_test.py new file mode 100644 index 0000000..47d417f --- /dev/null +++ b/ut_frontend/icache/iprefetchpipe/test/iprefetchpipe_test.py @@ -0,0 +1,8 @@ +from .iprefetchpipe_fixture import iprefetchpipe_env +from ..env import IPrefetchPipeEnv +import toffee_test + + +@toffee_test.testcase +async def test_smoke(iprefetchpipe_env: IPrefetchPipeEnv): + await iprefetchpipe_env.agent.set_s1_flush() diff --git a/ut_frontend/icache/mainpipe/__init__.py b/ut_frontend/icache/mainpipe/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ut_frontend/icache/mainpipe/agent/__init__.py b/ut_frontend/icache/mainpipe/agent/__init__.py new file mode 100644 index 0000000..3419acb --- /dev/null +++ b/ut_frontend/icache/mainpipe/agent/__init__.py @@ -0,0 +1 @@ +from .mainpipe_agent import ICacheMainPipeAgent diff --git a/ut_frontend/icache/mainpipe/agent/mainpipe_agent.py b/ut_frontend/icache/mainpipe/agent/mainpipe_agent.py new file mode 100644 index 0000000..3bb21b1 --- /dev/null +++ b/ut_frontend/icache/mainpipe/agent/mainpipe_agent.py @@ -0,0 +1,32 @@ +from toffee import Agent +from ..bundle import ICacheMainPipeBundle + + +class ICacheMainPipeAgent(Agent): + def __init__(self, bundle: ICacheMainPipeBundle): + super().__init__(bundle) + bundle.set_all(0) + self.bundle = bundle + + async def set_flush(self): + + print( + f"\nBefore setting: \n \ + s2_fire is: {self.bundle.ICacheMainPipe_s2._fire.value}\n \ + _toMSHRArbiter_io_in_0_valid is: {self.bundle.ICacheMainPipe__toMSHRArbiter_io_in._0._valid_T._4.value}\n \ + _toMSHRArbiter_io_in_1_valid is: {self.bundle.ICacheMainPipe__toMSHRArbiter_io_in._1._valid_T._4.value}\n \ + s2_valid is: {self.bundle.ICacheMainPipe_s2._valid.value}\n \ + " + ) + + self.bundle.io._flush.value = 1 + await self.bundle.step() + + print( + f"After setting: \n \ + s2_fire is: {self.bundle.ICacheMainPipe_s2._fire.value}\n \ + _toMSHRArbiter_io_in_0_valid is: {self.bundle.ICacheMainPipe__toMSHRArbiter_io_in._0._valid_T._4.value}\n \ + _toMSHRArbiter_io_in_1_valid is: {self.bundle.ICacheMainPipe__toMSHRArbiter_io_in._1._valid_T._4.value}\n \ + s2_valid is: {self.bundle.ICacheMainPipe_s2._valid.value}\n \ + " + ) diff --git a/ut_frontend/icache/mainpipe/bundle/__init__.py b/ut_frontend/icache/mainpipe/bundle/__init__.py new file mode 100644 index 0000000..86b90ec --- /dev/null +++ b/ut_frontend/icache/mainpipe/bundle/__init__.py @@ -0,0 +1 @@ +from .mainpipe_bundle import ICacheMainPipeBundle diff --git a/ut_frontend/icache/mainpipe/bundle/mainpipe_bundle.py b/ut_frontend/icache/mainpipe/bundle/mainpipe_bundle.py new file mode 100644 index 0000000..e6fc0dd --- /dev/null +++ b/ut_frontend/icache/mainpipe/bundle/mainpipe_bundle.py @@ -0,0 +1,282 @@ +from toffee import Bundle, Signals, Signal + + +class _0Bundle(Bundle): + _4 = Signal() + + +class _1Bundle(Bundle): + _valid_T = _0Bundle.from_prefix("_valid_T") + + +class _2Bundle(Bundle): + _0 = _1Bundle.from_prefix("_0") + _1 = _1Bundle.from_prefix("_1") + + +class _3Bundle(Bundle): + _fire, _valid = Signals(2) + + +class _4Bundle(Bundle): + _6, _7, _3, _5, _4, _1, _2, _0 = Signals(8) + + +class _5Bundle(Bundle): + _datas = _4Bundle.from_prefix("_datas") + _codes = _4Bundle.from_prefix("_codes") + + +class _6Bundle(Bundle): + _1, _0 = Signals(2) + + +class _7Bundle(Bundle): + _1, _0, _2, _3 = Signals(4) + + +class _8Bundle(Bundle): + _1 = _7Bundle.from_prefix("_1") + _0 = _7Bundle.from_prefix("_0") + + +class _9Bundle(Bundle): + _waymask = _8Bundle.from_prefix("_waymask") + _vSetIdx = _6Bundle.from_prefix("_vSetIdx") + _blkOffset = Signal() + + +class _10Bundle(Bundle): + _bits = _9Bundle.from_prefix("_bits") + _valid = Signal() + + +class _11Bundle(Bundle): + _bits_vSetIdx = _6Bundle.from_prefix("_bits_vSetIdx") + _valid = Signal() + + +class _12Bundle(Bundle): + _bits_vSetIdx = _6Bundle.from_prefix("_bits_vSetIdx") + _ready, _valid = Signals(2) + + +class _13Bundle(Bundle): + _3 = _12Bundle.from_prefix("_3") + _1 = _11Bundle.from_prefix("_1") + _2 = _11Bundle.from_prefix("_2") + _0 = _10Bundle.from_prefix("_0") + + +class _14Bundle(Bundle): + _fromIData = _5Bundle.from_prefix("_fromIData") + _toIData = _13Bundle.from_prefix("_toIData") + + +class _15Bundle(Bundle): + _report_to_beu, _paddr = Signals(2) + + +class _16Bundle(Bundle): + _bits = _15Bundle.from_prefix("_bits") + _valid = Signal() + + +class _17Bundle(Bundle): + _0 = _16Bundle.from_prefix("_0") + _1 = _16Bundle.from_prefix("_1") + + +class _18Bundle(Bundle): + _startAddr, _nextlineStart = Signals(2) + + +class _19Bundle(Bundle): + _1 = _18Bundle.from_prefix("_1") + _3 = _18Bundle.from_prefix("_3") + _0 = _18Bundle.from_prefix("_0") + _4 = _18Bundle.from_prefix("_4") + _2 = _18Bundle.from_prefix("_2") + + +class _20Bundle(Bundle): + _3, _4, _1, _2, _0 = Signals(5) + + +class _21Bundle(Bundle): + _readValid = _20Bundle.from_prefix("_readValid") + _pcMemRead = _19Bundle.from_prefix("_pcMemRead") + _backendException = Signal() + + +class _22Bundle(Bundle): + _bits = _21Bundle.from_prefix("_bits") + _ready, _valid = Signals(2) + + +class _23Bundle(Bundle): + _0 = Signal() + + +class _24Bundle(Bundle): + _exception = _6Bundle.from_prefix("_exception") + _itlb_pbmt = _6Bundle.from_prefix("_itlb_pbmt") + _vaddr = _6Bundle.from_prefix("_vaddr") + _pmp_mmio = _6Bundle.from_prefix("_pmp_mmio") + _paddr = _23Bundle.from_prefix("_paddr") + _data, _doubleline, _gpaddr, _backendException, _isForVSnonLeafPTE = Signals(5) + + +class _25Bundle(Bundle): + _bits = _24Bundle.from_prefix("_bits") + _valid = Signal() + + +class _26Bundle(Bundle): + _resp = _25Bundle.from_prefix("_resp") + _req = _22Bundle.from_prefix("_req") + _topdownIcacheMiss, _topdownItlbMiss = Signals(2) + + +class _27Bundle(Bundle): + _virIdx, _waymask = Signals(2) + + +class _28Bundle(Bundle): + _bits = _27Bundle.from_prefix("_bits") + _valid = Signal() + + +class _29Bundle(Bundle): + _0 = _28Bundle.from_prefix("_0") + _1 = _28Bundle.from_prefix("_1") + + +class _30Bundle(Bundle): + _blkPaddr, _vSetIdx = Signals(2) + + +class _31Bundle(Bundle): + _bits = _30Bundle.from_prefix("_bits") + _valid, _ready = Signals(2) + + +class _32Bundle(Bundle): + _corrupt, _blkPaddr, _vSetIdx, _data = Signals(4) + + +class _33Bundle(Bundle): + _bits = _32Bundle.from_prefix("_bits") + _valid = Signal() + + +class _34Bundle(Bundle): + _resp = _33Bundle.from_prefix("_resp") + _req = _31Bundle.from_prefix("_req") + + +class _35Bundle(Bundle): + _1 = Signal() + + +class _36Bundle(Bundle): + _miss = _35Bundle.from_prefix("_miss") + _hit = _35Bundle.from_prefix("_hit") + _except = _35Bundle.from_prefix("_except") + + +class _37Bundle(Bundle): + _0 = _36Bundle.from_prefix("_0") + + +class _38Bundle(Bundle): + _hit, _miss = Signals(2) + + +class _39Bundle(Bundle): + _0 = _38Bundle.from_prefix("_0") + + +class _40Bundle(Bundle): + _except = _23Bundle.from_prefix("_except") + _miss = _37Bundle.from_prefix("_miss") + _bank_hit = _6Bundle.from_prefix("_bank_hit") + _only = _39Bundle.from_prefix("_only") + _hit = Signal() + + +class _41Bundle(Bundle): + _mmio, _instr = Signals(2) + + +class _42Bundle(Bundle): + _resp = _41Bundle.from_prefix("_resp") + _req_bits_addr = Signal() + + +class _43Bundle(Bundle): + _0 = _42Bundle.from_prefix("_0") + _1 = _42Bundle.from_prefix("_1") + + +class _44Bundle(Bundle): + _way, _vSetIdx = Signals(2) + + +class _45Bundle(Bundle): + _bits = _44Bundle.from_prefix("_bits") + _valid = Signal() + + +class _46Bundle(Bundle): + _0 = _45Bundle.from_prefix("_0") + _1 = _45Bundle.from_prefix("_1") + + +class _47Bundle(Bundle): + _pbmt = _6Bundle.from_prefix("_pbmt") + _exception = _6Bundle.from_prefix("_exception") + + +class _48Bundle(Bundle): + _waymask = _6Bundle.from_prefix("_waymask") + _ptag = _6Bundle.from_prefix("_ptag") + _vSetIdx = _6Bundle.from_prefix("_vSetIdx") + _meta_codes = _6Bundle.from_prefix("_meta_codes") + _itlb = _47Bundle.from_prefix("_itlb") + + +class _49Bundle(Bundle): + _gpaddr, _isForVSnonLeafPTE = Signals(2) + + +class _50Bundle(Bundle): + _entry = _48Bundle.from_prefix("_entry") + _gpf = _49Bundle.from_prefix("_gpf") + + +class _51Bundle(Bundle): + _bits = _50Bundle.from_prefix("_bits") + _ready, _valid = Signals(2) + + +class _52Bundle(Bundle): + _dataArray = _14Bundle.from_prefix("_dataArray") + _pmp = _43Bundle.from_prefix("_pmp") + _mshr = _34Bundle.from_prefix("_mshr") + _wayLookupRead = _51Bundle.from_prefix("_wayLookupRead") + _perfInfo = _40Bundle.from_prefix("_perfInfo") + _errors = _17Bundle.from_prefix("_errors") + _fetch = _26Bundle.from_prefix("_fetch") + _metaArrayFlush = _29Bundle.from_prefix("_metaArrayFlush") + _touch = _46Bundle.from_prefix("_touch") + _flush, _respStall, _ecc_enable, _hartId = Signals(4) + + +class ICacheMainPipeBundle(Bundle): + ICacheMainPipe_s2 = _3Bundle.from_prefix("ICacheMainPipe_s2") + io = _52Bundle.from_prefix("io") + ICacheMainPipe__toMSHRArbiter_io_in = _2Bundle.from_prefix( + "ICacheMainPipe__toMSHRArbiter_io_in" + ) + clock, reset = Signals(2) diff --git a/ut_frontend/icache/mainpipe/env/__init__.py b/ut_frontend/icache/mainpipe/env/__init__.py new file mode 100644 index 0000000..d9124d7 --- /dev/null +++ b/ut_frontend/icache/mainpipe/env/__init__.py @@ -0,0 +1 @@ +from .mainpipe_env import ICacheMainPipeEnv \ No newline at end of file diff --git a/ut_frontend/icache/mainpipe/env/mainpipe_env.py b/ut_frontend/icache/mainpipe/env/mainpipe_env.py new file mode 100644 index 0000000..3cd95c7 --- /dev/null +++ b/ut_frontend/icache/mainpipe/env/mainpipe_env.py @@ -0,0 +1,12 @@ +from toffee import Env +from dut.ICacheMainPipe import DUTICacheMainPipe +from..agent import ICacheMainPipeAgent +from..bundle import ICacheMainPipeBundle + +class ICacheMainPipeEnv(Env): + def __init__(self, dut: DUTICacheMainPipe): + super().__init__() + self.dut = dut + self.bundle = ICacheMainPipeBundle.from_prefix("").bind(dut) + self.agent = ICacheMainPipeAgent(self.bundle) + self.bundle.set_all(0) \ No newline at end of file diff --git a/ut_frontend/icache/mainpipe/test/__init__.py b/ut_frontend/icache/mainpipe/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ut_frontend/icache/mainpipe/test/mainpiepe_test.py b/ut_frontend/icache/mainpipe/test/mainpiepe_test.py new file mode 100644 index 0000000..b7e7996 --- /dev/null +++ b/ut_frontend/icache/mainpipe/test/mainpiepe_test.py @@ -0,0 +1,7 @@ +from .mainpipe_fixture import icachemainpipe_env +from ..env import ICacheMainPipeEnv +import toffee_test + +@toffee_test.testcase +async def test_smoke(icachemainpipe_env: ICacheMainPipeEnv): + await icachemainpipe_env.agent.set_flush() \ No newline at end of file diff --git a/ut_frontend/icache/mainpipe/test/mainpipe_fixture.py b/ut_frontend/icache/mainpipe/test/mainpipe_fixture.py new file mode 100644 index 0000000..b8e8a51 --- /dev/null +++ b/ut_frontend/icache/mainpipe/test/mainpipe_fixture.py @@ -0,0 +1,25 @@ +import asyncio +import toffee +import toffee_test +from toffee import start_clock +from dut.ICacheMainPipe import DUTICacheMainPipe +from ..env import ICacheMainPipeEnv + + +@toffee_test.fixture +async def icachemainpipe_env(toffee_request: toffee_test.ToffeeRequest): + toffee.setup_logging(toffee.ERROR) + dut = toffee_request.create_dut(DUTICacheMainPipe) + dut.InitClock("clock") + start_clock(dut) + icachemainpipe_env = ICacheMainPipeEnv(dut) + yield icachemainpipe_env + + cur_loop = asyncio.get_event_loop() + for task in asyncio.all_tasks(cur_loop): + if task.get_name() == "__clock_loop": + task.cancel() + try: + await task + except asyncio.CancelledError: + break diff --git a/ut_frontend/icache/missunit/__init__.py b/ut_frontend/icache/missunit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ut_frontend/icache/missunit/agent/__init__.py b/ut_frontend/icache/missunit/agent/__init__.py new file mode 100644 index 0000000..e86f7a2 --- /dev/null +++ b/ut_frontend/icache/missunit/agent/__init__.py @@ -0,0 +1 @@ +from .missunit_agent import ICacheMissUnitAgent diff --git a/ut_frontend/icache/missunit/agent/missunit_agent.py b/ut_frontend/icache/missunit/agent/missunit_agent.py new file mode 100644 index 0000000..33e3ca3 --- /dev/null +++ b/ut_frontend/icache/missunit/agent/missunit_agent.py @@ -0,0 +1,73 @@ +from toffee import Agent +from ..bundle import ICacheMissUnitBundle + + +class ICacheMissUnitAgent(Agent): + def __init__(self, bundle: ICacheMissUnitBundle): + super().__init__(bundle) + self.bundle = bundle + + async def fencei_func(self, value): + + print(f"\nBefore setting fencei :") + + for i in range(10): + print( + f"prefetchMSHRs.{i}.io.req.ready.value:", + getattr( + self.bundle.ICacheMissUnit_._prefetchMSHRs, f"_{i}" + )._io._req_ready.value, + ) + print( + f"prefetchMSHRs.{i}.io.acquire.valid.value:", + getattr( + self.bundle.ICacheMissUnit_._prefetchMSHRs, f"_{i}" + )._io._acquire_valid.value, + ) + + for i in range(4): + print( + f"fetchMSHRs.{i}.io.req.ready.value:", + getattr( + self.bundle.ICacheMissUnit_._fetchMSHRs, f"_{i}" + )._io._req_ready.value, + ) + print( + f"fetchMSHRs.{i}.io.acquire.valid.value:", + getattr( + self.bundle.ICacheMissUnit_._fetchMSHRs, f"_{i}" + )._io._acquire_valid.value, + ) + + self.bundle.io._fencei.value = value + await self.bundle.step() + + print(f"\nAfter setting fencei = {value}") + + for i in range(10): + print( + f"prefetchMSHRs.{i}.io.req.ready.value:", + getattr( + self.bundle.ICacheMissUnit_._prefetchMSHRs, f"_{i}" + )._io._req_ready.value, + ) + print( + f"prefetchMSHRs.{i}.io.acquire.valid.value:", + getattr( + self.bundle.ICacheMissUnit_._prefetchMSHRs, f"_{i}" + )._io._acquire_valid.value, + ) + + for i in range(4): + print( + f"fetchMSHRs.{i}.io.req.ready.value:", + getattr( + self.bundle.ICacheMissUnit_._fetchMSHRs, f"_{i}" + )._io._req_ready.value, + ) + print( + f"fetchMSHRs.{i}.io.acquire.valid.value:", + getattr( + self.bundle.ICacheMissUnit_._fetchMSHRs, f"_{i}" + )._io._acquire_valid.value, + ) diff --git a/ut_frontend/icache/missunit/bundle/__init__.py b/ut_frontend/icache/missunit/bundle/__init__.py new file mode 100644 index 0000000..c371558 --- /dev/null +++ b/ut_frontend/icache/missunit/bundle/__init__.py @@ -0,0 +1 @@ +from .missunit_bundle import ICacheMissUnitBundle diff --git a/ut_frontend/icache/missunit/bundle/missunit_bundle.py b/ut_frontend/icache/missunit/bundle/missunit_bundle.py new file mode 100644 index 0000000..c8f40a3 --- /dev/null +++ b/ut_frontend/icache/missunit/bundle/missunit_bundle.py @@ -0,0 +1,100 @@ +from toffee import Bundle, Signals, Signal + +class _0Bundle(Bundle): + _acquire_valid, _req_ready = Signals(2) + +class _1Bundle(Bundle): + _io = _0Bundle.from_prefix("_io") + +class _2Bundle(Bundle): + _2 = _1Bundle.from_prefix("_2") + _0 = _1Bundle.from_prefix("_0") + _3 = _1Bundle.from_prefix("_3") + _1 = _1Bundle.from_prefix("_1") + +class _3Bundle(Bundle): + _5 = _1Bundle.from_prefix("_5") + _8 = _1Bundle.from_prefix("_8") + _9 = _1Bundle.from_prefix("_9") + _7 = _1Bundle.from_prefix("_7") + _2 = _1Bundle.from_prefix("_2") + _6 = _1Bundle.from_prefix("_6") + _3 = _1Bundle.from_prefix("_3") + _4 = _1Bundle.from_prefix("_4") + _0 = _1Bundle.from_prefix("_0") + _1 = _1Bundle.from_prefix("_1") + +class _4Bundle(Bundle): + _prefetchMSHRs = _3Bundle.from_prefix("_prefetchMSHRs") + _fetchMSHRs = _2Bundle.from_prefix("_fetchMSHRs") + +class _5Bundle(Bundle): + _data, _virIdx, _waymask = Signals(3) + +class _6Bundle(Bundle): + _bits = _5Bundle.from_prefix("_bits") + _valid = Signal() + +class _7Bundle(Bundle): + _vSetIdx, _blkPaddr = Signals(2) + +class _8Bundle(Bundle): + _bits = _7Bundle.from_prefix("_bits") + _valid, _ready = Signals(2) + +class _9Bundle(Bundle): + _waymask, _corrupt, _data, _vSetIdx, _blkPaddr = Signals(5) + +class _10Bundle(Bundle): + _bits = _9Bundle.from_prefix("_bits") + _valid = Signal() + +class _11Bundle(Bundle): + _resp = _10Bundle.from_prefix("_resp") + _req = _8Bundle.from_prefix("_req") + +class _12Bundle(Bundle): + _source, _address = Signals(2) + +class _13Bundle(Bundle): + _bits = _12Bundle.from_prefix("_bits") + _valid, _ready = Signals(2) + +class _14Bundle(Bundle): + _source, _corrupt, _data, _size, _opcode = Signals(5) + +class _15Bundle(Bundle): + _bits = _14Bundle.from_prefix("_bits") + _valid = Signal() + +class _16Bundle(Bundle): + _acquire = _13Bundle.from_prefix("_acquire") + _grant = _15Bundle.from_prefix("_grant") + +class _17Bundle(Bundle): + _bankIdx, _phyTag, _virIdx, _waymask = Signals(4) + +class _18Bundle(Bundle): + _bits = _17Bundle.from_prefix("_bits") + _valid = Signal() + +class _19Bundle(Bundle): + _valid, _bits = Signals(2) + +class _20Bundle(Bundle): + _vSetIdx = _19Bundle.from_prefix("_vSetIdx") + _way = Signal() + +class _21Bundle(Bundle): + _meta_write = _18Bundle.from_prefix("_meta_write") + _mem = _16Bundle.from_prefix("_mem") + _data_write = _6Bundle.from_prefix("_data_write") + _victim = _20Bundle.from_prefix("_victim") + _fetch = _11Bundle.from_prefix("_fetch") + _prefetch_req = _8Bundle.from_prefix("_prefetch_req") + _fencei, _flush, _hartId = Signals(3) + +class ICacheMissUnitBundle(Bundle): + io = _21Bundle.from_prefix("io") + ICacheMissUnit_ = _4Bundle.from_prefix("ICacheMissUnit_") + reset, clock = Signals(2) diff --git a/ut_frontend/icache/missunit/env/__init__.py b/ut_frontend/icache/missunit/env/__init__.py new file mode 100644 index 0000000..672874a --- /dev/null +++ b/ut_frontend/icache/missunit/env/__init__.py @@ -0,0 +1 @@ +from .missunit_env import ICacheMissUnitEnv diff --git a/ut_frontend/icache/missunit/env/missunit_env.py b/ut_frontend/icache/missunit/env/missunit_env.py new file mode 100644 index 0000000..fe42da7 --- /dev/null +++ b/ut_frontend/icache/missunit/env/missunit_env.py @@ -0,0 +1,13 @@ +from toffee import Env +from dut.ICacheMissUnit import DUTICacheMissUnit +from ..agent import ICacheMissUnitAgent +from ..bundle import ICacheMissUnitBundle + + +class ICacheMissUnitEnv(Env): + def __init__(self, dut: DUTICacheMissUnit): + super().__init__() + self.dut = dut + self.bundle = ICacheMissUnitBundle.from_prefix("").bind(dut) + self.agent = ICacheMissUnitAgent(self.bundle) + self.bundle.set_all(0) diff --git a/ut_frontend/icache/missunit/test/__init__.py b/ut_frontend/icache/missunit/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ut_frontend/icache/missunit/test/missunit_fixture.py b/ut_frontend/icache/missunit/test/missunit_fixture.py new file mode 100644 index 0000000..462105b --- /dev/null +++ b/ut_frontend/icache/missunit/test/missunit_fixture.py @@ -0,0 +1,25 @@ +import asyncio +import toffee +import toffee_test +from toffee import start_clock +from dut.ICacheMissUnit import DUTICacheMissUnit +from ..env import ICacheMissUnitEnv + + +@toffee_test.fixture +async def icachemissunit_env(toffee_request: toffee_test.ToffeeRequest): + toffee.setup_logging(toffee.ERROR) + dut = toffee_request.create_dut(DUTICacheMissUnit) + dut.InitClock("clock") + start_clock(dut) + icachemissunit_env = ICacheMissUnitEnv(dut) + yield icachemissunit_env + + cur_loop = asyncio.get_event_loop() + for task in asyncio.all_tasks(cur_loop): + if task.get_name() == "__clock_loop": + task.cancel() + try: + await task + except asyncio.CancelledError: + break diff --git a/ut_frontend/icache/missunit/test/missunit_test.py b/ut_frontend/icache/missunit/test/missunit_test.py new file mode 100644 index 0000000..c7c21db --- /dev/null +++ b/ut_frontend/icache/missunit/test/missunit_test.py @@ -0,0 +1,8 @@ +from .missunit_fixture import icachemissunit_env +from ..env import ICacheMissUnitEnv +import toffee_test + + +@toffee_test.testcase +async def test_smoke(icachemissunit_env: ICacheMissUnitEnv): + await icachemissunit_env.agent.fencei_func(1) diff --git a/ut_frontend/icache/waylookup/__init__.py b/ut_frontend/icache/waylookup/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ut_frontend/icache/waylookup/agent/__init__.py b/ut_frontend/icache/waylookup/agent/__init__.py new file mode 100644 index 0000000..06ec759 --- /dev/null +++ b/ut_frontend/icache/waylookup/agent/__init__.py @@ -0,0 +1 @@ +from .waylookup_agent import WayLookupAgent \ No newline at end of file diff --git a/ut_frontend/icache/waylookup/agent/waylookup_agent.py b/ut_frontend/icache/waylookup/agent/waylookup_agent.py new file mode 100644 index 0000000..55c2a10 --- /dev/null +++ b/ut_frontend/icache/waylookup/agent/waylookup_agent.py @@ -0,0 +1,73 @@ +from toffee import Agent +from ..bundle import WayLookupBundle + + +class ReadBitsData: + + WayLookup_readPtr_value_before_flush: int + WayLookup_readPtr_value_after_flush: int + WayLookup_readPtr_flag_before_flush: bool + WayLookup_readPtr_flag_after_flush: bool + WayLookup_writePtr_value_before_flush: int + WayLookup_writePtr_value_after_flush: int + WayLookup_writePtr_flag_before_flush: bool + WayLookup_writePtr_flag_after_flush: bool + flush: int + + def __str__(self): + return ( + f"\n" + f"WayLookup_readPtr_value_before_flush: {self.WayLookup_readPtr_value_before_flush}\n" + f"WayLookup_readPtr_value_after_flush: {self.WayLookup_readPtr_value_after_flush}\n" + f"WayLookup_readPtr_flag_before_flush: {self.WayLookup_readPtr_flag_before_flush}\n" + f"WayLookup_readPtr_flag_after_flush: {self.WayLookup_readPtr_flag_after_flush}\n" + f"WayLookup_writePtr_value_before_flush: {self.WayLookup_writePtr_value_before_flush}\n" + f"WayLookup_writePtr_value_after_flush: {self.WayLookup_writePtr_value_after_flush}\n" + f"WayLookup_writePtr_flag_before_flush: {self.WayLookup_writePtr_flag_before_flush}\n" + f"WayLookup_writePtr_flag_after_flush: {self.WayLookup_writePtr_flag_after_flush}\n" + f"flush: {self.flush}\n" + ) + + +class WayLookupAgent(Agent): + + def __init__(self, bundle: WayLookupBundle): + super().__init__(bundle) + bundle.set_all(0) + self.bundle = bundle + + async def flush(self) -> ReadBitsData: + + ret = ReadBitsData() + + ret.WayLookup_readPtr_value_before_flush = ( + self.bundle.WayLookup._readPtr._value.value + ) + ret.WayLookup_readPtr_flag_before_flush = ( + self.bundle.WayLookup._readPtr._flag.value + ) + ret.WayLookup_writePtr_value_before_flush = ( + self.bundle.WayLookup._writePtr._value.value + ) + ret.WayLookup_writePtr_flag_before_flush = ( + self.bundle.WayLookup._writePtr._flag.value + ) + + self.bundle.io._flush.value = 1 + await self.bundle.step() + + ret.WayLookup_readPtr_value_after_flush = ( + self.bundle.WayLookup._readPtr._value.value + ) + ret.WayLookup_readPtr_flag_after_flush = ( + self.bundle.WayLookup._readPtr._flag.value + ) + ret.WayLookup_writePtr_value_after_flush = ( + self.bundle.WayLookup._writePtr._value.value + ) + ret.WayLookup_writePtr_flag_after_flush = ( + self.bundle.WayLookup._writePtr._flag.value + ) + + ret.flush = self.bundle.io._flush.value + return ret diff --git a/ut_frontend/icache/waylookup/bundle/__init__.py b/ut_frontend/icache/waylookup/bundle/__init__.py new file mode 100644 index 0000000..252b1b9 --- /dev/null +++ b/ut_frontend/icache/waylookup/bundle/__init__.py @@ -0,0 +1 @@ +from .waylookup_bundle import WayLookupBundle \ No newline at end of file diff --git a/ut_frontend/icache/waylookup/bundle/waylookup_bundle.py b/ut_frontend/icache/waylookup/bundle/waylookup_bundle.py new file mode 100644 index 0000000..4547ae3 --- /dev/null +++ b/ut_frontend/icache/waylookup/bundle/waylookup_bundle.py @@ -0,0 +1,69 @@ +from toffee import Bundle, Signals, Signal + + +class _00Bundle(Bundle): + _0 = Signal() + + +class _0Bundle(Bundle): + _flag, _value = Signals(2) + + +class _1Bundle(Bundle): + _readPtr = _0Bundle.from_prefix("_readPtr") + _writePtr = _0Bundle.from_prefix("_writePtr") + _io_write_ready = _00Bundle.from_prefix("_io_write_ready") + _entries_30_waymask_0 = Signal() + + +class _2Bundle(Bundle): + _0, _1 = Signals(2) + + +class _3Bundle(Bundle): + _exception = _2Bundle.from_prefix("_exception") + _pbmt = _2Bundle.from_prefix("_pbmt") + + +class _4Bundle(Bundle): + _waymask = _2Bundle.from_prefix("_waymask") + _vSetIdx = _2Bundle.from_prefix("_vSetIdx") + _ptag = _2Bundle.from_prefix("_ptag") + _meta_codes = _2Bundle.from_prefix("_meta_codes") + _itlb = _3Bundle.from_prefix("_itlb") + + +class _5Bundle(Bundle): + _isForVSnonLeafPTE, _gpaddr = Signals(2) + + +class _6Bundle(Bundle): + _gpf = _5Bundle.from_prefix("_gpf") + _entry = _4Bundle.from_prefix("_entry") + + +class _7Bundle(Bundle): + _bits = _6Bundle.from_prefix("_bits") + _valid, _ready = Signals(2) + + +class _8Bundle(Bundle): + _corrupt, _vSetIdx, _blkPaddr, _waymask = Signals(4) + + +class _9Bundle(Bundle): + _bits = _8Bundle.from_prefix("_bits") + _valid = Signal() + + +class _10Bundle(Bundle): + _update = _9Bundle.from_prefix("_update") + _write = _7Bundle.from_prefix("_write") + _read = _7Bundle.from_prefix("_read") + _flush = Signal() + + +class WayLookupBundle(Bundle): + io = _10Bundle.from_prefix("io") + WayLookup = _1Bundle.from_prefix("WayLookup") + reset, clock = Signals(2) diff --git a/ut_frontend/icache/waylookup/env/__init__.py b/ut_frontend/icache/waylookup/env/__init__.py new file mode 100644 index 0000000..7ccd410 --- /dev/null +++ b/ut_frontend/icache/waylookup/env/__init__.py @@ -0,0 +1 @@ +from .waylookup_env import WayLookupEnv diff --git a/ut_frontend/icache/waylookup/env/waylookup_env.py b/ut_frontend/icache/waylookup/env/waylookup_env.py new file mode 100644 index 0000000..0fb08be --- /dev/null +++ b/ut_frontend/icache/waylookup/env/waylookup_env.py @@ -0,0 +1,14 @@ +from toffee import Env +from dut.WayLookup import DUTWayLookup +from ..agent import WayLookupAgent +from ..bundle import WayLookupBundle + + +class WayLookupEnv(Env): + + def __init__(self, dut: DUTWayLookup): + super().__init__() + self.dut = dut + self.bundle = WayLookupBundle.from_prefix("").bind(dut) + self.agent = WayLookupAgent(self.bundle) + self.bundle.set_all(0) \ No newline at end of file diff --git a/ut_frontend/icache/waylookup/test/__init__.py b/ut_frontend/icache/waylookup/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ut_frontend/icache/waylookup/test/waylookup_fixture.py b/ut_frontend/icache/waylookup/test/waylookup_fixture.py new file mode 100644 index 0000000..d3a0b2f --- /dev/null +++ b/ut_frontend/icache/waylookup/test/waylookup_fixture.py @@ -0,0 +1,25 @@ +import toffee_test +import toffee +from dut.WayLookup import DUTWayLookup +from toffee import start_clock +from ..env import WayLookupEnv +import asyncio + + +@toffee_test.fixture +async def waylookup_env(toffee_request: toffee_test.ToffeeRequest): + toffee.setup_logging(toffee.ERROR) + dut = toffee_request.create_dut(DUTWayLookup) + dut.InitClock("clock") + start_clock(dut) + waylookup_env = WayLookupEnv(dut) + yield waylookup_env + + cur_loop = asyncio.get_event_loop() + for task in asyncio.all_tasks(cur_loop): + if task.get_name() == "__clock_loop": + task.cancel() + try: + await task + except asyncio.CancelledError: + break diff --git a/ut_frontend/icache/waylookup/test/waylookup_test.py b/ut_frontend/icache/waylookup/test/waylookup_test.py new file mode 100644 index 0000000..bb3d721 --- /dev/null +++ b/ut_frontend/icache/waylookup/test/waylookup_test.py @@ -0,0 +1,9 @@ +from .waylookup_fixture import waylookup_env +from ..env import WayLookupEnv +import toffee_test + + +@toffee_test.testcase +async def test_smoke(waylookup_env: WayLookupEnv): + + print(await waylookup_env.agent.flush())