-
Notifications
You must be signed in to change notification settings - Fork 0
/
local-search.xml
107 lines (51 loc) · 128 KB
/
local-search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>蜂鸟E203</title>
<link href="/2023/11/15/riscv-e203/"/>
<url>/2023/11/15/riscv-e203/</url>
<content type="html"><![CDATA[<h2 id="4-2-蜂鸟E200简介"><a href="#4-2-蜂鸟E200简介" class="headerlink" title="4.2 蜂鸟E200简介"></a>4.2 蜂鸟E200简介</h2><p>蜂鸟E200 系列处理器核的特性简介如下。</p><ul><li>采用两级流水线结构</li><li>能够运行RISC-V指令集,支持RV32I/E/A/M/C/F/D等指令子集的配置组合,支持机器模式(Machine Mode Only)</li><li>支持标准的JTAG调试接口以及成熟的软件调试工具</li><li>提供成熟的GCC编译工具链</li><li>配套SOC提供紧耦合系统IP模块,包括中断控制器、计时器、UART、QSPI和PWM等,即使能用(Ready-to-Use)的SoC平台与FPGA原型系统</li></ul><p>蜂鸟E200 系列处理器的系统示意图如图所示,提供丰富的存储和接口<br><img src="/2023/11/15/riscv-e203/e203_soc.png" alt="image"></p><ul><li>私有的ITCM与DTCM,实现指令与数据的分离存储同时提高性能</li><li>中断接口用于与SoC 级别的中断控制器连接</li><li>调试接口用于与soc 级别的JTAG 调试器连接</li><li>系统总线接口,用于访存指令或者数据</li><li>紧祸合的私有外设接口,用于访存数据</li><li>紧稠合的快速IO 接口,用于访存数据</li><li>所有的ITCM 、DTCM 、系统总线接口、私有外设接口以及快速IO 接口均可以配置地址区间</li></ul><h2 id="4-5-蜂鸟E200配套SOC"><a href="#4-5-蜂鸟E200配套SOC" class="headerlink" title="4.5 蜂鸟E200配套SOC"></a>4.5 蜂鸟E200配套SOC</h2><p><img src="/2023/11/15/riscv-e203/cpmplete_soc_platform.png" alt="image"><br><img src="/2023/11/15/riscv-e203/soc_submodule.png" alt="image"></p><h2 id="5-3-RTL-代码风格"><a href="#5-3-RTL-代码风格" class="headerlink" title="5.3 RTL 代码风格"></a>5.3 RTL 代码风格</h2><h3 id="5-3-1-使用标准DFF模块例化生成寄存器"><a href="#5-3-1-使用标准DFF模块例化生成寄存器" class="headerlink" title="5.3.1 使用标准DFF模块例化生成寄存器"></a>5.3.1 使用标准DFF模块例化生成寄存器</h3><p><img src="/2023/11/15/riscv-e203/code_style_dff.png" alt="image"><br><img src="/2023/11/15/riscv-e203/dff_type.png" alt="image"><br><img src="/2023/11/15/riscv-e203/std_always_block.png" alt="image"><br><img src="/2023/11/15/riscv-e203/assert_xstate.png" alt="image"></p><h3 id="5-3-2-推荐使用assign语法代替if-else和case语法"><a href="#5-3-2-推荐使用assign语法代替if-else和case语法" class="headerlink" title="5.3.2 推荐使用assign语法代替if-else和case语法"></a>5.3.2 推荐使用assign语法代替if-else和case语法</h3><p>Verilog 中的if-else 和 case语法存在两大缺点</p><ul><li>不能传播不定态</li><li>会产生优先级的选择电路而非并行选择电路,不利于时序和面积</li></ul><h2 id="5-4-蜂鸟E200模块层次划分"><a href="#5-4-蜂鸟E200模块层次划分" class="headerlink" title="5.4 蜂鸟E200模块层次划分"></a>5.4 蜂鸟E200模块层次划分</h2><p><img src="/2023/11/15/riscv-e203/module_hier.png" alt="image"><br><img src="/2023/11/15/riscv-e203/rtl_hier.png" alt="image"></p><h2 id="6-3-处理器流水"><a href="#6-3-处理器流水" class="headerlink" title="6.3 处理器流水"></a>6.3 处理器流水</h2><p>流水线存在的时序问题: 流水线越深,由于每一级流水线需要进行握手,流水线最后一级的反压信号可能会一直串扰到最前一级造成严重的反压时序问题</p><p>解决方法:</p><ul><li><p>取消握手:此方法能够杜绝反压的发生,时序表现非常好。但是取消握手,即意味着流水线中的每一级并不会与其下一级进行握手,可能会造成功能错误或者指令丢失。因此这种方法往往需要配合其他的机制, 譬如重执行( Replay )、预留大缓存等。</p></li><li><p>加入乒乓缓存:加入乒乓缓存( Ping-pong Buffer )是一种用面积换时序的方法,也是在解决反压的最简单方法。通过使用乒乓缓存(有两个表项〉替换掉普通的一级流水线(只有一个表项〉,可以使得此级流水线向上一级流水线的握手接收信号仅关注乒乓缓存中是否有一个以上有空的表项即可,而无需将下一级的握手接收信号串扰至上一级。<br><img src="/2023/11/15/riscv-e203/pingpong_buffer.png" alt="image"></p></li><li><p>加入前向旁路缓存:加入前向旁路缓存( Forward Bypass Buffer )也是一种用面积换<br>时序的方法,是在解决反压时的一种非常巧妙的方法。旁路缓存仅只有一个表项,由于增加了这一个额外的缓存表项,可以将后向的握手信号时序路径砍断,但是对前向路径不受影响,因此可以广泛使用于握手接口。蜂鸟E200 即于设计中采用此方法,有效地解决了多处反压造成的时序瓶颈。<br><img src="/2023/11/15/riscv-e203/reg_slice.png" alt="image"><br><a href="https://zipcpu.com/blog/2019/05/22/skidbuffer.html">https://zipcpu.com/blog/2019/05/22/skidbuffer.html</a></p></li></ul><h3 id="6-4-2-流水线中的数据冲突"><a href="#6-4-2-流水线中的数据冲突" class="headerlink" title="6.4.2 流水线中的数据冲突"></a>6.4.2 流水线中的数据冲突</h3><p>WAW和WAR可以通过寄存器重命名的方法将相关性去除</p><ul><li>寄存器重命名技术在Tomasulo算法中通过保留站和ROB完成,或者采用纯物理寄存器(而不用ROB)的方式完成</li></ul><p>RAW是真数据相关,无法通过寄存器重命名的方式去除相关性,后序的指令要使用前级指令执行完成的结果,从而造成流水线停顿,为了减少流水线停顿带来的性能损失,可以采用“动态调度”</p><ul><li>采用数据旁路传播(Data Bypass and Forward)技术</li><li>尽量让后续指令在等待过程中不阻塞流水线,而让其他无关的指令继续顺利执行</li><li>早期的Tomasulo算法中通过保留站可以达到上述两方面的功效,但是保留站保存了操作数,无法做到深度很大</li><li>最新的高性能处理器普遍采用在每个运算单元前配置乱序发射队列(Issue Queue)的方式,发射队列仅追踪RAW 相关性,而并不存放操作数,因此可以做到很深(譬如16 个表项〉。在发射队列中的指令一旦相关性解除之后,再从发射队列中发射出来读取物理寄存器组( Physical Register File ),然后发送给运算单元开始计算。</li></ul><h2 id="6-5-蜂鸟E200处理器的流水线"><a href="#6-5-蜂鸟E200处理器的流水线" class="headerlink" title="6.5 蜂鸟E200处理器的流水线"></a>6.5 蜂鸟E200处理器的流水线</h2><p><img src="/2023/11/15/riscv-e203/e200_pipeline_struct.png" alt="image"></p><h2 id="7-1-取指"><a href="#7-1-取指" class="headerlink" title="7.1 取指"></a>7.1 取指</h2><h3 id="7-1-1-取指特点"><a href="#7-1-1-取指特点" class="headerlink" title="7.1.1 取指特点"></a>7.1.1 取指特点</h3><ul><li>指令的编码长度可以不相等,有的指令编码宽度是16位,有的是32位。对于32位的指令,其对应PC地址可能与32位地址边界不对齐</li></ul><h3 id="7-1-3-如何处理非对齐指令"><a href="#7-1-3-如何处理非对齐指令" class="headerlink" title="7.1.3 如何处理非对齐指令"></a>7.1.3 如何处理非对齐指令</h3><ol><li>普通指令非对齐,使用Leftover buffer</li><li>分支跳转指令非对齐,使用多bank sram进行指令存储。常见的奇偶交错方式为例,使用两块32位SRAM交错存储,两个连续的32位指令分别存储在不同的SRAM。对于32位地址不对齐的指令,一个周期同时访问两块SRAM取出连续的32位指令字,各取一部分进行拼接成需要的23位指令</li></ol><h3 id="7-1-4-如何处理分支指令"><a href="#7-1-4-如何处理分支指令" class="headerlink" title="7.1.4 如何处理分支指令"></a>7.1.4 如何处理分支指令</h3><ol><li>分支指令类型:RISC-V没有带条件间接跳转跳转/分支指令,对于这种指令,流水线只有在执行阶段完成后才能解析最终跳转结果,会stall流水线,影响性能为了提高性能,现代处理采用分支预测(Branch Prediction)技术<ul><li>预测方向</li><li>预测地址</li></ul></li><li>预测方向</li></ol><ul><li>静态预测<ul><li>静态预测总是预测不跳转(在译码阶段得出真正预测结果,用delay slot减少流水线冲刷造成的性能损失)</li><li>BTFN(Back Taken, Forward Not Taken),for循环生成的汇编往往使用向后跳转(PC小)的分支指令</li></ul></li><li>动态预测<ul><li>一比特饱和计数器:喜爱次分支指令永远使用上一次记录的方向作为本次预测</li><li>两比特饱和计数器</li></ul></li></ul><ol start="3"><li>预测地址</li></ol><ul><li>BTB(Branch Target Bufer),缓存分支指令的PC以及跳转目标地址。不能太大,不能应用于间接跳转/分支指令</li><li>RAS(Return Address Stack)使用容量有限的硬件堆栈(先进后出)存储函数调用的返回地址。<ul><li>函数的调用和返回在程序中往往是成对出现的,因此可以在函数调用(使用分支跳转指令)时将当前PC 值加4 (或者2 ) 。即其顺序执行的下一条指令的PC 值压入孔气S 堆枝中,等到函数返回(使用分支跳转指令〉时将RAS中的值弹出,这样就可以快速地为该函数返回的分支跳转指令预测目标地址。</li><li>多层嵌套哦那个一造成堆栈溢出</li></ul></li><li>Indirect BTB</li></ul><h2 id="7-2-RISC-V架构特点对于取指的简化"><a href="#7-2-RISC-V架构特点对于取指的简化" class="headerlink" title="7.2 RISC-V架构特点对于取指的简化"></a>7.2 RISC-V架构特点对于取指的简化</h2><p><img src="/2023/11/15/riscv-e203/jump_branch_instructions.png" alt="image"></p><h2 id="7-3-IFU"><a href="#7-3-IFU" class="headerlink" title="7.3 IFU"></a>7.3 IFU</h2><h3 id="7-3-1-整体设计思路"><a href="#7-3-1-整体设计思路" class="headerlink" title="7.3.1 整体设计思路"></a>7.3.1 整体设计思路</h3><p><img src="/2023/11/15/riscv-e203/ifu.png" alt="image"><br><img src="/2023/11/15/riscv-e203/ifu_macro_arch.png" alt="image"></p><p>主要包括如下功能:</p><ul><li>对取回地址简单译码(Mini-Decode)</li><li>简单的分支预测(Simple-BPU)</li><li>生成取指的PC(PC生成)</li><li>根据PC地址访问ITCM或BIU(地址判断和ICB总线控制)</li></ul><p>(1)针对”快”的设计理念</p><ul><li>假定绝大多数取指都发生在ITCM,没有I-Cache,使用ITCM满足实时性要求</li><li>ITCM使用单周期访问的SRAM</li><li>某些特殊情况,指令需要从外部存储器中读取(比如上电引导程序从外部Flash读取)。此时IFU通过BIU使用系统存储器接口访问外部存储器,访问延迟不可能做到单周期,对这种特殊情况不做优化<br>(2)针对”连续不断”的设计思路</li><li>每个周期都能生成下一条待取指令的PC,因此需要判断本指令是普通指令还是分支跳转指令,需要对当前取回的指令进行译码</li><li>将取回的指令在同周期进行部分译码,如果是分支跳转指令,直接在同周期进行分支预测,使用译码得到的信息和分支预测的信息进行下一条待取指令PC的生成</li><li>在一个周期内完成读回指令,译码,分支预测,PC生成操作,理论上可以做到背靠背取指令</li><li>上述操作是一条关键时序路径,可能会制约主频</li></ul><p>分支预测:</p><ul><li>静态分支预测</li><li>back taken</li></ul><h3 id="8-3-3-decode"><a href="#8-3-3-decode" class="headerlink" title="8.3.3 decode"></a>8.3.3 decode</h3><h4 id="RV32I"><a href="#RV32I" class="headerlink" title="RV32I"></a>RV32I</h4><p><img src="/2023/11/15/riscv-e203/RV32I_instr.png" alt="image"><br><img src="/2023/11/15/riscv-e203/RV32I_format.png" alt="iamge"><br><img src="/2023/11/15/riscv-e203/RV32I_instr_encode.png" alt="image"></p><h4 id="RV32C"><a href="#RV32C" class="headerlink" title="RV32C"></a>RV32C</h4><p><img src="/2023/11/15/riscv-e203/RV32C_instr.png" alt="image"><br><img src="/2023/11/15/riscv-e203/RV32C_format.png" alt="image"><br><img src="/2023/11/15/riscv-e203/RV32C_00.png" alt="image"><br><img src="/2023/11/15/riscv-e203/RV32C_01.png" alt="image"><br><img src="/2023/11/15/riscv-e203/RV32C_10.png" alt="image"></p><h4 id="RV32M"><a href="#RV32M" class="headerlink" title="RV32M"></a>RV32M</h4><p><img src="/2023/11/15/riscv-e203/RV32M_instr.png" alt="image"><br><img src="/2023/11/15/riscv-e203/RV32M_instr_encode.png" alt="image"></p><h4 id="ABI"><a href="#ABI" class="headerlink" title="ABI"></a>ABI</h4><p><img src="/2023/11/15/riscv-e203/ABI.png" alt="image"></p><h2 id="10-3-机器模式下的异常处理"><a href="#10-3-机器模式下的异常处理" class="headerlink" title="10.3 机器模式下的异常处理"></a>10.3 机器模式下的异常处理</h2><p><img src="/2023/11/15/riscv-e203/exception.png" alt="image"></p>]]></content>
<categories>
<category>RISC-V CPU</category>
</categories>
<tags>
<tag>e203</tag>
</tags>
</entry>
<entry>
<title>Nyuzi-RTL分析</title>
<link href="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/"/>
<url>/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<h1 id="ifetch-tag-stage"><a href="#ifetch-tag-stage" class="headerlink" title="ifetch_tag_stage"></a>ifetch_tag_stage</h1><ol><li><p>只考虑没被阻塞的thread,不排除在当前周期正在roll back的thread,rollback信号的时序路径critical,影响clock freqency。当选中的thread在同周期正在rollback,invalidate这条指令,通过deasserting ift_instruction_requested。这样会浪费一个周期,但是这种场景不常见。</p></li><li><p>当一条指令正在update TLB,不能访问它来进行地址翻译,所以空一拍取指<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/cache_fetch_en.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/tlb_entry.png" alt="iamge"></p></li><li><p>LRU 每个set维护一个least recently used list,判断哪个way to fill new cacheline。有两个接口update LRU,fill和access。</p><ul><li>Fill<ul><li>cache assert fill_en and fill_set 当需要load a cacheline</li><li>1周期后,这个模块把当前LRU way当作fill_way信号输出,并且把这个way设置成most recently used position</li></ul></li><li>Access<ul><li>在cache load的第一个cycle,the client asserts access_en 和 access_set。</li><li>1周期后,如果cache hit了,it asserts update_en 和 update_way 把 hit way 更新成MRU position</li><li>如果上个周期没有assert access_en,绝不能assert update_en,as the former fetches the old LRU value</li></ul></li><li>如果同时有fill_en 和 access_en,这是合法的,fill会优先处理. This is important:<ul><li>避免evict刚load的数据,当有很多fill back to back</li><li>避免livelock,不同的thread先后互相evict对方的lines</li></ul></li></ul></li></ol><p><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/lru.png" alt="image"></p><h1 id="ifetch-data-stage"><a href="#ifetch-data-stage" class="headerlink" title="ifetch_data_stage"></a>ifetch_data_stage</h1><ol><li>ifd_near_miss is high:上周期requested line is filled this cycle<ul><li>treate this as miss 会fill两份cacheline在set中</li><li>blocking the thread会hang住,因为错过了这一拍的wakeup signal</li><li>the cache interface update the tag, then the data a cycle later,所以下个周期可以得到新的数据,为了pick up the data,这个信号返回上一级流水,retry 这个请求</li></ul></li></ol><p><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/signal2decode.png" alt="iamge"></p><h1 id="decode"><a href="#decode" class="headerlink" title="decode"></a>decode</h1><p>Populate the decoded_instruction_t structure with fields from the instruction.<br>当interrupt is pending,一条现有的指令会将trap flag置位,有必要保证 interrupt is precise(从软件的角度,中断发生在两条指令的边界,前面的指令执行,后面的不执行).<br>we must do it this way because:</p><ul><li>instruction can retire out-of-order</li><li>有pending instructions in the pipeline for the thread that will cause a rollback in a subquence cycle.<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/register_map.png" alt="image"></li></ul><ol><li><p>某些指令需要发射两次,包括I/O requests 和 sync memory access. The first queues the transaction and the second collects the result. 第一个指令更新内部状态,如果中断在两次命令之间dispatch,会发生bad things。为了避免这种场景,如果第一次指令已经被发射了(indicated by dd_load_sync_pending, sq_store_sync_pending, ior_pending),don’t dispatch an interrupt<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/rise_interrupt.png" alt="image"></p></li><li><p>dlut package<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/dlut.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/imm_loc.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/vec_src.png" alt="image"></p></li><li><p>decoded_instruction_t<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/decoded_instrs.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/trap_cause.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/trap_type.png" alt="iamge"></p></li><li><p>treat call as move ra, pc<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/alu_op.png" alt="image"></p></li><li><p>immediate extend<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/immediate_extend.png" alt="iamge"></p></li><li><p>pipeline sel<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/pipeline_sel.png" alt="image"></p></li><li><p>last_subcycle<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/last_subcycle.png" alt="iamge"></p></li><li><p>output<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/output.png" alt="image"></p></li></ol><h1 id="Thread-Select-Stage"><a href="#Thread-Select-Stage" class="headerlink" title="Thread Select Stage"></a>Thread Select Stage</h1><ul><li>每个thread包含一个instruction FIFO</li><li>每个cycle,picks a thread to issue using a RR scheduling,避免conflict<ul><li>使用scoreboard track inner instruction register dependencies(WAW,RAW,WAR),each thread</li><li>writeback hazards between the pipelines of different lengths, tracked with a shared shift register</li></ul></li><li>Track D-cache miss and suspends threads until they are resolved</li></ul><ol><li><p>Interface<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/ts_intf.png" alt="image"></p></li><li><p>thread_state for simulation<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/thread_state_define.png" alt="iamge"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/thread_state_logic.png" alt="image"></p></li><li><p>instruction fifo for each thread<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/instr_fifo.png" alt="image"><br>enqueue: 前一级流水有valid的指令<br>dequeue:last subcycle 处理完<br>almost_full: 反压第一级instruction fetch,避免fifo爆满<br>empty: indicate issue valid</p></li><li><p>Scoreboard</p></li></ol><ul><li>register bitmap<br>clear : writeback or rollback. 只有int 和 mem execution pipeline 可以rise exception,rollback时不能把floating pipeline中先于exception 命令的指令rollback<br>如果exception 是 int指令,刷掉operand fetch-> int exec-> writeback stage中的指令<br>如果exception 是 mem指令,刷掉operand fetch-> dcache_tag -> dcache_data -> writeback stage中的指令<br>所以最多需要使用移位寄存器跟踪四条指令的目的寄存器,int 指令roll back时,刷掉三条指令[0:2], mem 指令rollback时刷掉四条指令[0:3]<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/rollback_bitmap.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/roolback_trace.png" alt="image"><br>set:被发射指令的dest, 仲裁成功(will issue)才能set<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/scoreboard_reg_update.png" alt="image"></li><li>dep check<br>当前准备发射指令的dest和src register,组合成一个bitmap,与当前scoreboard bitmap reg 对比是否有重合<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/dep_bitmap.png" alt="image"></li></ul><ol start="5"><li><p>writeback structure conflict check<br>记录前面发射指令在execution pipeline中的位置,根据当前要发射指令的类型 check对应bit<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/wb_conflict_check.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/wb_allocate_track.png" alt="image"></p></li><li><p>can_issue_thread<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/can_issue_logic.png" alt="image"></p></li></ol><p>多cycle指令,不会lock arbiter,当作16次单独的命令参与仲裁,subcycle!=0的命令,不需要检查scoreboard,因为第一次发的命令会把后续的命令挡住,包括subcycle!=0的命令。<br>rollback 会恢复到没有writeback的subcycle继续执行。</p><p>thread_blocked,没有考虑当拍的wb_suspend_thread_oh</p><ol start="7"><li><p>RR-arbiter<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/ts_arbiter.png" alt="image"></p></li><li><p>ts2exec pipe<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/ts2exec_pipe.png" alt="image"></p></li><li><p>thread block logic<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/thread_blocked.png" alt="image"></p></li></ol><p>疑问:为什么cache miss 要block 对应thread?miss的命令是怎么处理的?miss 指令发射到blocked reg更新期间发射的命令怎么处理?这期间发射的与之前miss的指令相同地址,如果在wb_suspend同一拍,回来了wakeup信号,则不suspend。</p><h1 id="dcache-tag-stage"><a href="#dcache-tag-stage" class="headerlink" title="dcache_tag_stage"></a>dcache_tag_stage</h1><ol><li><p>Snoop Interface<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/l1dcache_snoop_output.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/l1dcache_snoop_input.png" alt="image"><br>snoop 会像normal 指令 lookup一样读tag</p></li><li><p>PIPE_MEM 命令类型<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/pipe_mem_req_type.png" alt="image"><br>读写请求,有I/O空间和mem空间,两种</p></li><li><p>cache control instruction<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/cache_control.png" alt="image"></p></li><li><p>SET NUM config assert<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/set_num_assert.png" alt="image"></p></li><li><p>tag sram<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/l1d_tag_sram.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/mem_pipe_decode.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/mem_op.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/creg_index.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/cache_op.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/mem_access_decode.png" alt="iamge"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/exec_pipe_distinct.png" alt="iamge"></p></li></ol><p>疑问:只有cache load指令读tag?</p><ol start="6"><li><p>scgath request addr<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/scgath_addr_gen.png" alt="image"></p></li><li><p>tlb_lookup_en<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/dcache_tag_tlb_lookup_en.png" alt="image"><br>dinvalidate,dflush,普通的memory access,都需要VA translate to PA.</p></li></ol><h1 id="dcache-data-stage"><a href="#dcache-data-stage" class="headerlink" title="dcache_data_stage"></a>dcache_data_stage</h1><p>目标:</p><ul><li>确定store指令怎么处理的</li><li>确定sync指令怎么处理的</li><li>membar怎么处理的</li><li><strong>I/O访问也过cache?</strong></li><li>control reg 指令怎么处理的?</li></ul><ol><li><p>lane enable<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/ddata_lane_enable.png" alt="image"><br>scater/gather 指令对于mask掉的lane的请求,等效于invalidate memory access request</p></li><li><p>TLB read 判断<br>memory access中非control register,并且lane enable指令<br>cache control中DFLUSH,DINVALIDATE指令<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/l1ddata_tlb_read.png" alt="image"></p></li><li><p>Check faults<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/check_faults.png" alt="image"></p></li><li><p>把sync load 第一次发生,当作cache miss处理,需要发往L2 to register it<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/dcache_hit.png" alt="image"></p></li><li><p>L1 data cache or store buffer access<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/dd_store.png" alt="image"><br>store bypass 是什么作用?</p></li><li><p>写cacheline时,word_store_mask<br>分三种情况:</p></li></ol><ul><li>load_v/store_v , word_store_mask 与指令中的mask一致,一次性操作一整条cacheline</li><li>load_gath/store_scat, 一次操作某个cacheline的某个lane(根据req_addr来),根据subcycle迭代,同时还需要指令中mask限制</li><li>标量load/store,cacheline的store mask根据req addr确定<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/word_store_mask.png" alt="image"><br>cache_lane_index = dt_request_paddr.offset[CACHE_LINE_INDEX_WIDTH-1:2]</li></ul><ol start="7"><li><p>endian mode<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/endian_mode.png" alt="image"></p></li><li><p>byte_store_mask<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/byte_store_mask.png" alt="image"></p></li><li><p>整个cacheline,以byte为粒度的,左边为低地址的 store mask<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/dd_store_mask.png" alt="image"></p></li><li><p>load_sync_pending<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/load_sync_pending.png" alt="image"><br>疑问:为什么是来回取反,第一次第二次是针对sync load来说的吗?</p></li><li><p>cache control request都发往 l1_l2_interface 处理分发<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/l1d_cache_control.png" alt="image"></p></li></ol><h1 id="L2-Cache"><a href="#L2-Cache" class="headerlink" title="L2 Cache"></a>L2 Cache</h1><p>The L2 cache is write-back and allocates cache lines both on load and store misses.</p><h2 id="stage1-ARB"><a href="#stage1-ARB" class="headerlink" title="stage1: ARB"></a>stage1: ARB</h2><p><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/arb_intf.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/l2_arbiter.png" alt="image"><br>restart请求为什么会有flush?<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/restart_flush.png" alt="image"><br>collided_miss 是什么信号?<br>处理的命令类型:<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/packet_type.png" alt="image"><br>restart命令:iinvalidate和dinvalidate不会造成cache miss,不会enqueue fill 队列,其余的类型load,store,sync load,sync store,flush都会restart,但是flush restart和与pending miss重复的请求,不会真正的fill</p><h2 id="stage2-Tag"><a href="#stage2-Tag" class="headerlink" title="stage2: Tag"></a>stage2: Tag</h2><p>增加了update dirty接口,用宽度为1bit的sram实现。read stage update tag 和 dirty sram</p><ul><li>更新dirty:fill || (cache_hit & (store || first_flush))</li><li>更新tag和valid:fill || (cache_hit & dinvalidate)</li><li>way 根据fill_way 和 hit_way 选择</li><li>set与request.set一致</li><li>store 会set dirty bit</li><li>dinvalidate会 clear valid way bit</li></ul><h2 id="stage3:Read"><a href="#stage3:Read" class="headerlink" title="stage3:Read"></a>stage3:Read</h2><ul><li>检查cache hit</li><li>如果hit,更新LRU</li><li>读 data memory<ul><li>如果当前请求是fill,evict line如果是dirty,需要读出来write back to system memory</li><li>如果是flush并且hit,read the data in the line to write back<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/writeback_way.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/writeback_en.png" alt="iamge"></li><li>如果是普通的请求,hit,读相应数据</li></ul></li><li>更新dirty bit<ul><li>flush,clear dirty bit</li><li>store,set dirty bit</li></ul></li><li>fill 更新tag</li><li>tracks synchronized load/store state<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/can_store_sync.png" alt="image"><br>sync_store 在 load_sync_address_valid时才可以<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/load_sync_address_valid.png" alt="image"><br>load_sync 把地址在monitor注册成valid条件:<ul><li>cache hit</li><li>miss后,转成fill,restart</li><li>之后任何thread对这个地址的写操作(store 和 成功的sync_store)都会使得monitor失效</li></ul></li></ul><h2 id="stage4:Update"><a href="#stage4:Update" class="headerlink" title="stage4:Update"></a>stage4:Update</h2><p>对cache hit和fill命令进行写cache,读和写命令miss都会转成fill命令,对于写转成的fill命令,需要将原始数据和写数据通过byte mask合并。<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/merge_write_data.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/write_cache_en.png" alt="image"></p><ol><li>第一次flush miss,会restart 第二次? flush 的处理流程?<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/complete_flush.png" alt="image"><br>flush complete 标志:</li></ol><ul><li>第二次flush</li><li>第一次flush<ul><li>没有cache hit</li><li>cache hit,但是不需要writeback</li></ul></li></ul><p>cache hit 并且需要writeback的flush,会再次restart,从而完成flush操作,回core响应。</p><ol><li><p>l2_response_valid<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/response_valid.png" alt="image"></p></li><li><p>response packet<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/response_packet.png" alt="image"></p></li></ol><h1 id="l2-axi-bus-interface"><a href="#l2-axi-bus-interface" class="headerlink" title="l2_axi_bus_interface"></a>l2_axi_bus_interface</h1><p>从低位到高位找第一个1<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/find1_lsb.png" alt="image"></p><ol><li><p>pending miss cam<br>用来查找当前miss request是不是已经存在pending miss,这类请求无需再次fill,直接跳过load system memory过程<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/pending_miss_cam.png" alt="image"><br>set: 没有hit,更新next entry<br>clear: fill 命令,更新hit entry</p></li><li><p>pending writeback fifo<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/pending_writeback_fifo.png" alt="image"></p></li><li><p>pending fill fifo<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/pending_fill_fifo.png" alt="image"></p></li><li><p>一个状态机管理axi bus</p></li></ol><ul><li>writeback请求发完写数据,就restart flush</li><li>当接受完rdata,就restart fill</li><li>当fill命令是duplicate或者写整个cacheline时,可以跳过load system memory的过程,直接跳到complete 阶段</li><li>writeback 比 fill queue优先执行,避免load到stale data,并且fill命令也可能回enqueue writeback queue,防止write fifo full</li></ul><h1 id="l1-l2-interface"><a href="#l1-l2-interface" class="headerlink" title="l1_l2_interface"></a>l1_l2_interface</h1><ul><li>每个core都有一个这个模块,用于L1 和 L2 caches 交流</li><li>track pending load misses from L1 instruction and data caches(<strong>l1_load_miss_queue</strong>)</li><li>track pending store(<strong>l1_store_queue</strong>)</li><li>仲裁miss sources 发往L2 cache request</li><li>处理L2 response,update L1 instruction and data caches</li></ul><p>三级pipeline处理L2 responses:</p><ol><li>把response中的store地址发给L1D tag memory(1 cycle latency) to snoop it</li><li>check the snoop response,如果hit,select the hit way to update. 对于load responses,update tag memory</li><li>update L1D data memory。<strong>必须在更新tag之后的一个cycle避免race condiction</strong>疑问???????????</li></ol><p>target:</p><ul><li>搞明白snoop</li><li>搞明白thread wake up, load miss 会suspend thread select logic</li></ul><h2 id="l1-store-queue"><a href="#l1-store-queue" class="headerlink" title="l1_store_queue"></a>l1_store_queue</h2><ul><li>接收instruction pipeline中的store命令,send them to L2 interconnect,处理响应</li><li>cache control 指令也在这里处理</li><li>memory barrier request 等待all pending store request finish。处理起来像store一样,但是 dont enqueue anything</li></ul><p><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/pending_stores_packet.png" alt="image"></p><ol><li><p>can write combine<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/write_combine.png" alt="image"><br>normal store之间可以进行写合并</p></li><li><p>restart sync request<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/restart_sync_request.png" alt="image"><br>疑问:sync restart指的是什么????????????<br>sync 类请求,并且回了response</p></li><li><p>updata data of store entry<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/updata_store_entry.png" alt="image"></p></li></ol><ul><li>新请求类型是store_en</li><li>当前不能是restarted sync request</li><li>当前slot是空(allocate)或者当拍有response,can write combine(更新wakeup thread bitmap,更新mask,更新data)</li></ul><ol start="4"><li>rollback en<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/sq_rollback.png" alt="image"></li></ol><ul><li>第一次sync store总是会rollback,等待response(猜测,需要等待response确认sync success/fail,等response回来,wakeup后,restart,根据sync结果更新寄存器值0/1)</li><li>没有space,rollback</li><li>membar 只清空相同thread的 store buffer吗?疑问???????????????</li></ul><ol start="5"><li>bypass<br>store_queue module<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/sq_bypass.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/sq_bypass_logic.png" alt="image"><br>疑问?????????????????</li></ol><p>bypass信号来源:l1 dcache<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/l1dcache_bypass.png" alt="image"></p><p>去检查当前命令地址是否与store buffer里的pending store 一致</p><p>在wb stage中merge sq bypass data和dcache data<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/bypass_data_merge.png" alt="image"></p><p>为了解决同一个thread,RAW dependency,把store queue中的新数据bypass</p><h2 id="l1-load-miss-queue"><a href="#l1-load-miss-queue" class="headerlink" title="l1_load_miss_queue"></a>l1_load_miss_queue</h2><p>Tracks pending L1 misses. Detects and cosolidates multiple misses for the same address.<br>Wakes threads when loads complete.<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/pending_entry_packet.png" alt="image"></p><h2 id="response-3-stage-pipeline"><a href="#response-3-stage-pipeline" class="headerlink" title="response 3 stage pipeline"></a>response 3 stage pipeline</h2><p>target: 搞清楚snoop 机制,什么时候发snoop</p><h3 id="stage-1"><a href="#stage-1" class="headerlink" title="stage 1"></a>stage 1</h3><p>收到l2 response后</p><ul><li>如果response packet中 cache type = DCACHE,发起snoop en, read tag mem</li><li>如果是load response,发起fill lru</li><li>pass response valid and packet to next stage</li></ul><h3 id="stage-2"><a href="#stage-2" class="headerlink" title="stage 2"></a>stage 2</h3><ol><li>update dcache tag mem<br>check snoop result, 找到 hit way<br>update way:</li></ol><ul><li>fill(load): lru fill way</li><li>store,invalidate:snoop hit way<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/dupdate_way.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/update_dcache_tag.png" alt="image"></li></ul><ol start="2"><li><p>update icache tag mem<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/update_icache_tag.png" alt="image"><br>由于不能像dcache一样可以snoop,精准确定invalidate哪个way,所以直接一个set的所有way都invalidate</p></li><li><p>dequeue store queue和load miss queue<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/dequeue_sq_missq.png" alt="image"></p></li></ol><h3 id="stage-3"><a href="#stage-3" class="headerlink" title="stage 3"></a>stage 3</h3><p>update cache data mem<br>要遵循原有的读tag,读data时序,如果更新tag和data同在stage2,那么流水线中当前正在读data 的命令可能会因为fill出错,或者其他场景的错误</p><h2 id="request-logic"><a href="#request-logic" class="headerlink" title="request logic"></a>request logic</h2><p><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/l12l2_req.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/l12l2_store_req.png" alt="image"></p><h1 id="IO-request-queue"><a href="#IO-request-queue" class="headerlink" title="IO request queue"></a>IO request queue</h1><p>处理non-cacheable memory operations to <strong>memory mapped registers</strong><br>总是会block the thread直到transaction 完成</p><ol><li><p>interface<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/io_req_queue_intf.png" alt="image"></p></li><li><p>response 入entry的value field,等待restart 返回给wb<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/resp_enqueue_value.png" alt="image"><br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/rdata2wb.png" alt="image"></p></li><li><p>io命令入队列,发到io interconnect进行处理,同时instruction pipeline rollback,thread select suspend,等待io响应回来,wakeup thread,restart 相同的io指令,再次进入到io queue,表示该指令处理完成,将queue的read value返回给wb</p></li><li><p>queue管理<br><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/queue_manage.png" alt="image"></p></li></ol><h1 id="IO-interconnect"><a href="#IO-interconnect" class="headerlink" title="IO interconnect"></a>IO interconnect</h1><h1 id="Writeback"><a href="#Writeback" class="headerlink" title="Writeback"></a>Writeback</h1><p>target</p><ul><li>branch predict miss 怎么处理的</li><li>exception 怎么处理的</li><li>rollback 怎么处理的</li></ul><p><img src="/2023/11/15/Nyuzi-RTL%E5%88%86%E6%9E%90/handler_pointer.png" alt="image"></p><p>rollback 注意要点:</p><ul><li>thread select里的scoreboard,只回退受影响的一部分</li><li>只rollback 之后发射的指令流水线, fp流水线中会存在之前发射的命令</li></ul>]]></content>
<categories>
<category>GPGPU</category>
</categories>
<tags>
<tag>Nyuzi</tag>
</tags>
</entry>
<entry>
<title>Nyuzi-微架构分析</title>
<link href="/2023/11/15/Nyuzi-%E5%BE%AE%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90/"/>
<url>/2023/11/15/Nyuzi-%E5%BE%AE%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<p><img src="/2023/11/15/Nyuzi-%E5%BE%AE%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90/mirco_arch.png" alt="image"></p><h1 id="Execution-Pipeline"><a href="#Execution-Pipeline" class="headerlink" title="Execution Pipeline"></a>Execution Pipeline</h1><p><img src="/2023/11/15/Nyuzi-%E5%BE%AE%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90/exe_pipeline.png" alt="iamge"></p><p>每个core有多个hardware threads.</p><ul><li>each thread 对于软件来说像是一个独立的processor,有自己的PC和通用寄存器</li><li>threads共享算数pipeline和 L1 caches on their core</li></ul><p>multithreading 为了让shared units busy. A core issues an instruction from a different thread every cycle whenever possible.</p><p>当一条指令造成 data cache miss,branch,or exception,the core会把这个thread 发射的,在这条指令之后的,pipeline中的指令invalidate.</p><ul><li>branch指令会reset PC to destination and continue from there</li><li>A cache miss 会suspend the thread,直到memory subsystem fills it,然后从这条指令resume</li></ul><p><strong>有16个 parallel arithmetic pipeline</strong>: one for each vector lane.</p><ul><li>scalar instruction 使用lowest lane</li><li>mix scalar and vector instruction,会复制scalar operands across all lanes</li><li>an instruction也可以通过mask register指定vector lane</li></ul><h2 id="Instruction-Cache-Tag"><a href="#Instruction-Cache-Tag" class="headerlink" title="Instruction Cache Tag"></a>Instruction Cache Tag</h2><p>It increments the program counter of the issued thread, effectively <strong>always predicting branches are not taken</strong>.</p><h3 id="L1-Cache-tag"><a href="#L1-Cache-tag" class="headerlink" title="L1$ Cache tag"></a>L1$ Cache tag</h3><ul><li>VIPT</li><li>Tag memory has one cycle of latency,next stage check result</li><li>Tag memory 有独立的read and write port,instruction fetch 和 miss fill可以在同一个cycle执行</li><li>entry consists of a tag value and valid bit</li><li>for each set,维护一个LRU table(least recently used).<ul><li><strong>signal from next stage updates when there is a cache hit</strong></li></ul></li></ul><h3 id="L1-TLB"><a href="#L1-TLB" class="headerlink" title="L1$ TLB"></a>L1$ TLB</h3><ul><li>look up the address translation and pass it to next stage</li><li>managed by software<ul><li>TLB miss raises an exception</li><li>software exception routine looks up the PA and use special instructions put a new mapping in TLB</li></ul></li></ul><p>这个stage detect an exception(TLB miss,misaligned PC),会set a exception flag随着instruction 继续流水。在writeback stage 处理。</p><h2 id="Instruction-Cache-Data"><a href="#Instruction-Cache-Data" class="headerlink" title="Instruction Cache Data"></a>Instruction Cache Data</h2><p>This stage compare physical tag(TLB PA and readed tag from last stage)</p><ul><li>Hit:read data from i-cache data memory</li><li>Miss:enqueue a fill request in instruction cache load miss queue</li></ul><h2 id="Instruction-Decode"><a href="#Instruction-Decode" class="headerlink" title="Instruction Decode"></a>Instruction Decode</h2><ul><li>判断指令需要哪个register operand,提取register indices and map them to register file ports</li><li>判断指令要被发射到哪个execution pipeline</li><li>判断which the next stage uses to track register writeback conflicts</li></ul><h2 id="Therad-Select"><a href="#Therad-Select" class="headerlink" title="Therad Select"></a>Therad Select</h2><p>It uses a round robin selection policy, issuing from a different thread every cycle whenever possible. There is a FIFO of decoded instructions for each thread.</p><p>每周期从一个thread中pick 一条指令.<br>It skip threads that it cannot issue because of <strong>operand dependencies, structual hazard, or cache miss</strong>.</p><h3 id="Operand-dependencies-———-Scoreboard-each-thread"><a href="#Operand-dependencies-———-Scoreboard-each-thread" class="headerlink" title="Operand dependencies ——— Scoreboard each thread"></a>Operand dependencies ——— Scoreboard each thread</h3><p>每个thread 有一个scoreboard which tracks operand dependencies between instructions.<br>The data dependency : <strong>RAW, WAW</strong>(WAR,读stage在write stage 之前,这种conflict不存在)</p><p>A scoreboard contains a pending bit for each register, which it use as follows:</p><ol><li>当发射一条指令时,set the pending bit for the destination register</li><li>当writeback register with the result of an instruction,it clear the pending bit for the destination register</li><li>当instructions 由于rollback倍冲刷时,clear the pending bits for them</li><li>在发射指令之前,the thread select stage 检查source and destination register of the instruction.<ul><li>if set, 这个cycle 不发射</li><li>check source register for RAW</li><li>check destination register for WAW</li></ul></li></ol><h3 id="Structual-hazard-———-Shift-register-shared-between-all-threads"><a href="#Structual-hazard-———-Shift-register-shared-between-all-threads" class="headerlink" title="Structual hazard ——— Shift register shared between all threads"></a>Structual hazard ——— Shift register shared between all threads</h3><p>由于integer,memory,floating point execution 流水线长度不同,不同cycle发射的命令可能会同时到达writeback stage. The writeback stage 一个周期只能retire一条指令.</p><p>The thread select stage 延迟有结构冲突的指令发射。<br>A shared shift register tracks which stages have active instructions.<br>每个thread 基于将要发射指令的delay 检查对应的bit。</p><h3 id="Cache-miss"><a href="#Cache-miss" class="headerlink" title="Cache miss"></a>Cache miss</h3><p>The thread select stage tracks threads that are waiting because</p><ul><li>their store queues are full</li><li>or waiting on a data cache miss</li></ul><h3 id="The-scatter-gather-vector-memory-operations"><a href="#The-scatter-gather-vector-memory-operations" class="headerlink" title="The scatter/gather vector memory operations"></a>The scatter/gather vector memory operations</h3><p>这类操作每个lane进行独立的cache access.<br>The thread select 发射16次,伴随一个subcycle counter计数.<br>当 rollback发生时,subcycle counter复位到the one that triggered it.<br>This generic subcycle mechanism could support having fewer execution units than vector lanes or other complex instructions.</p><h2 id="Operand-fetch"><a href="#Operand-fetch" class="headerlink" title="Operand fetch"></a>Operand fetch</h2><ul><li>Contain vector and scalar registers for all threads</li><li>The register memories have 1 cycle latency</li><li>Each register file has 2 read port and 1 write port (writeback stage control write port)</li></ul><h2 id="Floating-Point-Arithmetic-Pipeline"><a href="#Floating-Point-Arithmetic-Pipeline" class="headerlink" title="Floating Point Arithmetic Pipeline"></a>Floating Point Arithmetic Pipeline</h2><p>TODO</p><h2 id="Interger-Arithmetic"><a href="#Interger-Arithmetic" class="headerlink" title="Interger Arithmetic"></a>Interger Arithmetic</h2><ul><li>执行整数operation,页执行一些简单的floating point操作</li><li><strong>check for branch</strong></li></ul><h2 id="Data-Cache-Tag"><a href="#Data-Cache-Tag" class="headerlink" title="Data Cache Tag"></a>Data Cache Tag</h2><ul><li>contains multiple tag memory banks, a LRU list, TLB</li><li>an extra read port to allow cache snoop operations 不影响 execution pipeline load instructions</li><li>TLB translate store and cache control addresses that are sent to the L2 cache in the next stage, in addtion to translating read addresses.</li></ul><h2 id="Data-Cache-Data-——-write-through-write-no-allocate"><a href="#Data-Cache-Data-——-write-through-write-no-allocate" class="headerlink" title="Data Cache Data —— write through, write no allocate"></a>Data Cache Data —— write through, write no allocate</h2><h3 id="Load"><a href="#Load" class="headerlink" title="Load"></a>Load</h3><ul><li>Load Hit: 从cache SRAM读取数据,512bit位宽的data bus,a contiguous vector load occurs in one cycle</li><li>load hit: check the store queue, forward the store data</li><li>load miss: search all load miss queue entries,<ul><li>if have same address pending request, <strong>it sets the bit in that entry’s wait bitmap that corresponds to the thread</strong>.</li><li>if not, allocate a new load miss queue entry</li></ul></li></ul><h3 id="Store"><a href="#Store" class="headerlink" title="Store"></a>Store</h3><ul><li>Store(hit) 指令不直接更新L1 cache:<ul><li>而是使用PA,往store queue插入一个request</li><li>然后这会往L2 cache发起request<ul><li>each entry in the queue contain a full cacheline</li></ul></li><li>当收到response时更新L1 cache</li></ul></li><li>Store(miss)<ul><li>write miss no allocate</li></ul></li><li>This stage aligns smaller writes to their location in line 并且 设置64bit(each for 8bit lane)的mask</li><li>write combining: a store 在store queue中遇到相同地址相同thread,并且没发往L2的request时,the store queue merge 写数据</li></ul><p>When load miss or store queue full, it roll back the thread and suspend it<br>当L2回response时,load miss queue 或者 store queue assert a signal to the thread select stage to wakeup the thread</p><p>Non-cached device register 在物理地址空间的顶部,通过单独的request/response bus交互,only be 32bit transfer.</p><h2 id="Writeback"><a href="#Writeback" class="headerlink" title="Writeback"></a>Writeback</h2><h1 id="L2-Cache"><a href="#L2-Cache" class="headerlink" title="L2 Cache"></a>L2 Cache</h1><h2 id="L1-L2-Cache-Interconnect"><a href="#L1-L2-Cache-Interconnect" class="headerlink" title="L1/L2 Cache Interconnect"></a>L1/L2 Cache Interconnect</h2><ul><li>有单独的request and response接口,让core与L2 cache交互。</li><li>当core发送请求到L2时(store or load miss),会assert signals on L2 request interface。</li><li>当L2 已经接收到请求时(虽然在这时,没处理完这个请求),L2 cache asserts an acknowledgement signal</li><li>Store request contains an entire 512bit cacheline and a 64bit mask</li><li>当L2 完成处理时,broadcast a response on the L2 response interface,which all cores monitor</li><li>L2 可能会乱序返回response,由于restart mechanism can reorder transactions,用ID识别。</li><li>L2 cache is PIPT, writeback allocate(read and write miss)</li></ul><h2 id="L2-Cache-Pipeline"><a href="#L2-Cache-Pipeline" class="headerlink" title="L2 Cache Pipeline"></a>L2 Cache Pipeline</h2><p><img src="/2023/11/15/Nyuzi-%E5%BE%AE%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90/L2_pipeline.png" alt="image"></p><ul><li>Arbitrate : 从 core request和restart request 仲裁一个传给下级流水。restart request 优先级高,防止miss queue 填满导致死锁。L2 pipeline 不 stall,但是可以反压core request。</li><li>Tag : 读tag和dirty bit,1 cycle latency</li><li>Read : 如果L2 hit,会读出data,在response中返回。如果是 fill,读出dirty evict,放到writeback queue。更新tag memory</li><li>Updata : assert signals to update cache memory,combining with previously read data for each byte where the store mask is set.</li></ul><p>L2 cache 是writeback allocate,当cache miss时,把request 放入 miss queue。一个状态机控制从外部内存取回数据。当transaction结束时, the request restarts at the beginning of the L2 cache pipeline with the fetched data.</p><p>writeback queue存储将写回到主存的数据,要在load miss queue之前处理writeback queue,防止load到旧数据。</p><p>L2使用axi接口模块,也可以更换成其他协议,比如wishbone。</p><p>The L2 cache is not inclusive or exclusive: A cache line may only be in the L1 cache, only in the L2 cache, or in both.</p><h2 id="Cache-Coherence"><a href="#Cache-Coherence" class="headerlink" title="Cache Coherence"></a>Cache Coherence</h2><p>为了简化软件,处理器支持一片coherent address空间,在所有的core和thread之间共享。<br>coherent cache 遵循三个规则:</p><ol><li>一个thread写一个地址然后读回,如果期间没有其他thread写这个地址,必须读回一样的值</li><li>当一个thread写了某个地址,其他thread必须最终能看到</li><li>all other threads必须看到同样的store order(不必须按照store发生的顺序)</li></ol><p>L1 cache使用 a snooping write-update protocol. L1 cache is write-through, 不会有像write-back cache一样的 dirty or exclusive L1 cacheline, 所以不需要复杂的协议 to get exclusive access to a line. 但是当write发生时,需要update 其他L1 cache中的cacheline.</p><p>Bypass(forwarding) logic in L1 store queue 让 pending store对于发射它的thread 可见,遵循了rule 1.</p><p>The write update mechanism enforce rule 2.</p><p>所有的write先去L2 cache,而不更新 L1D cache,这样可以serialize the result, this enforce rule 3. The store bypass logic 不能让同一个core里的其他thread 提前看到write 数据,否则同一个core里的其他thread 就与其他core里的thread看到不一样的write order.</p><p>每个core snoops L2 responses for store updates. 每个core里有三级流水线处理来自L2 cache的信息.<br>这段逻辑处理store update和 L1 load miss fills:</p><ul><li>第一级 发送 response中的地址到L1D tag memory,判断是否在cache中. L1D cache 有一个单独的snoop read port 不影响 load from instruction pipeline</li><li>第二级检查hit/miss结果. 如果miss,the pipeline asserts signals to update tag RAM</li><li>第三级更新data memory. 不在第二级更新,是为了避免race conditon,因为execution pipeline在check tag后一个周期read data memory</li></ul><p>The snoop address from L2 cache is PA,但是L1 tag are indexed by VA.<br>cacheline 64 byte, 如果set num 小于或等于64,the set index is same for VA and PA,否则会出现alias问题。为了简化硬件,当MMU enable时,set num被限制到64.</p><p>处理器使用 a relaxed memory consistency model. 因为 pipeline 发射 memory instruction in order,it preserves read-to-read,and write-to-write ordering,但是不能保证 write-to-read ordering between threads,因为当一个线程的write在store queue中时,还可以进行命令处理,导致read看不到这个写数据。 The membar instruction enforce explict ordering by suspending the thread, 如store queue中有pending store。当L2 cache acknowledge时,it resumes the thread。这保证other L1 caches have the stored data.</p><p>如果 a store 写整个cacheline 并且这个cacheline not cache resident,不需要load it from memory,直接写整个cacheline</p><h2 id="Synchronized-load-store"><a href="#Synchronized-load-store" class="headerlink" title="Synchronized load/store"></a>Synchronized load/store</h2><p>A synchronized store should follow a synchronized load.<br>When a thread executes a synchronized memory load, the L2 cache latches the address of the load (it tracks each thread independently).<br>If another thread writes to that cache line, it clears this address.<br>Synchronized store operations check the latched address. If it doesn’t match, the synchronized store fails and it copies zero into the source register of the instruction.<br>If it does match, the store proceeds and it copies one into the source register.</p><p>To operate correctly, a synchronized load must atomically set the internal register in the L2 and return the current data in the line. Synchronized loads behave like L1 cache misses to ensure this. The cache load miss queue handles this request.</p>]]></content>
<categories>
<category>GPGPU</category>
</categories>
<tags>
<tag>Nyuzi</tag>
</tags>
</entry>
<entry>
<title>《超标量处理器设计》笔记</title>
<link href="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/"/>
<url>/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h2 id="2-2-提高Cache的性能"><a href="#2-2-提高Cache的性能" class="headerlink" title="2.2 提高Cache的性能"></a>2.2 提高Cache的性能</h2><h3 id="2-2-1-写缓存(write-buffer)"><a href="#2-2-1-写缓存(write-buffer)" class="headerlink" title="2.2.1 写缓存(write buffer)"></a>2.2.1 写缓存(write buffer)</h3><p>当D-Cache miss时,需要选择一个cacheline evict,并且读取下级存储器fill,如果evivt cacheline dirty,则需要先write back再fill。由于下级存储器访问时间长,这种串行的先写回再读取会导致D-Cache发生缺失的处理时间变长。<br>此时可以使用Write Buffer解决,dirty cacheline会首先放到写缓存中,等到下级存储器空闲时,将写缓存中的数据写回。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/write_buffer.png" alt="image"></p><p>Write Buffer可以将D-Cache中的数据写到下级存储器的时间进行隐藏。<br>当采用write through策略时,也会缩短处理时间。<br>同时,当cache miss需要从下级存储器读取时,也需要在write buffer中查找,这需要在write buffer中加入CAM电路(Content Addressable Memory)</p><h3 id="2-2-2-流水线"><a href="#2-2-2-流水线" class="headerlink" title="2.2.2 流水线"></a>2.2.2 流水线</h3><p>读D-Cache,tag sram和data sram可以同时读取,所以当处理器周期时间要求不严格时,可以在一个周期内完成读取操作。<br>写D-Cache,读tag sram和写data sram操作只能串行的完成,很难在一个周期完成,需要采用流水线结构</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/cache_pipeline.png" alt="image"></p><p>load在hit情况下,可以一个周期完成<br>store第一个周期读取tag stam进行比较;第二个周期选择是否将数据写入data sram。<br>需要注意:当后续的load指令与流水线中的store指令地址相同时,store流水线中的数据应该bypass 给load指令</p><h3 id="2-2-3-多级结构"><a href="#2-2-3-多级结构" class="headerlink" title="2.2.3 多级结构"></a>2.2.3 多级结构</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/inclusive_exclusive.png" alt="image"></p><h3 id="2-2-4-Victim-Cache"><a href="#2-2-4-Victim-Cache" class="headerlink" title="2.2.4 Victim Cache"></a>2.2.4 Victim Cache</h3><p>通常Victim Cache采用全相连的方式,容量都比较小(4~16个数据)</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/victim_cache.png" alt="image"></p><p>Victim Cache 本质上相当于增加了Cache中way的个数,能够避免第一个数据竞争同一个set中有限的位置,从而降低miss rate。一般情况下,victim cache 和 cache时exclusive关系,不包含相同数据</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/filter_cache.png" alt="image"></p><h3 id="2-2-5-预取"><a href="#2-2-5-预取" class="headerlink" title="2.2.5 预取"></a>2.2.5 预取</h3><ol><li><p>硬件预取<br>指令预取,为了避免cache污染,可以将预取的指令放到一个单独的缓存中。对于分支指令无效<br><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/hardware_prefetch.png" alt="image"><br>数据预取,规律难以捕捉,一般情况下,当D-Cache miss时,除了将所需的cacheline读回,还会将下一个cacheline读回。预取无效会浪配带宽和功耗。一种激进的方法,strided prefetching,使用硬件观测程序中使用数据的规律。</p></li><li><p>软件预取<br>在编译阶段,compiler对程序进行分析,如果指令集中设有预取指令,编译器可以直接控制程序进行预取,此时预取有针对性。<br>使用软件预取的方法,当执行预取指令的时候,处理器需要能够继续执行,即能继续从D-Cache中读取数据,而不能让预取指令阻碍了后面指令的执行,这就要求D-Cache是非阻塞的结构。<br>prefetch指令可坑会引起exception,例如page fault,virtual address fault和protection violation。可以选择处理(Faulting Prefetch Instruction),反之不处理(Nonfaulting Prefetch Instruction),此时发生异常的预取指令就变成一条空指令。</p></li></ol><h2 id="2-3-多端口cache"><a href="#2-3-多端口cache" class="headerlink" title="2.3 多端口cache"></a>2.3 多端口cache</h2><p>超标量处理器需要在每周期执行多条load/store指令</p><h3 id="2-3-1-True-Multi-port"><a href="#2-3-1-True-Multi-port" class="headerlink" title="2.3.1 True Multi-port"></a>2.3.1 True Multi-port</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/true_multi_port.png" alt="image"></p><p>Tag sram和Data sram本身不需要复制一份,但是它们当中每个cell都需要同时支持两个并行的读取操作</p><h3 id="2-3-2-Multiple-Cache-Copies"><a href="#2-3-2-Multiple-Cache-Copies" class="headerlink" title="2.3.2 Multiple Cache Copies"></a>2.3.2 Multiple Cache Copies</h3><h3 id="2-3-3-Multi-banking"><a href="#2-3-3-Multi-banking" class="headerlink" title="2.3.3 Multi-banking"></a>2.3.3 Multi-banking</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/multi_bank_cache.png" alt="image"><br>对于Data sram此时不需要多端口结构,这样提高了速度,并减少了面积。<br>由于需要判断cache的每个端口是不是命中,对tag sram仍需提供多个端口同时读取的功能,需要采用多端口sram或者单端口sram复制的方法。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/multi_bank_example.png" alt="image"></p><h2 id="2-4-超标量处理器的取指令"><a href="#2-4-超标量处理器的取指令" class="headerlink" title="2.4 超标量处理器的取指令"></a>2.4 超标量处理器的取指令</h2><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/inst_fetch.png" alt="image"></p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/4word_cacheline.png" alt="image"></p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/4sram_cache.png" alt="image"></p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/inst_reorder.png" alt="image"><br>当取指令地址指向了cacheline中最后三条指令之一时,在本周期不能输出四条指令,因此在重排序逻辑电路中还需要加入指示每条指令是否有效的标志信号,这样才能将有效的指令写入到后续的指令缓存(Instruction Buffer)中。</p><h3 id="3-2-3-page-fault"><a href="#3-2-3-page-fault" class="headerlink" title="3.2.3 page fault"></a>3.2.3 page fault</h3><p>直接使用虚拟地址并不能知道一个页位于硬盘的哪个位置,也需要一种机制来记录一个进程的每个页位于硬盘中的位置。通常,操作系统会在硬盘中为一个进程的所有页开辟一块空间,这就是之前说的swap空间,这个空间中存储一个进程所有的页,操作系统在开辟swap空间的同时,还会使用一个表格记录每个页在硬盘中存储的位置,这个表的结构和页表一样,可以单独存在,理论上也可以和页表合并,如下图</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/page_fault.png" alt="image"></p><p>在write back类型的Cache中,load/store指令在执行的时候,只会对D-Cache起作用,对物理内存的更新可能会有延迟,<strong>当操作系统需要查询页表中的这些状态位时,首先需要将D-Cache中的内容更新到物理内存中,这样才能使用到页表中正确的状态位</strong></p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/pte_context.png" alt="image"><br><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/no_page_fault_routine.png" alt="image"><br><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/page_fault_routine.png" alt="image"></p><h2 id="3-3-程序保护"><a href="#3-3-程序保护" class="headerlink" title="3.3 程序保护"></a>3.3 程序保护</h2><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/memory_protection.png" alt="image"><br><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/pte_with_protection.png" alt="image"></p><p>第一级页表也可以控制权限,控制范围更大(比如二级页表结构中,以及页表控制1024 X 4k).<br>通过粗粒度(一级页表)和细粒度(二级页表)组合,可以提高处理器效率</p><p>存在D-Cache的系统中,处理器送出的虚拟地址经过页表转化为物理地址后,并不会直接访问物理内存,而是先访问D-Cache</p><ul><li>读操作,直接从D-Cache中读取数据</li><li>写操作,直接写D-Cache(假设命中),并将被写入的cacheline置为dirty状态。</li></ul><p>只要D-Cache hit就不用访问物理内存。但是当处理器发出的虚拟地址不是要访问物理内存,是想访问外设,此时这些地址是不允许经过D-Cache缓存的。<br>因此在处理器的memory map中,总有一块区域是不可以被缓存的。</p><p>目前,页表中每个PTE包括内容:</p><ul><li>PFN</li><li>Valid</li><li>Dirty</li><li>Use</li><li>AP</li><li>Cacheable</li></ul><h2 id="3-4-TLB-和-Cache"><a href="#3-4-TLB-和-Cache" class="headerlink" title="3.4 TLB 和 Cache"></a>3.4 TLB 和 Cache</h2><p>在现代处理器中很多采用两级TLB</p><ul><li>第一级采用哈佛结构,分为I-TLB 和 D-TLB,一般采用全相连的方式</li><li>第二级TLB指令和数据共用,一般采用组相连的方式</li></ul><p>在很多处理器中,还支持容量更大的页,随着程序越来越大,4KB页不能满足要求了,对于一个128个entry的I-TLB,只能映射4K X 128 = 512KB大小的程序,对当代的程序不够,需要使用容量更大的页,例如1M,4M,这样可以是TLB映射更大的潘伟,避免频繁对TLB进行替换<br>大页存在缺点:</p><ul><li>很多程序用不大这么大的页,会造成页内空间浪费,这种现象叫页内碎片(Page Fragement),降低了页的使用效率</li><li>当page fault时,更大的页意味着搬移更多的数据,花费更多的时间</li></ul><p>为了解决这种矛盾,现代处理器中都支持大小可变的页,由操作系统进行管理,根据不同应用特点选用不同大小的页,最大限度的利用TLB优先空间,又不至于在页内产生过多碎片<br>为了支持这种特性,在TLB中需要相应的位进行管理。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/mips_pagemask.png" alt="image"></p><p>在TLB以上的所有项中,除了use 和 dirty bit外,其他的项在TLB中是不会改变的。当TLB采用write back方式时,TLB中表项被替换,只有这两个bit被写回到页表中</p><p>TLB缺失,解决的本质是从页表中找到对应的映射关系,并将其写回到TLB中,这个过程叫Page Table Walk</p><ol><li>软件实现</li><li>硬件实现</li></ol><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/tlb_miss_process.png" alt="image"><br>3. TLB写入<br>当页从物理内存中换出时,操作系统需要知道这个页的dirty状态,选择写回还是直接覆盖(dirty状态可能存在D-Cache中,还没有写回到页表项)。如果使用了TLB作为页表的缓存,处理器送出的虚拟地址会首先访问TLB,如果命中,直接从TLB中得到物理地址,不需要访问页表。执行load/store指令会使TLB中对应entry的use 和 dirty bit置位,如果TLB采用write back册罗,use 和 dirty bit不会马上从TLB写回到页表中,只有TLB entry被替换时才会将对应信息写到页表中。</p><p>这样页表中记录的状态可能是过时的,在page fault替换时带来问题。为了避免每次page fault都需要flush TLB,操作系统可以认为被TLB记录的页都是需要使用的,在物理内存中不能被替换。操作系统可以采用一些办法记录页表中哪些PTE被放到了TLB中。这样做还有一个好处,页被替换出去后,不用查TLB将其invalidate。</p><p><strong>总结:TLB中的页不能从物理内存中被替换</strong></p><p>当page fault时,如果从物理内存中选出要替换的页是dirty状态,如果使用了D-Cache,物理内存每个页的最新内容可能在D-Cache,要将一个页写回到硬盘,就需要首先确认D-Cache中是否保留了这个页中的数据。此时操作系统必须能从D-Cache中找出这个页的内容,<strong>这要求操作系统有控制D-Cache的能力</strong>。</p><ol start="4"><li>对TLB进行控制</li></ol><ul><li>能够将I-TLB和D-TLB所有entry置为无效</li><li>能够将I-TLB和D-TLB中某个ASID对应的所有entry置为无效</li><li>能够将I-TLB和D-TLB中某个VPN对应的entry置为无效</li></ul><h3 id="3-4-2-Cache的设计"><a href="#3-4-2-Cache的设计" class="headerlink" title="3.4.2 Cache的设计"></a>3.4.2 Cache的设计</h3><p>virtual cache有问题</p><ol><li>同义问题(synonyms),也叫重名(aliasing),多个不同的虚拟地址对应同一个物理地址</li></ol><ul><li>浪费cache空间</li><li>存在修改不一致的问题</li></ul><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/cache_alias.png" alt="image"></p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/bank_alias.png" alt="image"></p><ol start="2"><li>同名问题(homonyms),同一个虚拟地址对应不同物理地址<br>增加ASID 和 Global 项</li></ol><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/asid_pt.png" alt="image"></p><p>当使用多级页表的系统中,只有第一级别页表才会常驻物理内存中,第一级页表的基地址由处理器中专用的寄存器指定,即PTR寄存器。支持ASID的处理器中的进程分配ASID,并将其写入ASID寄存器,这个进程中的所有虚拟地址会在前面附上这个ASID,在TLB中,ASID和VPN一起组成新的虚拟地址,参与地址比较,这样就在TLB中对不同进程进行了区分。</p><p>当ASID分配完后,操作系统需要从已分配的ASID中挑一个不常使用的值,将它在TLB中的内容清空,并将这个ASID分配给新的进程。<strong>此时新的进程会更新PTR</strong>,为了能够对新的进程进行回复,操作系统需要将被覆盖的PTR寄存器的值保存起来,这样等旧的进程再次被执行时,就可以知道它存在物理内存的哪个位置。</p><p><strong>总的来说,如果使用ASID,操作系统就要管理ASID的使用,尤其对那些已经退出的进程,要及时回收ASID的值,以供新的进程使用</strong></p><ol start="3"><li>对Cache进行控制</li></ol><h3 id="3-4-3-将TLB和Cache放入流水线"><a href="#3-4-3-将TLB和Cache放入流水线" class="headerlink" title="3.4.3 将TLB和Cache放入流水线"></a>3.4.3 将TLB和Cache放入流水线</h3><p>解决重名问题<br>VIPT,index超出4K地址边界,不同虚拟地址存在不同cache set,对应相同物理地址</p><p>除了之前介绍的多bank结构的cache,还可以让这些重名的虚拟地址只有一个存在于cache中<br>可以使用L2 Cache实现这个功能,使L2 Cache中包括所有L1 Cache的内容 (inclusive关系)<br><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/uniform_l2cache.png" alt="image"></p><p>当L1 miss,L2也miss,会从物理内存中读回数据/指令,fill到L1和L2;<br>当L1 dirty evict,首先写到L2 Cache。<br>只要保证从物理内存读回的数据在写道L1的同时写入L2,就能保证L2中包含所有L1的内容,当然必须保证L2 大于 L1 I-Cache和D-Cache 容量之和。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/l2_alias.png" alt="image"></p><p>小结<br><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/access_memory_type.png" alt="image"></p><h1 id="4-分支预测"><a href="#4-分支预测" class="headerlink" title="4 分支预测"></a>4 分支预测</h1><h2 id="4-1-概述"><a href="#4-1-概述" class="headerlink" title="4.1 概述"></a>4.1 概述</h2><p>如果能在取指阶段,就可以“预知”本周期所取出的指令中是否存在分支指令,并且可以知道它的方向以及目标地址的话,就可以在下个周期从分支指令的目标地址开始取指令,这样不会对流水线造成影响。</p><p>要进行分支预测,首先需要知道从I-Cache取出的指令中,哪条指令是分支指令,对于每周期取出多条指令的超标量处理器来说,需要从fetch group中找出分支指令。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/fast_decode.png" alt="image"></p><p>在指令从L2 Cache写入I-Cache之前进行快速解码,也叫预解码,I-Cache占用更多面积,但是省掉了快速解码电路,缓解了对处理器周期的影响。但是,从取指令到得到预测结构两个阶段的间隔时间过长,无法解决。</p><p>分支预测的最好时机是当前周期得到取指令地址的时候,在取指令的同时进行分支预测,这样在下个周期就可以根据预测的结果继续取指令。使用PC值进行分支预测,在进程切换之后,需要将分支预测器中的内容进行清空,保证不同进程之间的分支预测不会互相干扰。如果使用了ASID,可以用来一起和PC进行分支预测,此时就不用进程切换清空分支预测器。</p><p>在PC值刚产生的那个周期,根据PC值预测本周起的fetch group中是否存在分支指令,以及分支指令的方向和目标地址。</p><p>一旦程序开始执行,每条指令对应的PC值固定。只要这条分支指令第一次被执行完后,后面再次遇到这个PC值,就可以知道当前要取的指令是分支指令。即使发生了自修改,也会将分支预测其清空,重新开始进行分支预测,并不影响分支预测的过程。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/pc_to_branch.png" alt="image"></p><h2 id="4-2-分支指令的方向预测"><a href="#4-2-分支指令的方向预测" class="headerlink" title="4.2 分支指令的方向预测"></a>4.2 分支指令的方向预测</h2><h3 id="4-2-1-基于两位饱和计数器的分支预测"><a href="#4-2-1-基于两位饱和计数器的分支预测" class="headerlink" title="4.2.1 基于两位饱和计数器的分支预测"></a>4.2.1 基于两位饱和计数器的分支预测</h3><p>根据一条分支指令前两次执行的结果预测本次的方向。这种方法用一个4状态的状态机表示:</p><ul><li>11 Strongly taken: 预测跳转</li><li>10 Weakly taken: 预测跳转</li><li>01 Weakly not taken: 预测不跳转</li><li>00 Strongly not taken: 预测不跳转</li></ul><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/2bit_saturate_counter.png" alt="image"></p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/pht.png" alt="image"></p><p>别名会降低分支预测准确度,可以采用更高级的方法来避免别名情况的发生,比较典型的方法是哈希,对PC值进行处理后再去寻址PHT。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/hash_pht.png" alt="image"></p><p>在流水线的提交(Commit)阶段,当分支指令要离开流水线时,更新PHT</p><h3 id="4-2-2-基于局部历史的分支预测"><a href="#4-2-2-基于局部历史的分支预测" class="headerlink" title="4.2.2 基于局部历史的分支预测"></a>4.2.2 基于局部历史的分支预测</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/local_branch_predict.png" alt="image"><br><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/example_local_predict.png" alt="image"><br><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/pc_bht_pht.png" alt="image"></p><p>由于PC的一部分来寻址BHT和PHTs,可能会有重名(alias)的情况。</p><p>PHT占用空间较大,为了更大限度节约PHTs所占内存,一个极端情况是PHTs中只含有一个PHT</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/one_pht.png" alt="image"></p><p>对于一个BHR来说,只会用到PHT的少部分内容,这样多个BHR可以分别使用PHT中不同的部分,最大限度的利用PHT中的计数器,避免PHT存在过多用不到的内容而浪费内存。但是不可避免,会存在下面两种冲突:</p><ul><li>两条分支指令的k部分相同,对应同一个BHR,对应PHT中同一个饱和计数器,会降低分支预测准确度</li><li>两条分支指令对应不同的BHR,但是BHR的内容一样,这两条分支指令也会共用PHT中的同一个饱和计数器,降低准确度</li></ul><p>为了避免上述两种情况,可以将PC值和对应的BHR进行处理,使用处理之后的值寻址PHT</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/hash_bhr.png" alt="image"></p><ul><li>PC经过hash得到BHR,解决了两条分支指令PC,k部分相等的问题</li><li>BHR再拼接一部分PC,解决BHR内容相同的问题。有多种方法进行PC和BHR处理,这种位拼接是最简单的方式,还有其他方法,比如异或法(XOR)</li></ul><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/pc_xor_bhr.png" alt="iamge"></p><p>当一条分支指令的循环周期太大时,需要一个很宽的BHR寄存器,还会导致过长的训练时间,同时PHT会随之占用更多的存储器资源。现实设计中,只能使用有限位宽的BHR,这样会导致,即使对很有规律的分支指令,例如999次跳转加一次不跳转,也无法获得最完美的准确预测。这种方法没有考虑分支指令之前的其他分支指令对自身预测结果的影响。有时,一条分支指令的结果并不却决于自身再过去的执行情况,而是与之前的分支指令息息相关。</p><h3 id="4-2-3-基于全局历史的分支预测"><a href="#4-2-3-基于全局历史的分支预测" class="headerlink" title="4.2.3 基于全局历史的分支预测"></a>4.2.3 基于全局历史的分支预测</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/global_predict_code.png" alt="image"></p><p>b1,b2都执行,分支b3不会执行,只依靠分支b3的局部历史进行分支预测,是永远不会发现这个规律的。</p><p>局部历史分支预测方法:每个分支指令有自己对应的BHR寄存器(为了考虑成本,某几条分支指令对应一个BHR寄存器),每个BHR寄存器只是记录与之对应的分支指令在过去的执行情况;<br>全局历史分支预测方法:也需要一个寄存器,来记录程序中所有的分支指令在过去的执行情况。</p><p>当然在现实中,GHR不能记录所有分支指令的执行结果,一般使用有限位宽的GHR记录最近执行的所有分支指令的结果。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/global_predict_hash.png" alt="image"></p><p>这种方法实际是局部历史分支预测的一种特殊情况,BHT只有一个BHR寄存器,这个BHR对所有分支指令执行结果进行记录,这个BHR就成了GHR。</p><p>在上面的方法中,PHT有很多饱和计数器没有被使用,可以采用一种更简单的方式,只使用一个PHT</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/global_predict_one_pht.png" alt="image"></p><p>这种方法的缺点:当两条分支指令对应的GHR值恰好相同,这两条分支指令会用PHT中同一个饱和计数器。如果两个分支指令朝着同一个分支方向,彼此的预测结果没有负面影响,如果朝着相反的分支方向,那么会对彼此的预测结果造成负面影响。</p><p>为了解决这个问题,可以采用之前的方法,因为两条指令PC值不一样,可以将PC和GHR做一定处理:位拼接,异或的方法。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/GHR_conflict.png" alt="iamge"></p><h3 id="4-2-4-竞争的分支预测"><a href="#4-2-4-竞争的分支预测" class="headerlink" title="4.2.4 竞争的分支预测"></a>4.2.4 竞争的分支预测</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/tournament_predictor.png" alt="iamge"><br><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/tournament_predictor_detils.png" alt="iamge"></p><h3 id="4-2-5-分支预测的更新"><a href="#4-2-5-分支预测的更新" class="headerlink" title="4.2.5 分支预测的更新"></a>4.2.5 分支预测的更新</h3><p><strong>更新历史寄存器</strong></p><p>有三个阶段可以更新历史寄存器</p><ul><li>取指令阶段,预测结束立即更新GHR</li><li>当分支方向被计算出来,比如执行阶段更新GHR</li><li>当分支指令到达提交阶段,要离开流水线retire时</li></ul><p>在取指令阶段,根据分支预测的结果对GHR进行更新,可以使后续的分支指令使用最新的GHR,而且当一条指令的分支预测失败时,即使后续的分支指令使用到错误的GHR,这也没有关系,因为后续指令都在分支预测失败(mis-prediction)的路径上,都会被从流水线中抹去。<br>在取指令阶段更新GHR是推测的(speculative),这种更新GHR的方式在分支预测失败时,需要一种机制对GHR进行修复,使GHR能够恢复到正确的值。</p><ul><li>方法一: 提交(commit)阶段修复法。<br> <img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/commit_restore_ghr.png" alt="iamge"></li><li>方法二: checkpoint修复法<br> <img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/checkpoint_restore_ghr.png" alt="iamge"></li></ul><p><strong>更新两位饱和计数器</strong><br>一般都是在分支指令退休的时候对PHT的饱和计数器进行更新。</p><h2 id="4-4-分支预测失败时的恢复"><a href="#4-4-分支预测失败时的恢复" class="headerlink" title="4.4 分支预测失败时的恢复"></a>4.4 分支预测失败时的恢复</h2><p>分支预测是一种预测技术,需要处理器中有对应的验证和恢复机制,在流水线中的很多阶段可以对分支预测是否正确进行检查:</p><ul><li>解码阶段<br>可以得到一个PC值对应的指令是否是分支指令,以及这个分支指令的类型,如果是直接跳转(PC-relative)类型的分支指令,还可以在这个阶段得到目标地址,因此可以在解码阶段对一部分的分支指令是否预测正确进行检查。当然,很多时候即使在解码阶段发现预测方向不正确,可能也无法在这个阶段得到正确的目标地址,例如MIPS中的JR指令,可以简单的停止流水线,避免以后从流水线中抹掉指令造成的功耗浪费。</li><li>读取物理寄存器阶段(有可能在寄存器重命名阶段之后,也有可能在发射阶段之后,这取决于处理器架构)<br>如果此时得到了寄存器的值,就可以对间接跳转类型的分支指令所预测的目标地址进程检查,如果错误,那么就使用正确的地址开始重新取指令,此时仍需将分支指令之后进入到流水线的所有指令从流水线抹掉,这不容易实现,因为这些指令可能已经进入Issue Queue,发射队列中还存在正确的指令,因此需要对其中的指令进行选择性清除</li><li>执行阶段<br>不管什么类型的分支指令都可以被计算出结果,此时可以对分支预测是否正确进行检查。在这一阶段才对分支预测是否正确进行检查,发现预测失败时的惩罚最大</li></ul><p>在执行阶段发现一条分支指令预测失败,在这条分支指令之后进入到流水线的指令都需要被抹掉,但是超标量处理器采用乱序执行的方式,在此时的流水线中还存在部分指令在这条分支指令之前进入到流水线中,这些指令可能位于发射队列中,也可能位于执行阶段,它们不应该受到分支预测失败的影响,可以继续执行</p><p><strong>方式一:ROB</strong><br>当执行阶段发现一条分支指令预测失败,这个信息记录在这条分支指令在ROB对应的entry中,并暂停取指令,但是流水线继续执行,当这条分支指令变为流水线中最旧的指令时,表明在这条分支指令之前进入流水线的指令都离开了流水线,此时流水线中所有的指令都处于分支预测失败的路径上,可以将整个流水线中的指令全部抹掉,从正确的地址重新取指令执行。<br>缺点:需要分支指令在六十线中等待一段时间才能进行处理,在这段时间无法取指令,造成penalty很大,降低性能。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/rob_pipeline_restore.png" alt="image"></p><p><strong>方式二:checkpoint</strong></p><p>为了区分流水线中哪些指令在预测失败的路径上,对每一条分支指令进行编号,所有在这条分支指令后面进入流水线的指令都会得到这个编号,直到遇到下一条分支指令为止</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/branch_inst_tag.png" alt="image"></p><p>利用编号可以方便的识别哪些指令在这条分支指令之后,可以利用这个编号对流水线中指令有选择性的抹掉,而不需要等到这个预测错误的分支指令变为流水线中最旧的指令。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/branch_tag_allocate.png" alt="image"></p><p>当一条分支指令在执行阶段发现预测错误时,需要将它之后的所有指令抹掉,对于超标量处理器,这个过程有两部分:</p><ul><li>在发射阶段之前的所有指令需要全部抹掉,因为在发射阶段之前指令依然维持着程序中的原始顺序in-order</li><li>在发射阶段之后的流水线中,指令乱序执行,一部分位于预测错误路径上,这就需要使用编号列表中的表闹值将他们找出来抹掉,这个过程比较复杂,一个周期可能无法完成。</li></ul><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/tag_list_clear.png" alt="image"></p><p>将流水线中相关指令抹掉之后,就需要使用checkpoint对处理器状态进行恢复,主要是对寄存器重命名姐u但的映射表(mapping table)进行恢复。</p><p>对于分支预测结果的检查,还需考虑一个问题:当分支指令在执行阶段得到了实际的结果,并对分支预测是否正确进行检查时,如何才能知道这条分支指令之前的预测值?</p><p>要想在流水线后续阶段得到分支指令的预测值,就需要在流水线解码阶段,将分支指令的预测值保存起来,例如将每条分支指令的预测值写到一个buffer,每条分支指令的预测地址会跟着分支指令在流水线中流动,这样当分支指令到达流水线的执行阶段,进行分支预测是否正确的检查时,可以从缓存中读取这条分支指令之前的预测值</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/prediction_target_address_buffer.png" alt="iamge"></p><ul><li>实际不跳转,在PTAB中没有找到对应内容,表示一条分支指令预测不跳转,实际也没跳转,预测正确</li><li>实际结果不跳转,在PTAB中找到相应内容,表示预测跳转,实际没跳转,预测错误,需要使用Next PC值作为正确目标地址</li><li>实际跳转,PTAB没相应内容,表示预测不跳转,实际跳转,需要使用实际计算出来的目标地址</li><li>实际跳转,PTAB有相应内容,表示预测发生跳转,实际也跳转,需要将实际计算出的目标地址与PTAB中记录的预测地址比较,如果相等,表示预测正确,如果不等,表示预测错误,需要使用实际计算出来的目标地址</li></ul><p>由于PTAB只需要保存所有预测跳转的分支指令,而不需要将全部的分支指令的信息进行保存,可以减少PTAB的容量,在这种情况下,写PTAB在流水线的取指令阶段完成,只要在取指令阶段预测到一条发生跳转的分支指令,就将其写到PTAB。</p><h2 id="4-5-超标量处理器的分支预测"><a href="#4-5-超标量处理器的分支预测" class="headerlink" title="4.5 超标量处理器的分支预测"></a>4.5 超标量处理器的分支预测</h2><p>之前讲的都是对一条指令(一个PC值)进行分支预测。但是对于超标量处理器,在取指令时给出一个地址,会从I-Cache中取出多条指令,这些指令组成一个指令组(fetch group),处理器会自动根据指令组中指令个数,调整取指令的地址,用来进行下个周期的取指令。因此超标量处理器中的取指令地址并不是连续的,每次增加的值等于指令组的字长,送到I-Cache中取指令的地址只是指令组中第一条指令的地址。</p><p>如果对取指令过程进行限制,例如一个4-way的超标量处理器,每个周期取出的指令位于4字对齐的边界内,这样就可以使用它们的公共地址PC[31:4]来寻址分支预测器,而对于分支预测器,只需要记录下每组四字对齐的指令中,第一个预测跳转的分支指令的信息就可以。</p><p>在BTB中需要记录下分支指令在四条指令中的位置。当进行分支预测时,发现分支指令不再当前取指令范围内,就不会使用它的预测值了</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/branch_pc_offset.png" alt="image"></p><p>当四字对齐的四条指令中存在多于一条的分支指令时,它们会互相干扰,影响分支预测准确度。</p><p>如果一个4-way超标量处理器,每周期读出的指令可以不局限于四字对齐边界内。为了达到最理想效果,就需要<strong>对一个周期内所有指令都进行分支预测</strong>,将第一个预测跳转的分支指令的目标地址作为下个周期取指令的地址。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/every_inst_predict.png" alt="image"></p><p>上图对多条分支指令同时进行分支预测的方法,对分支指令的方向预测和目标地址预测是同时进行的,这样可以保证尽快的得到分支预测的结果,但是实际中很难实现。</p><p>对于目标地址的预测,需要一个周期内提供四个PC值对应的目标地址,这就需要BTB支持四个读端口,即使可以使用interleaving的方式避免真正的多端口,但是实际使用中,最多只会使用其中的一个端口的值,对硬件利用效率低。</p><p>如果可以在分支指令方向预测完毕后,利用结果信息再进行目标地址预测,可以避免对BTB等部件的多端口需求,这种方法对方向预测和目标地址预测两部分访问时串行的,这样,分支预测可能无法再一个周期内完成,会造成取指令有气泡,但是硬件复杂度降低。</p><p><strong>对于目标地址的预测</strong><br>大部分的情况下可能并不需要这么复杂。对于RISC处理器比如MIPS,程序中大部分分支指令都是PC-relative直接跳转,目标地址能很快被计算出来。对于这种分支指令,可以直接在取指令阶段进行目标地址计算,虽然可能不能在一个周期内完成,但是耗费的时间可能不比串行访问所需时间长,而且还能得到正确的目标地址,这种方法更优。<br>想要实现这样的功能,最好在指令进入I-Cache之前,进行<strong>预解码</strong>,使得指令从I-Cache中取出来的时候,马上识别出分支指令,从而快速进行目标地址计算。</p><p><strong>对于分支方向的预测</strong><br>需要每周期对多条指令进行方向预测,从而找到第一条预测跳转的指令。</p><ul><li>对基于局部历史的分支预测方法,需要PHT和BHT都支持多个读端口,可以使用interleaving的方法模拟多端口。</li><li>对基于全局历史的分支预测,不是简单的将PHT扩展为多个读端口就可以了。因为一个周期内进行分支预测的多条指令对应的GHR有可能是不一样的,需要进行特殊处理。</li></ul><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/interleaving_pht.png" alt="iamge"></p><h1 id="6-指令解码"><a href="#6-指令解码" class="headerlink" title="6. 指令解码"></a>6. 指令解码</h1><h2 id="6-2-一般情况"><a href="#6-2-一般情况" class="headerlink" title="6.2 一般情况"></a>6.2 一般情况</h2><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/inst_decode.png" alt="image"></p><p>一般情况下,RISC处理器在解码阶段完成任务可以概括为3个what</p><ul><li>What type,例如指令是算数指令,访问寄存器指令还是分支指令等</li><li>What operation,当前指令是算数指令时进行何种算数运算,当指令是分支指令时它的条件是什么样的,当指令是存储器访问指令时是load还是store</li><li>What resource,例如对于算数指令来说,源寄存器和目的寄存器是哪些,指令中是否有立即数</li></ul><p>当然,在解码阶段可能还需要得到一些其他信息,需要根据具体指令集进行分析。</p><h2 id="6-3-特殊情况"><a href="#6-3-特殊情况" class="headerlink" title="6.3 特殊情况"></a>6.3 特殊情况</h2><p>超标量处理器中处理RISC指令集中的复杂指令,遵循的原则是将复杂的指令拆解成普通的RISC指令,这部分内容在RISC处理器的解码电路中是比较复杂的,对解码电路的延迟也产生了很大的负面影响。</p><h3 id="6-3-1-分支指令的处理"><a href="#6-3-1-分支指令的处理" class="headerlink" title="6.3.1 分支指令的处理"></a>6.3.1 分支指令的处理</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/branch_decode.png" alt="image"></p><h3 id="6-3-2-乘累加-乘法指令的处理"><a href="#6-3-2-乘累加-乘法指令的处理" class="headerlink" title="6.3.2 乘累加/乘法指令的处理"></a>6.3.2 乘累加/乘法指令的处理</h3><h1 id="7-寄存器重命名"><a href="#7-寄存器重命名" class="headerlink" title="7 寄存器重命名"></a>7 寄存器重命名</h1><h2 id="7-2-寄存器重命名的方式"><a href="#7-2-寄存器重命名的方式" class="headerlink" title="7.2 寄存器重命名的方式"></a>7.2 寄存器重命名的方式</h2><h3 id="7-2-3-使用统一的PRF进行寄存器重命名"><a href="#7-2-3-使用统一的PRF进行寄存器重命名" class="headerlink" title="7.2.3 使用统一的PRF进行寄存器重命名"></a>7.2.3 使用统一的PRF进行寄存器重命名</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/PRF_rename.png" alt="image"></p><p>ARF与PRF进行了合并,合并之后的不见成为统一的PRF。其中存储了speculative和正确retire的寄存器值。</p><p>处于被占用状态的寄存器有三个阶段:</p><ul><li>值没被计算出来</li><li>值被计算出来但是没retire</li><li>retire</li></ul><p>在这个过程中,不需要对寄存器的内容进行搬移,这样便于后续指令读取操作数。</p><h3 id="组件构成"><a href="#组件构成" class="headerlink" title="组件构成"></a>组件构成</h3><p>如上图,需要有rename table记录逻辑寄存器与物理寄存器的对应关系。<br>需要有物理寄存器的free list,可以用FIFO来实现。对于4-way超标量处理器,每周期需要对四条指令进行重命名,需要FIFO每周期提供4个物理寄存器编号,每周期读取四个值。如果流水线中,每周期退休指令个数也是四条,FIFO每周期最多可以写入四个值。这种多端口的FIFO需要使用interleaving的方式实现。</p><h3 id="第二个RAT"><a href="#第二个RAT" class="headerlink" title="第二个RAT"></a>第二个RAT</h3><p>当从外部查看处理器状态时,处于speculative的状态的物理寄存器不能够被外部看到,只有retire后才可以。这就需要有另外一个RAT,用来存储所有retire状态的指令与物理寄存器的对应关系。</p><h3 id="什么时候free-pr"><a href="#什么时候free-pr" class="headerlink" title="什么时候free pr"></a>什么时候free pr</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/free_pr.png" alt="iamge"></p><p>当一条指令和后面的某条指令都写到同一个目的寄存器时,则后面的指令退休的时候,前面指令对应的物理寄存器就没有用处了。为了实现这个功能,在ROB中除了记录逻辑寄存器当前对应的物理寄存器之外,还需要存储它之前对应的物理寄存器,以便在退休的时候将它对应的旧映射关系进行释放。</p><h2 id="7-3-重命名映射表"><a href="#7-3-重命名映射表" class="headerlink" title="7.3 重命名映射表"></a>7.3 重命名映射表</h2><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/sram_cam_rename_table.png" alt="image"></p><h3 id="7-3-1-基于SRAM的重命名映射表"><a href="#7-3-1-基于SRAM的重命名映射表" class="headerlink" title="7.3.1 基于SRAM的重命名映射表"></a>7.3.1 基于SRAM的重命名映射表</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/sRAT.png" alt="iamge"></p><p>当需要对分支指令的状态进行Checkpoint保存时,需要将整个sRAT保存,比如MIPS R10000处理器,只对分支指令使用四个Checkpoint,最多允许四条分支指令存在于流水线中。</p><p>对于4-way的超标量处理器,sRAT需要支持8个读端口和4个写端口。</p><p>新写入到sRAT的值会覆盖原来旧的对应关系,如果不保存,这个信息就永远丢失了,原因:<br>(1)一条指令retire时,会将它之前对应的物理寄存器变为空闲状态,因此新的物理寄存器编号写入sRAT之前,需要将被覆盖的信息保存在ROB中<br>(2)当一条指令之前存在异常或者分支预测的指令,这条指令需要从流水线中抹掉,同时这条指令对RAT的修改也需要被恢复,通过将旧的映射关系保存,协助完成RAT修复(Checkpoint)</p><p>存在的问题:无法使用个数较多的Checkpoint<br>如何解决:<br>分支指令预测正确,那么对应的Checkpoint起始没作用。<strong>可以对分支指令预测的正确度也进行预测</strong>,对于预测正确率高的分支指令,不需要对它们使用Checkpoint,而那些经常预测错误的,对它们使用Chekpoint,由于经常预测错误的分支指令只是一小部分,这种方法可以大大减少Chckpoint的个数。</p><p>对于没有使用Checkpoint的分支指令,一旦发现预测错误,就需要使用速度较慢的方法对RAT进行恢复。通过ROB的记录对RAT进行倒带(WALK)。</p><h3 id="7-3-2-基于CAM的重命名映射表"><a href="#7-3-2-基于CAM的重命名映射表" class="headerlink" title="7.3.2 基于CAM的重命名映射表"></a>7.3.2 基于CAM的重命名映射表</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/c.png" alt="image"></p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/cRAT_organization.png" alt="image"></p><p>通过Checkpoint进行恢复时,存在的场景:<br>(1)保存时valid为0,但可能只是刚刚被覆盖,还没有等到之后的<strong>原因指令</strong>retire,所以这个物理寄存器不是空闲状态,在进行恢复时,这个物理寄存器可能已经变成空闲状态了,此时恢复成valid = 0,不会引起任何错误<br>(2)保存时valid为0,但是恢复时可能已经变成1,分支指令之后这个物理寄存器被新的指令使用,这些新指令在错误路径或异常路径上,本来就需要被恢复成0</p><h2 id="7-4-寄存器重命名中的相关性"><a href="#7-4-寄存器重命名中的相关性" class="headerlink" title="7.4 寄存器重命名中的相关性"></a>7.4 寄存器重命名中的相关性</h2><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/4way_rat_example.png" alt="image"></p><h3 id="7-4-1-RAW"><a href="#7-4-1-RAW" class="headerlink" title="7.4.1 RAW"></a>7.4.1 RAW</h3><p>src reg索引需要根据前一条指令分配给dst的物理寄存器编号,而不是从RAT中读取</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/rename_raw_dep.png" alt="image"></p><h3 id="7-4-2-WAW"><a href="#7-4-2-WAW" class="headerlink" title="7.4.2 WAW"></a>7.4.2 WAW</h3><p>只需要将最新的指令的映射关系写到RAT中<br><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/rename_waw_dep1.png" alt="image"></p><p>往ROB中保存的旧对应关系应该根据前一条指令分配给dst的物理寄存器编号,而不是从RAT中读取</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/rename_waw_dep2.png" alt="image"></p><h1 id="8-发射"><a href="#8-发射" class="headerlink" title="8 发射"></a>8 发射</h1><p>重命名之后的指令被写到ROB的同时,也会写到issue queue中<br>发射执行过程涉及到的一些重要部件:</p><ul><li>发射队列</li><li>allocation电路</li><li>选择电路/仲裁arbiter电路</li><li>wake-up电路</li></ul><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/issue_process.png" alt="image"></p><p>issue queue的实现方式:</p><h3 id="8-1-1-集中式CIQVS分布式DIQ"><a href="#8-1-1-集中式CIQVS分布式DIQ" class="headerlink" title="8.1.1 集中式CIQVS分布式DIQ"></a>8.1.1 集中式CIQVS分布式DIQ</h3><h3 id="8-1-2-数据捕捉VS非数据捕捉"><a href="#8-1-2-数据捕捉VS非数据捕捉" class="headerlink" title="8.1.2 数据捕捉VS非数据捕捉"></a>8.1.2 数据捕捉VS非数据捕捉</h3><h3 id="数据捕捉结构"><a href="#数据捕捉结构" class="headerlink" title="数据捕捉结构"></a>数据捕捉结构</h3><p>在发射阶段之前读取寄存器,这种为数据捕捉的结构,将读到的值随指令一起写入到发射队列。<br><strong>如果有的寄存器的值还没被计算出来</strong>,则会将寄存器编号写到发射队列,以供wake-up使用,会被标记为non-available,这些值会在之后的时间通过bypass network得到值,不需要再访问物理寄存器堆</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/data_capture_issue_queue.png" alt="image"></p><p>bypass network 处理过程:</p><ul><li>一条指令被仲裁的同时,将dst寄存器编号广播</li><li>issue queue中其他指令根据自身src寄存器编号与广播编号对比,如果相等,则在payload RAM对应位置进行标记</li><li>当被选中指令计算完毕,将结果写到payload RAM对应位置</li></ul><p>物理寄存器堆,需要读端口个数 machine width X 2</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/data_capture_issue_queue1.png" alt="iamge"></p><h3 id="非数据捕捉结构"><a href="#非数据捕捉结构" class="headerlink" title="非数据捕捉结构"></a>非数据捕捉结构</h3><p>在发射阶段之后读取物理寄存器堆,nin-data-capture<br><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/non_data_capture_issue_queue.png" alt="iamge"></p><p>物理寄存器堆需要读端口个数 issue width X 2, issue width > machine width</p><h3 id="8-1-3-压缩VS非压缩"><a href="#8-1-3-压缩VS非压缩" class="headerlink" title="8.1.3 压缩VS非压缩"></a>8.1.3 压缩VS非压缩</h3><h3 id="压缩的发射队列"><a href="#压缩的发射队列" class="headerlink" title="压缩的发射队列"></a>压缩的发射队列</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/compress_queue.png" alt="image"></p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/compress_queue_arch.png" alt="image"></p><p>选择电路简单,这种队列已经自然按照新旧顺序排列,只需要使用优先级编码器进行选择,oldest-first</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/oldest_first.png" alt="iamge"></p><h3 id="非压缩的发射队列"><a href="#非压缩的发射队列" class="headerlink" title="非压缩的发射队列"></a>非压缩的发射队列</h3><p>存在的问题:<br>(1)实现lodest-first电路复杂<br>(2)一周期allocate 多条指令电路复杂,需要扫描所有空间</p><h2 id="8-2-发射过程的流水线"><a href="#8-2-发射过程的流水线" class="headerlink" title="8.2 发射过程的流水线"></a>8.2 发射过程的流水线</h2><h3 id="8-2-1-非数据捕捉结构的流水线"><a href="#8-2-1-非数据捕捉结构的流水线" class="headerlink" title="8.2.1 非数据捕捉结构的流水线"></a>8.2.1 非数据捕捉结构的流水线</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/non_data_capture_pipeline.png" alt="image"><br><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/delay_wakeup.png" alt="image"></p><h3 id="8-2-2-数据捕捉结构的流水线"><a href="#8-2-2-数据捕捉结构的流水线" class="headerlink" title="8.2.2 数据捕捉结构的流水线"></a>8.2.2 数据捕捉结构的流水线</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/data_capture_pipeline.png" alt="image"><br><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/payload_pipeline.png" alt="image"></p><h2 id="8-3-allocation"><a href="#8-3-allocation" class="headerlink" title="8.3 allocation"></a>8.3 allocation</h2><h2 id="8-4-arbiter"><a href="#8-4-arbiter" class="headerlink" title="8.4 arbiter"></a>8.4 arbiter</h2><h2 id="8-5-wake-up"><a href="#8-5-wake-up" class="headerlink" title="8.5 wake up"></a>8.5 wake up</h2><h3 id="8-5-1-单周期指令唤醒"><a href="#8-5-1-单周期指令唤醒" class="headerlink" title="8.5.1 单周期指令唤醒"></a>8.5.1 单周期指令唤醒</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/wakeup_scheme.png" alt="iamge"></p><p>Issued :一条指令被仲裁电路选中后,可能不会马上离开队列,需要将它进行标记,这样的指令不会再发起仲裁请求</p><p><strong>如果这条指令使用了load指令的结果的话,即使被仲裁,也不能马上离开队列</strong></p><h3 id="8-5-2-多周期指令唤醒"><a href="#8-5-2-多周期指令唤醒" class="headerlink" title="8.5.2 多周期指令唤醒"></a>8.5.2 多周期指令唤醒</h3><p>对唤醒过程进行延迟:</p><ul><li>延迟广播 delayed tag broadcast</li><li>延迟唤醒 delayed wake-up</li></ul><h3 id="延迟广播"><a href="#延迟广播" class="headerlink" title="延迟广播"></a>延迟广播</h3><p>根据这条指令所需执行周期N,延迟N-1周期后,将它送到总线上。可能会发生总线冲突</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/delay_braoadcast.png" alt="iamge"></p><p>解决方案:<br>(1)增加总线数量<br>(2)使用表格记录当前在FU中执行指令所需的周期数,在仲裁申请时mask</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/delay_broadcast_conflict.png" alt="image"></p><p>访问记录表格和申请仲裁可以并行也具有串行,在设计中权衡。</p><h3 id="延迟唤醒"><a href="#延迟唤醒" class="headerlink" title="延迟唤醒"></a>延迟唤醒</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/delay_wakeup1.png" alt="image"></p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/delay_wakeup_pipeline.png" alt="iamge"></p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/issue_queue_entry.png" alt="iamge"></p><p>Freed:表示这个表项是否时空闲的。什么时候变成空闲是个问题,最保守的,这条指令retire后,entry释放。<br>发射后确定不会有问题就可以释放,发射后不一定可以安全得到操作数,比如操作数来自load指令,采用了speculative wwake-up策略。</p><p>SrcL-M:比较match时置1,接收到仲裁电路给出的响应后,这一位清0,是移位寄存器SrcL_SHIFT进行算数右移的使能信号。</p><p>ROB ID: 指令的年龄信息,用于实现oldest first 选择电路</p><h3 id="8-5-3-推测唤醒"><a href="#8-5-3-推测唤醒" class="headerlink" title="8.5.3 推测唤醒"></a>8.5.3 推测唤醒</h3><p>对于处理周期数不确定的指令怎么进行delay?<br>最简单的方式,这条指令执行完再对其他相关指令进行唤醒操作</p><p>load指令总是预测D-Cache hit。当D-Cache miss,预测错误,需要进行状态恢复<br>(1)被load指令唤醒的所有寄存器应该被重新置为not ready<br>(2)如果一些指令离开了发射队列,还需要将其从流水线抹掉,重新放回到发射队列中(如何识别流水线中部分指令)</p><p>等待load指令唤醒,load指令继续预测L2 Cache命中</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/assume_l2_hit.png" alt="image"></p><p>当load 指令发生D-Cache miss时,这条load指令对应的SW窗口中,相关指令需要从流水线被抹掉,重新被放回issue queue,等待重新被唤醒,这个过程称为replay。</p><h3 id="issue-queue-based-replay"><a href="#issue-queue-based-replay" class="headerlink" title="issue queue based replay"></a>issue queue based replay</h3><p>这种方法中,指令被选中,不会马上离开队列,只有被确认正确执行后,才允许离开,需要issued标志位</p><p>对于load指令,仍需使用发射队列进行replay,过程可繁可简<br>(1)Non-Selective Replay<br>load指令在执行阶段发现D-Cache miss后,将load指令后被仲裁的指令全都放回到队列中(不是真的放回,这些指令还没释放,只需修改状态位),这些指令被重新唤醒和选择。这种方法将很多不需要replay的也进行了replay</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/non_selective_replay.png" alt="image"></p><p>(2)改进改多Non-Selective Replay<br>只需要将Select和RF 阶段的指令replay</p><p>将相关指令放回队列的同时,还需要将相关寄存器都置位not ready。<br><strong>这要求在发射队列中能够识别哪些寄存器和load指令存在相关性。</strong><br>相关性包括,直接相关性和间接相关性。</p><p>有两种方法:<br>(1)用一个值记录流水线中所有的load指令,每一位表示一条load指令,这个值的宽度等于流水线中最多能够存在的load指令个数,称为load-vector。<br>每个寄存器都有一个vector,指令重命名后,将每个源寄存器对应的向量值伴随写入发射队列中,每当发现一条load指令产生D-Cache miss,根据这条load在向量值中对应的位,就可以从发射队列中找到哪些寄存器和它存在直接或间接的关系,标记为not ready。</p><p>(2)对于一条load指令,在SW窗口中的指令才可能和它存在相关性。为了识别哪些寄存器和load指令存在相关性,对每条load指令都是用一个5bit的值,用来表示这条load指令处于流水线中那一级(Select,RF,Cal Addr,TLB/Tag,Data),称为load position vector(LPV)。</p><p>其他指令中match的源寄存器都会得到这条load指令的LPV,在之后的周期中,发射队列中所有LPV都右移一位,用来追踪load指令在流水线中的位置。被与load直接相关指令唤醒的指令(即与load间接相关),也需要将LPV赋给被唤醒的寄存器。</p><p>假设在Data 阶段发生miss,那些在发射队列中LPV最低为为1的源寄存器,都会和这个load指令存在直接或者间接相关性,需要被置为not ready。</p><h3 id="Replay-Queue-Based-Replay"><a href="#Replay-Queue-Based-Replay" class="headerlink" title="Replay Queue Based Replay"></a>Replay Queue Based Replay</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/replay_queue.png" alt="image"></p><p>这种方法支持由于load D-Cache miss引起的replay,也支持由于store/load,load/load违例引起的replay。<br>当发现D-Cache miss时</p><ul><li>将相关指令从流水线中抹掉</li><li>将这些指令在RQ中唤醒,并向仲裁电路申请,比issue queue有更高优先级</li></ul><h1 id="9-执行"><a href="#9-执行" class="headerlink" title="9 执行"></a>9 执行</h1><h2 id="9-6-存储指令的加速"><a href="#9-6-存储指令的加速" class="headerlink" title="9.6 存储指令的加速"></a>9.6 存储指令的加速</h2><h3 id="9-6-1-Memory-Disambiguation"><a href="#9-6-1-Memory-Disambiguation" class="headerlink" title="9.6.1 Memory Disambiguation"></a>9.6.1 Memory Disambiguation</h3><p>存储器访问指令之间的相关性(RAW,WAW,WAR),为了降低设计难度,也为了保持处理器中存储器的正确性,大部分处理器中的store指令都是顺序执行,可以避免WAW相关性,但是load指令可以有不同的实现方式:<br>(1)完全顺序执行<br>(2)部分乱序,顺序执行的store将程序划分为不同的块,当store地址计算出来后,这条store与后面一个store指令之间的所有load指令可以乱序执行,这样可以避免WAR(后面的store等待load完成才执行),也可以降低RAW相关性的检测难度(中间的load与当前计算出地址的store之间可能会有相关性)<br>(3)完全乱序</p><h3 id="部分乱序执行"><a href="#部分乱序执行" class="headerlink" title="部分乱序执行"></a>部分乱序执行</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/ls_partial_ooo.png" alt="image"></p><p>当一条store指令所携带的地址被计算出来后,在它之后进入到流水线的load指令就具备条件去判断RAW相关性了。每条load将携带地址计算出来后,需要和前面已执行的store指令所携带地址进行比较。<br>为了实现这个功能,需要一个缓存保存已经被仲裁执行但是还没retire的store指令,这个缓存称为Store Buffer。<br>如果在Store Buffer里发现了相同地址的store,则说明有RAW,直接从这个缓存中得到load指令所需数据。</p><p>优化点:当store被select时,就可以打开门,允许后面的load参与仲裁,这样已经可以保证。load地址计算出来时store指令也被计算出了。</p><p>load指令与store进行地址比较时,仅需比较之前的store,因此需要对load/store指令前后顺序进行标号<br>(1)ROB编号,缺点:稀疏,比较电路宽<br>(2)在decode时给load/store分配编号</p><p>另一种解决方案:当前一段load都被select后,后面的store才能参与仲裁,这样load只能在store buffer里遇到自己之前的store。</p><h3 id="9-6-2-非阻塞cache"><a href="#9-6-2-非阻塞cache" class="headerlink" title="9.6.2 非阻塞cache"></a>9.6.2 非阻塞cache</h3><p>在发生D-Cache miss时,处理器可以继续执行后面的load/store指令,这种设计方法为非阻塞Cache,也叫lookup-free cache。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/non_block_cache.png" alt="image"></p><p>这种方法load/store完成D-Cache miss处理的时间可能和原始的顺序不一样。这就需要保存已经发生D-Cache miss的load/store指令的信息,当这些miss处理完,得到数据写回D-Cache时,才能知道哪条load/store需要这个数据。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/mshr.png" alt="iamge"></p><p>MSHR entry:当一个cacheline从下级存储器取回,通过和MSHR本体中的block address比较,就可以直到它在MSHR本体中的位置,就可以到LOAD/STORE Table中找到哪些load/store指令属于这个cacheline。</p><p>Dest.register:</p><ul><li>对于load,记录目的寄存器编号,当数据取回写到目的寄存器</li><li>对于store,记录Store Buffer编号,作用有两个:(1)找到store携带的数据,以便和下级存储器取回的数据合并;(2)能释放store指令占据store buffer的空间。</li></ul><p>存在问题:处在错误路径上的load/store应该在探测到预测失败时被抹掉,从load/store table中删掉,数据取回时不写入目的寄存器或者D-Cache。需要一种机制。</p><h1 id="10-提交"><a href="#10-提交" class="headerlink" title="10 提交"></a>10 提交</h1><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/rob_entry.png" alt="iamge"></p><h3 id="10-4-1-分支预测失败的处理"><a href="#10-4-1-分支预测失败的处理" class="headerlink" title="10.4.1 分支预测失败的处理"></a>10.4.1 分支预测失败的处理</h3><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/mispred_recovery.png" alt="iamge"></p><h4 id="在基于ROB进行重命名的架构中进行状态恢复"><a href="#在基于ROB进行重命名的架构中进行状态恢复" class="headerlink" title="在基于ROB进行重命名的架构中进行状态恢复"></a>在基于ROB进行重命名的架构中进行状态恢复</h4><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/ROB-based_rename.png" alt="image"></p><p>指令A: ADD R1, R2, R3<br>指令B: ADD R1, R1, R4<br>指令C: ADD R1, R1, R5</p><p>经过rename stage后,只有指令C的映射关系被真正写入到RAT,即使指令A retire,将ROB中R1的值搬到ARF后,后续的指令使用寄存器R1的值时,仍然会使用指令C的结果。因此,指令A retire也不能将RAT中R1对应的映射关系修改为ARF。</p><p>为了实现这样的功能,ROB中每个指令都要检查自身是否是最新的映射关系,只有当retire并且自身是最新的映射关系,才能将RAT中对应内容改为ARF状态。</p><p>如何检查自身?通过目的寄存器(AR)读取RAT,读出对应的ROB pointer,如果值与当前退休指令在ROB中占据位置一样,则表明这条退休指令是最新的映射关系。</p><p>当发现分支预测失败时(执行阶段),流水线中有一部份指令在分支指令之前进入到流水线,可以被继续执行,因此发现预测失败时不能马上修复,而是停止取指,流水线继续,将流水线抽干(drain out)。等到这条分支指令之前的所有指令(包括分支指令本身)都退休的时候,此时ARF中所有寄存器都是正确的,同时流水线中所有指令都在错误路径上,可以全抹掉,然后将RAT中所有内容都标记为ARF状态,这样处理器就从分支预测失败状态恢复,可以从正确地址取指。</p><h4 id="在基于统一PRF进行重命名的架构中进行状态恢复"><a href="#在基于统一PRF进行重命名的架构中进行状态恢复" class="headerlink" title="在基于统一PRF进行重命名的架构中进行状态恢复"></a>在基于统一PRF进行重命名的架构中进行状态恢复</h4><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/prf-based_recovery.png" alt="image"></p><p>Checkpoint</p><h1 id="11-Alpha-21264-处理器微架构分析"><a href="#11-Alpha-21264-处理器微架构分析" class="headerlink" title="11 Alpha 21264 处理器微架构分析"></a>11 Alpha 21264 处理器微架构分析</h1><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/alpha_pipeline.png" alt="image"></p><h2 id="11-2-取指令和分支预测"><a href="#11-2-取指令和分支预测" class="headerlink" title="11.2 取指令和分支预测"></a>11.2 取指令和分支预测</h2><h3 id="11-2-1-line-way预测"><a href="#11-2-1-line-way预测" class="headerlink" title="11.2.1 line/way预测"></a>11.2.1 line/way预测</h3><p>本质是将BTB放到了I-Cache。<br>由于处理器每周期取出的四条指令必须是位于四字对齐的边界之内,不能跨越四字对齐的边界,所以只需要对cacheline中每一组四字对齐的四条指令使用一个line/way预测就可以了。</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/line_way_prediction.png" alt="iamge"></p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/prediction_example.png" alt="iamge"></p><h3 id="11-2-2-分支预测"><a href="#11-2-2-分支预测" class="headerlink" title="11.2.2 分支预测"></a>11.2.2 分支预测</h3><p>竞争的分支预测</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/tourament_branch_prediction.png" alt="image"></p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/ghr_checkpoint.png" alt="iamge"></p><h2 id="11-3-寄存器重命名"><a href="#11-3-寄存器重命名" class="headerlink" title="11.3 寄存器重命名"></a>11.3 寄存器重命名</h2><p>采用统一的PRF进行寄存器重命名;<br>当指令retire时,对应的物理寄存器才变为Archtecture状态(映射关系更新到aRAT),从处理器外部可以访问这个值;<br>RAT采用CAM的结构,为80条指令的每一条提供checkpoint,再异常和预测失败时,可以快速恢复状态;</p><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/alpha_rename.png" alt="image"></p><h2 id="11-4-发射"><a href="#11-4-发射" class="headerlink" title="11.4 发射"></a>11.4 发射</h2><p><img src="/2023/11/15/%E3%80%8A%E8%B6%85%E6%A0%87%E9%87%8F%E5%A4%84%E7%90%86%E5%99%A8%E8%AE%BE%E8%AE%A1%E3%80%8B%E7%AC%94%E8%AE%B0/alpha_issue_cluster.png" alt="image"></p><p>L0/L1 处理load/store<br>U0/U1 处理shift<br>加减法和逻辑运算,再L0 U0 L1 U1 都可以执行</p><p>每个仲裁电路只需要2-of-20功能就可以。<br>当指令经过重命名之后,再写入到发射队列时,会根据指令类型,将这些指令指派给对应的仲裁电路,比如每个entry有两个请求信号,分别送到两个仲裁电路。<br>仲裁电路根据age信息,从队列中选出年龄最旧的两条指令。</p><p>被仲裁出的两条指令如何分配到对应的Subcluster中执行,这是一个动态的过程,遵循下面三条原则:</p><p>为了便于仲裁电路按年龄选择,21264处理器的issue queue采用了压缩的方式。</p><p>采用issue queue based replay<br>non-selective replay,再SW窗口内的指令全都清掉replay,不用判断那些指令和load真实相关。</p><h2 id="11-5-执行单元"><a href="#11-5-执行单元" class="headerlink" title="11.5 执行单元"></a>11.5 执行单元</h2><p>采用非数据捕捉结构</p><h2 id="11-6-存储器的访问"><a href="#11-6-存储器的访问" class="headerlink" title="11.6 存储器的访问"></a>11.6 存储器的访问</h2><ol><li>load/store之间存在的RAW相关性进行预测(采用完全乱序的执行方式)</li><li>访问D-Cache时是否命中进行预测,避免不必要的唤醒操作,乘坐load hit/miss prediction</li></ol><h3 id="11-6-1-Speculative-Disambiguation"><a href="#11-6-1-Speculative-Disambiguation" class="headerlink" title="11.6.1 Speculative Disambiguation"></a>11.6.1 Speculative Disambiguation</h3><p>D-Cache访问使用了两级流水线,</p><ul><li>第一级访问D-Cache,但是这个周期得不到结果</li><li>第二级得到数据和Tag值,并进行tag比较,判断是否命中。</li></ul>]]></content>
<categories>
<category>CPU Arch</category>
</categories>
<tags>
<tag>superscalar processor</tag>
</tags>
</entry>
</search>