跳转至

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场景分析

时刻999: access for ReadReq [80000000:8000003f] IF miss, block access lat 1
- 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
- tick 1332: 向下级发送miss packet - 延迟:1332 - 999 = 333 ticks = 1拍,符合tag_latency

时刻60939: recvTimingResp: Handling response ReadResp [80000000:8000003f] IF
时刻60939: Scheduling 0x80000000 to response to sender 0 at tick 60939
- tick 60939: 收到下级响应,立即调度给fetch - response_latency = 0,所以没有额外延迟

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
- tick 61605: 发起访问,hit延迟1拍 - 由于sequential_access = False且tag_latency = data_latency = 1 - 计算: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
- tick 121545: Hit但block未ready,额外1拍延迟 - 总延迟:1(tag/data) + 1(block not ready) = 2拍 - 调度响应:121545 + 666 = 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):

lat = ticksToCycles(delay) + tag_latency + data_latency;
// 对于L1_ICache: lat = 1 + 1 = 2拍

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关键时序点

  1. Tag Lookup: tick + tag_latency * clockPeriod
  2. Data Access: 与tag并行或串行,取决于sequential_access
  3. Response Generation: request_time(包含上述所有延迟)
  4. 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 会执行以下操作:

  1. 标记为跨行访问:

    • fetchMisaligned[tid] = true;
  2. 创建并发送第一个请求 (访问当前Cache Line的尾部):

    • 计算大小: fetchSize = 64 - fetchPC % 64;
    • 创建请求: RequestPtr mem_req = std::make_shared<Request>(...);
    • 保存请求: 此时,会将这个请求同时赋值给 memReq[tid]anotherMemReq[tid]
    • 发起翻译: cpu->mmu->translateTiming(mem_req, ...);
  3. 创建并发送第二个请求 (访问下一个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)。

  1. 检查是否为跨行访问:

    • if (fetchMisaligned[tid]) { ... }
  2. 处理第一个返回包:

    • 通过比对返回包的地址和 anotherMemReq[tid] 中保存的第一个请求的地址,来判断这是否是第一个响应。
    • if (pkt->getAddr() == anotherMemReq[tid]->getVaddr()) { ... }
    • 如果是第一个响应,将其数据临时存储在 firstPktData[tid] 中,然后直接 return,等待第二个包的到来。
  3. 处理第二个返回包并合并数据:

    • 如果当前返回的包不是第一个包,那么它就是第二个包。
    • 数据合并:
      • 将第一个包的数据(来自firstPktData)和当前第二个包的数据,依次拷贝到连续的 fetchBuffer 中。
      • memcpy(fetchBuffer[tid], firstPktData[tid], first_size);
      • memcpy(fetchBuffer[tid] + first_size, pkt_data, second_size);
    • 清理状态: 合并完成后,将 fetchMisaligned[tid] 设回 false,并释放临时资源。
  4. 处理普通(非跨行)访问:

    • 如果 fetchMisaligned[tid]false,则直接将返回的数据包内容拷贝到 fetchBuffer

8.4 总结

  • ICache的访问粒度: ICache本身处理的是对齐的Cache Line请求。
  • Fetch的责任: Fetch单元通过精巧的状态管理(fetchMisaligned标志和多个请求指针),将一个对用户透明的跨行访问分解为两个独立的、对齐的Cache访问,并在响应返回后将结果无缝合并。
  • 与RTL对齐: 这种"拆分请求、缓存结果、最终合并"的模式,是为了更精确地模拟某些高性能处理器RTL设计的行为,从而获得更准确的性能和时序模拟结果。