https://mp.weixin.qq.com/s/7YF5cZLYkvo-Dbp30DxwsA
简单介绍take_pc相关的实现。
本文目录:
1. take_pc
2. 用途
3. 意义
4. take_pc_mem
5. take_pc_wb
6. 总结
以下正文:
1. take_pc
take_pc由take_pc_wb和take_pc_mem相或产生:
因为take_pc_wb和take_pc_mem都是wire类型,所以take_pc也是一个wire类型。
2. 用途
take_pc同时用于控制ibuf和frontend:
其中:
a. 连接到ibuf的kill信号,使ibuf中的指令变为非法;
b. 连接到frontend的req信号,使其接收新的pc:
3. 意义
这里take_pc的用法与通常对流水线pc的理解有较大的不同之处。
通常情况下,流水线中包含一个pc+4的功能,使得pc指向下一个32位宽的指令。这里在某些特殊的情况下,才向frontend发起take新的pc的请求。由此可以推测,在顺序执行的情况下,frontend内部包含一个pc自加的功能,并把获取到的指令返回给ibuf。
1) take_pc向frontend发起请求
在frontend中,s0_valid包含两种情况:
a. 随take_pc输入的io.cpu.req.valid为真;
b. 或者是fq包含空间;fq是一个包含5个entry的队列,其中存放了FrontendResp结构:
从中可以看出,即便take_pc没有为真,只要fq未满,仍然可以发起请求,获取指令。而流水线通过ibuf向frontend发起FrontendResp出队请求。这样流水线每取出一条指令,frontend便会自动的通过icache请求一条指令。这里还有一个需要注意的地方,就是take_pc作为fq的复位信号使用,如果take_pc为真,则fq复位,其中缓存的内容被清空。
2) pc自加
如果take_pc为真,则io.cpu.npc使用外部输入的io.cpu.req.bits.pc:
如果take_pc为假,则使用自加计算而出的npc。
其中:
a. 不考虑replay的情况,npc = predicted_npc;
b. predicted_npc = ntpc;
c. ntpc = s1_base_pc + fetchBytes.U,这里就包含了自加的动作;
d. s1_base_pc使用s1_pc计算得来,s1_pc是一个寄存器,其值更新使用io.cpu.npc,这就又转回来了,不过根据寄存器的特性,其值要在下一个时钟才会更新;
e. 如此便实现了自加fetchBytes的动作;
3) 向tlb发起请求,对pc进行地址转换:
4) 向icache发起请求:
5) 使用icache返回的数据,存入到fq中:
6) fq向ibuf出队:
4. take_pc_mem
take_pc_mem表示mem阶段需要改变执行分支的情况。
首先要求mem阶段流水线寄存器的值是合法的。
然后要改变执行分支的情况存在两种:
a. 预测错误:分支预测错误,导致frontend取指及流水线执行的分支出错,需要刷新重新执行;
b. 内存屏障需要刷新流水线;
5. take_pc_wb
take_pc_wb表示wb阶段需要改变执行分支的情况。
包含四种情况:
a. 重新执行;
b. 异常或者中断需要跳转以进行处理;
c. eret指令要恢复到正常执行流程;
d. 需要flush流水线的情况;
6. 总结
总结一下:如果take_pc为假,则frontend自动顺序取指,流水线执行同一个分支上的指令。如果take_pc为真,则要求frontend切换分支进行跳转,则流水线也要跟着进行切换执行另一条分支上的指令。