GEM5 XiangShan Fetch阶段访问I-Cache的时序分析
1. I-Cache配置参数
基本配置 (来自configs/common/Caches.py)
class L1_ICache(L1Cache):
mshrs = 2 # Miss Handling Status Registers
is_read_only = True # 只读cache
writeback_clean = False # 不回写clean lines
tag_latency = 1 # Tag访问延迟:1拍
data_latency = 1 # Data array访问延迟:1拍
sequential_access = False # 并行访问tag和data
response_latency = 0 # 响应延迟:0拍
enable_wayprediction = False # 禁用way预测
时序关键参数
- 主频: 3GHz,即每拍333 ticks
- tag_latency: 控制tag lookup的时间,影响cache hit/miss判断
- data_latency: 控制data array访问时间,影响数据读取
- sequential_access = False: 表示tag和data并行访问
2. Fetch阶段访问I-Cache的完整流程
2.1 fetchCacheLine()函数 - 发起访问
// src/cpu/o3/fetch.cc:761-860
bool Fetch::fetchCacheLine(Addr vaddr, ThreadID tid, Addr pc)
{
// 1. 检查cache阻塞状态
if (cacheBlocked) {
setAllFetchStalls(StallReason::IcacheStall);
return false;
}
// 2. 检查中断状态
if (checkInterrupt(pc) && !delayedCommit[tid]) {
setAllFetchStalls(StallReason::IntStall);
return false;
}
// 3. 处理跨cache line的misaligned访问
if (fetchPC % 64 + fetchBufferSize > 64) {
fetchMisaligned[tid] = true;
// 需要两次访问:第一次获取当前cache line的剩余部分
// 第二次获取下一个cache line的开始部分
}
// 4. 创建memory request
RequestPtr mem_req = std::make_shared<Request>(
fetchPC, fetchSize,
Request::INST_FETCH, cpu->instRequestorId(), pc,
cpu->thread[tid]->contextId());
// 5. 启动地址翻译
fetchStatus[tid] = ItlbWait;
setAllFetchStalls(StallReason::ITlbStall);
FetchTranslation *trans = new FetchTranslation(this);
cpu->mmu->translateTiming(mem_req, cpu->thread[tid]->getTC(),
trans, BaseMMU::Execute);
return true;
}
2.2 地址翻译完成后的I-Cache访问
// 地址翻译完成后,通过finishTranslation()发送到I-Cache
void Fetch::finishTranslation(const Fault &fault, const RequestPtr &req,
ThreadContext *tc, BaseMMU::Mode mode)
{
// 发送timing request到I-Cache
if (!icachePort.sendTimingReq(fetchPkt)) {
// 如果无法发送,设置retry状态
fetchStatus[tid] = IcacheWaitRetry;
} else {
// 成功发送,等待响应
fetchStatus[tid] = IcacheWaitResponse;
}
}
3. I-Cache内部的时序处理
3.1 Cache::access() - 核心访问逻辑
// src/mem/cache/base.cc:1693
bool BaseCache::access(PacketPtr pkt, CacheBlk *&blk, Cycles &lat,
PacketList &writebacks)
{
// 1. 访问tag array
Cycles tag_latency(0);
blk = tags->accessBlock(pkt, tag_latency); // tag_latency = 1拍
DPRINTF(Cache, "%s for %s %s, block access lat %lu\n", __func__,
pkt->print(), blk ? "hit " + blk->print() : "miss", tag_latency);
// 2. 根据hit/miss计算总延迟
if (blk && blk->isValid() && blk->isSet(CacheBlk::ReadableBit)) {
// Cache Hit路径
lat = calculateAccessLatency(blk, pkt->headerDelay, tag_latency);
return true; // Hit
} else {
// Cache Miss路径
lat = calculateAccessLatency(blk, pkt->headerDelay, tag_latency);
return false; // Miss
}
}
3.2 延迟计算函数
// src/mem/cache/base.cc:1647
Cycles BaseCache::calculateAccessLatency(const CacheBlk* blk,
const uint32_t delay,
const Cycles lookup_lat) const
{
Cycles lat(0);
if (blk != nullptr) { // Cache Hit
if (sequentialAccess) {
// 顺序访问:先tag,后data
lat = ticksToCycles(delay) + lookup_lat + dataLatency;
} else {
// 并行访问:取tag和data中的最大值
lat = ticksToCycles(delay) + std::max(lookup_lat, dataLatency);
}
// 检查block是否ready(用于modeling bank conflict等)
const Tick tick = curTick() + delay;
const Tick when_ready = blk->getWhenReady();
if (when_ready > tick) {
lat += ticksToCycles(when_ready - tick);
DPRINTF(Cache, "block not ready, need %lu cycle\n",
ticksToCycles(when_ready - tick));
}
} else { // Cache Miss
// Miss情况下只需要tag lookup延迟
lat = ticksToCycles(delay) + lookup_lat;
}
return lat;
}
3.3 Cache Hit的响应处理
// src/mem/cache/base.cc:380-430
void BaseCache::handleTimingReqHit(PacketPtr pkt, CacheBlk *blk,
Tick request_time, bool first_acc_after_pf)
{
if (pkt->needsResponse()) {
// 1. 生成response
pkt->makeTimingResponse();
DPRINTF(Cache, "Making timing response for %s, schedule it at %llu\n",
pkt->print(), request_time);
// 2. 调度响应 - 关键时序点
if (cacheLevel == 1) {
// L1 cache有固定的pipeline延迟
this->schedule(new SendTimingRespEvent(this, pkt), request_time - 1);
} else {
cpuSidePort.schedTimingResp(pkt, request_time);
}
}
}
4. 具体时序分析(基于debug输出)
4.1 Cache Miss场景分析
- tick 999: Fetch发起请求,tag lookup耗时1拍 - tag_latency = 1,因为是miss所以只需要tag访问 - 实际延迟计算:lat = tag_latency = 1拍 = 333 ticks
时刻1332: Sending pkt ReadReq [80000000:8000003f] IF
时刻1332: createMissPacket: created ReadCleanReq [80000000:8000003f] IF
时刻60939: recvTimingResp: Handling response ReadResp [80000000:8000003f] IF
时刻60939: Scheduling 0x80000000 to response to sender 0 at tick 60939
4.2 Cache Hit场景分析
时刻61605: access for ReadReq [8000003e:8000003f] IF hit, block access lat 1
时刻61605: final cache read lat 1
时刻61605: Making timing response for ReadResp, schedule it at 61938
lat = max(tag_latency, data_latency) = max(1, 1) = 1拍
- 调度响应:61605 + 333 = 61938
时刻121545: access for ReadReq [8000007e:8000007f] IF hit, block access lat 1
时刻121545: block not ready, need 1 cycle
时刻121545: final cache read lat 2
时刻121545: Making timing response for ReadResp, schedule it at 122211
5. 关键时序参数的影响
5.1 tag_latency的影响
作用: - 控制tag lookup的时间 - 影响hit/miss判断的延迟 - Miss情况下决定最终访问延迟
时序计算:
- Cache Miss: latency = tag_latency
- Cache Hit并行访问: latency = max(tag_latency, data_latency)
- Cache Hit顺序访问: latency = tag_latency + data_latency
5.2 data_latency的影响
作用: - 控制data array访问时间 - 只在cache hit时影响总延迟 - 在miss时被忽略(因为不需要读取数据)
时序计算: - Cache Miss: data_latency不影响延迟 - Cache Hit: 与tag_latency共同决定访问延迟
5.3 sequential_access参数的影响
当前配置: sequential_access = False (并行访问)
并行访问 (当前使用):
lat = ticksToCycles(delay) + std::max(tag_latency, data_latency);
// 对于L1_ICache: lat = max(1, 1) = 1拍
顺序访问 (如果改为True):
6. Fetch与I-Cache的接口时序
6.1 Request路径时序
Fetch::fetchCacheLine()
↓ (地址翻译)
IcachePort::sendTimingReq()
↓ (bus延迟)
Cache::recvTimingReq()
↓ (access延迟:tag_latency)
Cache::handleTimingReqHit/Miss()
6.2 Response路径时序
Cache::handleTimingReqHit()
↓ (调度响应:request_time)
IcachePort::recvTimingResp()
↓ (处理数据)
Fetch::finishIcacheResponse()
↓ (更新fetch buffer)
继续fetch指令
6.3 Pipeline关键时序点
- Tag Lookup: tick + tag_latency * clockPeriod
- Data Access: 与tag并行或串行,取决于sequential_access
- Response Generation: request_time(包含上述所有延迟)
- Response Delivery: response_time(通常立即,response_latency=0)
7. 性能优化建议
7.1 减少Cache访问延迟
- 降低tag_latency和data_latency可直接减少hit延迟
- 但需要考虑实际硬件实现的限制
7.2 Way Prediction优化
- 当前禁用了way prediction
- 启用后可以减少data array访问时间
7.3 Bank Interleaving
- 通过bank interleaving减少bank conflict
- 避免"block not ready"导致的额外延迟
这个分析展示了在3GHz主频下,tag_latency和data_latency如何精确控制I-Cache的访问时序,每1拍的延迟对应333 ticks的时间成本。
8. 跨Cache Line取指 (Misaligned Fetch) 的处理
根据 src/cpu/o3/fetch.cc 的实现,Fetch阶段在处理跨越Cache Line边界的取指请求时,有一套特殊的处理逻辑,以对齐某些RTL设计。默认情况下,对ICache的访问是针对一个Cache Line(通常是64字节)的。当一次取指请求跨越两个Cache Line时,GEM5会将其拆分为两次独立的ICache访问,并在收到响应后将数据合并。
8.1 关键数据结构
为了管理跨行取指的复杂状态,Fetch逻辑使用了几个关键的线程相关([tid])数据结构:
bool fetchMisaligned[tid]: 状态标志。当一次取指被识别为跨行访问时,此标志被设为true。它控制后续逻辑将请求拆分,并在响应返回时进行合并。RequestPtr memReq[tid]: 指向当前内存请求的智能指针。在跨行访问场景下,它在fetchCacheLine函数执行过程中,最终会指向第二次的Cache访问请求。RequestPtr anotherMemReq[tid]: 另一个请求指针。在跨行访问中,它被用来保存第一次的Cache访问请求。uint8_t *firstPktData[tid]: 用于临时存储第一个Cache Line返回的数据。当第二个包返回时,这两个包的数据会被合并到最终的fetchBuffer中。
8.2 请求的拆分:fetchCacheLine() 逻辑详解
当检测到跨行访问时 (fetchPC % 64 + fetchBufferSize > 64),fetchCacheLine 会执行以下操作:
-
标记为跨行访问:
fetchMisaligned[tid] = true;
-
创建并发送第一个请求 (访问当前Cache Line的尾部):
- 计算大小:
fetchSize = 64 - fetchPC % 64; - 创建请求:
RequestPtr mem_req = std::make_shared<Request>(...); - 保存请求: 此时,会将这个请求同时赋值给
memReq[tid]和anotherMemReq[tid]。 - 发起翻译:
cpu->mmu->translateTiming(mem_req, ...);
- 计算大小:
-
创建并发送第二个请求 (访问下一个Cache Line的头部):
- 这部分代码紧接着第一个请求的创建逻辑。
- 更新PC和大小:
fetchPC指向下一个Line的开头,fetchSize为剩余大小。 - 创建请求:
RequestPtr mem_req = std::make_shared<Request>(...); - 更新请求指针:
memReq[tid]被更新为指向这个新的(第二个)请求。此时,anotherMemReq[tid]仍然保留着第一个请求。 - 发起翻译:
cpu->mmu->translateTiming(mem_req, ...);
函数执行完毕后,两个独立的取指请求都已被发送出去进行地址翻译,并最终由 finishTranslation 发往ICache。
8.3 响应的接收与合并:processCacheCompletion() 逻辑详解
ICache的响应最终会触发 processCacheCompletion() 函数。该函数通过检查 fetchMisaligned[tid] 标志来决定如何处理返回的数据包 (pkt)。
-
检查是否为跨行访问:
if (fetchMisaligned[tid]) { ... }
-
处理第一个返回包:
- 通过比对返回包的地址和
anotherMemReq[tid]中保存的第一个请求的地址,来判断这是否是第一个响应。 if (pkt->getAddr() == anotherMemReq[tid]->getVaddr()) { ... }- 如果是第一个响应,将其数据临时存储在
firstPktData[tid]中,然后直接return,等待第二个包的到来。
- 通过比对返回包的地址和
-
处理第二个返回包并合并数据:
- 如果当前返回的包不是第一个包,那么它就是第二个包。
- 数据合并:
- 将第一个包的数据(来自
firstPktData)和当前第二个包的数据,依次拷贝到连续的fetchBuffer中。 memcpy(fetchBuffer[tid], firstPktData[tid], first_size);memcpy(fetchBuffer[tid] + first_size, pkt_data, second_size);
- 将第一个包的数据(来自
- 清理状态: 合并完成后,将
fetchMisaligned[tid]设回false,并释放临时资源。
-
处理普通(非跨行)访问:
- 如果
fetchMisaligned[tid]为false,则直接将返回的数据包内容拷贝到fetchBuffer。
- 如果
8.4 总结
- ICache的访问粒度: ICache本身处理的是对齐的Cache Line请求。
- Fetch的责任: Fetch单元通过精巧的状态管理(
fetchMisaligned标志和多个请求指针),将一个对用户透明的跨行访问分解为两个独立的、对齐的Cache访问,并在响应返回后将结果无缝合并。 - 与RTL对齐: 这种"拆分请求、缓存结果、最终合并"的模式,是为了更精确地模拟某些高性能处理器RTL设计的行为,从而获得更准确的性能和时序模拟结果。