<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Computer Architecture on 0xc0de</title>
    <link>https://0xc0de.xyz/categories/computer-architecture/</link>
    <description>Recent content in Computer Architecture on 0xc0de</description>
    <image>
      <title>0xc0de</title>
      <url>https://0xc0de.xyz/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</url>
      <link>https://0xc0de.xyz/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</link>
    </image>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <lastBuildDate>Sat, 10 May 2025 00:00:00 +0800</lastBuildDate>
    <atom:link href="https://0xc0de.xyz/categories/computer-architecture/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>&lt;超標量處理器概覽&gt; 第 11 章 - 真實世界的例子：Alpha 21264 處理器</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch11/</link>
      <pubDate>Sat, 10 May 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch11/</guid>
      <description>&lt;h1 id=&#34;111----概述&#34;&gt;11.1 -  概述&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;DEC 的 Alpha 21264 是 superscalar CPU 的一個典範，其為 4-way out-of-order superscalar CPU，工作頻率是 466 ~ 667 MHz，benchmark：SPECint95 - 40、SPECfp95 - 86；與同時期其他的處理器 benchmark 對比：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://0xc0de.xyz/posts/superscalar-overview-ch11/image.png&#34;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Alpha 21264 的 pipeline 最多同時支持 80 條指令，以及 80 個 checkpoints，因此對於 branch mis-prediction、exception、interrupt，Alpha 21264 都可以快速地恢復 CPU 狀態&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Alpha 21264 的分支預測器實現了基於局部歷史 (Local prediction) 和基於全局歷史 (Global prediction) 兩種預測方式，並根據程式的執行情況，動態選擇預測率最高的方法，這就像是兩種分支預測方式在進行競爭一樣&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;對於 load/store 指令，Alpha 21264 採用了 Speculative Memory Disambiguation 和 Load hit/miss Prediction&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Alpha 21264 採用 7-stage pipeline，比較短的 pipeline 使 branch mis-prediction 發生時的 mis-penalty 比較低，從而提高處理器的效率：&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="111----概述">11.1 -  概述</h1>
<ul>
<li>
<p>DEC 的 Alpha 21264 是 superscalar CPU 的一個典範，其為 4-way out-of-order superscalar CPU，工作頻率是 466 ~ 667 MHz，benchmark：SPECint95 - 40、SPECfp95 - 86；與同時期其他的處理器 benchmark 對比：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image.png"></p>
</li>
<li>
<p>Alpha 21264 的 pipeline 最多同時支持 80 條指令，以及 80 個 checkpoints，因此對於 branch mis-prediction、exception、interrupt，Alpha 21264 都可以快速地恢復 CPU 狀態</p>
</li>
<li>
<p>Alpha 21264 的分支預測器實現了基於局部歷史 (Local prediction) 和基於全局歷史 (Global prediction) 兩種預測方式，並根據程式的執行情況，動態選擇預測率最高的方法，這就像是兩種分支預測方式在進行競爭一樣</p>
</li>
<li>
<p>對於 load/store 指令，Alpha 21264 採用了 Speculative Memory Disambiguation 和 Load hit/miss Prediction</p>
</li>
<li>
<p>Alpha 21264 採用 7-stage pipeline，比較短的 pipeline 使 branch mis-prediction 發生時的 mis-penalty 比較低，從而提高處理器的效率：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%201.png"></p>
</li>
<li>
<p>Alpha 21264 所使用的設計理念是領先於那個時代的，包含競爭的分支預測、store/load 指令間的相關性預測，數量豐富的 checkpoints 等，它的設計理念被用到了未來的 AMD 和 Intel CPU 中，直接或間接地影響了現代電腦產業</p>
</li>
</ul>
<h1 id="112---取指令和分支預測">11.2 - 取指令和分支預測</h1>
<ul>
<li>Alpha 21264 每個 cycle 可以從 I-Cache 中讀取四條指令進 pipeline，其 I-Cache 的 size 為 64 KB、採用 2-way associative，並使用了 2 個 stages 來讀取 I-Cache：<code>fetch0</code> 和 <code>fetch1</code>
<ul>
<li><code>fetch0</code>：讀取 I-Cache，但這個 cycle 只能得到 I-Cache 的指令 (i.e. data) 和 tag，無法再做其他的事情</li>
<li><code>fetch1</code>：進行 tag 比對的同時，還會將指令送到 decoder 和 register renaming 相關的元件
<ul>
<li>在 Alpha 21264 中，這個傳輸需要跨越大半個晶片，比較花時間，這也是 Alpha 21264 使用了 2 個 stages 來讀取 I-Cache 的原因</li>
</ul>
</li>
</ul>
</li>
<li>Alpha 21264 每個 cycle 都需要向 I-Cache 發送位址來讀取指令，為了盡早察覺到分支指令和分支指令的目標位址，Alpha 21264 透過兩個方法來提昇 instruction fetch 的準確度：
<ul>
<li>line/way 的預測 - 較簡單的分支預測器，可以在 <code>fetch0</code> 就得到結果，但是預測準確度比較低</li>
<li>分支預測 - 較複雜的分支預測器，需使用 2 個 cycles，也就是 <code>fetch1</code> 才能得到結果，但是預測準確度比較高
<ul>
<li>如果發現 <code>fetch0</code> 的預測結果與 <code>fetch1</code> 的預測結果不符，則會拋棄 <code>fetch0</code> 的預測結果，使用 <code>fetch1</code> 的預測結果來 fetch instruction，但也會因此產生 1 個 cycle 的 bubble
<ul>
<li>i.e. 使用 <code>fetch0</code> 的預測結果讀進來的 instruction 會被 flushed 掉</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="1121---lineway-的預測">11.2.1 - line/way 的預測</h2>
<ul>
<li>
<p>Alpha 21264 在 fetch stage，為了可以盡快地從 I-Cache fetch 指令，對 I-Cache 的 instruction fetch 位址也進行了預測，這就是 <strong>line/way 預測</strong></p>
<ul>
<li>這種方法本質上就是將 BTB 放進了 I-Cache 中</li>
</ul>
</li>
<li>
<p>由於 Alpha 21264 每個 cycle fetch 的四條指令必須是 4 words aligned 的，因此只需要對每條 cache line 中每一組 4 words aligned 的四條指令使用一個 line/way 預測即可：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%202.png"></p>
<ul>
<li>對於一條 64 bytes 的 cache line，需要使用 4 個 line/way 預測 (64 bytes / (4 words * 4 bytes/word) = 4)，每個 line/way 預測包含了以下的資訊：
<ul>
<li><code>Successor way</code>：
<ul>
<li>下一個 cycle 要取的 fetch group，位在 I-Cache 的那個 way
<ul>
<li>E.g. 4-way set-associative I-Cache，共需要 2 bits 來表示</li>
</ul>
</li>
</ul>
</li>
<li><code>Successor index</code>：
<ul>
<li>下一個 cycle 要取的 fetch group，第一條指令在 cache line 中的位置
<ul>
<li>E.g. 一個 64 KB、2-way set-associative cache、cache line 為 64 bytes 的 I-Cache：
<ul>
<li>需使用 <code>PC[14:6]</code> ⇒ <strong>9 bits</strong> (64 KB / 2-way set-associative / 64 byes = 512 bytes) 來找到一條 cache line</li>
<li>需使用 <code>PC[5:2]</code> ⇒ <strong>4 bits</strong> (64 bytes / 4 bytes/word ⇒ 16，一條 cache line 中共有 16 個 words) 來找到 cache line 中的某個 word</li>
<li>因此，<code>Successor index</code> 只需儲存 <code>PC[14:2]</code> ⇒ <strong>13 bits</strong> 即可</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><code>Branch position</code>：
<ul>
<li>此 cycle 所取出的 fetch group 中，如果存在分支指令，則將這條分支指令的下一條指令的位址資訊記錄在 <code>branch position</code> 欄位</li>
<li>之所以不記錄分支指令本身的位址資訊，而是其下一條指令的位址資訊，是因為如果此 cycle 取出的 fetch group 中存在預測會跳轉的分支指令，則它後面的指令都不會進入 pipeline，但分支指令本身是會進入 pipeline 的
<ul>
<li>因此記錄分支指令的下一條指令的位址資訊更容易找到此 cycle 所取出的 fetch group 中，哪些指令不應該進入 pipeline</li>
<li>當此 cycle 所取出的 fetch group 中不存在分支指令，或是存在預測結果為不跳轉的分支指令時，只需將 <code>branch position</code> 寫入 <code>2’b00</code>，就可以表示 “此 cycle 取出的 fetch group 中的所有指令，都需要進 pipeline” 了</li>
<li>如果預測跳轉的分支指令是這個 fetch group 的最後一條指令，那麼這個 fetch group 中的每條指令也都應該進 pipeline，同樣也只需將 <code>branch position</code> 寫入 <code>2’b00</code> 即可</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在 Alpha 21264 中，當一個 cache line 從 L2 Cache 讀進 L1 I-Cache 時，這條 cache line 包含的所有預測資訊都會被初始化為“沒有需要跳轉的分支指令”，此時 line/way 的預測資訊會指向下一個 PC 值，i.e. <code>PC + sizeof(fetch group)</code></p>
<ul>
<li>一旦在後面的過程中發現這個預測資訊是錯的 (e.g. <code>fetch1</code> 的預測結果與 <code>fetch0</code> 的預測結果不同)，就會修改 cache line 中的所記錄的預測資訊</li>
<li>可以按照之前 <code>2-bit saturating counter</code> 的方式來管理 line/way 的預測值，也就是只有當連續兩次預測失敗時，line/way 的預測值才會改變</li>
</ul>
</li>
<li>
<p>每個 cycle 都會依據目前 fetch group 的 line/way 的預測器，決定下個 cycle 要 fetch 的指令位址</p>
</li>
<li>
<p>範例：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%203.png"></p>
<ul>
<li>64 KB I-Cache，2-way set-associative，每條 cache line 是 32 bytes (i.e. 32 bytes / 4 bytes/word = 8 words)，每 4 個 words 使用一個 line/way 預測器，因此每條 cache line 都包含了 2 個 line/way 預測器
<ul>
<li>可以用 <code>PC[14:2]</code> 從 I-Cache 中找到需要的指令</li>
<li>Cycle 0：
<ul>
<li><code>PC = 0x20580354</code>，對應到 way 0；由於每個 cycle fetch 的四條指令必須是 4 words aligned 的，因此只能讀出三條指令</li>
<li><code>Branch position = 2’b00</code>，因此這三條指令都會進 pipeline
<ul>
<li>i.e. A0 ~ A2 會進 pipeline</li>
</ul>
</li>
<li><code>Successor index = 0x360</code>，因此下一個要 fetch 的指令位址：<code>0x20580360</code></li>
</ul>
</li>
<li>Cycle 1：
<ul>
<li><code>PC = 0x20580360</code>，對應到 way 1；由於是 4-word aligned 的，因此四條指令都可以被讀出</li>
<li><code>Branch prediction = 2’b11</code>，因此最後一條指令不會進 pipeline
<ul>
<li>i.e. A3 ~ A5 會進 pipeline</li>
</ul>
</li>
<li><code>Successor index = 0xa28</code>，因此下一個要 fetch 的指令位址：<code>0x20580a28</code></li>
</ul>
</li>
<li>Cycle 2：
<ul>
<li>PC = <code>0x20580a28</code>，對應到 way 1；由於每個 cycle fetch 的四條指令必須是 4 words aligned 的，因此只能讀出兩條指令</li>
<li><code>Branch position = 2’b00</code>，因此這兩條指令都會進 pipeline
<ul>
<li>i.e. B0 ~ B1 會進 pipeline</li>
</ul>
</li>
<li><code>Successor index = 0xa30</code>，因此下一個要 fetch 的指令位址：<code>0x20580a30</code></li>
</ul>
</li>
<li>Cycle 3：
<ul>
<li><code>PC = 0x20580a30</code>，對應到 way 1；由於是 4-word aligned 的，因此四條指令都可以被讀出</li>
<li><code>Branch prediction = 2’b11</code>，因此最後一條指令不會進 pipeline
<ul>
<li>i.e. B2 ~ B4 會進 pipeline</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Line/way 預測方法相當於將 BTB 放到了 I-Cache 中，然後相較於獨立的 BTB：</p>
<ul>
<li>缺點：
<ul>
<li>每條 cache line 都要使用固定個數的預測資訊，例如上述範例，每條 cache line 都包含了 2 個 line/way 預測器：32 bytes cache line / (4 words * 4 bytes/word)) = 2
<ul>
<li>然而很多 cache line 中其實並沒有分支指令，浪費硬體空間</li>
<li>獨立的 BTB 不會有這個問題，因為它只會將預測結果為跳轉的分支指令放進 BTB 中</li>
</ul>
</li>
<li>隨著 cache line size 的增大，所需要的 line/way 預測器的個數也會跟著增多
<ul>
<li>E.g. 64 bytes cache line 需要使用 4 個 line/way 預測器</li>
<li>獨立的 BTB size 都是固定的，不會隨著 cache line size 的變化而改變</li>
</ul>
</li>
<li>每當一條 cache line 被 replace 時，它的 line/way 預測資訊會被預設值所取代
<ul>
<li>預設值 ⇒  <code>successor index</code> 會指到下一道 PC address</li>
<li>獨立的 BTB 則與 cache replacement 無關</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在 fetch1 stage 會驗證 fetch0 stage 的預測結果是否正確</p>
<ul>
<li>將 cache 的 tag (i.e. <code>PC[47:15]</code>) 與 successor index (i.e. <code>PC[14:2]</code>) 合併，就可以組成使用 line/way 預測的 PC address，在 fetch 1 stage，就可以與使用分支預測的預測結果相比，如果 PC address 不同，就代表 line/way 的預測錯誤，就會改使用 fetch1 stage 分支預測的 PC address 重新 fetch 指令
<ul>
<li>因此會產生 1 個 cycle 的 bubble，也就是 line/way 預測的 mis-penalty</li>
</ul>
</li>
<li>Alpha 21264 是使用 <a href="../superscalar-overview-ch3/#343---%E5%B0%87-tlb-%E5%92%8C-cache-%E6%94%BE%E5%85%A5%E6%B5%81%E6%B0%B4%E7%B7%9A">VIVT 架構</a>的 I-Cache，因此 PC address 可以直接比對</li>
<li>如果是使用 <a href="../superscalar-overview-ch3/#343---%E5%B0%87-tlb-%E5%92%8C-cache-%E6%94%BE%E5%85%A5%E6%B5%81%E6%B0%B4%E7%B7%9A">VIPT 架構</a>的 I-Cache，由於 tag 是 physical address，因此還需要將 fetch1 分支預測的 PC address (VA) 透過 TLB 轉換成 PA 才可以比對 line/way 的預測結果是否正確
<ul>
<li>因此，使用 line/way 預測時，使用 VIVT 架構的 I-Cache 是比較合理的設計</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Line/way 預測總結：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%204.png"></p>
</li>
</ul>
<h2 id="1122---分支預測">11.2.2 - 分支預測</h2>
<ul>
<li>
<p>為了獲得準確的分支預測，Alpha 21264 使用的分支預測器是比較複雜的，因此無法在 fetch0 stage 完成，需要使用 2 個 stages，在 fetch1 stage 才可以獲得預測結果</p>
<ul>
<li>這個結果會和 line/way 的預測結果做比較，如果發現不一致，就以分支預測的結果為主</li>
</ul>
</li>
<li>
<p>Alpha 21264 使用了競爭的分支預測法 (tournament branch prediction)；有些分支指令使用基於全局歷史的預測方法準確度比較高，有些分支指令則是使用基於局部歷史的預測方法準確度比較高，Alpha 21264 則是兩種預測方法都實現了；對於每條分支指令來說，處理器會根據兩種分支預測方法的準確度，動態地替每條分支指令選擇適合的分支預測方法，相當於這兩種分支預測方法彼此在競爭</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%205.png"></p>
<ul>
<li>
<p>左邊為基於局部歷史的分支預測器：</p>
<ul>
<li>Local history table ⇒ <strong>BHT</strong>：
<ul>
<li>大小為 1024 x 10 bits，也就是使用了 10 bits 的 BHR，可以記錄一條分支指令過去 10 次的分支結果，總共可以記錄 1024 條分支指令的歷史記錄</li>
</ul>
</li>
<li>Local prediction ⇒ <strong>PHT</strong>：
<ul>
<li>Alpha 21264 的 local history PHT 共包含了：2^10 = 1024 個 <code>saturating counter</code>，每個 saturating counter 為 3 bits (i.e. <code>3 bits saturating counter)</code>
<ul>
<li>共需：1024 x 3 bits</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>右邊為基於全局歷史的分支預測：</p>
<ul>
<li>Path history ⇒ <strong>GHR</strong>
<ul>
<li>GHR 為 12 bits，可以記錄過去 12 條分支指令的分支結果</li>
</ul>
</li>
<li>global prediction ⇒ <strong>PHT</strong>：
<ul>
<li>Alpha 21264 的 global history PHT 共包含了：2^12 = 4096 個 <code>saturating counter</code>，每個 saturating counter 為 2 bits (i.e. <code>2 bits saturating counter)</code>
<ul>
<li>共需：4096 x 2 bits</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Alpha 21264 基於局部歷史的分支預測器，採用了 <strong>non-speculative</strong> 的更新方法</p>
<ul>
<li>只有當指令 retire 時，才會更新分支預測器的內容 (e.g. BHR、saturating counter… etc)</li>
</ul>
</li>
<li>
<p>Alpha 21264 基於全局歷史的分支預測器，採用了 <strong>speculative</strong> 的更新方法</p>
<ul>
<li>一旦有分支指令得到預測結果，就將這個預測結果更新到 GHR 中
<ul>
<li>i.e. GHR 的狀態有可能是不正確的</li>
</ul>
</li>
<li>為了在 mis-prediction 時恢復 GHR，因此採用了 <a href="../superscalar-overview-ch4-part1/#425---%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E7%9A%84%E6%9B%B4%E6%96%B0">Checkpoint GHR</a>；在更新 GHR 前，會將目前的 GHR 內容複製到 Checkpoint GHR 中；當發生 mis-prediction 時，就可以透過這個 Checkpoint GHR 來恢復 GHR</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%206.png"></p>
</li>
</ul>
</li>
<li>
<p>對於 PHT 的 saturating counters 一般都是在分支指令得到確定的結果後才更新 (參考：<a href="../superscalar-overview-ch4-part1/#425---%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E7%9A%84%E6%9B%B4%E6%96%B0">link</a>)</p>
</li>
</ul>
<h1 id="113---暫存器重命名">11.3 - 暫存器重<strong>命名</strong></h1>
<ul>
<li>
<p>Alpha 21264 <a href="../superscalar-overview-ch7/#723---%E4%BD%BF%E7%94%A8%E7%B5%B1%E4%B8%80%E7%9A%84-prf-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用了統一的 PRF 來實做暫存器重命名</a>，整個處理器中只有一個 PRF，一個 register 在它整個生命週期只會存在一的地方，不會發生位置的變化</p>
<ul>
<li>Register renaming 消除了 WAW 和 WAR 相關性</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%207.png"></p>
</li>
<li>
<p>Alpha 指令集中有一個特殊的 <code>CMOV (Conditional-move)</code> 指令，格式為：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1">1</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#a6e22e">CMOV</span>  <span style="color:#66d9ef">Ra</span>, <span style="color:#66d9ef">Rb</span> <span style="color:#960050;background-color:#1e0010">?</span> <span style="color:#66d9ef">Rc</span>  <span style="color:#75715e"># (Ra == 0) ? Rc = Rb : Rc = Rc
</span></span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>這條指令在 register renaming stage，需要讀取三個 source registers：<code>Ra</code>, <code>Rb</code>, <code>Rc</code>
<ul>
<li>
<p>由於 <code>Rc</code> 同時也是 destination register，因此需要分配一個 physical register 來存舊的 <code>Rc</code> 值，並再替 destination register：<code>Rc</code>，分配另一個 physical register</p>
</li>
<li>
<p>然而，對於 Alpha 的其他指令，都只需要讀取兩個 source registers 即可，因此不值得特別為 <code>CMOV</code> 指令增加 RAT 的 read port</p>
</li>
<li>
<p><code>CMOV</code> 指令可以拆解為下列兩條指令：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-2">2</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#a6e22e">CMOV1</span>  <span style="color:#66d9ef">Ra</span>, <span style="color:#66d9ef">oldRc</span> -<span style="color:#960050;background-color:#1e0010">&gt;</span> <span style="color:#66d9ef">newRc1</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">CMOV2</span>  <span style="color:#66d9ef">newRC1</span>, <span style="color:#66d9ef">Rb</span> -<span style="color:#960050;background-color:#1e0010">&gt;</span> <span style="color:#66d9ef">newRc2</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>透過這樣的方式，可以讓 <code>CMOV</code> 指令的 register renaming 按照一般的指令來處理，只是會需要分配兩個 physical registers</li>
</ul>
</li>
</ul>
</li>
<li>Alpha 21264 是一個 4-way superscalar CPU，每個 cycle 可以對四條指令做 register renaming，但如果這四條指令中存在 <code>CMOV</code> 指令，則會導致一個 cycle 內要對五條指令 register renaming
<ul>
<li>
<p>Alpha 21264 採用了比較簡單的方法，限制在做 register renaming 時，如果碰到 <code>COMV</code> 指令，則只有 <code>CMOV1</code> 指令以及其之前的指令可以在該 cycle 做 register renaming，<code>CMOV2</code> 以及其之後的指令必須等到下個 cycle 才可以做 register renaming</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%208.png"></p>
</li>
<li>
<p>雖然這樣的作法會讓 CPU 的執行效率有所降低，但 <code>CMOV</code> 指令出現的頻率並不高，且這種方法很容易實現，所以是一種可接受的折衷方法</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="114---發射">11.4 - 發射</h1>
<ul>
<li>Alpha 21264 包含了兩個 Issue Queue：Integer Issue Queue 以及 Floating-point Issue Queue，分別用來儲存整數類型的指令和浮點數類型的指令
<ul>
<li>Integer Issue Queue 可以儲存 20 條指令，並被 4 個 Integer FU 共享，每個 cycle 最多可以從 Integer Issue Queue 中選出 4 條整數指令出來執行</li>
<li>Floating-point Issue Queue 可以儲存 15 條指令，並被 2 個 Floating-point FU 共享，每個 cycle 可以從 Floating-point Issue Queue 中選出 2 條浮點數指令出來執行</li>
</ul>
</li>
<li>Alpha 21264 採用了 cluster 架構，將整數執行部份分成了 Cluster0 和 Cluster1 兩個部份，每個 cluster 都有完整的 Integer PRF，因此在處理器中共有兩個一模一樣的 Integer PRF
<ul>
<li>
<p>每個 cluster 內部又被分成了兩個 subcluster，分別為：upper (U) 和 lower (L)</p>
<ul>
<li>整數部份的 4 個 FU分佈在這幾個 subclusers 中：Cluster0 的 U0、L0 以及 Cluster1 的 U1、L1</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%209.png"></p>
<ul>
<li>Alpha 21264 並沒有直接實現 4-of-20 的 select 電路，而是：
<ul>
<li>Cluster0 中的 U0 和 U1 共用同一個 select 電路：select0</li>
<li>Cluster1 中的 L0 和 L1 共用同一個 select 電路：select1</li>
</ul>
</li>
<li>這樣每個 select 電路只需要實做 2-of-20 的功能即可</li>
</ul>
</li>
</ul>
</li>
<li>Alpha 21264 採用了 <a href="../superscalar-overview-ch8-part1/#813---%E5%A3%93%E7%B8%AE-vs-%E9%9D%9E%E5%A3%93%E7%B8%AE">Compressing Issue Queue</a>，使 select 電路可以很容易地實現 oldest-first 的功能，不過會增加 Issue Queue 的複雜度，功耗由於每條指令被 issued 時，都會需要移動大量的指令 (通常 oldest-first 的指令都是在 Issue Queue 的底部)，因此不適合使用在行動裝置上</li>
<li>Alpha 21264 中，除了 load 指令外，其他類型指令執行所需的 cycles 數都是固定的；Alpha 21264 簡化了 load 指令的 wake-up 機制：當 load 指令發生 D-Cache miss 時，在它的 <a href="../superscalar-overview-ch8-part2/#853---%E6%8E%A8%E6%B8%AC%E5%96%9A%E9%86%92">SW (Speculative Window)</a> 中的所有指令都會從 pipeline 中被 flushed 掉，這些指令會重新在 Issue Queue 中等待被 wake up，然後參與仲裁；對於那些與 load 無關的指令很快就會再次被 select 電路選中，然而那些與 load 指令相關的指令則需要等待 D-Cache miss 被解決
<ul>
<li>這種方式雖然降低了一點性能，但比較容易實現，因為不用識別 SW 中哪些指令和 load 指令存在相關性 (參考：<a href="../superscalar-overview-ch8-part2/#853---%E6%8E%A8%E6%B8%AC%E5%96%9A%E9%86%92">link</a>)，是一種可以接受的折衷方法</li>
</ul>
</li>
<li>為了配合上述的 wake-up 機制，指令在被 select 電路選中後，不能馬上”離開” Issue Queue，而是需要確認這條指令沒有被發生 D-Cache miss 的 load 指令所影響後，才能離開 Issue Queue
<ul>
<li>因此，一條指令被 select 電路選中後，在 Issue Queue 中需要等待 2 個 cycles，才可以從 Issue Queue 中刪除該指令
<ul>
<li><em>Cycle n</em>：
<ul>
<li>指令被 select 電路選中</li>
</ul>
</li>
<li><em>Cycle n + 1</em>：
<ul>
<li>指令仍會留在 Issue Queue 中，但不會再向 select 電路發出 request 訊號了</li>
</ul>
</li>
<li><em>Cycle n + 2</em>：
<ul>
<li>如果這條指令所需的 operands 都可獲得 (不論是來自 bypassing network 或是 PRF)，這條指令就可以被正常執行，並從 Issue Queue 中刪除該指令 (代表指令離開 Issue Queue 了)</li>
<li>如果這條指令所需的 operands 來自於前面的 load 指令，且該 load 指令發生了 D-Cache miss，那麼這條指令就沒辦法繼續被執行，需要重新”放回” Issue Queue 中並等待 D-Cache miss 被解決
<ul>
<li>實際上這條指令並沒有真的離開 Issue Queue (i.e. 還是佔著 Issue Queue 的 entry)，只需修改對應的 status bits 即可</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>浮點數的 load/store 指令也是在整數的 cluster 中被執行的</li>
</ul>
<h1 id="115---執行單元">11.5 - 執行單元</h1>
<h2 id="1151---整數的執行單元">11.5.1 - 整數的執行單元</h2>
<ul>
<li>
<p>Alpha 21264 共有 6 個 FU (4 個 Integer FU，2 個 Floating-point FU)，每個 cycle 可以同時執行六條指令</p>
<ul>
<li>i.e. Alpha 21264 是一個 machine width = 4，issue width = 6 的 superscalar CPU</li>
</ul>
</li>
<li>
<p>Alpha 21264 採用了 <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture">non-data-capturing</a> 的架構，在指令被 select 電路選中後，才去讀 PRF</p>
<ul>
<li>Integer PRF 因此需要支持四條指令的讀取，也就是需要 8 個 read ports (假設每條指令有兩個 source registers)，導致執行速度較慢</li>
<li>為了解決這個問題，Alpha 21264 採用了 cluster 架構，將整數執行部份分為了 cluster0 和 cluster1，每個 cluster 都使用了一個完整的 Integer PRF；每個 cluster 內部又被分成了兩個 subcluster：upper (U) 以及 lower (L)
<ul>
<li>因此，每個 Integer PRF 只需 4 個 read ports，簡化了 PRF 的設計，加快執行速度</li>
</ul>
</li>
</ul>
</li>
<li>
<p>如果兩條連續的指令是在同一個 cluster 內執行，那麼就可以 back-to-back 的執行；然而如果兩條連續的指令是在不同的 clusters，如果要將一條指令的計算結果透過 bypassing network 傳給另一個 cluster 的指令，就需要跨越 cluster，經過比較長的電路，因此需要獨立使用一個 stage，導致兩條連續的指令之間會間隔一個 cycle，沒辦法 back-to-back 的執行</p>
<ul>
<li>一個 cluster 中指令執行的計算結果需要間隔一個 cycle 才能寫入另一個 cluster 的 PRF 中</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2010.png"></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2011.png"></p>
<ul>
<li>由於 Alpha 21264 是 out-of-order CPU，因此硬體會盡可能地找到一條不相關的指令插入間隔中，就不會降低處理器的執行效率了</li>
</ul>
</li>
</ul>
<h2 id="1152---浮點數的執行單元">11.5.2 - 浮點數的執行單元</h2>
<ul>
<li>
<p>Alpha 21264 有 2 個 Floating-point FU，每個 cycle 可以執行兩條 floating-point 指令</p>
</li>
<li>
<p>為了盡量減少連線的延遲，每個 cluster 中的 FU 都與自己的 PRF 緊靠在一起，大量地縮短 cluster 內 bypassing network 的電路，保證同一個 cluser 內連續的指令可以 back-to-back 的執行</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2012.png"></p>
</li>
<li>
<p>Alpha 21264 同一個 cluster 內，不同指令的 latency：</p>
<table>
	<thead>
			<tr>
					<th>Instruction class</th>
					<th>Latency (cycles)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Simple integer operations</td>
					<td>1</td>
			</tr>
			<tr>
					<td>Special instruction</td>
					<td>3</td>
			</tr>
			<tr>
					<td>Integer multiply</td>
					<td>7</td>
			</tr>
			<tr>
					<td>Integer load</td>
					<td>3 (假設 D-Cache hit)</td>
			</tr>
			<tr>
					<td>Floating-point add</td>
					<td>4</td>
			</tr>
			<tr>
					<td>Floating-point load</td>
					<td>4 (假設 D-Cache hit)</td>
			</tr>
			<tr>
					<td>Floating-point multiply</td>
					<td>4</td>
			</tr>
			<tr>
					<td>Floating-point divide</td>
					<td>12 (single-precision); 15 (double-precision)</td>
			</tr>
			<tr>
					<td>Floating-point square-root</td>
					<td>12 (single-precision); 30 (double-precision)</td>
			</tr>
	</tbody>
</table>
<ul>
<li>如果跨 cluster，則需要再增加一個 cycle</li>
</ul>
</li>
</ul>
<h1 id="116---記憶體的存取">11.6 - 記憶體的存取</h1>
<ul>
<li>Alpha 21264 的訪問記憶體元件採用了兩個特殊的設計：
<ul>
<li>能夠對 load 指令和 store 指令之間存在的 RAW 相關性進行預測，從而規劃某些 load 指令進入 pipeline 的時間，防止它們提前進入 pipeline 做白工，稱為：<code>Speculative disambiguation</code></li>
<li>能夠對 load 指令存取 D-Cache 時是否 hit 做預測，從而避免不必要的 wake up，稱為：<code>Load hit/miss Prediction</code></li>
<li>以上兩種方法本質上都是透過預測的方式來提昇處理器的執行效率，並仍然在現代處理器中被廣泛地使用</li>
</ul>
</li>
</ul>
<h2 id="1161---speculative-disambiguation">11.6.1 - Speculative Disambiguation</h2>
<ul>
<li>
<p>對於 load 指令和 store 指令來說，它們之間的相關性在 register renaming stage 是無法被解決的，只有到了 execute stage，將指令中的存取位址計算出來後，才可以判斷它們之間是否存在相關性，也就是：<code>Memory Disambiguation</code></p>
</li>
<li>
<p>Alpha 21264 採用了<a href="../superscalar-overview-ch9/#961---memory-disambiguation">完全 out-of-order</a> 的方式來執行 load 指令和 store 指令，為了最大限度地減少對 pipeline 的負面影響，Alpha 21264 還對 load 指令是否和其之前的 store 指令存在相關性進行了預測</p>
<ul>
<li>如果預測一條 load 指令和 pipeline 中還沒有 retire 的 store 指令之間不存在 RAW 相應，那麼這條 load 指令就不須等待 store 指令的存取位置被計算出來，可以直接 out-of-order 進入 execute stage 被執行，提高了執行性能</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2013.png"></p>
<ul>
<li>Alpha 21264 將對 D-Cache 的存取拆分成兩個不同的 stages (<code>D-Cache1</code>、<code>DCache2</code>)：
<ul>
<li>D-Cache1 stage：讀取 D-Cache，但這個 cycle 沒辦法得到結果</li>
<li>D-Cache2 stage：得到 data 和 tag，並比較 tag，以判斷是否 cache hit</li>
</ul>
</li>
</ul>
</li>
<li>
<p>將 D-Cache 的存取採取了 pipeline 的方式：</p>
<ul>
<li>缺點：
<ul>
<li>增加了 load 指令的執行 cycles 數</li>
<li>增加了 load 指令和與其存在相關性的指令所間隔的 cycles 數，產生更多的 bubbles</li>
</ul>
</li>
<li>優點：
<ul>
<li>可以提高 CPU 的 frequency</li>
<li>Out-of-order CPU 會盡可能地找到不相關的指令插入間隔中，因此不會對性能造成太大的負面影響</li>
</ul>
</li>
<li>因此，現代處理器基本上都使用 pipeline 的方式來存取 D-Cache</li>
</ul>
</li>
<li>
<p>在 D-Cache1 stage 中，由於 load 指令和先前的 store 指令的存取位址都已經被計算出來，因此可以檢查 load 指令是否與先前的 store 指令存在相關性，也就是：<code>disambiguation</code>，可以透過下列的硬體元件來完成：</p>
<ol>
<li><strong>Load/store Issue Queue：</strong>
<ul>
<li>以 out-of-order 的方式將 load 指令和 store 指令送到 FU 來執行</li>
</ul>
</li>
<li><strong>Load Queue：</strong>
<ul>
<li>依照 program order 的順序儲存著所有 load 指令的存取位址</li>
<li>Load 指令在 register renaming stage 就會被寫進 load queue 中 (register renaming stage 還是 in-order 的)</li>
<li>當 load 指令 retire 時，就會將 load 指令在 load queue 中對應的 entry 給釋放</li>
<li>使用 CAM 來實現，以便可以快速的比較 load 指令的存取位址</li>
</ul>
</li>
<li><strong>Store Queue：</strong>
<ul>
<li>依照 program order 的順序儲存著所有 store 指令的存取位址</li>
<li>Store 指令在 register renaming stage 就會被寫進 store queue 中 (register renaming stage 還是 in-order 的)</li>
<li>當 store 指令 retire 時，就會將 store 指令在 store queue 中對應的 entry 給釋放</li>
<li>使用 CAM 來實現，以便可以快速的比較 store 指令的存取位址</li>
</ul>
</li>
<li><strong>Wait Table：</strong>
<ul>
<li>
<p>一個 1024 x 1 bit 的表格，<strong>使用 PC address 作為索引</strong>，用來儲存 load 指令的相關性資訊</p>
</li>
<li>
<p>Wait table 中的每個 bit 都稱為：<code>Wait bit</code>，初始值皆為 <strong>0</strong>，表示所有的 load 指令和其之前的 store 指令之間都不存在相關性，可以 out-of-order execution</p>
</li>
<li>
<p>當發現了 store/load 違例，也就是發現了一條或多條在 load 指令前的 store 指令，與該 load 指令所存取的記憶體地址相同 (實際上，只要是存取範圍有重疊就存在相關性)，此時就會將該 load 指令在 wait table 中所對應的 wait bit 給設成 <strong>1</strong></p>
</li>
<li>
<p>在 decode stage，每當 decode 出一條 load 指令，就需讀取 wait table，並將 load 指令所對應的 wait bit 一起寫進 Issue Queue 中</p>
<ul>
<li>如果一條 load 指令的 wait bit 為 1，則該 load 指令必須等到所有在其之前的 store 指令都計算出存取位址後，才可以向 select 電路發出 request 請求仲裁</li>
</ul>
</li>
<li>
<p>Wait table 每 16,384 個 cycles 就會全部清為 0，以避免 wait table 的內容在經過一段時間的執行後全部變成 1 (這樣所有的 load 指令都無法 out-of-order execute 了)</p>
<ul>
<li>就算是同一條 load 指令 (i.e. PC address 相同)，也不一定每次執行時都與 store 指令仍然存在相關性</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2014.png"></p>
</li>
</ul>
</li>
</ol>
</li>
<li>
<p>指令進到 issue stage 後的下一個 cycle，進到 register read stage 並從 PRF 中讀取 operands，然後再到下一個 cycle，進到 address calculation stage 就可以計算指令的存取位址了，將存取位址計算出來後，就可以進到 D-Cache1 stage</p>
</li>
<li>
<p>在 D-Cache1 stage 會檢查 load 指令和 store 指令之間是否存在相關性，因此這個 cycle stage 也被稱為 <code>Disambiguation stage</code>，load 指令和 store 指令的 disambiguation 分別為：</p>
<ol>
<li><strong>Load Disambiguation：</strong>
<ul>
<li>Load 指令會將其存取位址寫到 <strong>load queue</strong> 對應 entry 中，同時 load 指令還會完成以下兩件事情：
<ol>
<li>Load 指令會去查詢 <strong>store queue</strong>，如果發現存在同樣存取位址且年齡比該 load 指令還<strong>老</strong>的 <strong>store 指令</strong>，那這條 load 指令就不需要存取 D-Cache 了，直接透過 store queue 就可以得到結果</li>
<li>Load 指令還需要去查詢 <strong>load queue</strong>，Alpha 21264 要求訪問相同位址的 load 指令必須是 in-order (i.e. program-order) 的，如果不滿足這個規則，在 multi-core 的環境下有可能會發生錯誤
<ul>
<li>
<p>如果發現存在存取位址相同且比該 load 指令還要<strong>年輕</strong>的 <strong>load 指令</strong>，就代表發生了 load/load 指令違例：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2015.png"></p>
<ul>
<li>Load 指令 X 和 load 指令 Y 執行的中間有可能被其他的 CPU 將該存取位址的值給更動了，因此如果 load 指令的執行順序改變，其結果就會出錯</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>
<li>Alpha 21264 定義了各種記憶體存取應該保持的執行順序：
<table>
	<thead>
			<tr>
					<th>First Instruction In Pair</th>
					<th>Second Instruction in Pair</th>
					<th>Reference Order</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Load memory to address X</td>
					<td>Load memory to address X</td>
					<td>Maintained</td>
			</tr>
			<tr>
					<td>Load memory to address X</td>
					<td>Load memory to address Y</td>
					<td>Not Maintained</td>
			</tr>
			<tr>
					<td>Store memory to address X</td>
					<td>Store memory to address X</td>
					<td>Maintained</td>
			</tr>
			<tr>
					<td>Store memory to address X</td>
					<td>Store memory to address Y</td>
					<td>Maintained</td>
			</tr>
			<tr>
					<td>Load memory to address X</td>
					<td>Store memory to address X</td>
					<td>Maintained</td>
			</tr>
			<tr>
					<td>Load memory to address X</td>
					<td>Store memory to address Y</td>
					<td>Not Maintained</td>
			</tr>
			<tr>
					<td>Store memory to address X</td>
					<td>Load memory to address X</td>
					<td>Maintained</td>
			</tr>
			<tr>
					<td>Store memory to address X</td>
					<td>Load memory to address Y</td>
					<td>Not Maintained</td>
			</tr>
	</tbody>
</table>
<ul>
<li>兩條 load 指令存取同一個位址，order 必須 maintain ⇒ 否則就是 load/load 指令違例</li>
<li>Store 指令一定是 in-order 執行，因此兩條 store 指令不論存取位址是否相同，order 都必須 maintain</li>
<li>一條比較年輕的 load 指令，與一條比較老的 store 存取同一個位址，order 必須 maintain ⇒ 否則就是 store/load 指令違例</li>
<li>一條比較年輕的 store 指令，與一條比較老的 load 指令存取同一個位址，order 必須 maintain ⇒ 否則就是 store/load 指令違例</li>
<li>其他狀況，都可以 out-of-order 執行</li>
<li>Alpha 21264 中 load 指令和 store 指令是 out-of-order 執行的，因此需要在 disambiguation stage 解決順序性</li>
<li>然而，load 指令在查詢 store queue 時，如果發現存在同樣存取位址且年齡比該 load 指令還老的 store 指令，這種情況不需要 flush pipeline，因為這只代表 load 指令可以直接從 store buffer 中讀取 store 指令的資料 (需等待 store 指令執行完畢)，不需存取 D-Cache，但是 store 指令和 load 指令之間的執行仍然要保持 in-order
<ul>
<li>
<p>但如果 load 指令需要的資料寬度大於 store 指令的資料寬度 (e.g. 32-bit load vs. 8-bit store) 時，代表 load 指令所需要的資料有一部分存在 store queue 中，另外一部份存在 D-Cache 中，load 指令就沒辦法在同一個 cycle 內得到其所需的資料了</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2016.png"></p>
<ul>
<li>此時，仍需將 load 指令和與其相關的指令都從 pipeline 中給 flush 掉，並重新從 I-Cache fetch 這些指令
<ul>
<li>在 Alpha 21264 中，這個過程稱為：<code>replay trap</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><strong>Store Disambiguation：</strong>
<ul>
<li>
<p>在 pipeline 的 disambiguation stage，store 指令會將其計算出來的存取位址寫到 <strong>store queue</strong> 中對應的 entry 中，並在同一個 cycle 查詢 <strong>load queue</strong>，如果發現存在同樣存取位址且年齡比該 store 指令還年輕的 load <strong>指令</strong>，則代表這條提前執行的 load 指令沒有使用到正確的計算結果，產生了 store/load 指令違例</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2017.png"></p>
</li>
<li>
<p>當發生 store/load 指令違例時，最理想的解決方法是只將違例的 load 指令以及所有與其相關的指令從 pipeline 中給 flush 掉 (參考：<a href="../superscalar-overview-ch8-part2/#853---%E6%8E%A8%E6%B8%AC%E5%96%9A%E9%86%92">Selective Replay</a>)；然而這種設計增加了設計的複雜度，因此 Alpha 21264 採用了相對比較簡單的 replay trap 處理方法</p>
<ul>
<li>當已經離開 issue queue 的 load 指令或 store 指令由於某個原因不能再繼續執行時，這條 load 指令或 store 指令及它之後的所有指令都會從 pipeline 中被 flushed 掉，並恢復 CPU 的狀態 (Alpha 21264 有非常豐富的 checkpoints)，然後重新從 I-Cache fetch 這些指令來執行</li>
<li>Alpha 21264 中，load/load 指令違例也是採用 replay trap 的方式來處理</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>降低了處理器的性能，如果經常發生 store/load 指令違例或是 load/load 指令違例，那麼就需要經常的 flush pipeline 中的部份指令，並重新從 I-Cache fetch 這些指令來執行
<ul>
<li>因此 Alpha 21264 才使用了 wait table 來對 store 和 load 之間的相關性進行預測，這樣可以避免大部分的 store/load 指令違例，降低需要 replay trap 的次數</li>
</ul>
</li>
</ul>
</li>
<li>
<p>當發生 store/load 指令違例後，除了進行 replay trap，還需要更新 wait table，將 load 指令在 wait table 所對應的 bit 給設成 1，這樣當下次這條違例的 load 指令再次被執行時，就需要等它之前所有的 store 指令都被 select 電路選中後，才允許這條 load 指令向 select 電路發出 request</p>
<ul>
<li>這條指令最後在執行時，有可能可以直接從 store queue 取得所需的資料</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2018.png"></p>
</li>
<li>
<p>Alpha 21264 選擇將這些被 flushed 的指令重新從 fetch stage 開始執行，而不是將違例的 load 指令及與其相關的所有指令重新放回 Issue Queue 中重新仲裁，是因為此時的 Issue Queue 可能已經沒有空間儲存這些指令了</p>
<ul>
<li>
<p>當 Issue Queue 沒有空間儲存這些指令時，這些指令就必須等待；這個等待時間可能會很長，而且需要額外一個硬體元件來儲存這些等待的指令</p>
</li>
<li>
<p>此外，由於這些指令沒辦法進入 Issue Queue，因此就不能 wake up Issue Queue 中其他的指令</p>
<ul>
<li>如果此時在 Issue Queue 中的指令正在等待這些指令來 wake up 它們，那麼在 Issue Queue 中的指令也沒辦法離開 Issue Queue，導致 Issue Queue 永遠沒有空間空出來，便會發生 <strong>dead lock</strong></li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2019.png"></p>
<ul>
<li>Store 指令在 disambiguation stage 發現在其之後的 load 指令先被執行了，因此發生了 store/load 指令違例</li>
<li>為了避免 dead lock，Alpha 21264 直接將第一組和第二組的全部指令從 pipeline 中給 flush 掉，並使用 checkpoint 來恢復 CPU 的狀態，然後重新從 I-Cache fetch 這些指令來執行</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Alpha 21264 採用的 replay 方法是基於 I-Cache 的，這種方法會降低處理器的效能，如果想基於 Issue Queue 來 replay，有兩種方式可以採用：</p>
<ol>
<li>
<p>當指令被 select 電路選中時，不離開 Issue Queue，只要等到指令 retire 時才允許其離開 Issue Queue (參考：<a href="../superscalar-overview-ch8-part2/#853---%E6%8E%A8%E6%B8%AC%E5%96%9A%E9%86%92">Issue Queue Base Replay</a>)</p>
<ul>
<li>
<p>優點：</p>
<ul>
<li>設計複雜度比較低</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>由於指令被 select 電路選中後，並不會馬上離開 Issue Queue，只有在該條指令確認可以被正確執行後 (i.e. 不需要 replay)，才會允許該條指令離開 Issue Queue；然而實際上由於 D-Cache hit rate 是很高的，且發生 store/load 指令等違例的機率並不高，所以大部分指令被 select 電路選中後，其實並不需要 replay，但這些指令仍佔據著 Issue Queue 的空間，直到確認其不需要 replay 為止，導致 Issue Queue 中實際可用的 entries 數減少
<ul>
<li>增加 Issue Queue 的空間則會增加 select 和 wake-up 電路的 latency，因此無法無上限地增加</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>採用 <a href="../superscalar-overview-ch8-part2/#853---%E6%8E%A8%E6%B8%AC%E5%96%9A%E9%86%92">Replay Queue Based Replay</a> 的方式，增加一個 Replay Queue，用來保存離開 Issue Queue 但還沒 retire 的指令</p>
<ul>
<li>
<p>當發生 store/load 指令違例時，所有要被 replay 的指令只需從 Replay Queue 重新向 select 電路發出 request 即可</p>
<ul>
<li>Intel Pentium 4 採用了這種設計</li>
</ul>
</li>
<li>
<p>優點：</p>
<ul>
<li>提高了 Issue Queue 的使用效率</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>Replay Queue 會佔用額外的硬體空間</li>
<li>Replay Queue 也需一起參與 select 電路的仲裁和 wake up，因此設計較複雜</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
</ol>
</li>
</ul>
<h2 id="1162---load-hitmiss-prediction">11.6.2 - Load hit/miss Prediction</h2>
<ul>
<li>
<p>在 Alpha 21264 中，load 指令執行的 cycles 數是不固定的，D-Cache hit/miss、D-Cache 中是否有 bank conflict、是否和其他的元件產生 D-Cache read port conflict 等，都會影響 load 指令執行的 cycles 數；但是，如果需要等到 load 指令讀取到資料後才 wake up Issue Queue 中等待的指令，會導致 latency 過長</p>
<ul>
<li>
<p>然而，大部分的情況下 D-Cache 都會是 hit 的，因此可以直接假設 D-Cache 總是 hit 來 wake up Issue Queue 中相關的指令，也就是採用 <a href="../superscalar-overview-ch8-part2/#853---%E6%8E%A8%E6%B8%AC%E5%96%9A%E9%86%92">Speculative wake-up</a></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2020.png"></p>
<ul>
<li>Load 指令被 select 電路選中後，需要等待 2 個 cycles 才可以 wake up 相關的指令</li>
<li>Cycel 4、cycle 5 和 load 相關的指令就可以被 wake up 了，這兩個 cycles 也稱為：<code>Speculative Window (SW)</code>
<ul>
<li>在這兩個 cycles 被 select 電路所選中的指令都不能離開 Issue Queue (i.e. 採用 <a href="../superscalar-overview-ch8-part2/#853---%E6%8E%A8%E6%B8%AC%E5%96%9A%E9%86%92">Issue Queue Based Replay</a> 的設計)，不論它們是否真的和 load 指令存在相關性</li>
<li>當發現 load 指令真的是 D-Cache hit 時，這兩條指令就可以離開 Issue Queue</li>
<li>但如果發現 load 指令是 D-Cache miss 時，這兩條指令就必須從 pipeline 中給 flush 掉，並在 Issue Queue 中重新等待被 wake up，然後重新向 select 電路發出 request</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Alpha 21264 中並沒有區分 SW 中哪些指令和 load 指令相關，而是假設全部指令都與 load 指令相關；在發現 D-Cache miss 時會將 SW 中的所有指令從 pipeline 中給 flush 掉，並在 Issue Queue 中重新等待被 wake up，此時會假設 L2 Cache 是 hit 的，因此這些指令可以再次 (在 cycle 6 時) 被 select 電路選中，這會導致 4 個 cycles 的 latency</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2021.png"></p>
<ul>
<li>如果 L2 Cache hit，那麼這些指令就可以從 Issue Queue 中離開</li>
<li>如果 L2 Cache miss，那麼這些指令就必須在 Issue Queue 中繼續等待</li>
</ul>
</li>
<li>
<p>在發現 D-Cache miss 時會需要將 SW 中的所有指令從 pipeline 中給 flush 掉，但這樣就浪費了執行效率，因為這些 cycles 原本可以選擇其他指令來執行</p>
<ul>
<li>為了盡量避免這種情況發生，Alpha 21264 中對 load 指令是否會 D-Cache hit 也進行了預測，只有那些預測 D-Cache hit 的 load 指令才會以 2 cycles latency 的方式來 wake up 相關的指令，否則就以 4 cycles latency (假設 L2 Cache 是 hit) 的方式來 wake up 相關的指令</li>
<li>Alpha 21264  使用了一個 <code>4-bit saturating counter</code> 來預測 load 指令是否會 D-Cache hit
<ul>
<li>每當 D-Cache hit 時，counter += 1</li>
<li>每當 D-Cache miss 時，counter -= 2</li>
<li>使用 counter 的 MSB 來作為 load 指令的預測值
<ul>
<li><code>MSB = 1</code>：預測 load 指令會 D-Cache hit</li>
<li><code>MSB = 0</code>：預測 load 指令會 D-Cache miss</li>
</ul>
</li>
</ul>
</li>
<li>在 <code>Independent Window (IW)</code> (i.e. 2 cycles latency 或是 4 cycles latency) 中可以選擇與 load 指令不相關的指令來執行，即使預測 D-Cache miss，這 4 個 cycles latency 由於仍然可以執行其他不相關的指令，因此也不會對 CPU 的性能造成太大的影響</li>
</ul>
</li>
<li>
<p>對於浮點數 load 指令來說，由於需要 128 bits 的資料，因此需要 2 個 cycles 才可以從 D-Cache 中讀出完整的結果 (64 bits x 2)，因此浮點數 load 指令的 latency 需要增加 1 個 cycle ⇒ 共 3 個 cycles</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2022.png"></p>
<ul>
<li>此時 SW 只有 1 個 cycle (從 load 指令等待 3 個 cycles 後開始將相關的指令 wake up，直到執行時發現是否為 D-Cache hit/miss，中間所間隔的 cycles 數)
<ul>
<li>如果發現 load 指令是 D-Cache hit 時，這條指令就可以離開 Issue Queue</li>
<li>如果發現 load 指令是 D-Cache miss 時，這條指令就必須從 pipeline 中給 flush 掉，並在 Issue Queue 中重新等待被 wake up，然後重新向 select 電路發出 request</li>
</ul>
</li>
</ul>
</li>
<li>
<p>當發生 D-Cache miss 時，需要從 L2 Cache 讀取資料，浮點數 load 指令 L2 Cache hit 的 latency 同樣需要增加 1 個 cycle ⇒ 共 5 個 cycles</p>
<ul>
<li>i.e. 指令 A 可以在 cycle 7 時再次被 select 電路選中</li>
</ul>
</li>
<li>
<p>對於浮點數 load 指令來說，由於其 SW 只有 1 個 cycle，且浮點數指令執行所需的 cycles 通常都比較長，當浮點數 load 指令發生 D-Cache miss 時有足夠的時間進行處理，因此浮點數 load 指令<strong>並不會</strong>使用 saturating counter 來預測其 D-Cache 是否會 hit</p>
</li>
</ul>
<h1 id="117---退休">11.7 - 退休</h1>
<ul>
<li>在 Alpha 21264 中，一條指令要 retire 時發現了 exception，那麼在 pipeline 中的所有指令都會被 flushed 掉，這些被 flushed 的指令所佔據的 physical registers 也會被釋放，並放回 free list 中，RAT 可以透過 checkpoint 來恢復到產生 exception 那道指令之前的狀態
<ul>
<li>Alpha 21264 的 ROB 中每條指令都有一個對應的 checkpoint，80 個 ROB entries 共對應了 80 個 checkpoints，以便快速的恢復 CPU 的狀態</li>
<li>Alpha 21264 的 RAT 是<a href="../superscalar-overview-ch7/#732---%E5%9F%BA%E6%96%BC-cam-%E7%9A%84%E9%87%8D%E5%91%BD%E5%90%8D%E6%98%A0%E5%B0%84%E8%A1%A8">使用 CAM 實現</a>的，因此佔用的硬體資源也是很小的</li>
</ul>
</li>
<li>Alpha 21264 一個 cycle 內最多可以 retire 8 條指令</li>
</ul>
<h1 id="118---結論">11.8 - 結論</h1>
<ul>
<li>
<p>Alpha 21264 為了達到很高的 CPU frequency，在很多地方都加了額外的 pipeline stage，例如 對 cache 的存取；然而，這也同時增加了 mis-prediction penalty，並增大了 load latency</p>
<ul>
<li>Out-of-order execution 可以緩解 load latency 增大所引起的問題</li>
</ul>
</li>
<li>
<p>Alpha 21264 使用了複雜的分支預測方法來準確地預測分支指令，為了加快 branch mis-prediction 及 exception 發生時恢復 CPU 狀態的效率，Alpha 21264 為每個 ROB 中的每條指令都分配了一個對應的 checkpoint</p>
</li>
<li>
<p>Alpha 21264 使用了 cluster 架構來解決 multi-port PRF 所產生的問題</p>
</li>
<li>
<p>Alpha 21264 使用 load hit/miss prediction 和 speculative disambiguation 這兩種預測方法來加快 load/store 指令的執行</p>
</li>
<li>
<p>更深的 pipeline 加上更準確的預測方法，使 Alpha 21264 在運行更快的 CPU frequency 的同時，保持了較高的執行效率</p>
</li>
<li>
<p>Alpha 21264 的 pipeline：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch11/image%2023.png"></p>
<ul>
<li>對於普通的指令，Alpha 21264 使用了 7 stages 的 pipeline</li>
<li>對於 load/store 類型的指令，由於對 D-Cache 的存取也採取了 pipeline 的方式，因此 Alpha 21264 使用了 9 stages 的 pipeline</li>
<li>對於 floating-point 類型的指令，Alpha 21264 使用了 10 stages 的 pipeline</li>
</ul>
</li>
<li>
<p>事實上，不能依 pipeline 的 stages 個數來判斷一個 CPU 的性能高低，而是還需要考慮 pipeline 的執行效率，而這需要使用更準確的各種預測方法，才能使 pipeline 保持高效率的執行</p>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>&lt;超標量處理器概覽&gt; 第 10 章 - 提交</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch10/</link>
      <pubDate>Sun, 04 May 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch10/</guid>
      <description>&lt;h1 id=&#34;101---概述&#34;&gt;10.1 - 概述&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;在 out-of-order superscalar CPU 中，為了保持程式執行順序的一致性，一般通常都會在 pipeline 的最後增加一個 &lt;code&gt;commit&lt;/code&gt; stage
&lt;ul&gt;
&lt;li&gt;當指令抵達 commit stage 時，會將這條指令在 ROB 中的 entry 標記為 complete&lt;/li&gt;
&lt;li&gt;此時只代表這個指令已經計算完成，不代表可以離開 pipeline (i.e. retire)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;指令進到 commit stage 並不代表它一定是正確的，有可能這條指令處在 mis-prediction 或是 exception 的路徑上，最後需要從 pipeline 中被 flushed 掉
&lt;ul&gt;
&lt;li&gt;只有當這條指令之前的指令都已經 retire 了，且這條指令已經是 complete 的狀態，才可以 retire 並離開 pipeline&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在一條指令 retire 前，它的狀態都是 speculative 的，只有當這條指令真的 retire 後，才可以將它的狀態更新到 CPU 的 architecture state&lt;/li&gt;
&lt;li&gt;對一個 &lt;em&gt;N-way&lt;/em&gt; 的 superscalar，因為每個 cycle 最少可以 fetch &lt;em&gt;N&lt;/em&gt; 條指令進 pipeline 中，pipeline &lt;strong&gt;至少&lt;/strong&gt;也需要 retire &lt;em&gt;N&lt;/em&gt; 條指令，才能確保 pipeline 不會被堵塞&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;102---rob&#34;&gt;10.2 - ROB&lt;/h1&gt;
&lt;h2 id=&#34;1021---一般架構&#34;&gt;10.2.1 - 一般架構&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ROB 本質上是一個 FIFO，ROB 中儲存了指令的相關訊息，例如：一條指令的類型、結果、destination register 和 exception 的類型等&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="101---概述">10.1 - 概述</h1>
<ul>
<li>在 out-of-order superscalar CPU 中，為了保持程式執行順序的一致性，一般通常都會在 pipeline 的最後增加一個 <code>commit</code> stage
<ul>
<li>當指令抵達 commit stage 時，會將這條指令在 ROB 中的 entry 標記為 complete</li>
<li>此時只代表這個指令已經計算完成，不代表可以離開 pipeline (i.e. retire)</li>
</ul>
</li>
<li>指令進到 commit stage 並不代表它一定是正確的，有可能這條指令處在 mis-prediction 或是 exception 的路徑上，最後需要從 pipeline 中被 flushed 掉
<ul>
<li>只有當這條指令之前的指令都已經 retire 了，且這條指令已經是 complete 的狀態，才可以 retire 並離開 pipeline</li>
</ul>
</li>
<li>在一條指令 retire 前，它的狀態都是 speculative 的，只有當這條指令真的 retire 後，才可以將它的狀態更新到 CPU 的 architecture state</li>
<li>對一個 <em>N-way</em> 的 superscalar，因為每個 cycle 最少可以 fetch <em>N</em> 條指令進 pipeline 中，pipeline <strong>至少</strong>也需要 retire <em>N</em> 條指令，才能確保 pipeline 不會被堵塞</li>
</ul>
<h1 id="102---rob">10.2 - ROB</h1>
<h2 id="1021---一般架構">10.2.1 - 一般架構</h2>
<ul>
<li>
<p>ROB 本質上是一個 FIFO，ROB 中儲存了指令的相關訊息，例如：一條指令的類型、結果、destination register 和 exception 的類型等</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image.png"></p>
<ul>
<li><code>Complete</code>：
<ul>
<li>一條指令是否執行完畢</li>
</ul>
</li>
<li><code>Areg</code>：
<ul>
<li>指令的 architecture destination register 編號</li>
</ul>
</li>
<li><code>Preg</code>：
<ul>
<li>指令的 physical destination register 編號</li>
</ul>
</li>
<li><code>OPreg</code>：
<ul>
<li>指令的 architecture destination register 被 renamed 成 <code>Preg</code> 前，舊的 <code>Preg</code> 編號
<ul>
<li>當需要恢復 CPU 的狀態時 (e.g. mis-prediction、exception)，需要透過這個映射關係來<a href="../superscalar-overview-ch7/#75---%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D%E9%81%8E%E7%A8%8B%E7%9A%84%E6%81%A2%E5%BE%A9">恢復 RAT</a></li>
</ul>
</li>
</ul>
</li>
<li><code>PC</code>：
<ul>
<li>指令的 PC 值，當發生 interrupt 或 exception 時，需要保存指令的 PC 值，以便可以重新執行指令
<ul>
<li>E.g. RISC-V 的 <code>$mepc</code>、<code>$sepc</code> 會保存發生 interrupt 或 exception 指令的 PC 值，就可以從 <code>PC</code> 獲得</li>
</ul>
</li>
</ul>
</li>
<li><code>Exception</code>：
<ul>
<li>發生 exception 時的 exception 類型</li>
<li>當指令 retire 並需要處理 exception 時，會依據 <code>Exception</code> 的資訊來做對應的處理</li>
</ul>
</li>
<li><code>Type</code>：
<ul>
<li>指令的類型</li>
<li>當指令 retire 時，不同類型的指令會有不同的處理
<ul>
<li>例如：
<ul>
<li>Store 指令要寫 D-Cache</li>
<li>分支指令要釋放 checkpoint 資源</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>一個 ROB 的範例 (不包含 source registers 的部份)，假設採用<a href="../superscalar-overview-ch7/#723---%E4%BD%BF%E7%94%A8%E7%B5%B1%E4%B8%80%E7%9A%84-prf-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用統一的 PRF 來實做暫存器重命名</a>：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%201.png"></p>
<ul>
<li><code>i1</code> (除法) 和 <code>i2</code> (依賴 <code>i1</code>) 的執行時間會很長，<code>i3</code> 和 <code>i4</code> 會提前被執行完成，但是 <code>i3</code> 和 <code>i4</code> 的結果在 <code>i1</code> 和 <code>i2</code> retire 前不能更新到 CPU 的 architecture state</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%202.png"></p>
<ul>
<li>(a)：指令 <code>i1</code> ~ <code>i3</code> 完成 register renaming，但指令都還沒完成，因此 <code>Complete</code> 都是 Not ready
<ul>
<li><code>i1</code> 和 <code>i3</code> 的 architecture destination registers 都是 <code>r1</code>，可以透過 register renaming 解決 WAW 相關性</li>
</ul>
</li>
<li>(b)：指令 <code>i4</code> 進到 ROB；指令 <code>i3</code> 和 <code>i4</code> 執行完成，<code>Complete</code> 更新為 Ready，但由於還不是最舊的指令，因此無法 retire</li>
<li>(c)：指令 <code>i1</code> 執行完畢，且由於其為最舊的指令，因此可以 retire，ROB entry 會被釋放 (ROB Head index++)，physical register：<code>p8</code> 同樣也會被釋放</li>
</ul>
</li>
</ul>
<h2 id="1022---端口需求">10.2.2 - 端口需求</h2>
<ul>
<li>
<p>對於 4-way superscalar CPU，ROB 中每個 cycle 至少可以 retire 4 條指令；需要對 ROB 中從 head pointer 開始連續 4 條指令的 <code>Complete</code> 訊號進行判斷，如果某個指令的 <code>Complete</code> 訊號為 <code>0</code>，那麼在它後面的所有指令都不用允許在這個 cycle 被 retired：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%203.png"></p>
<ul>
<li>對於 4-way superscalar CPU，除了 <code>en0</code> ~ <code>en3</code> (4 個 read ports) 外，其他的 pipeline stages 也會存取 ROB，因此需要更多的 read ports 和 write ports</li>
<li><a href="../superscalar-overview-ch7/#721---%E4%BD%BF%E7%94%A8-rob-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用 ROB 來實做暫存器重命名</a>對於 ROB 的 ports 需求是最多的：
<ul>
<li>在 register renaming stage，需要從 ROB 中讀取 4 條指令的 source registers，假設每條指令有 2 個 source registers，那麼就需要 4 * 2 = 8 個 read ports</li>
<li>在 dispatch stage，需要向 ROB 寫入 4 條指令 (需將 register renamed 後的指令寫入 ROB)，因此需要 4 個 write ports</li>
<li>在 write-back stage，需要向 ROB 寫入”至少” 4 條指令的計算結果 (issue width 是有可能 ≥ machine width 的)，因此需要至少 4 個 write ports</li>
<li>因此，對於 4-way superscalar CPU，ROB 至少需要 12 個 read ports 以及 8 個 write ports；然而這種 multi-port 的 FIFO 很難優化其硬體面積及 latency，這也是<a href="../superscalar-overview-ch7/#721---%E4%BD%BF%E7%94%A8-rob-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用 ROB 來實做暫存器重命名</a>所面臨的最大問題</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="103---管理處理器的狀態">10.3 - 管理處理器的狀態</h2>
<ul>
<li>Superscalar CPU 內部有兩個狀態：
<ul>
<li>Architecture state (指令集定義的狀態)</li>
<li>Speculative state</li>
</ul>
</li>
<li>一條指令只有在 retire 時才會更新處理器的 architecture state，在此之前，指令只能更新處理器的 speculative state</li>
<li>在 superscalar CPU 中，根據架構的不同，會用不同的方法來管理 architecture state，包含：
<ul>
<li>使用 ROB 管理 architecture state
<ul>
<li>Intel P6 架構、Intel Core 架構採用此方法</li>
</ul>
</li>
<li>使用 physical register 管理 architecture state
<ul>
<li>Intel Pentium 4、Alpha 21264、MIPS R10000 採用此方法</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="1031---使用-rob-管理指令集定義的狀態">10.3.1 - 使用 ROB 管理指令集定義的狀態</h2>
<ul>
<li>
<p>對於<a href="../superscalar-overview-ch7/#721---%E4%BD%BF%E7%94%A8-rob-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用 ROB 來實做暫存器重命名</a>來說，一條指令在 retire 之前，都會使用 ROB 來保存指令的結果；當指令 retire 時，就可以用這條指令的計算結果來更新 CPU 的 architecture state，此時會將這條指令的計算結果從 ROB 中搬到 architecture registers</p>
<ul>
<li>在這種架構中，architecture registers 是真實存在的；由於 architecture registers 儲存了所有 retired 指令所對應的 destination registers 的值，因此這種方法的 ARF 也被稱為 <code>RRF (Retire Register File)</code>：</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%204.png"></p>
</li>
<li>
<p>在 ROB 中所記錄的所有指令的計算結果都是 speculative state</p>
<ul>
<li>當一條指令退休時，會將這條指令的計算結果從 ROB 搬到 ARF/RRF 中，同時也會將這條指令所佔用的 ROB entry 給釋放掉</li>
</ul>
</li>
<li>
<p><a href="../superscalar-overview-ch7/#721---%E4%BD%BF%E7%94%A8-rob-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用 ROB 來實做暫存器重命名</a>通常都會搭配 <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture"><strong>data-capture</strong></a> 的 issue 方式 (參考：<a href="../superscalar-overview-ch7/#721---%E4%BD%BF%E7%94%A8-rob-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">link</a>)</p>
<ul>
<li>當指令在做 register renaming 時會讀 RAT
<ul>
<li>如果發現這個指令的 source registers 的值已經被先前的指令給計算出來了，那麼就可以直接從 ROB (如果計算結果的指令尚未 retire) 或是 architecture registers (如果計算結果的指令已經 retire) 讀取這個值，然後寫到 payload RAM 中</li>
<li>如果發現這個指令的 source registers 的值還沒被先前的指令給計算出來，就將 source registers 在 ROB 中的位址寫到 payload RAM 中，並等待 bypassing network 將計算結果 bypass 至 payload RAM，此時這個指令就可以被 wake up (已進入 issue stage) 並在被 select 電路選中後，從 payload RAM 獲得所需的計算結果</li>
</ul>
</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%205.png"></p>
<ul>
<li>指令 A 會將其計算結果在 write-back stage 寫進 ROB，並透過 bypassing network 將計算結果寫進 paylad RAM；此時也會 wake up 正在 Issue Queue 中等待的指令 B</li>
<li>指令 B 在做 register renaming 時發現 source registers 的值還沒被指令 A 給計算出來，因此只能先將 source registers 在 ROB 中的位址寫到 payload RAM 中；等到指令 A 將其結果計算出來並 wake up 指令 B，指令 B 在被 select 電路選中後就可以讀取 payload RAM 獲得其所需的 source registers 的值並進入 execute stage</li>
<li>指令 C 和指令 D 在做 register renaming 時發現 source registers 的值已經被指令 A 給計算出來了，由於指令 A 還沒有 retire，因此其計算結果仍儲存在 ROB 中，因此需要讀取 ROB 獲得其所需的 source registers 的值，並寫到 payload RAM 中</li>
<li>指令 E 在做 register renaming 時發現 source registers 的值已經被指令 A 給計算出來了，由於指令 A 已經 retire 了，因此可以直接讀取 ARF 來獲得其所需的 source registers 的值，並寫到 payload RAM 中</li>
<li><strong>由於都是從 payload RAM 獲得 source registers 的值，因此不必操心其值是存在 ROB 或 ARF 中</strong></li>
</ul>
</li>
<li>
<p>如果<a href="../superscalar-overview-ch7/#721---%E4%BD%BF%E7%94%A8-rob-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用 ROB 來實做暫存器重命名</a>但搭配 <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture"><strong>non-data-capture</strong></a> 的 issue 方式：</p>
<ul>
<li>
<p>Non-data-capture issue 方式在指令被 select 電路選中後，需要直接從需要的位置讀取 source registers 的值，這個位置資訊是在 register renaming 時透過讀取 RAT 時一起被寫入 Issue Queue 中的，但當指令被 wake up 時，有可能 source registers 的值還存在 ROB 中，也有可能已經被搬進 ARF 了，這個位置變更的資訊，需要一併通知 Issue Queue 中的指令，因此 Issue Queue 需要增加額外的 write ports 以及 bypassing network，增加了設計的複雜度</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%206.png"></p>
<ul>
<li>指令 A 會將其計算結果在 write-back stage 寫進 ROB</li>
<li>指令 B 和指令 C 都可以從 ROB 中讀取指令 A 的計算結果</li>
<li>由於指令 A retire 時會將計算結果從 ROB 搬至 ARF，因此指令 D 得從 ARF 讀取指令 A 的計算結果
<ul>
<li>在這中間需要有額外的 write ports 以及 bypassing network 通知在 Issue Queue 中的指令 D，指令 A 計算結果存放的位置被更新了</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="1032---使用物理暫存器管理指令集定義的狀態">10.3.2 - 使用物理暫存器管理指令集定義的狀態</h2>
<ul>
<li>
<p><a href="../superscalar-overview-ch7/#723---%E4%BD%BF%E7%94%A8%E7%B5%B1%E4%B8%80%E7%9A%84-prf-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用統一的 PRF 來實做暫存器重命名</a>在一條指令做 register renaming 時，architecture register 的計算結果會一直被存在 PRF 中</p>
</li>
<li>
<p><a href="../superscalar-overview-ch7/#721---%E4%BD%BF%E7%94%A8-rob-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用 ROB 來實做暫存器重命名</a>：</p>
<ul>
<li>
<p>優點：</p>
<ul>
<li>Register renaming 流程簡單，在指令寫入 ROB 時，將 architecture register 和 physical register 的映射關係一併記錄到 ROB 即可，不須增加複雜的硬體控制邏輯電路</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>
<p>暫存器的值需要被搬移 (ROB → ARF)，功耗較大</p>
</li>
<li>
<p>由於暫存器的值有可能存在 ROB 或是 ARF，當指令 retire 時，需要額外的電路同步所有有使用到該暫存器的指令來告知該暫存器已經從 ROB 搬移到 ARF，功耗較大</p>
</li>
<li>
<p>很多指令並不會更新 destination register，因此也就不用對 destination register 做 renaming，但每條指令仍然會佔用 ROB entry 中 destination register 的 physical register renaming 的空間，無法省略，浪費硬體空間</p>
</li>
<li>
<p>對於一條指令而言，它既可以從 ROB 中讀取 operands，也可以從 ARF 中讀取 operands，所以 ROB 和 ARF 最壞的情況就是一個 cycle 內，所有的指令都需要同時讀取 ROB 或 ARF，會增加 ROB 和 ARF 的 read ports 所需的數量</p>
<ul>
<li>例如：4-way issue CPU，指令最多需要 2 個 source registers，那麼 ROB 和 ARF 都需要準備 2 x 4 = 8 個 read ports，對硬體面積和延遲造成負面的影響</li>
<li>如果 CPU 有支援 multiple destination registers 的指令，那麼同樣也會對 ROB 和 ARF 的 write ports 數量造成影響</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p><a href="../superscalar-overview-ch7/#723---%E4%BD%BF%E7%94%A8%E7%B5%B1%E4%B8%80%E7%9A%84-prf-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用統一的 PRF 來實做暫存器重命名</a>：</p>
<ul>
<li>
<p>優點：</p>
<ul>
<li>暫存器的值只需寫入一次，不需要再被搬移，功耗較小</li>
<li>暫存器的值只會存在一個地方，不需要</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>需要使用一個 free list 以及兩個 RAT，因此需要使用複雜的硬體控制邏輯電路</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="104---特殊情況的處理">10.4 - 特殊情況的處理</h1>
<ul>
<li>由於 out-of-order CPU 採用了很多種的預測方式來執行指令，因此並不是 pipeline 中所有的指令都可以 retire
<ul>
<li>分支指令和發生了異常的指令，都需要將其後面的指令從 pipeline 中給 flush 掉，並恢復處理器的狀態，然後從指定的位址開始重新 fetch 指令來執行</li>
<li>此外，在 commit stage 還需要對 store 指令做特別的處理
<ul>
<li>Store 指令只有在指令 retire 時才可以更新 D-Cache 的內容，如果在這期間發生了 D-Cache miss，store 指令會 block 所有其後面的指令 retire (retire 是 in-order 的)，因此需要對 store 指令做特別的處理</li>
</ul>
</li>
</ul>
</li>
<li>在 commit stage 還會對其他的指令有一些特殊的限制，以減少其對處理器中其他的元件造成影響</li>
</ul>
<h2 id="1041---分支預測失敗的處理">10.4.1 - 分支預測失敗的處理</h2>
<ul>
<li>
<p>當發生 branch mis-prediction 時，除了需要從 pipeline 中 flush 掉分支指令後面的指令，還需要恢復這些指令對處理器的修改，包含：</p>
<ul>
<li>RAT</li>
<li>ARF</li>
<li>PRF</li>
<li>PC</li>
<li>… etc</li>
</ul>
</li>
<li>
<p>當發生 branch mis-prediction 時，處理器的狀態恢復可以以 <strong>register renaming</strong> 為分界，分為兩個獨立的任務：</p>
<ul>
<li><strong>Front-end recovery</strong>
<ul>
<li>較簡單</li>
<li>需將 register renaming stage 前的指令給全部 flush 掉</li>
<li>需將 GHR、BHR 等 branch predictor 的 history tables 給恢復</li>
<li>使用正確的位址重新 fetch 指令</li>
</ul>
</li>
<li><strong>Back-end recovery</strong>
<ul>
<li>需將處理器所有的內部元件給恢復，包含：
<ul>
<li>Issue Queue</li>
<li>Store buffer</li>
<li>RAT
<ul>
<li>將錯誤指令對 RAT 的修改給恢復</li>
</ul>
</li>
<li>PRF
<ul>
<li>將錯誤指令佔用的 physical registers 給釋放</li>
</ul>
</li>
<li>ROB
<ul>
<li>將錯誤指令佔用的 ROB entries 給釋放</li>
</ul>
</li>
<li>… etc</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%207.png"></p>
<ul>
<li>Front-end recovery 和 back-end recovery 可以同步進行</li>
<li>Front-end recovery 通常可以很快完成，處理器此時就可以從正確的位址開始 fetch 指令
<ul>
<li>這些新 fetch 的指令可以一直執行到 register renaming stage
<ul>
<li>如果 back-end recovery 在這些指令準備離開 register renaming stage 前已經完成，此時就不需要 stall pipeline
<ul>
<li>i.e. <code>Time (backe-end recovery) &lt; Time (front-end recovery) + Time (fetch → renaming)</code></li>
</ul>
</li>
<li>反之，pipeline 就必須 stall，直到 back-end recovery 完成，這些新 fetch 的指令才可以離開 register renaming stage</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>當發生 branch mis-prediction 時，大部分的恢復任務都是對 register renaming 相關的元件做恢復，因為大部分在 mis-prediction 路徑上的指令，都會經過這個 stage 並修改了 RAT 及相關的元件</p>
</li>
<li>
<p>除此之外，pipeline 中其他的元件也必須被恢復，包含 Issue Queue、ROB 和 store buffer 等，不過這些元件的恢復相對比較容易</p>
</li>
<li>
<p>Register renaming 的實做方式直接決定了要如何恢復 register rename 元件的狀態：</p>
<ul>
<li>
<p><a href="../superscalar-overview-ch7/#721---%E4%BD%BF%E7%94%A8-rob-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用 ROB 來實做暫存器重命名</a></p>
<ul>
<li>
<p>每當一條指令 retire 時，其計算結果會從 ROB 移至 ARF，因此一個 register 在其生命週期內，有兩個位置可以存放它的值 (ROB 和 ARF)</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%208.png"></p>
<ul>
<li>
<p>RAT 中記錄了 registers 的映射關係 (存在 ROB 或 ARF 中)</p>
</li>
<li>
<p>一條 retired 的指令將 destination register 的值從 ROB 搬到 ARF 後，不一定代表後續的指令就一定需要從 ARF 讀取這個 destination register 的值：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-3">3</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">A</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">add</span>  <span style="color:#66d9ef">$r1</span>, <span style="color:#66d9ef">$r2</span>, <span style="color:#66d9ef">$r3</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">B</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">add</span>  <span style="color:#66d9ef">$r1</span>, <span style="color:#66d9ef">$r1</span>, <span style="color:#66d9ef">$r4</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">C</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">add</span>  <span style="color:#66d9ef">$r1</span>, <span style="color:#66d9ef">$r1</span>, <span style="color:#66d9ef">$r5</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>經過 register renamed 後，只有指令 C 的 destination register：<code>$r1</code> 的映射關係才會被寫進 RAT 中，因此即使指令 A 已經 retired 並將 destination register 的值從 ROB 搬到 ARF，後續的指令在使用 <code>$r1</code> 時，仍會使用指令 C 的結果</li>
<li>只有一條 retire 的指令發現自己的 destination register 是最新的映射狀態，才能將其 destination register 在 RAT 中的映射關係從 ROB 改成 ARF</li>
</ul>
</li>
<li>
<p>一條 retire 的指令要如何檢查自身的 destination register 是否是最新的映射關係呢?</p>
<ul>
<li>在這條指令要 retire 時，可以使用其 destination register 編號來讀取 RAT，讀出這個 architecture register 所對應的 ROB index，如果發現這個 ROB index 與現在要 retire 的指令在 ROB 中所佔據的位址是一樣的，就表示這條 retire 的指令就是最新的映射關係了
<ul>
<li>如果不一樣，就代表有其他的指令更新了 RAT 中該 register 的映射關係，因此這條要 retire 的指令就不是最新的映射關係了</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>當發生 branch mis-prediction 時，會先停止 fetch 新的指令，而在發生 mis-prediction 的分支指令前的指令，會被繼續執行 (i.e. pipeline 會被 drain out)；等到這條分支指令和其前面的指令都 retire 後，此時 ARF 的 registers 內容就都會是正確的了，剩餘在 pipeline 中的指令，都是在 mis-prediction 的錯誤路徑上，因此可以直接將 pipeline 給 flush，並將 RAT 中所有的映射關係都改映射到 ARF，這樣就完成了 RAT 的恢復，並可以開始從新的位址 fetch 指令</p>
</li>
<li>
<p>優點：</p>
<ul>
<li>Register renaming 容易實現</li>
<li>當發生 branch mis-prediction 時，處理器的狀態恢復也相對容易</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>如果分支指令之前有 load/store 指令發生 D-Cache miss，則這條分支指令需要等一段時間才能 retire，mis-prediction penalty 會增加</li>
</ul>
</li>
</ul>
</li>
<li>
<p><a href="../superscalar-overview-ch7/#722---%E6%93%B4%E5%85%85-arf-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">擴充 ARF 來實做暫存器重命名</a></p>
<ul>
<li>與上述使用 ROB 來實做暫存器重命名雷同</li>
</ul>
</li>
<li>
<p><a href="../superscalar-overview-ch7/#723---%E4%BD%BF%E7%94%A8%E7%B5%B1%E4%B8%80%E7%9A%84-prf-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用統一的 PRF 來實做暫存器重命名</a></p>
<ul>
<li>使用統一的 PRF 來實做暫存器重命名使用了兩個 RAT：<strong>Speculative RAT</strong> 及 <strong>Architecture RAT</strong>
<ul>
<li>Architecture RAT 的內容永遠是正確的</li>
</ul>
</li>
<li>當發生 branch mis-prediction 時，會先停止 fetch 新的指令，而在發生 mis-prediction 的分支指令前的指令，會被繼續執行 (i.e. pipeline 會被 drain out)；等到這條分支指令和其前面的指令都 retire 後，剩餘在 pipeline 中的指令，都是在 mis-prediction 的錯誤路徑上，因此可以直接將 pipeline 給 flush，並將 architecture RAT 的內容全部複製到 speculative RAT，這樣就完成了 RAT 的恢復，並可以開始從新的位址 fetch 指令</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%209.png"></p>
<ul>
<li>缺點：
<ul>
<li>如果分支指令之前有 load/store 指令發生 D-Cache miss，則這條分支指令需要等一段時間才能 retire，mis-prediction penalty 會增加</li>
</ul>
</li>
</ul>
</li>
<li>
<p>上述兩種在指令 retire 時恢復 CPU 狀態的方法，也被稱為：<code>Recovery at Retire</code></p>
</li>
<li>
<p>使用 <a href="../superscalar-overview-ch4-part2/#44---%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E5%A4%B1%E6%95%97%E6%99%82%E7%9A%84%E6%81%A2%E5%BE%A9">checkpoint</a> 的方式，就可以在發生 branch mis-prediction 時 (e.g. execute stage)，馬上恢復 RAT 的狀態</p>
<ul>
<li>在每條分支指令改變處理器的狀態前，都將處理器的狀態 (e.g. RAT) 給存下來 (i.e. checkpoint)，當發生 branch mis-prediction 時，除了將 pipeline 中分支指令之後的指令給全部 flush 掉外，同時使用 checkpoint 來恢復處理器的狀態，然後就可以開始從新的位址 fetch 指令</li>
<li>優點：
<ul>
<li>處理器狀態恢復的速度比較快</li>
<li>對於<a href="../superscalar-overview-ch7/#732---%E5%9F%BA%E6%96%BC-cam-%E7%9A%84%E9%87%8D%E5%91%BD%E5%90%8D%E6%98%A0%E5%B0%84%E8%A1%A8">基於 CAM 的重命名映射表</a>，由於每個 checkpoint 只需保存 valid bits，因此可以支援很多個 checkpoints，在 pipeline 中也就可以同時存在多條分支指令</li>
</ul>
</li>
<li>缺點：
<ul>
<li>對於<a href="../superscalar-overview-ch7/#731---%E5%9F%BA%E6%96%BC-sram-%E7%9A%84%E9%87%8D%E5%91%BD%E5%90%8D%E6%98%A0%E5%B0%84%E8%A1%A8">基於 SRAM 的重命名映射表</a>，由於每個 checkpoint 都需要保存完整的 RAT，因此限制了 checkpoints 的數量，也就限制了 pipeline 中同時可以存在的分支指令的數量</li>
<li>對於<a href="../superscalar-overview-ch7/#732---%E5%9F%BA%E6%96%BC-cam-%E7%9A%84%E9%87%8D%E5%91%BD%E5%90%8D%E6%98%A0%E5%B0%84%E8%A1%A8">基於 CAM 的重命名映射表</a>，使用 CAM 的電路面積和延遲會很大
<ul>
<li>使用 CAM 實做的 RAT，RAT entries 的個數跟 physical registers 的個數成正本；當 physical registers 的數量增多時，RAT 的面積也會一併增大</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>除此之外，也可以對哪些分支指令需要做 checkpoint 進行預測，因為大部分的分支指令的預測準確度很高，因此實際上不需要使用那麼多的 checkpoints</p>
<ul>
<li>可以同樣透過 <code>2-bit saturating counter</code> 來實現，當一條分支指令的預測準確率很高時，其 <code>2-bit saturating counter</code> 會處於飽和的狀態，這樣就不用替這條分支指令分配 checkpoint 了</li>
<li>但當預測錯誤時：
<ul>
<li>同樣等待分支指令和其前面的指令都 retire，並 flush pipeline 後，用上述的方式恢復 RAT 並重新從新的位址 fetch 指令</li>
<li>也可以透過 ROB 來恢復 RAT：
<ul>
<li>ROB 中記錄著 RAT 被修改的歷史，每當一條指令被 register renamed 後，除了需要將 register 新的映射關係寫進 RAT 外，也需要將舊的映射關係寫進 ROB 中 (參考：<a href="#1021---%E4%B8%80%E8%88%AC%E6%9E%B6%E6%A7%8B">link</a>)</li>
<li>因此當一條分支指令沒有被分配 checkpoint 時，可以透過 ROB 來恢復 RAT</li>
</ul>
</li>
</ul>
</li>
<li>優點：
<ul>
<li>需要的 checkpoints 硬體比較少，因此硬體面積也比較小</li>
</ul>
</li>
<li>缺點：
<ul>
<li>在分支指令沒有被分配 checkpoint 並發生預測錯誤時，RAT 的恢復速度會比較慢</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="1042---異常的處理">10.4.2 - 異常的處理</h2>
<ul>
<li>
<p>處理器的 exceptions 必須依照程式的執行順序來處理，一個比較早觸發的 exception 不代表它就一定會比比較晚觸發的 exception 早處理</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%2010.png"></p>
<ul>
<li>依照程式的執行順序，Page Fault Exception (由第一條指令觸發) 需要比 Undefined Instruction Exception (由第二條指令觸發) 先被處理，縱使 Page Fault Exception 比 Undefined Instruction Exception 晚觸發</li>
</ul>
</li>
<li>
<p>一條指令的 exception 要被處理，必須先保證在它之前的指令的 exception 都已經被處理了，如果發現一條指令在準備要 retire 時發現其 ROB 中被標記有 pending 的 exception，那這條指令就不能 retire，需要先處理 exception</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%2011.png"></p>
</li>
<li>
<p>在 superscalar CPU 中，為了支持 precise exception，當發現要 retire 的指令有 pending 的 exception 時，在跳到 exception handler 前，必須先將 pipeline 中這條指令之後的所有指令都 flush 掉，並恢復 CPU 的狀態</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%2012.png"></p>
</li>
<li>
<p>Exception 的 CPU 狀態恢復與 <a href="#1041---%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E5%A4%B1%E6%95%97%E7%9A%84%E8%99%95%E7%90%86">Recovery at Retire</a> 相似，待 CPU 狀態恢復後，就可以跳到 exception handler 並 fetch 新的指令來執行</p>
</li>
<li>
<p>使用 <a href="#1041---%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E5%A4%B1%E6%95%97%E7%9A%84%E8%99%95%E7%90%86">Recovery at Retire</a> 來恢復 exception 的其中一個好處是，很多的 exceptions 其實是不用被處理的，比如在 mis-prediction 路徑上的指令所觸發的 exceptions，只在指令 retire 時才處理 exception 可以簡化設計和避免不必要的處理</p>
</li>
<li>
<p>除了 <a href="#1041---%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E5%A4%B1%E6%95%97%E7%9A%84%E8%99%95%E7%90%86">Recovery at Retire</a> 外，還可以在處理 exception 時，透過 ROB 中所記錄的舊的 physical registers (<code>Preg</code>) 的映射關係來恢復 RAT，也就是先前介紹的 <a href="../superscalar-overview-ch7/#752---%E4%BD%BF%E7%94%A8-walk">WALK</a> 方式</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%2013.png"></p>
</li>
<li>
<p><a href="../superscalar-overview-ch7/#723---%E4%BD%BF%E7%94%A8%E7%B5%B1%E4%B8%80%E7%9A%84-prf-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用統一的 PRF 來實做暫存器重命名</a>除了 RAT 外，還有 <strong>free list</strong> - 用來記錄 free 狀態的 physical registers；當 exception 發生時，除了 RAT 要恢復外，free list 也需要一併被恢復：</p>
<ul>
<li>Free list 使用 FIFO 來實做，每當有指令做 register renaming 時，就會從 free list 讀出 free 狀態的 physical registers，此時只需移動 free list 的 read pointer 即可，free list entry 的內容無須改變；當有一條指令 retire 時並將其所使用的 physical register 釋放時，只需將該 physical register 寫入 free list 中，並移動 write pointer 即可
<ul>
<li>因此要恢復 free list，只需每次在 ROB 讀取一條指令來恢復 RAT 時，同時也移動一次 read pointer，這樣當所有被 flushed 的指令對處理器的修改都恢復時，free list 也一併恢復完成了</li>
</ul>
</li>
</ul>
</li>
<li>
<p><a href="../superscalar-overview-ch7/#723---%E4%BD%BF%E7%94%A8%E7%B5%B1%E4%B8%80%E7%9A%84-prf-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用統一的 PRF 來實做暫存器重命名</a>的架構在處理 exception 時，採用 ROB 的方式來恢復狀態是比較合適的；相反地，採用 <a href="#1041---%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E5%A4%B1%E6%95%97%E7%9A%84%E8%99%95%E7%90%86">Recovery at Retire</a> 的方式雖然可以利用 architecture RAT 來恢復 speculatvie RAT，但是對 free list 的恢復就沒有那麼直接了，有可能需要透過 architecture RAT 中所記錄的 physical registers 映射關係來恢復 free list，增加處理 exception 的 latency，降低執行效率</p>
</li>
<li>
<p>結論：</p>
<ul>
<li>當處理 exception 時：
<ul>
<li><a href="../superscalar-overview-ch7/#723---%E4%BD%BF%E7%94%A8%E7%B5%B1%E4%B8%80%E7%9A%84-prf-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用統一的 PRF 來實做暫存器重命名</a>：比較適合使用 <a href="#1041---%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E5%A4%B1%E6%95%97%E7%9A%84%E8%99%95%E7%90%86">Recovery at Retire</a> 方式來恢復 CPU 狀態</li>
<li><a href="../superscalar-overview-ch7/#721---%E4%BD%BF%E7%94%A8-rob-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用 ROB 來實做暫存器重命名</a>：比較適合使用 <a href="../superscalar-overview-ch7/#752---%E4%BD%BF%E7%94%A8-walk">WALK</a> 方式 (i.e. 透過 ROB) 來恢復 CPU 狀態</li>
</ul>
</li>
</ul>
</li>
<li>
<p>相較於 branch mis-prediction，exception 的發生頻率是更低的，因此對 exception 的處理可以慢一點，透過 <a href="../superscalar-overview-ch7/#752---%E4%BD%BF%E7%94%A8-walk">WALK</a> 方式 (i.e. 透過 ROB) 來恢復 CPU 狀態也是非常常見的 (e.g. MIPS R10000)</p>
</li>
</ul>
<h2 id="1043---中斷的處理">10.4.3 - 中斷的處理</h2>
<ul>
<li>Exception 是同步的，interrupt 是非同步的，因此不能按照處理 exception 的方式來處理 interrupt</li>
<li>一般來說，有兩種方式處理 interrupt：
<ol>
<li>馬上處理：
<ul>
<li>當 interrupt 發生時，flush 掉 pipeline 中所有的指令，並將 pipeline 中最老的指令的 PC 值以及其他的 status registers (e.g. ARM CSPR、SPSR) 給保存下來，恢復 CPU 的狀態後，跳到對應的 interrupt handler</li>
<li>當從中斷返回時，使用當初所記下來的 PC 值重新 fetch 指令，也就是將先前被 flushed 掉的指令重新 fetch 進 pipeline</li>
<li>優點：
<ul>
<li>Interrupt response latency 最低，可以很快的響應中斷，如果對 interrupt 響應時間有嚴格要求的，可以採用此設計</li>
</ul>
</li>
<li>缺點：
<ul>
<li>原本執行到一半的指令因為 interrupt 的關係被 flushed 掉，相當於這些指令白作工，且這些指令在中斷返回後還是需要重新被執行，浪費了一些效率</li>
</ul>
</li>
</ul>
</li>
<li>延遲處理：
<ul>
<li>當 interrupt 發生時，停止 fetch 新的指令，但要等到所有在 pipeline 中的指令都 retire 後，才會處理 interrupt
<ul>
<li>由於 pipeline 中所有的指令都 retire 了，CPU 的狀態一定是正確的，因此不需要恢復 CPU 的狀態</li>
</ul>
</li>
<li>缺點：
<ul>
<li>如果這些等待 retire 的指令中間發生了 D-Cache miss，需要很長的時間才能解決，導致 interrupt response latency 變長</li>
<li>如果這些等待 retire 的指令中間發生了 branch mis-prediction，需要恢復 CPU 的狀態，也會消耗一定的時間，導致 interrupt response latency 變長</li>
<li>如果這些等待 retire 的指令中間發生了 exception，那麼應該先處理 interrupt 或是 exception?
<ul>
<li>一般來說都是先處理 interrupt
<ul>
<li>因為很多類型的 exception 處理都需要花費很長的時間，像是：D-Cache miss、TLB miss 或是 page fault… 等，會導致 interrupt response latency 變長</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>
</ul>
<h2 id="1044---store-指令的處理">10.4.4 - Store 指令的處理</h2>
<ul>
<li>Store 指令只有在 retire 時，才可以將資料寫到 D-Cache 中，在此之前，即使 store 指令已經計算完畢，也只會將結果先暫時存在 store buffer 中，直到 store 指令 retire 時，才會將 store buffer 中對應的內容寫到 D-Cache 中
<ul>
<li>在 store 指令被 dispatch 時，就會在 store buffer 中佔據一個 entry 了</li>
</ul>
</li>
<li>在使用 store buffer 後，所有的 load 指令除了讀取 D-Cache 外，也需要檢查 store buffer 中是否有存取位址相等且比這條 load 指令還老的 store 指令
<ul>
<li>如果有發現，那 load 指令的資料就是來自 store buffer 中對應的 store 指令</li>
</ul>
</li>
<li>Store 指令要成功的將資料寫入 D-Cache 後，才可以 retire 並離開 pipeline，但如果發生了 D-Cache miss，就需要等待很長的時間才可以 retire，然而，這會導致後續的指令即使已經執行完成，也無法 retire (retire 必須是 in-order 的，只要前面指令的 ROB entry 還尚未被釋放，這條指令就無法 retire)，造成處理器性能的降低
<ul>
<li>
<p>要解決這個問題，最簡單的就是在 store buffer 中的每個 entry 增加一個 state bit，用來標記一條 store 指令是否具備 retire 的條件：<code>un-complete</code>、<code>complete</code> 和 <code>retire</code></p>
<ul>
<li>當一條 store 指令被 dispatched 時，會在對應的 store buffer entry 中，標記這條 store 指令是 <code>un-complete</code> 的</li>
<li>當這條 store 指令的存取位址已經被計算出來並讀取到其所需的 store 的 register 資料，且尚未變成 pipeline 中最老的指令，就標記這條 store 指令是 <code>complete</code> 的</li>
<li>當這條 store 指令變成 pipeline 中最老的指令，就標記這條 store 指令是 <code>retire</code> 的；此時就可以將這條 store 指令所佔據的 ROB entry 給釋放，讓後面的指令繼續執行；Store buffer 的 entry 則要等到 store 指令資料真的寫進 D-Cache 中後，才會被釋放
<ul>
<li>要注意的是，此時 store buffer 標記為 retire 的 entry，也會變成 CPU architecture state 的一部分
<ul>
<li>也就是程式在讀取記憶體資料時，也要檢查 store buffer 中是否有相同位址且標記為 <code>retire</code> 的 store 指令的資料</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>由於一旦 store buffer 滿了以後，就無法再處理新的 store 指令；採用上述的方法，會降低 store buffer 可用的空間，不過由於這樣的設計實做相對簡單，因此算是可以接受的折衷方法</p>
</li>
<li>
<p>如果不想造成 store buffer 實際可用空間的降低，可以將 retire 的 store 指令存在另外一塊<code>write back buffer</code> 中，write back buffer 中 store 指令的值會再被寫入 D-Cache 中；Store 指令寫入 write back buffer 中後，store buffer 中對應的 entry 以及 ROB 中對應的 entry 就可以被釋放了</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%2014.png"></p>
<ul>
<li>要注意的是，此時 write back buffer，也會變成 CPU architecture state 的一部分，因此 load 指令會需要同時在 store buffer 和 write back buffer 中尋找是否有位址相同的 store 指令，因此會增加設計的複雜度</li>
<li>同樣的，一旦 write back buffer 中沒有空間了，就沒辦法再 retire store 指令了</li>
<li>Store 指令是 in-order 寫入 write back buffer 中的，因此在 store 指令寫入 write back buffer 時，還需要檢查是否存在相同位址的 store 指令，如果有的話，就必須將該 store 指令標記為 invalid，這樣 load 指令在讀取 write back buffer 時，才可以讀到最新的資料</li>
<li>P.S. 其實這就類似擴充了 store buffer 的容量，除了 write back buffer 中可能不需要儲存這麼多 store 指令相關的資訊，所需的硬體空間可能比較小外，不確定採用這樣方式具體的好處有多少?</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="1045---指令離開流水線的限制">10.4.5 - 指令離開流水線的限制</h2>
<ul>
<li>
<p>對於一個 4-way 的 superscalar CPU，在沒有發生 branch mis-prediciton、interrupt 以及 exception，且 ROB 中最老的四條指令都已經是可以 retire 的情況下，這四條指令理論上是可以在同一個 cycle 一起 retire 的；然而，這也代表：</p>
<ul>
<li>D-Cache 或 write back buffer 需要 4 個 write ports</li>
<li>假設這四條指令都是分支指令，那麼需要在 1 個 cycle 內，將這些分支指令的資訊寫回 branch predictor，也就需要 branch predictor 中的所有元件 (BTB、PHT… 等)，都需要 4 個 write ports；除此之外，每個 cycle 也需要能 release 4 個 checkpoints 的資源</li>
<li>如果有對 load 指令和 store 指令做 <a href="../superscalar-overview-ch9/#961---memory-disambiguation">load/store 相關性的預測</a>，假設這四條指令都是 load 指令，那麼就需要能在 1 個 cycle 內將 load 指令的資訊，寫回相關的預測器中，這些預測器也就需要 4 個 write ports</li>
</ul>
</li>
<li>
<p>然而，上述的情況其實發生的機率非常小，為了滿足這些情境而增加硬體設計的複雜度是非常划不來的，因此在 superscalar CPU 中，可以對上述的情境加以限制：</p>
<ul>
<li>例如：限制每個 cycle 最多只能 retire 一條分支指令，如果同時有超過一條的分支指令準備 retire，第二條以及其後的分支指令都只不允許在當個 cycle retire，這樣就不需要這麼多 write ports 了</li>
</ul>
</li>
<li>
<p>在 commit stage 需要對分支指令、store 指令和 load 指令的個數進行限制：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch10/image%2015.png"></p>
<ul>
<li>ROB 本質是個 FIFO，在 commit stage 會讀取 ROB 中最老的四條指令，根據它們的 complete 訊號來判斷哪些指令可以在這個 cycle retire，然後再透過 Branch、Store、Load 檢查電路產生 masks，將多餘的指令給 mask 掉，進而得到這個 cycle 有哪些指令可以退休了
<ul>
<li>mask：
<ul>
<li><code>1</code>：代表第一條 [branch|store|load] 指令，或是非 [branch|store|load] 指令，可以 retire</li>
<li><code>0</code>：代表第二條 (或第三條… etc) [branch|store|load] 指令，不可以 retire</li>
</ul>
</li>
<li>例如：
<ul>
<li><code>br_mask = 2’b1110</code></li>
<li><code>st_mask = 2’b1111</code></li>
<li><code>ld_mask = 2’b1111</code></li>
<li>代表這個 cycle 中，這四條指令不存在多餘一條的 store 和 load 指令，但存在多餘一條的分支指令
<ul>
<li>第四條指令就是多餘的分支指令</li>
</ul>
</li>
<li><code>(br_mask &amp; st_mask &amp; ld_mask) = 2’b1110</code> =&gt; 代表這個 cycle 可以 retire 前三條指令
<ul>
<li>實際上指令能不能 retire，還需考慮其他的條件，像是是否有發生 branch mis-prediction，或是是否有指令發生 exception… etc</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>每個 cycle 只能處理一個 exception，同樣可以用上述的方式來找到第一個被標記有 pending exception 的指令，並 mask 在其之後的指令</p>
</li>
<li>
<p>指令在 commit stage retire 後，需要根據最終 retire 指令的個數，更新 ROB 的 read pointer</p>
<ul>
<li>i.e. 釋放 ROB entries</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>&lt;超標量處理器概覽&gt; 第 9 章 - 執行</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch9/</link>
      <pubDate>Mon, 28 Apr 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch9/</guid>
      <description>&lt;h1 id=&#34;91---概述&#34;&gt;9.1 - 概述&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;FU 的個數決定了每個 cycle 最大可以同時執行的指令個數，也就是 issue width&lt;/li&gt;
&lt;li&gt;Execute stage 另一個重要的部份就是 bypassing network，其負責將 FU 的運算結果送到其他需要它的地方，像是 physical register file (PRF)、其他 FU 的輸入端、store buffer 等
&lt;ul&gt;
&lt;li&gt;隨著每個 cycle 可以同時執行的指令個數的增多，bypassing network 變得越來越複雜，已經是處理器中速度提昇的一個關鍵部份&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在不使用 bypassing network 的情況下，指令的 operands 值可以來自於 PRF (for &lt;a href=&#34;../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture&#34;&gt;&lt;em&gt;non-data-capture&lt;/em&gt;&lt;/a&gt;)，或是 payload RAM (for &lt;a href=&#34;../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture&#34;&gt;&lt;em&gt;data-capture&lt;/em&gt;&lt;/a&gt;)，每個 FU 都是透過 select 電路將 PRF 或是 payload RAM 串連起來的
&lt;ul&gt;
&lt;li&gt;每個 select 電路和 PRF (或是 payload RAM) 的 read ports 是一一對應的，每個 FU 和 PRF (或是 payload RAM) 的 read ports 也是一一對應的&lt;/li&gt;
&lt;li&gt;因此，PRF 總共需要的 read ports 個數與 issue width 是直接相關的；如果對處理器追求更大的 issue width，就代表著 PRF 需要更多的 read ports，反而會限制了處理器的執行速度，現代處理器多會採用 cluster 架構來解決這個矛盾
&lt;ul&gt;
&lt;li&gt;Payload RAM 由於需要與 Issue Queue 綁定在一起，使用 &lt;a href=&#34;../superscalar-overview-ch8-part1/#811----%E9%9B%86%E4%B8%AD%E5%BC%8F-centralized-vs-%E5%88%86%E4%BD%88%E5%BC%8F-distributed&#34;&gt;distributed Issue Queue&lt;/a&gt; 可以減少 payload RAM read ports 的需求&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;92---fu-的類型&#34;&gt;9.2 - FU 的類型&lt;/h1&gt;
&lt;h2 id=&#34;921---alu&#34;&gt;9.2.1 - ALU&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ALU (Arithmetic and Logic Unit) 通常負責：整數加減法、shift、簡單的乘除法、資料搬移 (e.g. mov、byte-swap 指令)、分支指令目標位址的計算、記憶體存取位址的計算&lt;/li&gt;
&lt;li&gt;很多處理器在 ALU 中也實現了比較簡單的乘法功能，用來支持乘法指令
&lt;ul&gt;
&lt;li&gt;不過為了追求比較高的處理效能，高性能處理器通常選擇將乘法器單獨使用一個 FU 來實現，並在這個 FU 中實現乘累加的功能&lt;/li&gt;
&lt;li&gt;有些處理器基於功耗和成本的考量，會將整數類型的乘法交由浮點數運算的 FU 來運算
&lt;ul&gt;
&lt;li&gt;整數轉成浮點數 → 浮點數乘法 → 浮點數轉為整數&lt;/li&gt;
&lt;li&gt;指令的 latency 會變長，但較省面積和功耗，Intel Atom CPU 就採用了此設計&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;922---agu&#34;&gt;9.2.2 - AGU&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;AGU (Address Generate Unit) 用來計算記憶體存取位址，如 load/store 指令
&lt;ul&gt;
&lt;li&gt;在一般 pipeline 處理器中，記憶體的存取位址都是交由 ALU 來計算，但是在 superscalar CPU 中，由於需要同時執行多條的指令，記憶體存取指令的執行效率直接影響了處理器的性能，所以通常會單獨使用一個 FU 來計算其位址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;923---bru&#34;&gt;9.2.3 - BRU&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;BRU (Branch Unit) 負責處理 control flow 類型的指令，如 branch、jump、call 和 return 類型的指令，BRU 負責將這些指令所攜帶的目標位址計算出來，並根據一定的條件決定是否使用這些位址；同時，在這個 FU 中還會對分支預測與否正確進行檢查，一旦發現 mis-prediction，就需要啟動相對硬的恢復機制&lt;/li&gt;
&lt;li&gt;ARM 和 PowerPC 等處理器在指令中的編碼加上了 condition code，根據 condition code 來決定指令是否執行
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;優點：&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="91---概述">9.1 - 概述</h1>
<ul>
<li>FU 的個數決定了每個 cycle 最大可以同時執行的指令個數，也就是 issue width</li>
<li>Execute stage 另一個重要的部份就是 bypassing network，其負責將 FU 的運算結果送到其他需要它的地方，像是 physical register file (PRF)、其他 FU 的輸入端、store buffer 等
<ul>
<li>隨著每個 cycle 可以同時執行的指令個數的增多，bypassing network 變得越來越複雜，已經是處理器中速度提昇的一個關鍵部份</li>
</ul>
</li>
<li>在不使用 bypassing network 的情況下，指令的 operands 值可以來自於 PRF (for <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture"><em>non-data-capture</em></a>)，或是 payload RAM (for <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture"><em>data-capture</em></a>)，每個 FU 都是透過 select 電路將 PRF 或是 payload RAM 串連起來的
<ul>
<li>每個 select 電路和 PRF (或是 payload RAM) 的 read ports 是一一對應的，每個 FU 和 PRF (或是 payload RAM) 的 read ports 也是一一對應的</li>
<li>因此，PRF 總共需要的 read ports 個數與 issue width 是直接相關的；如果對處理器追求更大的 issue width，就代表著 PRF 需要更多的 read ports，反而會限制了處理器的執行速度，現代處理器多會採用 cluster 架構來解決這個矛盾
<ul>
<li>Payload RAM 由於需要與 Issue Queue 綁定在一起，使用 <a href="../superscalar-overview-ch8-part1/#811----%E9%9B%86%E4%B8%AD%E5%BC%8F-centralized-vs-%E5%88%86%E4%BD%88%E5%BC%8F-distributed">distributed Issue Queue</a> 可以減少 payload RAM read ports 的需求</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="92---fu-的類型">9.2 - FU 的類型</h1>
<h2 id="921---alu">9.2.1 - ALU</h2>
<ul>
<li>ALU (Arithmetic and Logic Unit) 通常負責：整數加減法、shift、簡單的乘除法、資料搬移 (e.g. mov、byte-swap 指令)、分支指令目標位址的計算、記憶體存取位址的計算</li>
<li>很多處理器在 ALU 中也實現了比較簡單的乘法功能，用來支持乘法指令
<ul>
<li>不過為了追求比較高的處理效能，高性能處理器通常選擇將乘法器單獨使用一個 FU 來實現，並在這個 FU 中實現乘累加的功能</li>
<li>有些處理器基於功耗和成本的考量，會將整數類型的乘法交由浮點數運算的 FU 來運算
<ul>
<li>整數轉成浮點數 → 浮點數乘法 → 浮點數轉為整數</li>
<li>指令的 latency 會變長，但較省面積和功耗，Intel Atom CPU 就採用了此設計</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="922---agu">9.2.2 - AGU</h2>
<ul>
<li>AGU (Address Generate Unit) 用來計算記憶體存取位址，如 load/store 指令
<ul>
<li>在一般 pipeline 處理器中，記憶體的存取位址都是交由 ALU 來計算，但是在 superscalar CPU 中，由於需要同時執行多條的指令，記憶體存取指令的執行效率直接影響了處理器的性能，所以通常會單獨使用一個 FU 來計算其位址</li>
</ul>
</li>
</ul>
<h2 id="923---bru">9.2.3 - BRU</h2>
<ul>
<li>BRU (Branch Unit) 負責處理 control flow 類型的指令，如 branch、jump、call 和 return 類型的指令，BRU 負責將這些指令所攜帶的目標位址計算出來，並根據一定的條件決定是否使用這些位址；同時，在這個 FU 中還會對分支預測與否正確進行檢查，一旦發現 mis-prediction，就需要啟動相對硬的恢復機制</li>
<li>ARM 和 PowerPC 等處理器在指令中的編碼加上了 condition code，根據 condition code 來決定指令是否執行
<ul>
<li>
<p>優點：</p>
<ul>
<li>不須使用分支指令，因此可以降低分支指令使用的頻率，也就降低了分支預測錯誤的風險，可以獲得更好的執行性能</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>Condition code 佔據了指令 encoding 的一部分，例如 ARM 使用了 4 bits 的 condition code (<code>NCZV</code>)，扣除掉 opcode、immediate… etc，最後只剩 4 bits 可以用來 encode registers，因此 ARM 指令集支援的 registers 個數只有 16 個 (<code>$r0</code> ~ <code>$r15</code>)</li>
<li>使用更多的 registers 可以降低指令存取記憶體的頻率，增加處理器的執行效率</li>
<li>當 pipeline 中存在很多條件不符合，因此不會被執行的條件指令，就會造成 pipeline 中存在著大量無效的指令，反而降低了處理器的執行效率</li>
</ul>
</li>
<li>
<p>此外，條件執行指令也會對 register renaming 帶來額外的麻煩：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image.png"></p>
<ul>
<li>
<p>如果 <code>ADDEQ</code> 指令會被執行，但 <code>SUBNE</code> 指令不會被執行，那麼 <code>ADD</code> 指令應該使用 <code>P6</code> 而非 <code>P7</code></p>
</li>
<li>
<p>最簡單的方法就是 stall pipeline：一旦在 register renaming 的時候碰到條件執行指令，就 stall pipeline，直到這條指令的執行條件被計算出來，就可以決定後續的 register rename 的結果</p>
<ul>
<li>缺點：效能非常差</li>
</ul>
</li>
<li>
<p>另外一個方法就是對條件執行指令也進行預測：可以先假設所有條件執行指令都會被執行，等到後續指令的執行條件被計算出來，並發現預測錯誤時，再恢復執行前的狀態：</p>
<ul>
<li>將在這條條件執行指令之後的指令從 pipeline 中 flush</li>
<li>恢復這些指令對 RAT 的修改</li>
<li>重新將這些指令 fetch 進 pipeline，重新做 register renaming 時就會使用正確的結果</li>
<li>缺點：
<ul>
<li>條件指令相較於分支指令，比較沒有固定的 pattern (i.e. 執行 or 不執行)，因此 mis-prediction 的機率會高得多，效率不高</li>
</ul>
</li>
</ul>
</li>
<li>
<p>(Intel x86) 硬體插入額外的 <code>select-uOP</code> 指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%201.png"></p>
<ul>
<li><code>select-uOP</code> 指令可以針對兩個條件執行指令的結果做選擇，所以需要兩個條件執行指令才可以使用</li>
<li>需要編譯器的搭配，手寫 assembly 的時候也需要特別的處理硬體才會插入 <code>select-uOP</code> 指令</li>
<li>例如：
<ul>
<li><code>ADDEQ</code> 指令會執行：<code>P8</code> = <code>P6</code></li>
<li><code>SUBNE</code> 指令會執行：<code>P8</code> = <code>P7</code></li>
<li>P.S. <code>ADDEQ</code> 指令和 <code>SUBNE</code> 指令都不會被執行的時候???</li>
</ul>
</li>
</ul>
</li>
<li>
<p>硬體在每個條件指令後面都插入一條 <code>select-uOP</code> 指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%202.png"></p>
<ul>
<li>不用侷限於一定要兩個條件指令才能使用</li>
<li>缺點：
<ul>
<li>指令變多，執行效能會降低</li>
</ul>
</li>
<li>也可以先每兩條條件指令插入一條 <code>select-uOP</code> 指令，直到最後只剩一條條件執行指令時，再額外插入一條 <code>select-uOP</code> 指令</li>
</ul>
</li>
</ul>
</li>
<li>
<p>ARM 以 <strong>s</strong> 結尾的指令，在執行完畢後，也會修改 CPSR，CPSR 就相當於這條指令的另一個 destination register；所有在這條指令之後的條件執行指令，都會將 CPSR 視為其中一個的 source register (i.e. 相當於使用該指令的 destination register)</p>
<ul>
<li>因此，CPSR 也可以當作一個普通的 register，也可以對其做 register renaming</li>
<li>不過由於 CPSR 的寬度小於一般的暫存器，因此一般會為 CPSR 使用一個獨立的 PRF</li>
<li>由此可見，使用 CPSR 增加了 register renaming 的複雜度，功耗也會有所增加，各有利弊</li>
</ul>
</li>
</ul>
</li>
<li>對於實現分支預測的處理器來說，BRU 還必須負責檢查分支預測是否正確；如果發現 mis-prediction，就必須依照先前介紹分支預測失敗狀態恢復的方法 (參考：<a href="../superscalar-overview-ch4-part2/#44---%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E5%A4%B1%E6%95%97%E6%99%82%E7%9A%84%E6%81%A2%E5%BE%A9">4.4 - 分支預測失敗時的恢復</a>)，恢復 pipeline 的狀態</li>
</ul>
<h2 id="924---其他-fu">9.2.4 - 其他 FU</h2>
<ul>
<li>處理器中還包含其他很多類型的 FU：
<ul>
<li>浮點數運算 FU</li>
<li>SIMD 類型指令的 FU</li>
</ul>
</li>
</ul>
<h1 id="93---旁路網路">9.3 - 旁路網路</h1>
<ul>
<li>
<p>一條指令可以在 execute stage 才需要 operands，且在 execute stage 末端就可以得到它的結果，因此只需在 FU 的輸入端和 FU 的輸出端之間搭建一個通路，就可以提前將 FU 的計算結果送到所有 FU 的輸入端</p>
<ul>
<li>處理器內部也有其他的元件需要 FU 的計算結果：PRF、payload RAM 等，因此也需要將 FU 的計算結果送到這些地方</li>
<li>這個通路就稱為：<code>Bypassing network</code></li>
</ul>
</li>
<li>
<p>在處理器中，為了能夠 back-to-back 的執行指令，bypassing network 都是必須的：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%203.png"></p>
<ul>
<li>指令 A 的計算結果，需要提前在 EX stage 末端，bypass 計算結果給指令 B</li>
</ul>
</li>
<li>
<p>在 superscalar CPU 中，指令在被 select 電路選中的那個 stage，wake up pipeline 中相關的指令，仍舊可以 back-to-back 的執行指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%204.png"></p>
<ul>
<li>指令 A 在 Select stage wake up 指令 B，並提前在 Execute stage 末端，bypass 計算結果給指令 B</li>
</ul>
</li>
<li>
<p>一個更真實的 superscalar CPU，有可能會切分更細的 pipeline stages：</p>
<ul>
<li>Source operands 從 PRF 讀出來，需要經過一段很長的線路，才能抵達 FU 的輸入端，而且 FU 的輸入端還有大量的 multiplexer，用來選擇從不同的 bypassing network 或 PRF 的輸出中選擇合適的 operands
<ul>
<li>為了降低對 CPU cycle time 的影響，source operands 從 PRF 讀出來後，還需要經過一個 <code>Source Drive</code> stage (≥ 1 個 cycle)，才能抵達 FU 的輸入端</li>
</ul>
</li>
<li>FU 計算完後，也需要經過複雜的 bypassing network，才能抵達 FU 或是 PRF 的輸入端
<ul>
<li>為了降低對 CPU cycle time 的影響，FU 的結果被計算出來後，還需要經過一個 <code>Result Drive</code> stage (≥ 1 個 cycle)，才能 FU 或 PRF 的輸入端</li>
</ul>
</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%205.png"></p>
</li>
</ul>
<h2 id="931---簡單設計的旁路網路">9.3.1 - 簡單設計的旁路網路</h2>
<ul>
<li>
<p>當 FU 的個數比較少，對 CPU 頻率的要求不高時，bypassing network 可以採用比較簡單的設計</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%206.png"></p>
<ul>
<li>FU 的 operands 直接來自於 PRF</li>
<li>FU 的計算結果也直接寫入 PRF</li>
<li>一個 FU 想使用另一個 FU 的計算結果只能直接從 PRF 讀取</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%207.png"></p>
<ul>
<li>FU 的 operands 可以來自於 PRF、自身 FU 的結果、其他 FU 的結果
<ul>
<li>每個 FU 都需要一個 3-to-1 的 multiplexer 來選擇 operands 來源</li>
</ul>
</li>
<li>FU 的計算結果會透過 bypassing network，寫入 PRF 或是 FU 的輸入端</li>
</ul>
</li>
<li>
<p>很多 FU 都有多個功能，也就是有多個計算單元；然而，由於 FU 所支援的指令的 latency 不一定都相同，如加減法指令只需 1 個 cycle 即可完成，乘法指令需要 32 個 cycles 才能完成，此時如果使用採用正常的執行，就可能出現一個 FU 中兩個以上計算單元的結果在同一個 cycle 被計算出，並搶用 FU 的 bypassing network，造成衝突</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%208.png"></p>
<ul>
<li>最簡單的解法可以採用類似 <a href="../superscalar-overview-ch8-part2/#853---%E6%8E%A8%E6%B8%AC%E5%96%9A%E9%86%92">8.5.3 - 推測喚醒</a>的方法，先假設不會發生衝突，等到一條指令到達 FU 中被執行之前，檢查目前 FU 是否可以被使用，例如：
<ul>
<li>
<p>一個 FU 在上個 cycle 執行了 latency = 3 的指令，那麼這個 cycle 就沒辦法執行 latency = 2 的指令了</p>
</li>
<li>
<p>如果這個 cycle 不幸到達 FU 的指令的 latency = 2，那就必須將這條指令重新放回 Issue Queue 中參與仲裁</p>
</li>
<li>
<p>缺點：</p>
<ul>
<li>
<p>浪費了原本可以執行其他指令的機會，如果可以事先得知無法執行 latency = 2 的指令，那 select 電路就可以選擇其他 latency ≠ 2 的指令來執行</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%209.png"></p>
</li>
</ul>
</li>
<li>
<p>因此，select 電路在進行仲裁時，應該要將某些 latency 的指令給排除，那就可以在不降低性能的情況下解決 bypassing network 衝突的問題</p>
</li>
<li>
<p>例如：</p>
<ul>
<li>假設一個 FU 可以執行三種類型的指令，也就是該 FU 有三種計算單元，其對應的 latency 分別為：1 個 cycle、2 個 cycles、3 個 cycles，那麼：
<ul>
<li>當這個 cycle 執行 latency = 3 的指令時：
<ul>
<li>下個 cycle 不允許執行 latency = 2 的指令</li>
<li>下下個 cycle 不允許執行 latency = 1 的指令</li>
</ul>
</li>
<li>當這個 cycle 執行 latency = 2 的指令時：
<ul>
<li>下個 cycle 不允許執行 latency = 1 的指令</li>
</ul>
</li>
<li>當這個 cycle 執行 latency = 1 的指令時，沒有限制</li>
<li>對於 latency = 3 的指令，沒有限制 (假設 FU 是 pipeline 的)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Select 電路在仲裁時，需要考慮指令所對應的 FU 是否可以被使用，不會產生 bypassing network 衝突</p>
</li>
<li>
<p>從簡化設計的考量，最好使一個 FU 所執行所有類型的指令的 latency 都是相同的，就不用引入上述的判斷電路</p>
</li>
</ul>
<h2 id="932---複雜設計的旁路網路">9.3.2 - 複雜設計的旁路網路</h2>
<ul>
<li>
<p>真實的 superscalar CPU 中，execute stage 有可能會切分更細的 pipeline stages：<code>Source Drive</code> stage 以及 <code>Result Drive</code> stage，這也導致了 bypassing network 需要做對應的變化：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2010.png"></p>
<ul>
<li>
<p>指令 B 只能在 Execute stage 從指令 A 的 Result drive stage 獲得 operands</p>
</li>
<li>
<p>指令 C 可以在 Source drive stage 從指令 A 的 Result drive stage 獲得 operands</p>
</li>
<li>
<p>指令 C 也可以在 Execute stage 從指令 A 的 Write back stage 獲得 operands</p>
</li>
<li>
<p>指令 D 可以在 Source drive stage 從指令 A 的 Write back stage 獲得 operands</p>
</li>
<li>
<p>指令 E 在 RF read stage 讀去 PRF 時，就可以得到指令 A 寫入 PRF 的結果了，因此不需要透過 bypassing network</p>
<ul>
<li>假設 PRF write 可以在前半個 cycle 完成，PRF read 可以在後半個 cycle 完成</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在複雜的 pipeline 中加入 bypassing network，會使設計的複雜度變大，對 CPU cycle time 造成一定的影響</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2011.png"></p>
<ul>
<li>相較於先前簡單的設計，只增加了 Source Drive 和 Result Drive stages，不使用 bypassing network 並不會增加設計的複雜度</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2012.png"></p>
<ul>
<li>相較於先前簡單的設計，增加了 Source Drive 和 Result Drive stages 會導致在使用 bypassing network 時，設計的複雜度大幅度的增加</li>
</ul>
</li>
<li>
<p>每新增一個新的 pipeline stage，bypassing network 的複雜度會大幅地增加，因此無法無上限的新增 pipeline stage</p>
</li>
</ul>
<h1 id="94---操作數的選擇">9.4 - 操作數的選擇</h1>
<ul>
<li>
<p>FU 的輸入端來源包括：PRF、bypassing network、還有指令的 immediate value；而這些來源是透過 multiplexer 來選擇的，需要有對應的訊號來控制這個 multiplexer，決定要選擇的來源</p>
</li>
<li>
<p>所有的 physical register 可以保存在一個表格：<code>Scoreboard</code> 中，scoreboard 記錄了一個 physical register 生命週期經過的地方：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2013.png"></p>
<ul>
<li>真實的 scoreboard 原本圖片所示來得複雜</li>
<li>scoreboard 中對於每個 physical register，都記錄了：
<ul>
<li><code>FU #</code>：這個 physical register 會在哪個 FU 被計算出來
<ul>
<li>當需要透過 bypassing network 取得 physical register 的值時，需要知道它來自於哪個 FU，以控制 multiplexer 選擇對應的值</li>
<li>當一條指令被 select 電路選中時，就會將這條指令的 destination register 在哪個 FU 中執行的資訊記錄到 scoreboard 中</li>
</ul>
</li>
<li><code>R</code>：表示這個 physical register 已經被 FU 計算出來並寫進 PRF 中
<ul>
<li>只需使用一個 bit 即可：
<ul>
<li><code>0</code>：這個 physical register 還沒被寫進 PRF 中，需要透過 bypassing network 來取得值</li>
<li><code>1</code>：這個 physical register 已經被寫進 PRF 中了，可以直接讀取 PRF 來取得值</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在 pipeline 中加入 scoreboard 會對 pipeline 的性能造成一定的影響：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2014.png"></p>
<ul>
<li>指令 A 在 select stage 會將 <code>$r1</code> 的 physical register 在哪個 FU 中執行的資訊寫進 scoreboard 的 <code>FU #</code> 中</li>
<li>指令 A 在 write-back stage 除了會將 FU 的結果寫入 PRF，也會將 <code>$r1</code> 的 physical register 在 scoreboard 中的 <code>R</code> 設成 <code>1</code></li>
<li>指令 B 在 execute stage 時，可以讀取 scoreboard，得知其所需的 <code>$r1</code> 需透過 bypassing network 來取得</li>
<li>指令 C 在 execute stage 時，可以讀去 scoreboard，得知其所需的 <code>$r1</code> 可以直接讀取 PRF 來取得</li>
</ul>
</li>
<li>
<p>Scoreboard 的讀取如果放在 execute stage，會對 CPU 的 cycle time 造成一定的影響</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2015.png"></p>
<ul>
<li>
<p>當 CPU 頻率的需求比較高時，這種作法可能就無法滿足要求</p>
</li>
<li>
<p>如果需要提高 CPU 的頻率，可以將 scoreboard 的讀取放到 RF read stage，使 scoreboard 的讀取和 PRF 的讀取可以同時進行，”隱藏” scoreboard 的讀取時間：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2016.png"></p>
<ul>
<li>
<p>然而，這樣會導致新的問題：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2017.png"></p>
<ul>
<li>指令 C 原本可以在 execute stage 直接讀取 PRF 獲得 <code>$r1</code> 的值，但由於指令 C 讀取 scoreboard 時，指令 A 尚未完成 scoreboard <code>R</code> bit 的更新，因此會讓指令 C 錯誤地認為 <code>$r1</code> 必須透過 bypassing network 才能獲得</li>
</ul>
</li>
<li>
<p>可以在讀取和寫入 scoreboard 的地方加入新的邏輯：</p>
<ul>
<li>當寫入 scoreboard 所使用的 physical register 編號和讀取 scoreboard 的 physical register 編號相同時，就將 multiplexer 設為從 PRF 中獲得 operands</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>補充：</p>
<ul>
<li>
<p>實際上的 scoreboard 範例：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-3">3</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#a6e22e">MUL</span> <span style="color:#66d9ef">F2</span>, <span style="color:#66d9ef">F0</span>, <span style="color:#66d9ef">F4</span>    <span style="color:#75715e">; F2 = F0 * F4
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">ADD</span> <span style="color:#66d9ef">F6</span>, <span style="color:#66d9ef">F2</span>, <span style="color:#66d9ef">F8</span>    <span style="color:#75715e">; F6 = F2 + F8
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">SUB</span> <span style="color:#66d9ef">F10</span>, <span style="color:#66d9ef">F6</span>, <span style="color:#66d9ef">F12</span>  <span style="color:#75715e">; F10 = F6 - F12
</span></span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-5">5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-6">6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-7">7</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>+------+-------+-----+-----+-----+-----+-------+-------+-----+-----+
</span></span><span style="display:flex;"><span>|  FU  | Busy  | Op  | Fi  | Fj  | Fk  |  Qj   |  Qk   | Rj  | Rk  |
</span></span><span style="display:flex;"><span>+------+-------+-----+-----+-----+-----+-------+-------+-----+-----+
</span></span><span style="display:flex;"><span>| MUL1 |   Y   | MUL | F2  | F0  | F4  |   -   |   -   |  T  |  T  |
</span></span><span style="display:flex;"><span>| ADD1 |   Y   | ADD | F6  | F2  | F8  | MUL1  |   -   |  F  |  T  |
</span></span><span style="display:flex;"><span>| ADD2 |   Y   | SUB | F10 | F6  | F12 | ADD1  |   -   |  F  |  T  |
</span></span><span style="display:flex;"><span>+------+-------+-----+-----+-----+-----+-------+-------+-----+-----+
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li><code>FU</code>：FU 的名稱</li>
<li><code>Busy</code>：FU 是否正在忙</li>
<li><code>Op</code>：這個 FU 正在執行的 operation</li>
<li><code>Fi</code>：這個 FU 要寫的 destination register</li>
<li><code>Fj</code>, <code>Fk</code>：FU 所需的 source registers</li>
<li><code>Qj</code>, <code>Qk</code>：如果 <code>Fj</code>/<code>Fk</code> 尚未 ready，哪個 FU 會提供它</li>
<li><code>Rj</code>, <code>Rk</code>：來源是否 ready</li>
</ul>
</li>
<li>
<p>現代處理器，scoreboard 幾乎已經被 Tomasulo (Issue Queue + Register Renaming + Bypassing Network) 給取代了</p>
</li>
</ul>
</li>
</ul>
<h1 id="95---cluster">9.5 - Cluster</h1>
<ul>
<li>現在處理器隨著 pipeline stages 級數的提高，會導致硬體的設計越來越複雜，例如需要更多的 read/write ports、PRF，更複雜的 bypassing network，不僅增加了硬體的面積，也增加了硬體的功耗，限制了 CPU 的頻率，需要採用 <strong>Cluster</strong> 架構來解決這個問題</li>
</ul>
<h2 id="951---cluster-issue-queue">9.5.1 - Cluster Issue Queue</h2>
<ul>
<li>
<p>除了 Issue Queue 外，由於 register file 與 Issue Queue 彼此是緊密相關的，因此 register file 同時也會採用 cluster 架構</p>
</li>
<li>
<p><a href="../superscalar-overview-ch8-part1/#811----%E9%9B%86%E4%B8%AD%E5%BC%8F-centralized-vs-%E5%88%86%E4%BD%88%E5%BC%8F-distributed">8.1.1 -  集中式 (Centralized) vs. 分佈式 (Distributed)</a> 介紹的 distributed Issue Queue 就是將 Issue Queue 採用了 cluster 架構</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2018.png"></p>
<ul>
<li>每個 cluster Issue Queue 都對應一個 payload RAM (採用 <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture">data-capture</a> 架構) 和 FU</li>
<li>優點：
<ul>
<li>減少每個 Issue Queues 的 ports 數</li>
<li>每個 Issue Queue 對應的 select 電路只需從少量的指令中做仲裁，因此可以加快 select 電路的速度</li>
<li>由於 Issue Queue 的容量較小，因此 wake up 指令的速度也比較快</li>
<li>Payload RAM 也可以採用 cluster 架構，因此每個 payload RAM 的容量也會比較小，而且也不需要支援這麼多的 read/write ports</li>
</ul>
</li>
<li>缺點：
<ul>
<li>被 select 電路選中的指令如果要 wake up 的指令存在另外的 Issue Queue 中，那麼需要走比較長的 wake up 電路
<ul>
<li>
<p>對頻率有要求的 CPU，跨 cluster 的 wake up 有可能會需要額外新增一個 pipeline stage</p>
</li>
<li>
<p>當兩條存在相關性的指令存在不同 cluster 的 Issue Queue 時，有可能就會有 bubble，無法 back-to-back 的執行指令</p>
</li>
<li>
<p>例如：一個 2-way issue 的 CPU，執行指令 A ~ E，假設指令 A ~ E 的 dependency 如下：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2019.png"></p>
<ul>
<li>
<p>如果指令 A、B、E 被分配到同一個 cluster，指令 C、D 被分配到另外一個 cluster，共需 5 個 cycles 才能執行完畢：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2020.png"></p>
<ul>
<li>指令 A 需要多 1 個 cycle 才能 wake up 指令 C</li>
<li>指令 B 需要多 1 個 cycle 才能 wake up 指令 D</li>
<li>指令 D 需要多 1 個 cycle 才能 wake up 指令 E</li>
</ul>
</li>
<li>
<p>如果指令 A、C 被分配到同一個 cluster，指令 B、D、E 被分配到另一個 cluster，只需 3 個 cycles 才能執行完畢：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2021.png"></p>
<ul>
<li>指令 A 可以 back-to-back wake up 指令 C</li>
<li>指令 B 可以 back-to-back wake up 指令 D</li>
<li>指令 D 可以 back-to-back wake up 指令 E</li>
</ul>
</li>
<li>
<p>因此想使處理器有比較高的執行效率，指令 <a href="../superscalar-overview-ch8-part1/#83---%E5%88%86%E9%85%8D-allocation">allocation</a> 的演算法就必須有適當的設計</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>對於採用 <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture">non-data-capture</a> 架構的 CPU 來說，指令在被 select 電路選中後，會先去讀 PRF，因此就需要 PRF 支援 multiple read ports，因此也會增大硬體面積，執行效率也會比較慢，因此，也可以對 PRF 採用 cluster 架構</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2022.png"></p>
<ul>
<li>每個 cluster 都有”完整”的 PRF：每個 FU 在更新自己 cluster 內部 PRF 的同時，也同時需要更新其他 clusters 的 PRF</li>
<li>每個 FU 都只能在自己的 cluster 內使用 bypassing network</li>
<li>優點：
<ul>
<li>減少 PRF 所需的 read ports
<ul>
<li>4 個 FU → 每個 cluster 2 個 FU，因此 read ports 可以減少，進而減少硬體面積</li>
<li>但無法減少 write ports，因為 FU 仍需要更新其他 clusters 的 PRF</li>
</ul>
</li>
<li>減少 bypassing network 的複雜度</li>
</ul>
</li>
<li>缺點：
<ul>
<li>Bypassing network 無法跨 cluster，當兩個存在相關性的連續指令處於不同的 clusters 時，後面的指令需要等到前面的指令更新完 PRF 後，才能從 PRF 中讀取 operands
<ul>
<li>雖然硬體可以嘗試找其他不相關的指令到這兩條指令中間來執行，但當 pipeline 較深的時候，硬體可能沒辦法找到這麼多不相關的指令，引進較多的 bubbles</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="952---cluster-bypassing-network">9.5.2 - Cluster Bypassing Network</h2>
<ul>
<li>
<p>為了減少 bypassing network 的複雜度，也可以讓 bypassing network 採用 cluster 的架構</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2023.png"></p>
<ul>
<li>採用 cluster 架構的 bypassing network 後，每個 FU 就沒辦法將其計算結果送到其他 clusters 的 FU
<ul>
<li>當兩條相關的指令都使用同一個 FU 時，還是可以透過 bypassing network，back-to-back 的執行
<ul>
<li>跨 cluster 只能透過 PRF 來傳遞 operands</li>
</ul>
</li>
<li>但當兩條相關的指令使用不同 clusters 的 FU 時，就沒辦法 back-to-back 的執行了</li>
</ul>
</li>
<li>由於 bypassing network 的複雜度降低了，因此 pipeline 中的 Source Drive stage 和 Result Drive stage 就可以被移除了
<ul>
<li>
<p>雖然跨 cluster 只能透過 PRF 來傳遞 operands，但由於 pipeline 少了 Source Driver stage 以及 Result Driver stage，因此一條指令的執行效率是有被提高的，兩條跨 cluster 的指令只需間隔 1 個 cycle 即可</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2024.png"></p>
<ul>
<li>Out-of-order CPU 可以找一條不相關的指令插到這個 cycle 中來執行
<ul>
<li>然而，隨著 CPU 同時可以執行的指令數越來越多，PRF 的 ports 數量也會隨著增加，加上 CPU 的頻率的提高 (i.e. cycle time 的減少)，有可能會導致 PRF 沒辦法在同一個 cycle 中做 write &amp; read，兩條相關的指令就需要間隔更多的 cycles，Out-of-order CPU 有可能沒辦法找到這麼多條不相關的指令插到這幾個 cycles 中來執行，影響到 CPU 的執行效率</li>
</ul>
</li>
<li>In-order CPU 由於沒辦法調度不相干的指令來執行，只能產生 bubbles，因此通常 in-order CPU 都會採用”完全”的 bypassing network，而非採用 cluster 架構的 bypassing network
<ul>
<li>例如：Intel Atom 就有複雜的 bypassing network</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>對於 Cluster Issue Queue 和 cluster bypassing network，當兩條相關的指令分處於不同的 cluster 時，都會需要 delay 1 個 cycle，但這兩個 delays 其實並不會被累加：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2025.png"></p>
<ul>
<li>指令 A 需要 delay 1 個 cycle 才能 wake up 指令 B</li>
<li>由於已經 delay 1 個 cycle 才 wake up 指令 B，指令 A bypass 其 FU 的計算結果的 delay 剛好被隱藏起來了；當指令 B 進到 execute stage 時，指令 B 的 FU 剛好可以接收到來自指令 A 的 FU 的計算結果，不需要額外的 delay</li>
<li>總共只需要 delay 1 個 cycle 即可</li>
</ul>
</li>
</ul>
<h1 id="96---記憶體指令的加速">9.6 - 記憶體指令的加速</h1>
<h2 id="961---memory-disambiguation">9.6.1 - Memory Disambiguation</h2>
<ul>
<li>暫存器的相關性 (RAW、WAW、WAR)，都是可以在 register renaming stage 被解決；然而，針對記憶體存取的相關性 (i.e. load、store 指令)，由於記憶體存取位址是在 execute stage 才被計算出來的，才可以得知兩條記憶體存取的指令，是否存在相關性
<ul>
<li>記憶體存取指令間也存在 RAW、WAW、WAR 相關性：</li>
</ul>
</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2026.png"></p>
<ul>
<li>
<p>記憶體存取的 WAW、WAR 是沒辦法像暫存器可以用 register renaming 來消除的，因此記憶體存取的 RAW、WAW、WAR 都是真的相關性，需要特別處理</p>
</li>
<li>
<p>為了降低設計難度，大部分的處理器：</p>
<ul>
<li><strong>Store 指令都是 in-order 的</strong>，可以避免 WAW 相關性</li>
<li>Load 指令則有不同的實現方式：
<ul>
<li><strong>完全 in-order：</strong>
<ul>
<li>Load 指令和 store 指令都完全依照程式的順序來執行</li>
</ul>
</li>
<li><strong>部份 out-of-order：</strong>
<ul>
<li>In-order 的 store 指令將程式劃分成了不同的 blocks，當一條 store 指令的存取位址被計算出來後，這條 store 指令和它後面的 store 指令之間的 load 指令可以以 out-of-order 的方式來執行：</li>
<li>這種設計可以降低 RAW 相關性的檢查難度</li>
</ul>
</li>
<li><strong>完全 out-of-order：</strong>
<ul>
<li>Load 指令不受 store 指令的限制，只要 load 指令的 operands 都準備好了，它就可以被送到 FU 中執行</li>
<li>這種設計 WAR 和 RAW 相關性都必須在 pipeline 中被處理</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>完全 in-order：</p>
<ul>
<li>Load 指令和 store 指令都是嚴格得照程式的執行順序來執行，為最保守的一種方法</li>
<li>由於 load 指令通常處於相關性的頂端，這種方法沒辦法使 load 指令盡可能地提前被執行，導致所有相關的指令都必須等待 load 指令才能被執行，性能較差</li>
<li>在 superscalar CPU 中基本上不會採用此方法</li>
</ul>
</li>
<li>
<p>部份 out-of-order：</p>
<ul>
<li>
<p>Store 指令仍是 in-order 執行，但兩條 store 指令之間的所有 load 指令都可以以 out-of-order 的方式來執行</p>
</li>
<li>
<p>可以使 load 指令盡可能地提早被執行</p>
</li>
<li>
<p>當一條 store 指令的存取位址被計算出來後，在它之後進入到的所有 load 指令就可以判斷 RAW 的相關性了，每條 load 指令的存取位址被計算出來後，需要和前面所有已經執行的 store 指令的存取位址進行比較</p>
</li>
<li>
<p>因此，需要使用 <code>store buffer</code> 來儲存還沒 retire 的 store 指令，如果 load 指令在 store buffer 中發現了相同存取位址的 store 指令，則說明了存在 RAW 的相關性，此時 load 指令就可以直接從 store buffer 拿到 store 指令的值；如果在 store buffer 中沒有找到相同存取位址的 store 指令，那 load 指令就必須去存取 D-Cache 或是 memory 來取得值</p>
</li>
<li>
<p>實際上，當 store 指令被 select 電路選中後，就可以 wake up 後面的 load 指令了，無須等到 store 指令的存取位址真的被計算出來；當 load 指令的存取位址被計算出來時，其前面 store 指令的存取位址一定也早就被計算出來了</p>
<ul>
<li>
<p>然而除了比較<strong>存取位址</strong>外，還需要比較<strong>指令的先後順序</strong>，例如：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2027.png"></p>
<ul>
<li>即使 store 指令 E 和 store 指令 A 的存取位址相同，load 指令 B、C、D 也不會跟 store 指令 E 有 RAW 相關性，如果只比對 store buffer 中的存取位址，有可能就會判斷錯誤 (如果 load 指令 B、C、D 執行時，store 指令 E 已經存入 store buffer 中了)
<ul>
<li>RAW 相關性檢查只需檢查 load 指令之前的 store 指令即可，因此需要對 load 指令和 store 指令前後順序進行編號</li>
</ul>
</li>
<li>可以在 decode stage 替每條 load 指令和 store 指令進行編號，這個編號的 bits 寬度由 pipeline 中最多可容納的 load 指令和 store 指令的個數來決定
<ul>
<li>編號的比較方法可以參考 <a href="../superscalar-overview-ch8-part1/#841---1-of-m-%E7%9A%84%E4%BB%B2%E8%A3%81%E9%9B%BB%E8%B7%AF">ROB 編號的比較方式</a></li>
<li>由於 ROB 中儲存了所有的指令，使用 ROB 編號作為 load 指令和 store 指令的編號會變得非常的稀疏，浪費比較器的 bits 寬，增加硬體面積，不過在真實處理器中也有採用</li>
</ul>
</li>
</ul>
</li>
<li>
<p>P.S. 一條 load 指令是有可能會與多條 store 指令存在 RAW 相關性的</p>
<ul>
<li>例如：兩條 store 指令各 store 1 byte 的資料，但另外一條 load 指令 load 的 4 bytes 資料中，其中 2 bytes 是從這兩條 stores 而來，此時這條 load 指令就與這兩條 store 指令存在 RAW 相關性</li>
<li>因此不能單單只比較位址，還需比較<strong>存取的資料範圍</strong>
<ul>
<li>i.e. <code>addr + size</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>另外一種設計方法，可以限制後面的 store 指令只有在前面所有的 load 指令全部都被 select 電路選中後，才允許該 store 指令向 select 電路發出 request</p>
<ul>
<li>這樣每一條 load 指令在比對 store buffer 時，只會遇到在自己老的 store 指令，而不會遇到比自己年輕的 store 指令，就不需要比對指令的先後順序，簡化 RAW 相關性的檢查</li>
<li>例如上例中，store 指令 E 只能在 load 指令 B、C、D 都被 select 電路選中後，才會向 select 電路發出 request</li>
</ul>
</li>
<li>
<p>然而這種部份 out-of-order 的設計，仍然無法最大限度的同步執行指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2028.png"></p>
<ul>
<li>Load 指令 F 與 store 指令 B 沒有相關性，但卻沒辦法在 store 指令 B 之前被執行</li>
</ul>
</li>
</ul>
</li>
<li>
<p>完全 out-of-order：</p>
<ul>
<li>Store 指令仍是 in-order 執行，但只要 load 指令的 operands 都準備好了，就可以向 select 電路發出 request</li>
<li>可以採用 load 指令和 store 指令共用同一個 Issue Queue，每個 cycle 只 issue 一條 load 指令或 store 指令的設計，或是每個 cycle 同時 issue 一條 load 指令和 store 指令的設計
<ul>
<li>Load 指令和 store 指令在一般程式中佔的比例比較大，所以最好每個 cycle 都盡可能的增加每個 cycle 可以同時執行的 load 指令和 store 指令的個數</li>
<li>不過由於 load 指令和 store 指令的處理過程不同，即使使用同一個 Issue Queue，仍需要使用互相獨立的 select 電路
<ul>
<li>
<p>對於 store 指令的 select 電路來說：</p>
<ul>
<li>當指令被寫入 Issue Queue 中時，需根據指令的年齡資訊，找到最舊的那條 store 指令來 issue；如果還沒有準備好，則必須一直等待 ⇒ In-order</li>
</ul>
</li>
<li>
<p>對於 load 指令的 select 電路來說：</p>
<ul>
<li>只需要比對準備好的 load 指令的年齡資訊即可，找到最舊，且準備好的 load 指令來 issue ⇒ Out-of-order</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>也可以採用 load 指令和 store 指令各自使用獨立的 Issue Queue 的設計
<ul>
<li>Store 指令的 Issue Queue 可以直接使用 FIFO 來實做，select 電路不用比較年齡資訊，只需判斷 FIFO 中最舊的那條 store 指令是否準備好就好</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>Store/load 指令違例：</strong></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2029.png"></p>
<ul>
<li>第二條 load 指令被提前到 store 指令之前被執行，結果發現 store 指令與第二條 load 指令的存取位址相同，彼此之間存在 RAW 相關性，因此必須恢復 pipeline 的狀態
<ul>
<li>除此之外，不僅第二條 load 指令的結果有錯，所有與這條 load 指令有直接關係或間接關係的指令，都需要從 pipeline 中被 flushed 掉，恢復 pipeline 的狀態，並重新參與仲裁並執行，因此會影響處理器的執行效能</li>
</ul>
</li>
<li>需要採用 load/store 相關性的預測，來預測哪些 load 指令和前面的 store 指令存在 RAW 相關性而不能提前被執行：
<ul>
<li>透過 load/store 相關性的預測，一旦發現一條 load 指令和它前面的 store 指令存在 RAW 相關性，就將其記錄下來，之後再遇到這條 load 指令時，就不提前執行，而且需要從 store buffer 中獲取 load 指令所需要的值
<ul>
<li>額外好處：只有那些與 store 指令存在 RAW 相關性的 load 指令，才需要存取 store buffer，因此可以減少 store buffer 所需的 ports 數及比較電路的個數，並降低設計的複雜度和功耗</li>
</ul>
</li>
<li>P.S. 預測失敗並不需要恢復 pipeline 的狀態， 因為 load 指令只是比較晚被執行而已</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="962---non-blocking-cache">9.6.2 - Non-blocking Cache</h2>
<ul>
<li>
<p>I-Cache 由於 fetch 指令需要照 program order，不能簡單地使用 non-blocking cache，因此 non-blocking cache 主要會用在 D-Cache</p>
<ul>
<li>
<p>然而對於使用了 branch prediction 的 CPU 來說，是可以根據當前指令的位址來對下一條指令的位址做預測的；當使用一個 PC address 讀取 I-Cache 發生 I-Cache miss，且同時預測這個 PC address 所的指令是一條分支指令時，如果不採用 non-blocking I-Cache，那 CPU 就得等這條分支指令從 I-Cache 讀出來後，才可以繼續使用預測的 PC address 來 fetch 下一條指令，此時再次發生 I-Cache miss 的機率是很高的：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2030.png"></p>
<ul>
<li>有可能連續發生兩次 miss penalties</li>
</ul>
</li>
<li>
<p>如果使用 non-blocking I-Cache，就可以隱藏一部分的 miss penalty，提高處理器的執行效率：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2031.png"></p>
<ul>
<li>Non-blocking I-Cache 不用等到所預測的分支指令從 I-Cache 讀出來後，才開始 fetch 下一條指令 (有可能又會再發生一次 I-Cache miss)</li>
</ul>
</li>
<li>
<p>然而，由於指令的特性，就算後面的指令比前面一個指令先被 fetched 進來，也沒辦法先執行後面的指令；因此通常不用使用太大的 <a href="#962---non-blocking-cache"><strong>MSHR</strong></a></p>
</li>
</ul>
</li>
<li>
<p>對於 load 指令來說 (假設 L1 D-Cache 下一級就是記憶體)：</p>
<ul>
<li>如果需要的資料不在 D-Cache 中，就會發生 D-Cache miss，需要從下一級的記憶體中讀取data block (一條 cache line 的 size)，並找到一條 cache line 將該 data block 寫入
<ul>
<li>如果被寫入的 cache line 是 dirty 的，那在寫入之前，還需要先將 dirty 的 cache line flush 回記憶體後，才可以將讀到的 data block 寫入 cache line</li>
</ul>
</li>
</ul>
</li>
<li>
<p>對於 store 指令來說 (假設 L1 D-Cache 下一級就是記憶體)：</p>
<ul>
<li>如果 store 的位址不在 D-Cache 中，對於 “write back + write allocate” 類型的 D-Cache 來說，首先需要從記憶體中找到這個位址對應的 data block，讀出來後與 store 指令的資料合併，並找到一條 cache line 將合併後的資料寫入
<ul>
<li>如果被替換的 cache line 是 dirty 的，那在寫入之前，還需要先將 dirty 的 cache line flush 回記憶體後，才可以將合併後的資料寫入 cache line</li>
</ul>
</li>
</ul>
</li>
<li>
<p>不管是 load 指令還是 store 指令，當發生 D-Cache miss 時，D-Cache 和記憶體都是需要交換資料的，通常需要好幾個 cycles 才能完成；如果在這幾個 cycles 內，又發生了 D-Cache miss，該如何處理?</p>
<ul>
<li>
<p>最簡單的方法就是在發生 D-Cache miss 並未被解決前，鎖定 D-Cache 和記憶體之間的 bus，只處理目前發生 D-Cache miss 的指令</p>
<ul>
<li>缺點：
<ul>
<li>
<p>D-Cache miss 通常處理的時間都比較長，如果這段時間 block 了 load 指令和 store 指令的執行，在一般情況下，load 指令都是處於相關性的最頂端，這樣的 block 就會大大降低處理器可以同步執行指令的數量，降低處理效能</p>
</li>
<li>
<p>這種 cache 設計方法稱為：<code>Blocking Cache</code></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2032.png"></p>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>如果在發生 D-Cache miss 時，不用 block load 指令和 store 指令的執行，這種 cache 的設計方法稱為：<code>Non-blocking Cache</code></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2033.png"></p>
<ul>
<li>Non-blocking cache 可以在一定程度上掩蓋 D-Cache miss 所佔用的時間 (Miss Penalty)</li>
<li>採用 non-blocking cache 後，load 指令和 store 指令 D-Cache miss 被處理完的時間有可能就跟原始的程式執行順序不一樣了</li>
<li>為了支援 non-blocking cache，必須將產生 D-Cache miss 的 load 指令和 store 指令給記錄起來，用來保存這些資訊的硬體稱為：<code>MSHR (Miss Status/information Holding Register)</code></li>
</ul>
</li>
</ul>
</li>
<li>
<p>D-Cache miss 可以分為兩種：</p>
<ul>
<li><strong>Primary miss：</strong>
<ul>
<li>對於一位址來說，存取 D-Cache 時第一次發生的 miss 稱為：<em>primary miss</em></li>
</ul>
</li>
<li><strong>Secondary miss：</strong>
<ul>
<li>對於一位址來說，primary miss 還沒被處理完前，後續的 load 指令或 store 指令再次存取了<strong>同一條 cache line</strong> 並發生 D-Cache miss，此時的 miss 便稱為：<em>secondary miss</em></li>
</ul>
</li>
</ul>
</li>
<li>
<p>MSHR 和 Load/store Table 的架構如下：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2034.png"></p>
<ul>
<li>MSHR：
<ul>
<li>MSHR 記錄了所有發生 <strong>primary miss</strong> 的指令</li>
<li><code>V</code>：
<ul>
<li>Valid bit，用來表示此 MSHR entry 是否被佔用</li>
<li>當發生 primary miss 時，會將該指令存進一 MSHR entry 和 Load/store Table entry，並將 <code>V</code> 設成 1</li>
<li>當從下一級的記憶體讀回資料後，就會將對應的 MSHR entry 和 Load/store Table entry 清掉，並將 <code>V</code> 設成 0</li>
</ul>
</li>
<li><code>Block address</code>：
<ul>
<li>發生 primary miss 位址的 cache line address
<ul>
<li>例如：
<ul>
<li>Cache line = 64 bytes ⇒ 需要 6 bits 來選擇所存取的資料位址是在哪個 byte</li>
<li>假設存取位址是 32 bits ⇒ Block address = 32 - 6 = 26 bits</li>
</ul>
</li>
</ul>
</li>
<li>每條 load 指令或是 store 指令發生 D-Cache miss 時，都會查詢 MSHR 是否有同一條 cache line 正在被讀取的過程中，這需要比較 MSHR 每個 <code>V = 1</code> 的 entries
<ul>
<li>如果有發現同一條 cache line 正在被讀取的過程，那麼就不需要再發一次 request</li>
</ul>
</li>
<li>P.S. DDR3 SDRAM 的 burst size 為 64 bytes，因此處理器通常也會將 cache line size 設為 64 bytes</li>
</ul>
</li>
<li><code>Issued</code>：
<ul>
<li>表示發生 primary miss 的 load 指令或是 store 指令是否已經開始處理，i.e. 是否已經開始從下一級的記憶體讀取 data block
<ul>
<li>由於 bus 寬度有限，每個 MSHR entry 並不一定會馬上被處理，因此需要透過 <code>Issued</code> bit 來表示此 primary miss 是否已經在處理的過程中</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Load/store Table：
<ul>
<li>發生 <strong>primary miss</strong> 或是 <strong>secondary miss</strong> 的 load 指令或是 store 指令都會被記錄到 Load/store Table 中</li>
<li><code>V</code>：
<ul>
<li>Valid bit，用來表示此 Load/store Table entry 是否被佔用</li>
</ul>
</li>
<li><code>MSHR entry</code>：
<ul>
<li>發生 miss 的 load 指令或 store 指令所對應的 MSHR entry
<ul>
<li>不同的 load 指令或 store 指令有可能會存取到同一條 cache line，此時 MSHR entry 只會有一個，但 Load/store Table 會記錄所有發生 miss 的指令，以確保在 data block 讀回來後，可以在 Load/store Table 中找到哪些 load 指令和 store 指令屬於這條 cache line</li>
</ul>
</li>
</ul>
</li>
<li><code>Dest. register</code>：
<ul>
<li>對於 load 指令，<code>Dest. register</code> 欄位記錄 load 指令的 destination register 編號 (physical register)</li>
<li>對於 store 指令，<code>Dest. register</code> 欄位記錄 store 指令在 store buffer 中的編號
<ul>
<li>Store 指令在 retire 前會將其資訊記錄在 store buffer 中，當 store 指令變成 pipeline 中最老的指令時，需要將其資料寫進 D-Cache 中，如果此時發生了 D-cache miss，store buffer 中對應的 entry 就不會馬上被釋放，需要等到 D-Cache miss 解決並將合併後的資料寫進 D-Cache 後，才可以釋放 store buffer 中的 entry</li>
</ul>
</li>
</ul>
</li>
<li><code>Type</code>：
<ul>
<li>記錄指令的類型，如：Load word、Load half word、Load byte、Store word、Store half word、Store byte</li>
</ul>
</li>
<li><code>Offset</code>：
<ul>
<li>Load 指令或 store 指令所存取的位址在 data block 中的 offset
<ul>
<li>例如：64 bytes 共需要 6 bits 來找出 data block 中所對應的 byte(s)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>當 load 指令或 store 指令發生 D-Cache miss 時，會先搜尋 MSHR 中是否有對應的 entry</p>
<ul>
<li>如果沒有的話，代表是 primary miss，需將該指令寫入 MSHR 及 Load/store Table 中，並開始處理 D-Cache miss</li>
<li>如果有的話，代表同樣的 D-Cache miss 已經在被處理了 (<code>Issued = 1</code>)，或是即將要被處理了 (<code>Issued = 0</code>)，此時只需將指令寫入 Load/store Table 中即可</li>
</ul>
</li>
<li>
<p>如果 MSHR 或 Load/store Table 滿了，那就無法再處理任何的 load 指令或 store 指令，此時得暫停 pipeline 繼續處理 load 指令或 store 指令，直到有 D-Cache miss 被處理完畢，並釋放 MSHR 和 Load/store Table entries</p>
</li>
<li>
<p>當一個 D-Cache miss 被解決後，會釋放一個 MSHR entry，以及一或多個 Load/store Table entries</p>
<ul>
<li>對於 load 指令，在 refill 完 cache line 後，load 指令所需的資料會被送到對應的 destination register</li>
<li>對於 store 指令，store buffer 中的資料會與讀回來的 data block 合併，並寫回 D-Cache 中；Store buffer 中對應的 entry 也會被釋放</li>
</ul>
</li>
<li>
<p>在現實處理器中，基於硬體面積和功耗的考量，MSHR 和 Load/store Table 容量通常不會很大，一般多為 4 ~ 8 個 entries，也就是同時可以處理 4 ~ 8 個 D-Cache misses</p>
</li>
<li>
<p>對於 out-of-order CPU，由於採用了 branch prediction 及 out-of-order execution，發生 D-Cache miss 的指令有可能是處在 mis-prediction 路徑上的，除了需要 flush 這些指令外，也需要一種機制能夠選擇性的放棄正在被處理的 D-Cache miss，以及將 mis-prediction 指令所對應的 MSHR 和 Load/store Table entries 給釋放，此外，如果已經從下一級記憶體讀取出 data block，該 data block 也不能寫回 D-Cache</p>
</li>
</ul>
<h2 id="963---關鍵字優先">9.6.3 - 關鍵字優先</h2>
<ul>
<li>
<p>當執行一條 load 指令或 store 指令而發生 D-Cache miss 時，會將這條指令所需的整條 data block (e.g .64 bytes) 都從下一級的記憶體讀出來並寫進 D-Cache 中，如果考慮到 prefetching，還需要將相鄰的下一個 data block 也一起讀出來，而且這些 data blocks 的讀取過程是照順序的</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2035.png"></p>
</li>
<li>
<p>如果等到所有的數據都寫進 D-Cache 後才將所需的資料送給 CPU，可能就會讓 CPU 等待一段時間；為了加快執行速度，可以對 D-Cache 下一級的記憶體進行”改造”，使資料的讀取順序發生改變：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2036.png"></p>
<ul>
<li>如果 load 指令或 store 指令所需的資料在 data block 中的第 6 個 word，那麼可以從第 6 個 word 開始讀取記憶體，當讀到 data block 的尾端時，再從頭開始將第 0 ~ 第 5 個 words 給讀出來</li>
<li>當記憶體第一個讀取的 word (i.e. 第 6 個 word) 返回時，CPU 就可以得到所需的資料並繼續執行，D-Cache 會繼續完成剩餘資料的 refill</li>
<li>下一級的記憶體需要額外增加硬體才能支援這種特性，會一定程度上增加硬體的面積及功耗</li>
</ul>
</li>
<li>
<p>這種方式稱為：<code>關鍵字優先 (Critical Word First)</code></p>
</li>
</ul>
<h2 id="964---提前開始">9.6.4 - 提前開始</h2>
<ul>
<li>Critical Word First 由於需要改變資料讀取的順序，因此需要額外增加硬體才能支援此特性；如果不想增加硬體成本，可以改採用<code>提前開始 (Early Restart)</code> 的方法
<ul>
<li>
<p>這種方法不會改變硬體讀取資料的順序，因此不需要增加額外的硬體，當發生 D-Cache miss  的指令所需要的資料被讀出來後，CPU 就可以馬上繼續執行</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2037.png"></p>
<ul>
<li>當指令所需的第 6 個 word 被讀取出來後，就可以讓 CPU 繼續執行了，D-cache 會繼續完成剩餘資料的讀取</li>
<li>由於沒有改變資料讀取的順序，因此若是所需的資料位於 data block 比較後面的位置時，這種方法就沒有比較明顯的優勢</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>&lt;超標量處理器概覽&gt; 第 8 章 - 發射 (Part 2)</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch8-part2/</link>
      <pubDate>Sat, 19 Apr 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch8-part2/</guid>
      <description>&lt;h1 id=&#34;85---喚醒-wake-up&#34;&gt;8.5 - 喚醒 (Wake up)&lt;/h1&gt;
&lt;h2 id=&#34;851---單週期指令的喚醒&#34;&gt;8.5.1 - 單週期指令的喚醒&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Wake up 是指被 select 電路選中的指令將其執行完後，將其 destination register 和 Issue Queue 中所有的 source registers 編號做比較，並將相同編號的 source registers 標記為 ready 的過程&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;因為需要將被選中指令的 destination register 編號需要傳遞到 Issue Queue 中的所有 entries，當 Issue Queue 容量比較大，或是 Issue Queue 的個數比較多時，這個編號就需要走很長的路徑&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一般情況下，select 電路的個數等同於 &lt;code&gt;issue width&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;以下是 &lt;em&gt;Issue width = 4&lt;/em&gt; 的 wake-up 電路 (只包含一個 Issue Queue 的 entry，且所有 select 電路共用同一個 Issue Queue)：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://0xc0de.xyz/posts/superscalar-overview-ch8-part2/image.png&#34;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SrcL&lt;/code&gt; /&lt;code&gt;SrcR&lt;/code&gt;：第一、第二個 source register 的編號&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ValL&lt;/code&gt; / &lt;code&gt;ValR&lt;/code&gt;：指令是否存在第一、第二個 source register&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RdyL&lt;/code&gt; / &lt;code&gt;RdyR&lt;/code&gt;：第一、第二個 source register 是否準備好&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Dest&lt;/code&gt;：Destination register 的編號&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Issued&lt;/code&gt;：指令是否已經被 issued 出去
&lt;ul&gt;
&lt;li&gt;當指令準備好，並被 select 電路選中時，此時該指令不一定會馬上被 issued 出去 (e.g. 一條指令使用了 load 指令的結果，即使其被 select 電路選中，也不可以馬上離開 Issue Queue)，因此需要透過 &lt;code&gt;Issued&lt;/code&gt; 記錄該指令是否已經被 issued 了
&lt;ul&gt;
&lt;li&gt;如果已經被 issued，select 電路就不會重複選到該指令&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;當指令準備好時，就會向 select 電路送 request 訊號，請求被仲裁&lt;/li&gt;
&lt;li&gt;Select 電路會選擇根據是否選擇了該指令，送出 grant 訊號
&lt;ul&gt;
&lt;li&gt;四個 select 電路同時間只會有一個 grant 訊號是 valid 的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果一條指令被 granted，那麼就會將其 &lt;code&gt;Dest&lt;/code&gt; 送到 tag bus 上，以便 broadcast 給所有的 Issue Queue entries&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;實務上，為了提供速度，需要減少 Issue Queue 的容量，因此可以使用多個容量較小的 Issue Queue&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="85---喚醒-wake-up">8.5 - 喚醒 (Wake up)</h1>
<h2 id="851---單週期指令的喚醒">8.5.1 - 單週期指令的喚醒</h2>
<ul>
<li>
<p>Wake up 是指被 select 電路選中的指令將其執行完後，將其 destination register 和 Issue Queue 中所有的 source registers 編號做比較，並將相同編號的 source registers 標記為 ready 的過程</p>
<ul>
<li>因為需要將被選中指令的 destination register 編號需要傳遞到 Issue Queue 中的所有 entries，當 Issue Queue 容量比較大，或是 Issue Queue 的個數比較多時，這個編號就需要走很長的路徑</li>
</ul>
</li>
<li>
<p>一般情況下，select 電路的個數等同於 <code>issue width</code></p>
</li>
<li>
<p>以下是 <em>Issue width = 4</em> 的 wake-up 電路 (只包含一個 Issue Queue 的 entry，且所有 select 電路共用同一個 Issue Queue)：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image.png"></p>
<ul>
<li><code>SrcL</code> /<code>SrcR</code>：第一、第二個 source register 的編號</li>
<li><code>ValL</code> / <code>ValR</code>：指令是否存在第一、第二個 source register</li>
<li><code>RdyL</code> / <code>RdyR</code>：第一、第二個 source register 是否準備好</li>
<li><code>Dest</code>：Destination register 的編號</li>
<li><code>Issued</code>：指令是否已經被 issued 出去
<ul>
<li>當指令準備好，並被 select 電路選中時，此時該指令不一定會馬上被 issued 出去 (e.g. 一條指令使用了 load 指令的結果，即使其被 select 電路選中，也不可以馬上離開 Issue Queue)，因此需要透過 <code>Issued</code> 記錄該指令是否已經被 issued 了
<ul>
<li>如果已經被 issued，select 電路就不會重複選到該指令</li>
</ul>
</li>
</ul>
</li>
<li>當指令準備好時，就會向 select 電路送 request 訊號，請求被仲裁</li>
<li>Select 電路會選擇根據是否選擇了該指令，送出 grant 訊號
<ul>
<li>四個 select 電路同時間只會有一個 grant 訊號是 valid 的</li>
</ul>
</li>
<li>如果一條指令被 granted，那麼就會將其 <code>Dest</code> 送到 tag bus 上，以便 broadcast 給所有的 Issue Queue entries</li>
</ul>
</li>
<li>
<p>實務上，為了提供速度，需要減少 Issue Queue 的容量，因此可以使用多個容量較小的 Issue Queue</p>
<ul>
<li>例如 FU 可以拆分成：
<ul>
<li>兩個 ALU、一個 Mul/Div、一個 Load/Store ⇒ 共四個 ALU</li>
<li>可以使兩個 ALU 共用同一個 Issue Queue，其他 FU 各自有其獨立的 Issue Queue</li>
</ul>
</li>
<li>雖然每個 FU 仍然需要使用一個 select 電路，且沒辦法減少 wake-up 電路的複雜度，但由於 Issue Queue 的容量變小了，select 電路的 latency 也會因此減少，加快 select 電路的速度，進而減少 select 和 wake-up 兩個 stages 對 CPU cycle time 的影響</li>
</ul>
</li>
<li>
<p>大部分的 RISC ALU 指令都是 single-cycle 完成的，這種指令可以在 select 電路選中指令時，同個 cycle wake up 其他 Issue Queue 中的指令，被 wake up 的指令在 execute stage 可以透過 bypassing network 獲得被選中指令的執行結果，進而實現 back-to-back 的執行</p>
</li>
<li>
<p><strong>Single-cycle 指令</strong>的 wake-up 流程：</p>
<ol>
<li>被 select 電路選中的指令，其 destination register 會被送到 tag bus 上</li>
<li>每一條 tag bus 上的值會跟所有 Issue Queue 中所有指令的 source registers 編號做比較，如果相等，則將該 source registers 標記為 ready</li>
<li>當 Issue Queue 中的指令所有的 registers 都準備好了，並且還沒有被 select 電路選中過，其就可以向對應的 select 電路發出 request 訊號，要求被仲裁</li>
<li>如果 select 電路選擇了其他優先度更高的指令 (如年齡更老的指令)，則這條指令可以在下一個 cycle 持續向 select 電路發出 request 訊號，要求被仲裁</li>
<li>Issue Queue 中這條指令根據 grant 訊號，當 grant 訊號為 valid 時，將其 destination register 的編號送到對應的 tag bus 上，用來 wake up Issue Queue 中所有相關的 source register；同時這條指令就可以被 issued 至 FU 中執行了</li>
</ol>
</li>
</ul>
<h2 id="852---多週期指令的喚醒">8.5.2 - 多週期指令的喚醒</h2>
<ul>
<li>當一條指令無法在 1 個 cycle 內執行完成時，就不能在被 select 電路選中的那個 cycle，wake up Issue Queue 中的其他指令，而需要根據它在 FU 執行的 cycles 數，delay 這個 wake-up</li>
<li>Wake-up 過程主要分為兩個階段：
<ol>
<li>將被選中的指令的 destination register 送到 tag bus 上 broadcast</li>
<li>將 tag bus 上的值與所有 Issue Queue 中的 source registers 編號做比較</li>
</ol>
<ul>
<li>因此如果要 delay wake-up，就是需要 delay 這兩個階段</li>
</ul>
</li>
<li>總共有兩種 delay 方法：
<ul>
<li><code>Delay tag broadcast</code></li>
<li><code>Delay wake-up</code></li>
</ul>
</li>
<li><strong>Delay tag broadcast：</strong>
<ul>
<li>
<p>當發現被 select 電路選中的指令執行 cycles 數 &gt; 1，則在被選中的那個 cycle，並不將這條指令的 destination register 編號送到 tag bus 上，而是根據這條指令所需執行的 cycles 數：<code>N</code>，延遲 <code>N - 1</code> 個 cycles 後，才將 destination register 編號送到 tag bus 上</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%201.png"></p>
<ul>
<li>乘法指令的執行 cycles 數為 <strong>3</strong>，因此 delay：<strong>3 - 1 = 2</strong> 個 cycles 才將其 destination register 編號送到 tag bug 上</li>
<li>但當乘法和其他操作共用一條 tag bus 時，如：Mul/Div FU，乘法和除法會共用同一個 FU 以及同一條 tag bus，則有可能會出現：
<ul>
<li>
<p>當乘法指令的 destination register 編號 delay 了幾個 cycles 後送到 tag bus 上，其他的指令 (e.g. 除法指令)，同時間也要使用這條 tag bus，產生了衝突</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%202.png"></p>
<ul>
<li><code>MUL</code> 和 <code>ADD</code> 共用同一個 FU 以及同一條 tag bus，當 <code>MUL</code> 要將其 destination register 編號送到 tag bus 上時，<code>ADD</code> 指令也同時要將其 destination register 編號送到同一條 tag bus 上，產生了衝突</li>
<li>解決方法：
<ul>
<li>
<p>增加這個 FU 所對應的 tag bus 的數量</p>
<ul>
<li>缺點：
<ul>
<li>當處理器中有很多這種情況時，會導致需要大量的 bus，增加 wake-up 電路的複雜度，因此在實際設計中是無法使用的</li>
</ul>
</li>
</ul>
</li>
<li>
<p>使用一個表格記錄當前 FU 中執行的指令所需的 cycles 數，後續被 select 電路選中的指令，如果從這個表格中發現了衝突，那麼這條被選中的指令就不會馬上被送到 FU 中執行，而是在下個 cycle 繼續參與仲裁</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%203.png"></p>
<ul>
<li>指令 A 需要 3 個 cycles 來執行，因此指令 A 在被 select 電路選中後，需要 delay 2 個 cycles 才 wake up 其他的指令</li>
<li>指令 B 需要 2 個 cycles 來執行，因此指令 B 在被 select 電路選中後，需要 delay 1 個 cycle 才 wake up 其他的指令</li>
<li>透過表格可以知道，指令 B 和指令 A 在 <em>cycle 2</em> 會同時使用 tag bus，也就是存在衝突，因此指令 B 是不會被允許在 <em>cycle 2</em> 被選中的，會在下個 cycle 繼續參與仲裁</li>
<li>表格的查詢和 select 電路是同步運行的，因此指令 B 即使被 select 電路選中，也有可能被表格否決掉
<ul>
<li>優點：
<ul>
<li>沒有增加電路的延遲</li>
</ul>
</li>
<li>缺點：
<ul>
<li>指令 B 被否決的那個 cycle，指令 C 因為不會產生 tag bus 衝突，因此原本也是可以被 select 電路選中的，但因為指令 C 比指令 B 來得新，因此 select 電路選擇了指令 B，結果指令 B 被表格給否決，浪費了一個 cycle，造成性能的下降</li>
</ul>
</li>
</ul>
</li>
<li>因此，也可以採用先查詢表格，如果表格否決了該指令，則該指令就不會對 select 電路發出 request 訊號，把機會讓給其他的指令
<ul>
<li>缺點：
<ul>
<li>需要先查詢表格，再讓 select 電路仲裁指令，因此 cycle time 會增加</li>
</ul>
</li>
</ul>
</li>
<li>具體要選擇哪種設計，取決於實際設計中的折衷和權衡</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><strong>Delay wake-up：</strong>
<ul>
<li>
<p>被 select 電路選中的指令同樣會在當個 cycle 就將其 destination register 的編號 broadcast 到 tag bug 上，並和 Issue Queue 中所有的指令的 source registers 做比較；但此時比較結果相同的 source registers 的 ready bit 並不會馬上被設為 valid，而是會依據被 select 電路所選中的指令執行所需的 cycles 數，delay 相對應的 cycles 後，才將 ready bit 設為 valid</p>
</li>
<li>
<p>優點：</p>
<ul>
<li>這種方法不需要增加這個 FU 所對應的 tag bus 的數量</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%204.png"></p>
<ul>
<li>指令 A 需要 4 個 cycles 來執行，因此指令 A 在被 select 電路選中後，在 Issue Queue 中所有的 <code>R3</code> source registers (e.g. 指令 B 的 <code>R3</code>) 都需要 delay 3 個 cycles 後，ready bit 才會被設為 valid</li>
</ul>
</li>
<li>
<p>其中一種實做 delay wake-up 控制電路的方法：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%205.png"></p>
<ul>
<li>採用 <strong>shift register</strong> 來實現 delay wake-up</li>
<li>每個指令執行所需的 cycles 數在 decode stage 就可以得知了，可以在指令進入到 Issue Queue 時，將該指令的 <code>DELAY</code> 值一併寫入 Issue Queue 中</li>
<li>當一條指令被 select 電路選中時，除了將其 destination register 的編號 broadcast 到 tag bus 上，同時也會將其 <code>DELAY</code> 值一起 broadcast 到 delay bus 上
<ul>
<li>一般來說，每個 FU 都會對應一個 delay bus</li>
</ul>
</li>
<li>Issue Queue entry 中每個欄位的內容：
<ul>
<li><code>Freed</code>：
<ul>
<li>這個 Issue Queue entry 是否為空閒的</li>
<li>當一條指令被寫入此 Issue Queue entry 時，該 Issue Queue entry 就不是空閒的 (<code>Freed = 0</code>)</li>
<li>當該指令被 select 電路選中，且確定沒有問題，可以離開 Issue Queue 時，這個 Issue Queue entry 就會變為空閒的狀態 (<code>Freed = 1</code>)
<ul>
<li>一條指令被 select 電路選中時，並不一定馬上就會離開 Issue Queue
<ul>
<li>因為有可能此時 source registers 都還沒準備好 (e.g. 如果等待 load 指令的結果)，但處理器為了加速執行效率，採用了 <a href="#853---%E6%8E%A8%E6%B8%AC%E5%96%9A%E9%86%92">speculative wake-up</a> 的方式，此時就算該指令被 select 電路選中，也有可能會在之後的時間重新再次參與仲裁</li>
</ul>
</li>
</ul>
</li>
<li>最簡單管理 Issue Queue entry 的方式，就是等一條指令 retire 時，才將該 Issue Queue entry 變為空閒的狀態
<ul>
<li>缺點：
<ul>
<li>會導致許多無效的指令佔著 Issue Queue entries，使 Issue Queue 中可用的 entries 數變少，降低了處理器的執行效率</li>
</ul>
</li>
<li>因此現實中的處理器都會採用更高級的方法</li>
</ul>
</li>
</ul>
</li>
<li><code>Issued</code>：
<ul>
<li>是否被 select 電路選中
<ul>
<li>0：沒被 select 電路選中，當 source registers 皆 ready 時，向 select 電路發出 request 訊號</li>
<li>1：被 select 電路選中，不再向 select 電路發出 request 訊號</li>
</ul>
</li>
<li>被 select 電路選中的指令，並不一定馬上就會離開 Issue Queue，因此需要將該指令進行標記，使其不再繼續向 select 電路發出 request 訊號</li>
</ul>
</li>
<li><code>SrcL</code>：
<ul>
<li>指令的第一個 source register 的編號，會與 tag bus 上所有的 destination registers 編號值做比較</li>
</ul>
</li>
<li><code>SrcL_M</code>：
<ul>
<li>當 tag bus 上的 destination registers 與 <code>SrcL</code> 比較結果相同時，<code>SrcL_M</code> 就會被設為 1 (i.e. match)</li>
<li>當指令收到 select 電路的 grant 訊號時，<code>SrcL_M</code> 會被設為 <code>0</code>
<ul>
<li>被 select 電路選中的指令，就會將其 destination register 的編號 broadcast 到 tag bus 上，同時也會將其 <code>DELAY</code> 值一起 broadcast 到 delay bus 上</li>
</ul>
</li>
<li><code>SrcL_M</code> 為 <code>SrcL_SHIFT</code> 的 enable 訊號，當 <code>SrcL_M</code> 為 <code>1</code> 時，<code>SrcL_SHIFT</code> 每個 cycle 都會 arithmetic right shift 一位 (i.e. MSB 為 1 時，right shift 也會補 1)</li>
</ul>
</li>
<li><code>SrcL_SHIFT</code>：
<ul>
<li>
<p>當 tag bus 上的 destination registers 與 <code>SrcL</code> 比較結果相同時，被 select 電路選中的指令的 <code>DELAY</code> 值就會被寫入 <code>SrcL_SHIFT</code> 中</p>
<ul>
<li><code>DELAY</code> 值會被 encoded，如被 select 電路選中的指令執行需要 3 個 cycles，且 <code>DEALY</code> bit width 為 8 bits，那麼 <code>DELAY</code> 值就會被 encoded 為 <code>2’b11111100</code> (i.e. <code>bit[7:2]</code> 皆被設成 <code>1</code>)</li>
</ul>
</li>
<li>
<p>當 <code>SrcL_M</code> 為 <code>1</code> 時，<code>SrcL_SHIFT</code> 每個 cycle 都會 arithmetic right shift 一位</p>
</li>
<li>
<p>當 <code>SrcL_SHIFT</code> 的 LSB 為 <code>1</code> 時 (i.e. <code>Rdy = 1</code>)，就代表該指令可以被 wake up 了</p>
<ul>
<li>透過這樣的方式，就可以實現 delay wake-up</li>
</ul>
</li>
<li>
<p>當指令收到 select 電路的 grant 訊號時，<code>SrcL_SHIFT</code> 會被清為 <code>0</code></p>
</li>
</ul>
</li>
<li><code>Rdy(_R)</code>：
<ul>
<li>指令的第一個 source register 是否已經準備完成，其值為 <code>SrcL_SHIFT</code> 的 LSB</li>
</ul>
</li>
<li><code>SrcR</code>、<code>SrcR_M</code>、<code>SrcR_SHIFT</code>、<code>Rdy(_L)</code> 代表指令的第二個 source register，其定義皆與第一個 source register 相同</li>
<li><code>SrcR_imm_valid</code>：
<ul>
<li>指令的第二個 source register 是否為 immediate value</li>
</ul>
</li>
<li><code>DELAY</code>：
<ul>
<li>記錄該指令執行時所需的 cycles 數，在 decode stage 就可以得知</li>
<li>當該指令被 select 電路選中時，除了將其 destination register 的編號 broadcast 到 tag bus 上，同時也會將此 <code>DELAY</code> 值一起 broadcast 到 delay bus 上</li>
<li><code>DELAY</code> 值會被 encoded，如該指令執行需要 3 個 cycles，且 <code>DEALY</code> bit width 為 8 bits，那麼 <code>DELAY</code> 值就會被 encoded 為 <code>2’b11111100</code> (i.e. <code>bit[7:2]</code> 皆被設成 <code>1</code>)</li>
<li><code>DELAY</code> 的 bit width 由 FU 所有支援的指令中，執行所需 cycles 數最長的那個指令來決定</li>
</ul>
</li>
<li><code>ROB_ID</code>：
<ul>
<li>這條指令在 ROB 中的 index，用來表示該指令的年齡，使 select 電路可以實現 oldest-first 仲裁</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="853---推測喚醒">8.5.3 - 推測喚醒</h2>
<ul>
<li>
<p>先前的 wake-up 機制都是建立在指令的執行 cycles 數都是可以預知的前提，才可以替每個指令指定 <code>DELAY</code> 值，但實際上，還有很多指令的執行 cycles 數是沒辦法提前預知的：</p>
<ul>
<li>Load 指令：
<ul>
<li>Load 指令的執行 cycles 數取決於 D-Cache 是否 hit，甚至是 L2 Cache、L3 Cache 和 DRAM 的時間
<ul>
<li>DRAM 由於自身的結構 (e.g. queuing delay)，從其讀取資料所需的時間並不是一個固定的值</li>
</ul>
</li>
</ul>
</li>
<li>在某些處理器中，定義了一些特殊的情況，如：
<ul>
<li>PowerPC 603 CPU，當被乘數的值比較小時，乘法的結果可以提前被計算出來，稱為 early out</li>
<li>Intel Core2 CPU 對於除法操作也存在 early out</li>
<li>因此這些指令的執行 cycles 數並不是一個固定的值</li>
</ul>
</li>
</ul>
</li>
<li>
<p>對於這些執行 cycles 數不固定的指令，一個最簡單的處理方式，就是等這些指令執行完時，才 wake up 其他相關的指令</p>
</li>
<li>
<p>例如，針對 load 指令：</p>
<ul>
<li>
<p>當 load 指令 A 執行完畢後，才 wake up 指令 B，假設 D-Cache hit，那麼指令 B 就需要 delay 5 個 cycles</p>
<ul>
<li>如果在這之間找不到足夠的指令來執行，就會導致處理器執行效率的下降</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%206.png"></p>
</li>
<li>
<p>由於 D-Cache 是否 hit 可以在資料被讀出前得知，因此可以將 pipeline 改為：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%207.png"></p>
<ul>
<li><code>Cal Addr</code>：計算並得到 load address</li>
<li><code>TLB/Tag</code>：得知 D-Cache 是否 hit/miss
<ul>
<li>在此 cycle 就提前 wake up 相關的指令，減少所需的 delay cycles 數，指令 B 只需 delay 3 個 cycles</li>
</ul>
</li>
<li><code>Data</code>：讀出資料</li>
</ul>
</li>
<li>
<p>D-Cache hit 的機率應該是很高的，那麼可以進一步的假設 D-Cache 總是 hit，將 pipeline 改為：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%208.png"></p>
<ul>
<li>
<p>在 Cal Addr 就 wake up 相關的指令，並在 Bypass stage 就可以夠過 bypassing network 得到 load 指令的結果，指令 B 只需 delay 2 個 cycles</p>
</li>
<li>
<p>然而，如果 D-Cache miss 了，那麼就沒辦法透過 bypassing network 得到 load 指令的結果，指令 B 也就無法被執行</p>
<ul>
<li>此外，指令 B 也無法就此停住等待結果，因為指令 B 已經佔用了 FU 了，會導致其他的指令無法被執行</li>
</ul>
</li>
<li>
<p>因此，最好的方法就是直接將指令 B 重新放回 Issue Queue 中，並重新參與仲裁，讓 FU 繼續執行其他的指令</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%209.png"></p>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>讀取 L1 Cache、L2 Cache、甚至是 L3 Cache 所需要的 cycles 數是固定的 (假設皆為 hit 的情況)，所以可以使用上述 delay wake-up 的方法</p>
</li>
<li>
<p>然而，對於讀取時間不固定的情況，如讀取 DRAM，就只能等到從 DRAM 中讀回資料後，才能 wake up 相關的指令</p>
<ul>
<li>這種方式會需要比較多的 delay cycles，性能較差</li>
<li>但如果在這之間可以找到足夠的指令來執行，就可以降低性能上的損失</li>
</ul>
</li>
<li>
<p>針對像 load 指令這種執行所需 cycles 數不確定的指令，如果等到指令的結果被計算出來後，才 wake up 與其相關的指令，會降低處理器的性能，因此需要使用預測的方法來預測指令執行所需的 cycles 數，在指令得到結果之前，就 wake up 相關的指令，這種方法就稱為 <code>speculative wake-up</code>：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%2010.png"></p>
</li>
<li>
<p>既然是預測，就有可能會預測錯誤，因此就需要恢復預測錯誤前的狀態</p>
<ul>
<li>對於 load 指令，由於 D-Cache 的 hit rate 是很高的，因此可以假設所有的 load 指令的 D-Cache 都是 hit 的；當 load 指令被 select 指令選中後，就可以依照其最短的執行 cycles 數，wake up 與其相關的指令</li>
<li>當 load 指令真的存取 D-Cache 時，如果此時發現了 D-Cache miss，就代表預測錯誤，需要恢復預測錯誤前的狀態：
<ul>
<li>被 load 指令 wake up 的 source registers 都必須重新被設為 not ready</li>
<li>對於已經離開 Issue Queue 的指令，還需要將其從 pipeline stage 中給 flush 掉，並重新放回 Issue Queue 中，並等待 load 指令重新 wake up 它們
<ul>
<li>此時 load 指令可以繼續預測 L2 Cache 是 hit 的，依照 L2 Cache hit 時所需的執行 cycle 數，依照該 cycles 數 wake up 其相關的指令</li>
<li>如果此時預測也失敗，那麼就需要依照同樣的方式，恢復預測錯誤前的狀態</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Load 指令被 select 電路選中後，需要等待 2 個 cycles 才可以 wake up 相關的指令，在這 2 個 cycles，select 電路可以選擇和 load 指令不相關的指令，這 2 個 cycles 稱為 <code>Independent Window (IW)</code>；此外，從 load 指令等待 2 個 cycles 後開始將相關的指令 wake up，直到執行時發現是否為 D-Cache hit/miss，中間共間隔了 2 個 cycles，這 2 個 cycles 稱為 <code>Speculative Window (SW)</code>：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%2011.png"></p>
<ul>
<li>SW 中的指令不一定和 load 指令存在相關性
<ul>
<li>如果被 load 指令 wake up 的指令並沒有被 select 電路選中，SW 中的指令就不會使用 load 指令的結果</li>
</ul>
</li>
<li>IW 中的指令一定和對應的 load 指令不存在相關性，但仍有可能與其他的 load 指令存在相關性
<ul>
<li>
<p>因為兩道 load 指令，其 IW 和 SW 有可能是重疊的：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%2012.png"></p>
<ul>
<li>Load 3 指令的 IW 和 Load 1 指令的 SW 重疊</li>
<li>假設在 TLB/Tag stage 就可以得知 D-Cache 是 hit 或 miss，那麼在 <em>cycle 5</em> 的時候，如果 Load 1 指令發生了 D-Cache miss，那麼就需要將與其相關的指令 (i.e. 第四和第五條指令) 從 pipeline 中給 flush 掉，並重新放回 Issue Queue 中，等待重新被 wake up ⇒ 這個過程稱為 <code>replay</code>
<ul>
<li><strong>因為採用 speculative wake-up，預測失敗時，與其相關的指令是沒辦法獲得該指令的執行結果的，因此需要將與其相關的指令 replay</strong></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>一條指令被 select 電路選中後，可以選擇：
<ol>
<li>直接從 Issue Queue 離開</li>
<li>繼續停留在 Issue Queue 中，直到被允許時才離開</li>
</ol>
<ul>
<li>這兩種設計方式會需要使用不同狀態的恢復方法，產生不同的執行效率和硬體消耗</li>
<li>常用的兩種方法：
<ul>
<li><code>Issue Queue Based Replay</code></li>
<li><code>Replay Queue Based Replay</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>Issue Queue Based Replay：</strong></p>
<ul>
<li>
<p>採用這種設計，指令被 select 電路選中後，並不會馬上離開 Issue Queue，只有在該條指令確認可以被正確執行後 (i.e. 不需要 replay)，才會允許該條指令離開 Issue Queue</p>
<ul>
<li><span id="issued-flag"></span>
需要在 Issue Queue 中新增一個 <a href="#issued-flag"><code>issued</code></a> flag，標記該指令已經被 select 電路選中，但還沒離開 Issue Queue
<ul>
<li>當 <code>issued</code> flag 為 <code>1</code> 時，就不會繼續向 select 電路發出 request 訊號請求仲裁</li>
</ul>
</li>
<li>當這條指令需要 replay 時，會將 <code>issued</code> flag 清為 <code>0</code>，這樣它就可以繼續向 select 電路發出 request 訊號請求仲裁了</li>
</ul>
</li>
<li>
<p>實際上，有很多情況都會需要 replay 指令：</p>
<ul>
<li>
<p>Load 指令發生 D-Cache miss</p>
</li>
<li>
<p>採用 interleaving 架構的 D-Cache 發生了 bank 衝突</p>
</li>
<li>
<p>Store/load 指令的違例</p>
<ul>
<li>存取<strong>相同位址</strong>的 store、load 指令，結果 load 指令在 store 指令之前先被執行了
<ul>
<li>i.e. load 指令讀到了 store 指令前的結果</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Load/load 指令的違例：</p>
<ul>
<li>存取<strong>相同位址</strong>的兩條 load 指令，結果後面的 load 指令比前面的 load 指令先被執行了
<ul>
<li>在某些 multi-core 的處理器中，需要保持原始的程式執行順序</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>上述的情況都必須將與其相關的指令 replay，此時最簡單的方法就是讓所有的指令被 select 電路選中時，都不要馬上離開 Issue Queue，只有在確定指令可以被正確執行時，才將該指令從 Issue Queue 中移除</p>
</li>
<li>
<p>什麼時候才能確定指令可以被正確執行呢?</p>
<ul>
<li>如果是完全 out-of-order 來執行 load 和 store 指令：
<ul>
<li>即使 load 指令是 D-Cache hit，甚至 load 指令已經執行完畢，但只要 load 指令還沒有 retire，都有可能會發生：
<ul>
<li><strong>store/load 指令違例：</strong>
<ul>
<li>load → store 同一個位址，結果 store 指令比 load 指令先被執行了，而導致 load 指令的結果是錯誤的</li>
</ul>
</li>
<li><strong>load/load 指令違例：</strong>
<ul>
<li>load → load 同一個位址，結果比較年輕的 load 指令比比較老的 load 指令先被執行了
<ul>
<li>在 multi-core 的環境下，有可能兩條 load 指令之間會有另一個 CPU 的 store 指令寫入了同一個位址，因此兩條 load 指令的執行順序應該要保持 in-order</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>此時需要將 load 指令和與其相關的指令都 replay (同時把 <code>issued</code> flags 重新設回 <code>0</code>)
<ul>
<li>P.S. store → load 同一個位址，結果 load 指令比 store 指令先被執行了，這種情況<strong>並不算是指令違例，因此不需要 replay</strong>，因為這只代表 load 指令可以直接從 store buffer 中讀取 store 指令的資料 (需等待 store 指令執行完畢)，不需存取 D-Cache，但是 store 指令和 load 指令之間的執行仍然要保持 in-order
<ul>
<li>但如果 load 指令需要的資料寬度大於 store 指令的資料寬度 (e.g. 32-bit load vs. 8-bit store) 時，代表 load 指令所需要的資料有一部分存在 store buffer 中，另外一部份存在 D-Cache 中，load 指令就沒辦法在同一個 cycle 內得到其所需的資料了
<ul>
<li>此時，仍需將 load 指令和與其相關的指令都 replay (同時把 <code>issued</code> flags 重新設回 <code>0</code>)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>因此，只有在一條指令 retire 後，才能保證其是正確執行的，此時該指令才可以離開 Issue Queue</li>
</ul>
</li>
<li>如果是完全 in-order 或是 partially out-of-order 來執行 load 和 store 指令，則不會出現上述的違例情況
<ul>
<li>此時只有 load 指令的 SW 中的指令才有可能需要 replay <strong>(load 指令本身不需要 replay)</strong></li>
<li>因此，只需等到 load 指令確定是 D-Cache hit/miss 的結果後，就可以決定 SW 中的指令是否可以離開 Issue Queue 了
<ul>
<li>D-Cache hit ⇒ SW 中的指令不需要 replay ⇒ load 指令和其 SW 中的指令可以離開 Issue Queue</li>
<li>D-Cache miss ⇒ SW 中的指令需要 replay ⇒ SW 中的指令無法離開 Issue Queue</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>優點：</p>
<ul>
<li>設計複雜度比較低</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>由於指令被 select 電路選中後，並不會馬上離開 Issue Queue，只有在該條指令確認可以被正確執行後 (i.e. 不需要 replay)，才會允許該條指令離開 Issue Queue；然而實際上由於 D-Cache hit rate 是很高的，且發生 store/load 指令等違例的機率並不高，所以大部分指令被 select 電路選中後，其實並不需要 replay，但這些指令仍佔據著 Issue Queue 的空間，直到確認其不需要 replay 為止，導致 Issue Queue 中實際可用的 entries 數減少
<ul>
<li>增加 Issue Queue 的空間則會增加 select 和 wake-up 電路的 latency，因此無法無上限地增加</li>
</ul>
</li>
</ul>
</li>
<li>
<p>由於上述的缺點，針對<em>與 load 指令相關的指令</em>，可以採取適當的折衷：</p>
<ul>
<li>一旦 load 指令在執行階段得到 <strong>D-Cache hit</strong> 的結果，就直接釋放與其相關指令 (有被 select 電路選中的指令) 的 Issue Queue entries
<ul>
<li>優點：
<ul>
<li>這樣可以最大限度的減少指令對 Issue Queue 的佔用</li>
</ul>
</li>
<li>然而，這樣的折衷方式會導致當發生 store/load 指令違例，或是 load/load 指令違例時，其 replay 的方式會有所不同 (P.S. 只有採用完全 out-of-order 來執行 load 和 store 指令在 D-Cache hit 後還有可能會發生違例)：
<ul>
<li>由於很多需要 replay 的指令已經離開 Issue Queue 了，此時由於 Issue Queue 中有可能已經沒有多餘的空間了，因此就沒辦法將這些指令重新加回 Issue Queue 中，且有可能會發生 deadlock 的情況
<ul>
<li>需要被 replay 的指令因為 Issue Queue 中沒有空間，因此無法被 replay，導致那些依賴這些 replay 指令的指令，也無法被 wake up 來釋放 Issue Queue 的空間</li>
</ul>
</li>
<li>因此一旦需要 replay，就必須 flush 掉這些需要被 replay 的指令，並從 I-Cache 中重新讀取這些指令進 pipeline；這樣會損失一些性能，但這就是折衷的方法</li>
</ul>
</li>
</ul>
</li>
<li>但當 load 指令在執行階段得到 <strong>D-Cache miss</strong> 的結果時，一個比較簡單的方法就是將所有在 load 指令之後被 select 電路選到的指令都重新”放回” Issue Queue 中 (P.S. 只是將 <code>issued</code> flag 重新設回 <code>0</code>，因為這些指令還沒真的離開 Issue Queue)
<ul>
<li>
<p>然而，考量到 IW 的存在，甚至是 SW 中的指令，並不一定都與 load 指令存在相關性，這些原本並不需要 replay 的指令也會一併被 replay</p>
</li>
<li>
<p>這種方法稱為：<code>Non-Selective Replay</code></p>
</li>
<li>
<p>優點：</p>
<ul>
<li>實做比較簡單</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>由於不用 replay 的指令也 replay 了，因此在一定程度上損失了一些性能</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%2013.png"></p>
<ul>
<li>假設 <code>LD</code> 指令在 Data stage 得知 D-Cache miss，則需將 <code>LD</code> 指令之後被 select 電路選中的指令，全部從 pipeline 中 flush 掉，並重新”加回” Issue Queue 中等待重新被 wake up</li>
<li>但其實只有 <code>OR</code> 和 <code>SUB</code> 指令跟 <code>LD</code> 是有相關的，其他的指令其實沒有必要一起 replay</li>
</ul>
</li>
</ul>
</li>
<li>改進方法：
<ul>
<li>由於 IW 中的指令一定是與 load 指令無關的，因此處於 <code>Cal Addr</code> 和 <code>TLB/Tag</code> 這兩個 stages 的指令其實是可以不用 replay 的，只需將 <code>Select</code> 和 <code>RF</code> 這兩個 stages 的指令 replay 即可</li>
</ul>
</li>
</ul>
</li>
<li>
<p>如何判斷指令所操作的暫存器是否與 load 指令存在相關性?</p>
<ul>
<li>相關性分為兩種：
<ul>
<li>直接相關性
<ul>
<li>如上例的 <code>OR</code> 指令</li>
</ul>
</li>
<li>間接相關性
<ul>
<li>如上例的 <code>SUB</code> 指令
<ul>
<li><code>SUB</code> 指令與 <code>OR</code> 指令相關，<code>OR</code> 指令又與 <code>LD</code> 指令相關</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>可以使用一個 <code>load-vector</code> 來記錄 pipeline 中所有的 load 指令，每一個 bit 表示一條 load 指令，這個值的寬度等於 pipeline 中最多可以容納的 load 指令個數
<ul>
<li>
<p>每個 architecture register (或是經過 register renaming 後，physical register)，都會分配一個 load-vector</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%2014.png"></p>
<ul>
<li>假設由左至右：bits[0] → bits[4]</li>
<li>第一條 <code>load</code> 指令佔據 load-vector 一個 bit：<code>R1</code> ⇒ <code>2’b10000</code></li>
<li>第二條 <code>ADD</code> 指令，因為依賴 <code>load</code> 指令的 <code>R1</code>，因此其 load-vector = 第一條 load 指令的 load vector：<code>R1</code> ⇒ <code>2’b10000</code></li>
<li>第三條 <code>load</code> 指令佔據 load-vector 額外一個 bit：<code>bits[1]</code>，且因其也依賴第一條 load 指令的 <code>R1</code>，因此其 load vector 的值等於兩者 OR 後的結果：<code>R3</code> ⇒ <code>2’b01000 | 2’b10000</code> = <code>2’b11000</code></li>
<li>第四條 <code>SUB</code> 指令依賴第三條 <code>load</code> 指令的 <code>R3</code>，因此其 load-vector = 第三條 <code>load</code> 指令的 load-vector：<code>R4</code> ⇒ <code>2’b11000</code></li>
<li>第五條 <code>load</code> 指令佔據 load-vector 額外一個 bit：<code>bits[2]</code>：<code>R5</code> ⇒ <code>2’b00100</code></li>
<li>第六條 <code>ADD</code> 指令依賴 <code>SUB</code> 的 <code>R4</code>，以及第五條 <code>load</code> 指令的 <code>R5</code>，因此其 load vector 的值等於兩者 load-vector 值 OR 後的結果：<code>R7</code> = <code>2’b11000 | 2’b00100</code> = <code>2’b11100</code></li>
</ul>
</li>
<li>
<p>每當 load 指令發生 D-Cache miss 時，就可以透過查詢 Issue Queue 中每個指令的 source registers 的 load-vector，是否有 bit 與 load 指令的 load-vector 重疊，進而得知是否與 load 指令存在相依性</p>
</li>
<li>
<p>缺點：</p>
<ul>
<li>如果 pipeline 深度比較深，可以容納的 load 指令數跟著增加，所需的 load-vector 的寬度也會需要跟著增加，增大硬體面積
<ul>
<li>Load-vector 只適合 pipeline 深度不深的 CPU</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>每條 load 指令可以改用 5 個 bits 的 <code>Load Position Value (LPV)</code> 來記錄 load 指令在 pipeline 中哪個 stage (<code>Select</code>、<code>RF</code>、<code>Cal Addr</code>、<code>TLB/Tag</code>、<code>Data</code>，假設 load 指令在 FU 中執行需要 3 個 cycles)
<ul>
<li>
<p>每當一條 load 被 select 電路選中時，都會將其 LPV 重置為 <code>2’b10000</code>，表示這條 load 指令目前處在 <code>Select</code> stage</p>
<ul>
<li>在 <code>Select</code> stage，load 指令的 destination register 編號都會與 Issue Queue 中所有指令的 source registers 編號做比較，如果相符，則每個相符的 source register 都會得到該 load 指令的 LPV</li>
<li>每條指令在被 select 電路選中並 wake up 其他指令時，如果被 wake up 的指令的 source registers 與被 select 電路選中指令的 destination register 相同，則被 select 電路選中指令的 destination register 的 LPV 值也會一併被複製給這些被 wake up 的指令的 source registers，以識別哪些指令的 source registers 與 load 指令存在間接相關性</li>
<li>之後的每個 cycle，所有 source registers 的 LPV 都會 right shift 一位，用來追蹤 load 指令在 pipeline 中的 stage</li>
<li>同時，load 指令的 LPV 每個 cycle 也會 right shift 一位，記錄其在 pipeline stage 中哪個 stage</li>
</ul>
</li>
<li>
<p>當 load 指令抵達 Data stage，發現 D-Cache miss 時，在 Issue Queue 中那些 LPV 的最低位 (<code>bits[0]</code>) 為 <code>1</code> 的 source registers，都與這條 load 指令存在直接或間接的相關性，需要 replay 並將這些 source registers 在 Issue Queue 中對應的 <code>Rdy</code> bit 設為 <code>0</code></p>
</li>
<li>
<p>範例：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%2015.png"></p>
<ul>
<li><code>LD</code> 指令被 select，wake up <code>XOR</code> 指令，將 <code>LD</code> 的 LPV：<code>2’b10000</code> 複製給 <code>XOR</code> 指令的 source register：<code>R2</code></li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%2016.png"></p>
<ul>
<li><code>XOR</code> 指令被 select，wake up <code>ADD</code> 和 <code>SLL</code> 指令，將 <code>XOR</code> 此時的 LPV：<code>2’b00010</code> 複製給 <code>ADD</code> 指令和 <code>SLL</code> 指令的 source register：<code>R3</code></li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%2017.png"></p>
<ul>
<li><code>ADD</code> 指令和 <code>SLL</code> 指令被 select，<code>ADD</code> 指令 wake up <code>AND</code> 指令，並將其 LPV：<code>2’b00001</code> 複製給 <code>AND</code> 指令的 source register：<code>R4</code></li>
<li>此外，<code>AND</code> 指令還依賴 <code>LD2</code> 指令，因此也會將 <code>LD2</code> 指令的 LPV：<code>2’b00100</code> 複製給 <code>AND</code> 指令的 source register：<code>R6</code></li>
<li>此時 <code>LD1</code> 已經進到 <code>Data</code> stage 了 (假設在 <code>Data</code> stage 才可以得知 D-Cache hit 或是 miss 的即果)，所有與 <code>LD1</code> 指令有相關性的 source registers 其 LPV 最低位 (<code>bits[0])</code> 皆為 <code>1</code>
<ul>
<li>如果 D-Cache hit，那就繼續執行即可</li>
<li>如果 D-Cache miss，則必須：
<ul>
<li>將這些相關的 source register 在 Issue Queue entry 中的 <code>Rdy</code> bit 設為 <code>0</code></li>
<li>Replay SW 中與 load 指令相關的指令</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>優點：</p>
<ul>
<li>LPV 所需的 bits 數不會隨著 pipeline stages 的深度而改變，只會與 load 指令執行所需的 stages 個數有關</li>
<li>相較於 load-vector 的方法，硬體面積比較小，執行速度也比較快</li>
</ul>
</li>
<li>
<p>當一個 cycle 可以執行多條的 load 指令時，那麼在 Issue Queue 中每條指令 source register 的 LPV 電路個數就需要做相對應的複製</p>
<ul>
<li>例如：每個 cycle 可以執行 2 條 load 指令，那麼每個 source register 就需要 2 個 LPV 分別記錄這兩條 load 指令的 LPV</li>
<li>不過由於 D-Cache ports 數的限制，因此每個 cycle 可以執行的 load 指令個數並不會過多，因此此方法並不會對增加太多的硬體面積</li>
</ul>
</li>
<li>
<p>此外，LPV 也可以作為 select 電路選擇指令的依據：</p>
<ul>
<li>如果 LPV 為 <code>0</code>，代表該指令的 source register <strong>沒有與任何 load 指令相關</strong>，select 電路可以<strong>優先選擇該指令</strong></li>
<li>反之，如果 LPV 不為 <code>0</code>，代表該指令的 source register <strong>與某個 load 指令相關</strong>，select 電路可以<strong>優先選擇其他沒有相關性的指令</strong></li>
</ul>
</li>
<li>
<p>這種只 replay 與 load 指令相關的指令的方法，稱為：<code>Selective Replay</code></p>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Non-Selective Replay 總結：</p>
<ul>
<li>Load 指令在執行階段發現 D-Cache miss 時，就將 SW 中<strong>所有</strong>指令都 replay，並將 <code>issued</code> flag 清為 <code>0</code>；此外，與 load 指令相關的 source registers 的 <code>Rdy</code> bit 也會被清為 <code>0</code>
<ul>
<li>由於與 load 指令不相關的指令其 source registers 的 <code>Rdy</code> bit 都仍為 <code>1</code>，因此在下一個 cycle 馬上就可以向 select 電路發出 request 訊號請求仲裁</li>
<li>與 load 指令相關的指令則要等之後再次被 wake up 後才可以重新參加仲裁</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Selective Replay 總結：</p>
<ul>
<li>
<p>透過 LVM，當 load 指令在執行階段發現 D-Cache miss 時，<strong>只將 SW 中與 load 相關</strong>的指令 replay</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%2018.png"></p>
<ul>
<li>只需 replay 與 load 指令相關的 <code>OR</code> 和 <code>SUB</code> 指令</li>
</ul>
</li>
</ul>
</li>
<li>
<p>基於 Issue Queue replay 的方法，很多指令在被 select 電路選擇後沒辦法馬上離開 Issue Queue，因此其最大的缺點就是降低了 Issue Queue 中可以使用的空間，降低了處理器的執行效率</p>
</li>
</ul>
</li>
<li>
<p><strong>Replay Queue Based Replay：</strong></p>
<ul>
<li>
<p>採用這種設計，指令被 select 電路選中後，會馬上離開 Issue Queue，並存入 <strong>Replay Queue</strong> 中</p>
<ul>
<li>當一條指令 retire 時，就可以從 Replay Queue 中移除</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part2/image%2019.png"></p>
</li>
<li>
<p>舉例來說，當 load 指令發生 D-Cache miss 時：</p>
<ul>
<li>需將與 load 指令相關的指令，從 pipeline 中給 flush 掉</li>
<li>將這些相關的指令從 Replay Queue wake up，且這些指令被 select 電路仲裁的 priority，比其他在 Issue Queue 中的指令來得高，以便這些需要 replay 的指令可以優先重新送入 FU 中執行</li>
</ul>
</li>
<li>
<p>Replay Queue Based Replay 同樣支援處理：</p>
</li>
<li>
<p>優點：</p>
<ul>
<li>提高了 Issue Queue 的使用效率</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>Replay Queue 會佔用額外的硬體空間</li>
<li>Replay Queue 也需一起參與 select 電路的仲裁和 wake up，因此設計較複雜</li>
</ul>
</li>
</ul>
</li>
<li>
<p>從 select stage 到 execute stage 所需的 cycles 數越多，需要 replay 的指令也就越多，因此：</p>
<ul>
<li>對於 <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture">data-capture</a> 架構的設計：
<ul>
<li>由於其 select stage → execute stage 的時間比較短，需要 replay 的指令個數也就比較少
<ul>
<li>比較適合搭配 Issue Queue Based Replay 的設計</li>
</ul>
</li>
</ul>
</li>
<li>對於 <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture">non-data-capture</a> 架構的設計：
<ul>
<li>由於指令在 issued 後會讀取 physical register file，所以其 select stage → execute stage 的時間比較長，需要 replay 的指令個數也就比較多
<ul>
<li>採用 Issue Queue Based Replay 的設計會導致 Issue Queue 的可用空間變少</li>
<li>比較適合搭配 Replay Queue Based Replay 的設計</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>&lt;超標量處理器概覽&gt; 第 8 章 - 發射 (Part 1)</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch8-part1/</link>
      <pubDate>Wed, 09 Apr 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch8-part1/</guid>
      <description>&lt;h1 id=&#34;81---概述&#34;&gt;8.1 - 概述&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Issue 就是將符合一定條件的指令從 &lt;code&gt;Issue Queue&lt;/code&gt; 中選出來，並送到 FU (Function Unit) 中執行的過程&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Queue 會依照一定的規則，選擇那些 source registers 都已經準備好的指令，將其送到 FU 中執行，這個過程就稱為：&lt;code&gt;Issue&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Issue Queue 也被稱為 &lt;code&gt;Reservation Station&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;對於 in-order core，指令會按照程式原始順序加進 Issue Queue 中，此時 Issue Queue 就相當於一個 FIFO&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;對於 out-of-order core，只有少數指令，如 store 及分支指令，才會採用 in-order 的方式執行，對其他大部分的指令，都是採用 out-of-order 的方式執行&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;指令到了 Issue Queue 後，只要 Issue Queue 中任一條指令的 operands 都準備好了，且滿足 issue 的條件，就可以將其送到 FU 中執行&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Issue Queue 設計的好壞，會直接影響處理器的 concurrency&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Issue stage 的硬體比較複雜，一般它都在 critical path 上，直接影響 CPU 的 cycle time&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="81---概述">8.1 - 概述</h1>
<ul>
<li>
<p>Issue 就是將符合一定條件的指令從 <code>Issue Queue</code> 中選出來，並送到 FU (Function Unit) 中執行的過程</p>
<ul>
<li>Issue Queue 會依照一定的規則，選擇那些 source registers 都已經準備好的指令，將其送到 FU 中執行，這個過程就稱為：<code>Issue</code></li>
</ul>
</li>
<li>
<p>Issue Queue 也被稱為 <code>Reservation Station</code></p>
</li>
<li>
<p>對於 in-order core，指令會按照程式原始順序加進 Issue Queue 中，此時 Issue Queue 就相當於一個 FIFO</p>
</li>
<li>
<p>對於 out-of-order core，只有少數指令，如 store 及分支指令，才會採用 in-order 的方式執行，對其他大部分的指令，都是採用 out-of-order 的方式執行</p>
</li>
<li>
<p>指令到了 Issue Queue 後，只要 Issue Queue 中任一條指令的 operands 都準備好了，且滿足 issue 的條件，就可以將其送到 FU 中執行</p>
</li>
<li>
<p>Issue Queue 設計的好壞，會直接影響處理器的 concurrency</p>
</li>
<li>
<p>Issue stage 的硬體比較複雜，一般它都在 critical path 上，直接影響 CPU 的 cycle time</p>
<ul>
<li>因此這個 stage 的設計需要一定的折衷，才能在性能和複雜度之間找到一個可以接受的平衡點</li>
</ul>
</li>
<li>
<p>Issue stage 同時也是 in-order 和 out-of-order 的分界點</p>
<ul>
<li>Issue stage 之前的指令都是 in-order 的</li>
<li>Issue stage 之後的指令都是 out-of-order 的，直到到了 commit stage 才會透過 ROB 的資訊來將指令重排回 in-order 的順序</li>
</ul>
</li>
<li>
<p>Issue stage 除了 Issue Queue 外，還有：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image.png"></p>
<ul>
<li>Allocate 電路：用來從 Issue Queue 中找到 free 的 entries，將 register renamed 完的指令加入進 Issue Queue 中</li>
<li>Select 電路 (也稱為 Arbiter 電路)：如果在 Issue Queue 中有多條指令已經準備好了，Select 電路會依照一定的規則，選出最適合的指令送到 FU 中去執行
<ul>
<li>Select 電路是 issue stage 中比較關鍵的元件，會直接影響到 CPU 的執行效率</li>
</ul>
</li>
<li>Wake-up 電路：當一條指令經過 FU 而得到結果時，會通知給 Issue Queue 中所有等待此結果的指令；這些指令對用的 source operands 會被設為 valid 的狀態，這就稱為 wake-up
<ul>
<li>如果一條指令所有的 source operands 都已經準備好了，那該指令就處於 ready 狀態，Select 電路就有可能選擇該指令來 issue</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="811----集中式-centralized-vs-分佈式-distributed">8.1.1 -  集中式 (Centralized) vs. 分佈式 (Distributed)</h2>
<ul>
<li>在 superscalar CPU 中，為了並行執行指令，一般都會有很多的 FU，如有些 FU 負責 integer 的運算，有些 FU 負責乘法與除法運算，有些 FU 負責 memory access
<ul>
<li>如果這些 FU 都共用同一個 Issue Queue，那麼這種架構就稱為 <code>Centralized Issue Queue</code></li>
<li>如果每個 FU 都有一個獨立的 Issue Queue，那麼這種架構就稱為 <code>Distributed Issue Queue</code></li>
</ul>
</li>
<li>Centralized Issue Queue 因為要儲存所有 FU 的指令，因此它的容量需要很大
<ul>
<li>優點：
<ul>
<li>有最大的利用效率，不會浪費 Issue Queue 中任何的空間</li>
</ul>
</li>
<li>缺點：
<ul>
<li>Select 電路和 Wake-up 電路會比較複雜，因為需要從龐大數量的指令中選擇幾條可以被 issued 的指令；這些被選中的指令執行完後還需要 wake up 所有 Issue Queue 中相關的指令，增加了 Select 電路和 Wake-up 電路的面積和 latency</li>
</ul>
</li>
</ul>
</li>
<li>Distributed Issue Queue 由於為每個 FU 都配一個 Issue Queue，因此每個 Issue Queue 的空間可以很小
<ul>
<li>優點：
<ul>
<li>簡化了 Select 電路的設計 (每個 Issue Queue 都有自己的 Select 電路)</li>
</ul>
</li>
<li>缺點：
<ul>
<li>但 Issue Queue 使用率較低，當一個 Issue Queue 滿了以後，即使其他 Issue Queue 還有空間，也無法繼續向其寫入新的指令
<ul>
<li>此時就需要將 pipeline stall，直到這個 Issue Queue 有空出新的空間為止
<ul>
<li>例如：
<ul>
<li>一段時間內執行了大量的 add / sub 指令，則可能由於 add / sub 指令 FU 對應的 Issue Queue 已經滿了而無法處理新的 add / sub 指令
<ul>
<li>這會阻礙新的指令做 register renaming，因為 register renaming stage 仍然是 in-order 的</li>
<li>即使此時別的  FU 的 Issue Queue 仍有空間，也仍需 stall issue stage 之前的 pipeline，降低 Issue Queue 的使用率</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>由於 Issue Queue 是分散的，因此也會增加 Wake-up 電路的複雜度</li>
</ul>
</li>
</ul>
</li>
<li>由於上述兩種設計各有優缺點，因此現代處理器一般都會結合使用上述兩種方法，使某些 FU 共用一個 Issue Queue，某些 FU 有自己獨立的 Issue Queue</li>
</ul>
<h2 id="812---數據捕捉-data-capture-vs-非數據捕捉-non-data-capture">8.1.2 - 數據捕捉 (Data-capture) vs. 非數據捕捉 (Non-data-capture)</h2>
<ul>
<li>
<p>對於 superscalar CPU 而言，還需要考慮在哪個 pipeline stage 讀取 registers 的值</p>
<ul>
<li>何時讀取 registers 對於整個處理器架構有著直接的影響</li>
</ul>
</li>
<li>
<p>一般來說，有兩個時間點可以讀取 registers：</p>
<ul>
<li>
<p><strong>在 issue stage 之前讀取 registers：</strong></p>
<ul>
<li>
<p>這種方法也被稱為<strong>數據捕捉 (Data-capture)</strong> 的架構</p>
</li>
<li>
<p>被 register renamed 後的指令首先會讀取 physical register file，然後將 source registers 的<strong>編號</strong>寫進 Issue Queue，讀到的 source registers 的<strong>值</strong>寫進 <a href="#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture"><strong>payload RAM</strong></a></p>
<ul>
<li>如果有 source register 的值在指令寫進 Issue Queue 中時還沒有被計算出來，則該 register 會被標記為 non-available</li>
<li>在之後的 wake-up stage，可以根據 Issue Queue 中所記錄 source registers 編號，透過 bypassing network 取得結果，不需要再讀取 physical register file</li>
</ul>
</li>
<li>
<p>在 Issue Queue 中，儲存指令 operands 的元件稱為 <strong>payload RAM</strong>：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%201.png"></p>
<ul>
<li>Payload RAM 儲存了指令 source registers 的值，當指令從 Issue Queue 中被 select 電路選中時，可以直接從 payload RAM 中對應的 entry 將 source registers 的值給讀出來，並送到 FU 中執行</li>
<li>當一條指令從 Issue Queue 中被選中的同時，它會 broadcast 其 destination register 的編號，Issue Queue 中的其他的指令的 source registers 都會被比較，一旦發現相等時，就會在 payload RAM 中進行標記，等到被選中的指令被計算完畢後，就會透過 bypassing network 將其結果寫進 payload RAM 對應的 entries 中</li>
<li>Payload RAM 負責”捕捉” FU 計算的結果，因此稱為 <strong>data-capture</strong> 架構</li>
<li>Issue Queue 負責比對 registers 編號是否相等，payload RAM 負責儲存 source registers 的值並捕捉對應 FU 的計算結果</li>
</ul>
</li>
<li>
<p>在 superscalar CPU 中，使用 <code>machine width</code> 來表示每個 cycle 實際可以 decode 和 register rename 的指令個數，使用 <code>issue width</code> 來表示每個 cycle 可以被 issued 的指令個數</p>
<ul>
<li>考慮到由於指令間存在 dependencies，很多時候，沒辦法每個 cycle 都將 machine width 個數的指令 issue 到 FU 中執行，只有使 <code>issue width</code> 大於 <code>machine width</code>，才能最大限度地使用 FU
<ul>
<li>因此，現代高性能處理器都會採用多個 FU，盡可能增加每個 cycle 可以被 issued 的指令個數</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Data-capture 架構需要在 issue stage 前讀取 physical register file，因此 physical register file 需要的 <code>read ports 數量 = machine width x 2</code> (假設每條指令只有兩個 source registers)，是直接與 <code>machine width</code> 相關的：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%202.png"></p>
</li>
</ul>
</li>
<li>
<p><strong>在 issue stage 之後讀取 registers：</strong></p>
<ul>
<li>
<p>這種方法也被稱為<strong>非數據捕捉 (Non-data-capture)</strong> 的架構</p>
</li>
<li>
<p>在 non-data-capture 架構中，register renamed 後的指令並不會去讀取 physical register file，而是直接將 source registers 的編號寫進 Issue Queue 中</p>
<ul>
<li>當指令從 Issue Queue 中被 select 電路選中，會使用 source registers 的編號來讀取 physical register file，將讀到的 source registers 的值一併送到 FU 中執行</li>
<li>由於不需要 payload RAM，因此可以增加執行的速度</li>
</ul>
</li>
<li>
<p>由於指令在 issued 後才會讀取 physical register file，因此 physical register file 所需的 <code>read ports 數量 = issue width x 2</code>，是直接與 <code>issue width</code> 相關的，因此所需 physical register file 的 read ports 數量通常比較多</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%203.png"></p>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>Data-capture 架構：</strong></p>
<ul>
<li>優點：
<ul>
<li>需要的 physical register read ports 數量較少</li>
</ul>
</li>
<li>缺點：
<ul>
<li>需要額外的 payload RAM，硬體面積較大，執行速度較慢</li>
<li>source register 需要經過兩次 reads 和一次 write 的過程，功耗較高
<ul>
<li>如果 source register 的值在指令寫進 Issue Queue 中時已經有被計算出來，那麼就需要將 register 的值從 physical register file 中讀出來，寫進 Issue Queue，然後再從 Issue Queue 中將值讀出來，送到 FU 中</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>Non-data-capture 架構：</strong></p>
<ul>
<li>優點：
<ul>
<li>在 Issue Queue 中不需要儲存 registers 的值，也不需要使用 payload RAM，硬體面積較小，執行速度較快</li>
<li>Source register 只需要經過一次 read 即可，功耗較低
<ul>
<li>從 physical register file 讀出來送進 FU 中</li>
</ul>
</li>
</ul>
</li>
<li>缺點：
<ul>
<li>Physical register file 需要比較多的 read ports</li>
</ul>
</li>
</ul>
</li>
<li>
<p>這兩種架構的選擇，決定了 ROB 的實做方式：</p>
<ul>
<li>
<p>當<a href="../superscalar-overview-ch7/#721---%E4%BD%BF%E7%94%A8-rob-%E4%BE%86%E5%AF%A6%E5%81%9A%E6%9A%AB%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D">使用 ROB 做 register renaming</a> 時 (i.e. <strong>使用 ROB 當作 PRF</strong>)，一般都會配合使用 <a href="#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture"><strong>data-capture</strong></a> 的架構</p>
<ul>
<li>在一條指令 retire 前，其 architecture register 的計算結果會被存在 ROB 中，register renaming table 的 pointer 會指向 ROB；當這條指令 retire 時，其 architecture register 的計算結果會從 ROB 移到 ARF，RAT 的 pointer 也會一併更新指向 ARF</li>
<li>採用 data-capture 架構在讀取 source register 的值寫進 Issue Queue 時，可以直接透過 RAT 找到對應的 physical register，因此不用考慮位置的變化</li>
<li>反之，採用 non-data-capture 的架構，當指令從 Issue Queue 中被 select 電路選中，要讀取 PRF 來將 source registers 的值送到 FU 中執行時，並沒辦法直接使用 Issue Queue 中記錄的 source register 的編號 (physical register number) 來讀取 ROB 取得 source register 的值 (因為計算結果有可能已經被搬進 ARF 中了），因此需要額外的處理</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="813---壓縮-vs-非壓縮">8.1.3 - 壓縮 vs. 非壓縮</h2>
<ul>
<li>根據 Issue Queue 的工作方式，又可將其分為<strong>壓縮 (Compressing)</strong> 和<strong>非壓縮 (Non-compressing)</strong> 兩種架構</li>
<li><strong>Compressing Issue Queue：</strong>
<ul>
<li>
<p>每當一條指令被選中並從 Issue Queue 中移除時，會出現一個”空格”，這條指令上面的所有指令都會下移一格，將這條指令的空格給補上：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%204.png"></p>
</li>
<li>
<p>透過這種方式，可以保證 Issue Queue 中的 empty entries 都是在 Issue Queue 中的 tail</p>
<ul>
<li>當有新的指令寫入時，只需將其寫入 Issue Queue 中的 tail 即可</li>
</ul>
</li>
<li>
<p>要實現這樣的架構，需要在每個 Issue Queue 的 entry 前面加上 multiplexer，用來移動一個 entry 的內容</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%205.png"></p>
</li>
<li>
<p>優點：</p>
<ul>
<li>Allocation 電路較簡單，Issue Queue 中的 empty entries 都是在 Issue Queue 中的 tail，只需使用 Issue Queue 的 write pointer 即可得知指令寫入 Issue Queue 的位置</li>
<li>Select 電路設計比較簡單
<ul>
<li>
<p>通常為了讓處理器可以最大限度的同時執行多條指令，一般都是從所有準備好的指令中，優先選擇最老的指令到 FU 執行，也就是 <code>oldest-first</code> 方法</p>
<ul>
<li>最老的指令通常會與其他指令之間有更多的 RAW dependencies，因此可以 wake up 更多的指令</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%206.png"></p>
</li>
<li>
<p>Compressing Issue Queue 中的指令已經是照著 oldest → newest 的順序排好的，因此 select 電路可以很容易依照 oldest-first 的方法選出指令來執行</p>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>Issue Queue 的設計較複雜
<ul>
<li>需要額外加入 multiplexer 用來移動一個 entry 的內容</li>
</ul>
</li>
<li>Select 電路的 latency 與 Issue Queue 的容量成正比， 如果 Issue Queue 容量越大，就會使 select 電路的 latency 越長
<ul>
<li>雖然可以很容易的找出最老的指令，但因為要跟所有的 Issue Queue entries 比較才能找出要被 issue 的指令，因此 Issue Queue 容量越大，select 電路的 latency 越長</li>
</ul>
</li>
<li>同時間會有多個指令需要被移動，因此功耗也會比較高</li>
</ul>
</li>
</ul>
</li>
<li><strong>Non-compressing Issue Queue：</strong>
<ul>
<li>每當一條指令被選中並從 Issue Queue 中移除時，Issue Queue 中的其他指令並不會被搬移，而是會停留在原本的位置，i.e. 沒有壓縮的過程
<ul>
<li>無法根據指令在 Issue Queue 中的位置來決定指令的新舊</li>
</ul>
</li>
<li>實務上沒辦法把所有 Issue Queue 的指令都掃過一遍，找出最老已經準備好的指令；因此可以採用折衷的方式，從 Issue Queue 的 head 開始尋找，直到找到一條已經準備好的指令就直接選擇它
<ul>
<li>這種 select 電路的實做相對簡單，但由於沒辦法找出 oldest-first 的指令，因此沒辦法獲得最好的效能</li>
</ul>
</li>
<li>優點：
<ul>
<li>指令不用每個 cycle 都需要移動，功耗較低</li>
<li>少了 multiplexer，硬體面積較小</li>
</ul>
</li>
<li>缺點：
<ul>
<li>如果真的要實做 oldest-first 的功能，就需要更複雜的 select 電路設計 (參考：<a href="#84---%E4%BB%B2%E8%A3%81-arbitration">8.4 - 仲裁 (Arbitration)</a>)，會增加 latency</li>
<li>Allocation 電路設計較複雜，需要掃過 Issue Queue 中所有的 entries，才能找到 empty 的 entry 來將指令寫入；尤其 superscalar CPU 每個 cycle 都會寫入多條的指令到 Issue Queue 中，allocation 的電路設計會更為複雜</li>
</ul>
</li>
</ul>
</li>
<li><strong>Compressing Issue Queue</strong> 和 <strong>Non-compressing Issue Queue</strong> 設計各有優缺點，在現實世界的 superscalar CPU 中都有採用</li>
</ul>
<h1 id="82---發射過程的流水線">8.2 - 發射過程的流水線</h1>
<h2 id="821---非數據捕捉-non-data-capture-架構的流水線">8.2.1 - 非數據捕捉 (Non-data-capture) 架構的流水線</h2>
<ul>
<li>
<p>一條在 Issue Queue 中的指令需等到以下條件都成立後，才可以被 issued 到 FU 中執行：</p>
<ul>
<li>這條指令所有的 source registers 都準備好了</li>
<li>這條指令能從 Issue Queue 中被選中，也就是需要被 select 電路選中才能被 issued</li>
<li>需要能從 registers、payload RAM 或 bypassing network 中獲得 source registers 的值</li>
</ul>
</li>
<li>
<p>對於一個 source register 來說，如果被寫到 Issue Queue 時，還是 not-ready 的狀態，等到之後的某個時間，它變成 ready 的狀態，這個過程便稱為 <strong>Wake-up</strong></p>
<ul>
<li>需通過 bypassing network 才可以通知到 Issue Queue 中每個 source registers</li>
</ul>
</li>
<li>
<p>Wake-up 的過程：</p>
<ul>
<li>
<p>最簡單的作法：當一條指令在 FU 得到其運算的結果時，將 Issue Queue 中使用到這個計算結果的所有 source registers 都設為 ready 的狀態：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%207.png"></p>
<ul>
<li>Issue 被分為了 <strong>wake-up</strong> 和 <strong>select</strong> 兩個 stages
<ul>
<li>在 wake-up stage，Issue Queue 中所有得到計算結果的 source registers 都會被改為 ready 狀態</li>
<li>在 select stage，select 電路會從 Issue Queue 中選擇一條最合適的指令 issue 到 FU 中</li>
</ul>
</li>
<li>然後，這樣 RAW 相關的兩條指令，並沒辦法 back-to-back 的執行，中間間隔了 3 個 cycles，執行效率不佳</li>
<li>這種指令執行完才 wake up 相關的指令的方法便是：<strong>Tomasulo</strong></li>
</ul>
</li>
</ul>
</li>
<li>
<p>為了提高執行效率，可以將 wake-up 的過程提前，以便可以 back-to-back 執行指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%208.png"></p>
<ul>
<li>需要透過 bypassing network 才能 back-to-back 執行指令
<ul>
<li>當指令 B 到 execute stage 時，就可以透過 bypassing network 得到指令 A 的執行結果，便可以被 issued 到 FU 中執行了</li>
</ul>
</li>
</ul>
</li>
<li>
<p>實際上，指令 A 的 select 和指令 B 的 wake-up 這兩個操作是在同一個 cycle 中接續執行的</p>
<ul>
<li>
<p>只有當 select 電路選出可以 issue 的指令後，才可以對 Issue Queue 中相關的 source registers 進行 wake up，只有兩個操作是在<strong>同一個 cycle 內完成</strong>，才可以 back-to-back 執行兩條 RAW 的指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%209.png"></p>
</li>
<li>
<p>如果 select 和 wake-up 分為 2 個不同的 cycles，那麼就無法 back-to-back 執行兩條 RAW 的指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2010.png"></p>
<ul>
<li>指令 A 被選中，但要等到下一個 cycle 才能 wake up 指令B，那麼指令 A 和指令 B 之間的執行就會相差一個 cycle</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Select 電路和 wake-up 電路都是相對比較複雜的，將它們放在同一個 cycle 內執行，肯定會使 cycle time 變長，降低 CPU 的頻率；而將這兩個電路分成兩個不同的 cycles，可以降低 cycle time，提昇 CPU 的頻率，但會降低 CPU 的 IPC</p>
<ul>
<li>將 select 和 wake-up 分成兩個不同的 cycles，IPC 約會下降 10% ~15%
<ul>
<li>此外，由於增加了一個 cycle，因此也會帶來其他的負面影響：
<ol>
<li>Miss-prediction penalty 的增加</li>
<li>Cache 存取 cycles 數的增加
<ul>
<li>Cache 存取的時間是固定的，cycle time 變短，cache 存取需要的 cycles 數就變多了</li>
</ul>
</li>
<li>增加了一個 cycle，需要更多的硬體資源，功耗變大</li>
</ol>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Superscalar CPU 中存在多個 FU，每個 FU 的執行時間都是不同的；即使是同一個 FU，不同指令所需的執行時間也不一定相同</p>
<ul>
<li>
<p>當一條指令的 FU 沒辦法在一個 cycle 內完成，pipeline 的 execute stage 的執行就需要超過一個 cycle，有可能就來不及 bypass 結果給 RAW 的指令</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2011.png"></p>
<ul>
<li>
<p>指令 A 是乘法指令，在 FU 中需要 2 個 cycles 才能計算出結果，如果仍在指令 A 被 selected 時同時 wake up 指令 B，等到指令 B 進到 execute stage 時，由於指令 A 的結果在那個時候還沒有被計算出來，指令 B 就沒辦法執行</p>
</li>
<li>
<p>因此，需要將指令 B 的 wake-up delay 一個 cycle，才可以讓指令 B 進到 execute stage 時，透過 bypassing network 得到指令 A 的計算結果</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2012.png"></p>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>除此之外，像是 load/store 指令，其 execute 所需要的 cycles 是取決於 D-Cache 或 Store buffer 是否 hit/miss 的，都需要做特別的處理</p>
</li>
</ul>
<h2 id="822---數據捕捉-data-capture-架構的流水線">8.2.2 - 數據捕捉 (Data-capture) 架構的流水線</h2>
<ul>
<li>
<p>Data-capture 跟 non-data-capture 架構最大的差異就是使用了 payload RAM 來儲存所有指令的 source registers 的值，當指令被 issued 時，可以直接得到 source registers 的值，而不須再去讀取 physical register file</p>
<ul>
<li>相比於 non-data-capture 架構少了 <code>Regfile read</code> 的 stage</li>
</ul>
</li>
<li>
<p>Data-capture 架構同樣需要分為 select 和 wake-up 兩個階段，只不過指令在被 select 電路選中後，不需要再去讀取 physical register，而是可以直接讀取 payload RAM 取得 source registers 的值</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2013.png"></p>
<ul>
<li>一條指令被 select 後 (如：指令 A)，同個 cycle 也可以 wake up 其他有 dependency 的指令 (如：指令 B、C)；同時還會去讀取 payload RAM，得到 source registers 的值
<ul>
<li>Payload 和 wake-up 是在同一個 cycle 中發生的</li>
</ul>
</li>
<li>在得到 source registers 的值後，下一個 cycle 就可以進到 execute stage，將指令送到 FU 中執行</li>
<li>被 wake up 的指令 (如：指令 B、C)，可以在其 execute stage 透過 bypassing network 取得其所依賴的指令的計算結果 (如：指令 B)，也可以從 payload RAM 得到計算結果 (如：指令 C)
<ul>
<li>對於指令 C 來說，在其 execute stage 時，指令 A 已經將其計算結果寫回 payload RAM 了，因此可以直接從 payload RAM 讀取指令 A 的計算結果 (但仍需要將指令 B 的計算結果 bypass 給指令 C)</li>
</ul>
</li>
<li>此設計將 select 和讀取 payload RAM 放到同一個 cycle 內，因此在一個 cycle 內，需要同時讀取 payload RAM，又需要寫入 payload RAM，因此需要使用 multi-port 的設計，讓 cycle time 變得過大
<ul>
<li>如：<code>*Cycle i+2*</code>，需要將指令 A 的計算結果寫回 payload RAM；指令 C 又需要同時讀取 payload RAM 得到指令 A 的計算結果</li>
</ul>
</li>
<li>同時，當 FU 的個數比較多時，FU 的計算結果也需要透過 bypassing network 來傳遞，因此也會佔用不少的硬體支援和過長的執行時間，因此可以進一步將 pipeline 進行細分</li>
</ul>
</li>
<li>
<p>以下的設計將 payload RAM 的存取獨立成一個單獨的 pipeline stage，以避免 multi-port payload RAM 影響 CPU 的 cycle time：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2014.png"></p>
<ul>
<li>此外，在 execute stage，FU 得到計算結果後，也不會在同一個 cycle 就 bypass 結果給其他的指令，而是將 bypass 放到下一個 pipeline stage
<ul>
<li>在 bypass stage 中，會同時將計算結果寫進 payload RAM 以及 bypass 給其他指令</li>
</ul>
</li>
<li>由於指令 C 依賴指令 A 和指令 B 的計算結果，因此需要等到 <code>*Cycle i+1*</code> 才能真的被 wake up</li>
</ul>
</li>
<li>
<p>有些設計甚至還會將 select 和 wake-up 拆分成兩個不同的 stages，但缺點就是 RAW 相關的指令無法 back-to-back 的被執行</p>
</li>
</ul>
<h1 id="83---分配-allocation">8.3 - 分配 (Allocation)</h1>
<ul>
<li>
<p>Allocation 電路其實是屬於 dispatch stage，而不是 issue stage 的</p>
</li>
<li>
<p>採用 <a href="#813---%E5%A3%93%E7%B8%AE-vs-%E9%9D%9E%E5%A3%93%E7%B8%AE">Non-Compressing Issue Queue</a> 設計的 Issue Queue，其 empty entries 的分佈是沒有規律的；對於每個 cycle 可以 decode <em>N</em> 條指令的 superscalar CPU 來說，最理想的狀態就是可以從 Issue Queue 中找到 <em>N</em> 個 empty entries，需要 allocation 電路掃過整個 Issue Queue</p>
<ul>
<li>Allocation 所需要的時間，與 Issue Queue 的容量，以及每個 cycle 要寫入的指令個數成正比</li>
</ul>
</li>
<li>
<p>可以採用一個 free list 來記錄所有 Issue Queue 中 empty entries 的編號，每個 cycle 都從這個 free list 中讀出 <em>N</em> 個編號，就可以找到對應的 Issue Queue empty entries 了</p>
<ul>
<li>這個 free list 可以透過 FIFO 來實現</li>
<li>然而，這種方法仍有一定的複雜度，產生的 latency 也不一定會比較小
<ul>
<li>如果 <em>N</em> 很大，就需要在一個 cycle 內 pop 出 <em>N</em> 個 FIFO entries</li>
</ul>
</li>
</ul>
</li>
<li>
<p>一種折衷的方法，可以直接將 Issue Queue 分成 <em>N</em> 段，從每段找出一個 empty entry</p>
</li>
<li>
<p>如 <em>N = 4</em>：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2015.png"></p>
<ul>
<li>每段都會有一個獨立的 allocation 電路，這樣一個容量為 12 個 entries 的 Issue Queue，每段只需要從 3 個 entries 中找到 1 個 empty entry 即可</li>
<li>每個獨立的 allocation 電路都可以同時工作，所需查找的 entries 數也很少，因此 latency 很小</li>
<li>缺點：
<ul>
<li>
<p>如果一個段中沒有 empty entry 了，那麼就算其他段有多餘的 empty entry，這個段也沒辦法接收新的指令</p>
</li>
<li>
<p>更糟糕的是，在 register renaming stage，指令仍然是 in-order 的，因此如果一條指令沒辦法找到 Issue Queue 的 empty entry，其後的指令就算其對應的段仍有 empty entries，也無法被寫入 Issue Queue 中：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2016.png"></p>
<ul>
<li>指令 A 對應的段已經沒有 empty entries 了，無法被寫入 Issue Queue 中；但由於此時仍是 in-order 的，因此會導致縱使指令 B、C、D 的段有 empty entris，一樣無法被寫入 Issue Queue 中</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Allocation、select、wake-up 是 issue stage 中三個主要的步驟，每個步驟都需要做很多的事情，因此 issue stage 是 superscalar CPU 中工作量比較繁忙的 pipeline stage</p>
</li>
</ul>
<h1 id="84---仲裁-arbitration">8.4 - 仲裁 (Arbitration)</h1>
<h2 id="841---1-of-m-的仲裁電路">8.4.1 - 1-of-M 的仲裁電路</h2>
<ul>
<li>前面的章節有提到，<a href="#813---%E5%A3%93%E7%B8%AE-vs-%E9%9D%9E%E5%A3%93%E7%B8%AE">Non-compressing Issue Queue</a> 需要特別的處理才能實做 oldest-first 的功能，不能單純的由指令在 Issue Queue 中的位置來判斷</li>
<li>越舊的指令，和它存在 dependency 的指令就越多，因此優先 select 最舊的指令，則有機會可以wake up 更多的指令；除此之外，最舊的指令還佔據著處理器的其他資源，如 ROB 和 store buffer… etc，越早執行這些舊的指令，就可以越早釋放這些資源來讓更新的指令使用</li>
<li>要識別出 Issue Queue 中哪些指令是最舊的，就需要知道這些指令的年齡，也就是進到 pipeline 的先後順序
<ul>
<li>In-order core 的年齡資訊很容易判斷，在 pipeline 中先執行的指令肯定比後執行的指令來得老</li>
<li>Out-of-order core 的年齡資訊必須依靠 ROB 來判斷，可以使用指令在 ROB 中的 index 來判斷指令的年齡先後 (指令是從 ROB index 0 開始寫)
<ul>
<li>
<p>ROB 是一個 circular FIFO，因此沒辦法直接拿 ROB 的 index 來判斷，需要做一些處理</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2017.png"></p>
<ul>
<li>
<p>Write pointer 和 read pointer 在同一個 lap 上，可以直接比較 write pointer 和 read pointer 的 index 來識別指令的年齡</p>
</li>
<li>
<p>但當 write pointer 和 read pointer 不在同一個 lap 上 (i.e. write pointer 比 read pointer 多 wrap 了一次)，那就沒辦法透過直接比較 write pointer 和 read pointer 的 index 來識別指令的年齡</p>
</li>
<li>
<p>因此需要額外加 1 個 position bit，每次 write 或 read pointer wrap 時，write pointer 或 read pointer 所指到的 position bit 都會被 toggled (0 → 1、1 → 0)</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2018.png"></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2019.png"></p>
<ul>
<li>當  write pointer 指到的 position bit 與 read pointer 所指到的 position bit <strong>相同</strong>時 (i.e. write pointer 和 read pointer 在同一個 lap 上)，<strong>指令的 ROB index 越小，年齡越老</strong></li>
<li>當  write pointer 指到的 position bit 與 read pointer 所指到的 position bit <strong>不同</strong>時 ****(i.e. write pointer 比 read pointer 多 wrap 了一次)，<strong>指令的 ROB index 越大，年齡越老</strong></li>
</ul>
</li>
<li>
<p>為了記錄年齡資訊，需要將每條指令在 ROB 中的 index (i.e. 年齡) 也一併寫進 Issue Queue 中，這樣在 Issue Queue 中每條指令就有了年齡的資訊，select 電路就可以依據這個資訊，從所有準備好的指令中找到最舊的那個指令</p>
</li>
<li>
<p>如果 Issue Queue 中有 <em>M</em> 個 entry，這種 select 電路就稱為 <strong>1-of-M select 電路</strong></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2020.png"></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2021.png"></p>
<ul>
<li>
<p>Issue Queue 中每條指令都由 1 個 ready bit 來表示該指令是否已經準備好了：</p>
<ul>
<li>兩個指令的 ready bits 皆為 1 時：選擇年紀比較大 (i.e. ROB index 比較小) 的指令</li>
<li>只有一個指令的 ready bit 為 1 時：選擇 ready bit 為 1 的那條指令</li>
<li>兩個指令的 ready bits 皆為 0 時：選擇哪條指令都無所謂，因為下一級也不會被選中</li>
</ul>
</li>
<li>
<p>實際上，可以將每條指令在 Issue Queue 中的 index 一起加入上述的比較電路，這樣就可以在取得年齡值時，同時取得該指令在 Issue Queue 中的 index，進而找到 Issue Queue 中這個年齡值所對應的指令</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2022.png"></p>
<ul>
<li>新增的 multiplexer 是跟原有的同步執行的，因此不會影響到 CPU 的 latency</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="842---n-of-m-的仲裁電路">8.4.2 - N-of-M 的仲裁電路</h2>
<ul>
<li>
<p>多個 FU 有可能會共用同一個 Issue Queue，因此需要在 1 個 cycle 內為每個 FU 都選出一條指令，也就代表需要使用 <strong>N-of-M 的 select 電路</strong></p>
<ul>
<li>N：FU 個數</li>
<li>M：Issue Queue 的容量</li>
</ul>
</li>
<li>
<p>假設一 2-of-M select 電路，如果要遵循 oldest-first 的設計，每個 cycle 需要選出 2 條指令來：</p>
<ul>
<li>需要兩級的 select 電路，第一級 select 電路選出指令後，將該指令標記，這樣第二級 select 電路在挑選時就會排除第一級 select 電路所選出來的指令</li>
<li>但這樣的實做，其 latency 會是 1-of-M select 電路的兩倍，因此不可能擴展到 N-of-M select 電路</li>
</ul>
</li>
<li>
<p>因此，要設計一個 N-of-M select 電路，仍然需要使用折衷的方法，在性能、電路面積和速度之間找到一個平衡點：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2023.png"></p>
<ul>
<li>四個 ALU 共用同一個 Issue Queue，假設 Issue Queue 容量為 M，每個 FU 都有一個專屬的 1-of-M select 電路</li>
<li>Issue Queue 中的指令會依照指令類型，分配給對應的 FU；如果存在相同功能的 FU，則會按照 round-robin 或是 random 的方式分配指令給這幾個 FU</li>
<li>可以透過 demultiplexer 來實現
<ul>
<li>因為 Issue Queue 中每個 entry 都有可能會放不同類型指令，因此每個 FU 的 select 電路都會有 M 個輸入，執行完整的 1-of-M select</li>
</ul>
</li>
<li>整個 N-of-M select 電路的 latency 可以降到跟 1-of-M select 電路一樣 (demultiplexer 的 latency 很小，可以忽略)</li>
</ul>
</li>
<li>
<p>然而，這樣的設計無法實現理想的 N-of-M 功能：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2024.png"></p>
<ul>
<li>
<p><code>LOAD</code>、<code>DIV</code>、<code>MUL</code>、<code>SUB</code>、<code>ADD</code> 指令皆 ready，但只有 <code>LOAD</code>、<code>DIV</code>、<code>ADD</code> or <code>SUB</code> or <code>ADD</code> 其中一條指令可以被 issued，因此 4-of-M 的 select 電路，最後只能 issue 三條指令到 FU 中執行</p>
</li>
<li>
<p>為了解決這樣的問題，可以增加 FU 的數量，如使用兩個相同的 ALU：<code>ALU0</code> 和 <code>ALU1</code></p>
<ul>
<li>但會引入新的問題，一條 ALU 指令究竟要分配給哪個 ALU?</li>
</ul>
</li>
<li>
<p>可以採用一種 round-robin 的分配方法：</p>
<ul>
<li>目前 cycle 寫進 Issue Queue 的指令全部 issue 給 <code>ALU0</code>、下一個 cycle 寫進 Issue Queue 的指令改全部 issue 給 <code>ALU1</code></li>
<li>可以透過一個額外的 1 bit 訊號來標記這個 cycle 指令的 issue 狀態，且這個 1 bit 訊號每個 cycle 都會 toggle (<code>0</code> → <code>1</code>；<code>1</code> → <code>0</code>)
<ul>
<li><code>0</code>：ALU 類型的指令會被 issued 給 <code>ALU0</code></li>
<li><code>1</code>：ALU 類型的指令會被 issued 給 <code>ALU1</code></li>
</ul>
</li>
</ul>
</li>
<li>
<p>不過這仍然無法保證嚴格的 oldest-first：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2025.png"></p>
<ul>
<li><code>XOR</code> 和 <code>AND</code> ⇒ 只有 <code>AND</code> 可以被 issued 進 <code>ALU1</code></li>
<li><code>SUB</code> 和 <code>ADD</code> ⇒ 只有 <code>ADD</code> 可以被 issued 進 <code>ALU0</code></li>
<li>但 <code>SUB</code> 比 <code>AND</code> 還老，但 <code>AND</code> 卻比 <code>SUB</code> 先被執行</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch8-part1/image%2026.png"></p>
<ul>
<li><code>CMP</code>、<code>SUB</code> 和 <code>ADD</code> ⇒ 只有 <code>AND</code> 可以被 issued 進 <code>ALU0</code></li>
<li>雖然 <code>ALU1</code> 也可以用來計算，但此時卻沒有任何指令可以被 issued 進 <code>ALU1</code>，形成浪費</li>
</ul>
</li>
</ul>
</li>
<li>
<p>現代處理器大多一個 cycle 可以執行 4 ~ 6 條指令</p>
</li>
<li>
<p>需要將多個不同類型的指令加以合併使用同一個 FU：</p>
<ul>
<li>加減法、邏輯運算、移位運算 ⇒ 傳統意義上的 ALU</li>
<li>整數乘法、整數除法</li>
<li>浮點數運算</li>
<li>Memory access、Co-processor access</li>
</ul>
</li>
<li>
<p>實際的設計中，需要對不同的指令集，甚至不同的程式進行分析，才能對 FU 進行合理的分類，得到相對優化的分配結果</p>
</li>
<li>
<p>由於 select 電路的 latency 與 Issue Queue 的容量成正比；當越多的 FU 共用同一個 Issue Queue 時，Issue Queue 的容量也需要相對應的增加，因此就會增加 select 電路的 latency</p>
<ul>
<li>但如果是想使越少的 FU 共用同一個 Issue Queue，那麼就需要更多數量的 Issue Queue，此時 wake-up 就需要更多的佈線和 comparators，反而增加 wake-up 電路的 latency 和功耗</li>
<li>因此如何配置 Issue Queue 的分配並沒有一個標準答案，需要根據實際的需求並分析</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>&lt;超標量處理器概覽&gt; 第 7 章 - 暫存器重命名</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch7/</link>
      <pubDate>Thu, 27 Mar 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch7/</guid>
      <description>&lt;h1 id=&#34;71---概述&#34;&gt;7.1 - 概述&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;程式中不同指令的相關性 (dependency) 可以分類為：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;數據相關性 (data dependency)，包含以下幾種類型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;WAW (Write After Write)&lt;/code&gt; dependency，i.e. output dependence：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;兩條指令都將結果寫到同一個 destination register 中&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;WAR (Write After Read)&lt;/code&gt; dependency，i.e. anti-dependence：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一條指令的 destination register 和它前面某條指令的 source register 相同&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;RAW (Read After Write)&lt;/code&gt; dependency，i.e. true dependence：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一條指令的 source register 和它前面某條指令的 destination register 相同&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只有 &lt;code&gt;RAW&lt;/code&gt; 是真的相關性， &lt;code&gt;WAR&lt;/code&gt; 和 &lt;code&gt;RAW&lt;/code&gt; 可以透過 register renaming 來解決&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image.png&#34; loading=&#34;lazy&#34; src=&#34;https://0xc0de.xyz/posts/superscalar-overview-ch7/image.png&#34;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;記憶體數據相關性 (memory data dependency)：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Load 和 store 指令之間的相關性，代表 load 和 store 指令都存取到同一個位址&lt;/li&gt;
&lt;li&gt;同樣也分為 &lt;code&gt;WAW&lt;/code&gt;、&lt;code&gt;WAR&lt;/code&gt;、&lt;code&gt;RAW&lt;/code&gt; dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;控制相關性 (control dependency)：&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="71---概述">7.1 - 概述</h1>
<ul>
<li>
<p>程式中不同指令的相關性 (dependency) 可以分類為：</p>
<ol>
<li>
<p>數據相關性 (data dependency)，包含以下幾種類型：</p>
<ul>
<li>
<p><code>WAW (Write After Write)</code> dependency，i.e. output dependence：</p>
<ul>
<li>兩條指令都將結果寫到同一個 destination register 中</li>
</ul>
</li>
<li>
<p><code>WAR (Write After Read)</code> dependency，i.e. anti-dependence：</p>
<ul>
<li>一條指令的 destination register 和它前面某條指令的 source register 相同</li>
</ul>
</li>
<li>
<p><code>RAW (Read After Write)</code> dependency，i.e. true dependence：</p>
<ul>
<li>一條指令的 source register 和它前面某條指令的 destination register 相同</li>
</ul>
</li>
<li>
<p>只有 <code>RAW</code> 是真的相關性， <code>WAR</code> 和 <code>RAW</code> 可以透過 register renaming 來解決</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image.png"></p>
</li>
</ul>
</li>
<li>
<p>記憶體數據相關性 (memory data dependency)：</p>
<ul>
<li>Load 和 store 指令之間的相關性，代表 load 和 store 指令都存取到同一個位址</li>
<li>同樣也分為 <code>WAW</code>、<code>WAR</code>、<code>RAW</code> dependencies</li>
</ul>
</li>
<li>
<p>控制相關性 (control dependency)：</p>
<ul>
<li>由於分支指令所引起的相關性，可以透過分支預測來解決</li>
</ul>
</li>
<li>
<p>結構相關行 (structure dependency)：</p>
<ul>
<li>指令必須等到 CPU 中某些元件可以使用的時候才可以繼續執行
<ul>
<li>例如要等到 Issue Queue 和 ROB 中有空閒的 entry，或是 FU 計算資源是有空的</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>
<li>
<p><code>WAW</code> 和 <code>WAR</code> dependencies 雖然可以透過 register renaming 來解決，但這兩個 dependencies 仍然存在的原因為：</p>
<ul>
<li>暫存器個數有限，導致在某些地方重複地使用暫存器
<ul>
<li>需要透過 register renaming 並引進更多的 physical registers 來彌補</li>
</ul>
</li>
<li>Loop 的存在，如果 loop body 中重複的向某個暫存器寫值，那麼就會產生大量的 <code>WAW</code> dependencies
<ul>
<li>雖然可以透過 loop unrolling 的方法來解決這個問題，但由於暫存器的個數有限，在 unloop 到某一個時刻就會把所有的暫存器給用完，此時 <code>WAW</code> dependency 就是不可避免的了</li>
<li>此外，loop unrolling 也會導致程式的 size 變大，佔用更多的記憶體空間，並導致 I-Cache miss rate 的升高</li>
</ul>
</li>
<li>Code reuse，如 recursive call；如果 function 中會向某個暫存器寫值，並被 recursively called，那麼就會產生大量的 <code>WAW</code> dependencies
<ul>
<li>同樣可以採用 inline function 的方式來解決這個問題，但也會碰到跟 loop unrolling 同樣的問題</li>
</ul>
</li>
</ul>
</li>
<li>
<p>CPU 中實際存在的暫存器個數會比指令集定義的通用暫存器的個數還來得多：</p>
<ul>
<li>CPU 內部實際存在的暫存器被稱為<strong>物理暫存器</strong> (<strong>Physical registers</strong>)</li>
<li>指令集定義的暫存器被稱為<strong>邏輯暫存器</strong> (<strong>Logical registers</strong> 或是 <strong>Architecture registers</strong>)</li>
</ul>
</li>
<li>
<p>CPU 在做 register renaming 時，會動態的將 architecture registers 映射到 physical registers，這樣可以解決 <code>WAW</code> 和 <code>WAR</code> dependencies 的問題：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%201.png"></p>
</li>
<li>
<p>Register renaming table (RAT)：用來保存已經存在的映射關係，i.e. architecture register → physical register)</p>
<ul>
<li>可以基於 SRAM 實現</li>
<li>也可以基於 CAM 實現</li>
<li>甚至可以採用 SRAM + CAM 來實現</li>
</ul>
</li>
<li>
<p>Free register list：用來保存目前還沒被映射的 physical registers</p>
<ul>
<li>在做 register renaming 時，會透過 free register list 來取得目前可以被映射的 physical register 編號</li>
</ul>
</li>
</ul>
<h1 id="72---暫存器重命名方式">7.2 - 暫存器重命名方式</h1>
<ul>
<li>Register renaming 有三種實做方式：
<ol>
<li>使用 <strong>ROB</strong> 來實做 register renaming</li>
<li>擴充 <strong>Architecture Register File (ARF)</strong> 來實做 register renaming</li>
<li>使用<strong>統一的 Physical Register File (PRF)</strong> 來實做 register renaming</li>
</ol>
</li>
<li>在實做 register renaming 時，一般都要考慮以下的內容：
<ol>
<li>什麼時候佔用一個 physical register? 這個 physical register 來自於哪裡?</li>
<li>什麼時候要釋放一個 physical register? 這個 physical register 要被釋放到哪裡?</li>
<li>當發生 mis-prediction 時要如何處理?</li>
<li>當發生 exception 時要如何處理?</li>
</ol>
</li>
</ul>
<h2 id="721---使用-rob-來實做暫存器重命名">7.2.1 - 使用 ROB 來實做暫存器重命名</h2>
<ul>
<li>
<p>這種方法把 ROB 當做了 physical registers，在其中儲存了<em>推測</em>的結果，而 ARF 則儲存了<em>正確</em>的結果；使用此種方法，ROB 和 ARF 都可以儲存暫存器的結果，相當於是使用了 ROB 來擴充 ARF</p>
</li>
<li>
<p>當一個指令執行完後，其計算結果會被更新到 ROB 中對應的 entry 中，但是由於有可能會發生 mis-prediction 或是 exception，因此這些暫存器的狀態為推測 (speculative) 的；該條指令在被 retired 前都會一直存在 ROB 中，直到該指令變成 pipeline 中最舊的指令，且沒有發生 mis-prediction 或 exception，才會離開 pipeline，並使用其結果更新 CPU 的狀態 (e.g. 將結果寫到 ARF 中)</p>
<ul>
<li>
<p>這種方式相當於將 PRF 和 ROB 整合了在一起：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%202.png"></p>
</li>
</ul>
</li>
<li>
<p>使用此方法可以簡化 register renaming 的流程，只要 ROB 中還有空間，register renaming 就可以持續地進行</p>
</li>
<li>
<p>一個 architecture register 的值有可能會同時存在 ROB 或是 ARF 中</p>
<ul>
<li>E.g. 一條指令 retire 時更新了 <code>$r1</code>，其 architecture register 的計算結果會被記錄在 ARF 中；隨後又有另外一條指令被執行，也同樣更新了 <code>$r1</code>，其 architecture register 的計算結果會被記錄在 ROB 中
<ul>
<li>
<p>因此，需要使用 RAT 來標記每個 architecture register 的計算結果是存在 ROB 或是 ARF 中：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%203.png"></p>
<ul>
<li>在一條指令 retire 前，其 architecture register 的計算結果會被存在 ROB 中，register renaming table 的 pointer 會指向 ROB；當這條指令 retire 時，其 architecture register 的計算結果會從 ROB 移到 ARF，RAT 的 pointer 也會一併更新指向 ARF</li>
</ul>
</li>
</ul>
</li>
<li>因為一個暫存器在它的”生命週期”內會有兩個可以被存放的位置 (ROB 或是 ARF)，這會對指令 operands 的讀取造成影響，因此在實際的 CPU 中，都會搭配 <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture"><strong>data-capture</strong></a> 的 issue 方式，並採用 payload RAM 來儲存所需的 operands (參考：<a href="../superscalar-overview-ch10/#1031---%E4%BD%BF%E7%94%A8-rob-%E7%AE%A1%E7%90%86%E6%8C%87%E4%BB%A4%E9%9B%86%E5%AE%9A%E7%BE%A9%E7%9A%84%E7%8B%80%E6%85%8B">link</a>)
<ul>
<li>當一條指令的結果被 FU 計算出來後，會透過 bypassing network 將其結果寫進 payload RAM；Issue Queue 中所有等待這個結果的指令在被 select 電路選中時，就可以直接從 payload RAM 中得到 source registers 的值，而不用關心這些 source registers 的值是存在 ROB 或是 ARF 中</li>
</ul>
</li>
</ul>
</li>
<li>
<p>使用 ROB 做 register renaming：</p>
<ul>
<li>
<p>優點：</p>
<ul>
<li>實做容易，設計複雜度不高，且便於管理</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>
<p>很多指令並不會更新 destination register，因此也就不用對 destination register 做 renaming，但每條指令仍然會佔用 ROB entry 中 destination register 的 physical register renaming 的空間，無法省略，浪費硬體空間</p>
</li>
<li>
<p>對於一條指令而言，它既可以從 ROB 中讀取 operands，也可以從 ARF 中讀取 operands，所以 ROB 和 ARF 最壞的情況就是一個 cycle 內，所有的指令都需要同時讀取 ROB 或 ARF，會增加 ROB 和 ARF 的 read ports 所需的數量</p>
<ul>
<li>例如：4-way issue CPU，指令最多需要 2 個 source registers，那麼 ROB 和 ARF 都需要準備 2 x 4 = 8 個 read ports，對硬體面積和延遲造成負面的影響</li>
<li>如果 CPU 有支援 multiple destination registers 的指令，那麼同樣也會對 ROB 和 ARF 的 write ports 數量造成影響</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="722---擴充-arf-來實做暫存器重命名">7.2.2 - 擴充 <strong>ARF</strong> 來實做暫存器重命名</h2>
<ul>
<li>
<p>這種方法是基於使用 ROB 來實做暫存器重命名方法的延伸；由於很多指令並沒有 destination register，例如：store 指令、分支指令和比較指令… etc，且這些指令佔了約 25% 的比例，使用 ROB 來實做暫存器重命名方法會造成 ROB 空間的浪費</p>
</li>
<li>
<p>因此，可以使用一個獨立的元件取代 ROB 來儲存 architecture register 對 physical register 的映射關係，這個元件被稱為 <strong>PRF (Physical Register File)</strong> (可以使用 FIFO 來實做)，它可以被視為 ARF 的擴充</p>
</li>
<li>
<p>在做 register renaming 時，architecture register 的計算結果被存在 PRF 中，等到這條指令 retire 時，才會將該計算結果從 PRF 搬進 ARF</p>
<ul>
<li>如果 PRF 中沒有空間了，那就必須 stall register renaming 之前的 pipeline stages，直到有指令 retire，PRF 的空間被釋放後，才可以繼續執行</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%204.png"></p>
</li>
<li>
<p>缺點：</p>
<ul>
<li>Architecture register 的計算結果仍然可能存在 PRF 及 ARF 兩個地方，因此仍然會對後面的指令使用這個暫存器作為 operands 的過程造成影響</li>
</ul>
</li>
</ul>
<h2 id="723---使用統一的-prf-來實做暫存器重命名">7.2.3 - 使用統一的 PRF 來實做暫存器重命名</h2>
<ul>
<li>
<p>這種方法將上述方法的 ARF 和 PRF 合併，合併後同樣稱為 <strong>PRF (Physical Register File)</strong>，在其中同時儲存了 speculative 和 retire 的暫存氣值</p>
</li>
<li>
<p>這種統一的 PRF，所有沒有和指令產生映射關係的暫存器都是 free 的，並會使用一個 <strong>free list</strong> (可以使用 FIFO 來實做) 來記錄目前仍為 free 狀態的 physical registers</p>
<ul>
<li>在做 register renaming 時，architecture register 的計算結果會一直被存在 PRF 中，因此在 register renaming 的過程並不需要將 architecture register 的計算結果進行搬移，方便後續的指令讀取 operands</li>
<li>這種方法同樣需要使用一個 RAT 來記錄每個 architecture register 對 physical register 的映射關係</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%205.png"></p>
</li>
<li>
<p>Superscalar CPU 在一個 cycle 內可以 retire <em>N</em> 條指令，因此每個 cycle 會有多個 physical registers 的編號會被寫進這個 FIFO 中，因此這個 FIFO 也要支援多個寫入，可以採用 interleaving 的方式來實現，避免 multi-port 的設計</p>
</li>
<li>
<p>當程式讀取暫存器時，由於很多 physical registers 仍處在 speculative 的狀態，是不能夠被程式所看到的，因此只使用一個 RAT 並沒辦法滿足這樣的要求；此時還需要使用另一個 RAT 來記錄所有 retired 的指令和 architecture register 對 physical register 的映射關係</p>
<ul>
<li>當指令 retire 時，其 architecture registers 對 physical registers 的映射關係就會被寫進這個 RAT</li>
<li>外部透過查詢這個 RAT，就可以找到 architecture register 此時對應的 physical register，避免程式讀取到 CPU 內部一些可能錯誤的狀態</li>
</ul>
</li>
<li>
<p>當一個 physical register 被佔用時，何時才可以再改為 free 狀態並加回 free list 中?</p>
<ul>
<li>
<p>當一個 physical register 不會再被後面的指令使用時，這個 physical register 就可以改為 free 狀態並加回 free list 中了</p>
<ul>
<li>i.e. 當最後一條使用到這個 physical register 的指令要被 retired 時，就可以將此 physical register 改為 free 狀態</li>
</ul>
</li>
<li>
<p>但要在 CPU 中識別這個 physical register 最後一道使用到它的指令並不是一件容易的事情，CPU 可以採用一種很簡單也很保守的方法來辨別：</p>
<ul>
<li>當一條指令和其後面的指令都寫到同一個 destination register 時，當後面的指令 retire 時，前面指令的映射關係就沒有用處了，此時就可以將前面指令的 physical register 改為 free 狀態，並加回 free list 中</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%206.png"></p>
<ul>
<li>例如：指令 (a) 和指令 (b) 都使用到了 <code>$r1</code>，如果有任何指令使用到指令 (a) 的 <code>$p1</code>，那麼這些指令一定是存在指令 (a) 和指令 (b) 之間的，因此當指令 (b) retire 時，代表不會再有任何指令會使用指令 (a) 的 <code>$p1</code>，此時便可以將 <code>$p1</code> 改為 free 狀態，並加回 free list 中了
<ul>
<li><span id="previous-physical-register-mapping-in-rob"></span>
為了實現此功能，在 <strong>ROB</strong> 中還需要記錄指令 (如指令 (b)) 的 destination register 之前所對應的 physical register (如指令 (a) 的 <code>$r1</code> 所對應的 physical register - <code>$p1</code>)，以便在指令 retire 時 (如指令 (b))，將其舊的映射關係 (<code>$r1</code> → <code>$p1</code>) 給釋放掉 (i.e. 將 <code>$p1</code> 改為 free 狀態，並加回 free list 中)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>總結：</p>
<ul>
<li>
<p>使用 ROB 來實做 register renaming</p>
<ul>
<li>
<p>優點：</p>
<ul>
<li>Register renaming 流程簡單，在指令寫入 ROB 時，將 architecture register 和 physical register 的映射關係一併記錄到 ROB 即可，不須增加複雜的硬體控制邏輯電路</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>暫存器的值需要被搬移 (ROB → ARF)，功耗較大</li>
<li>由於暫存器的值有可能存在 ROB 或是 ARF，當指令 retire 時，需要額外的電路同步所有有使用到該暫存器的指令來告知該暫存器已經從 ROB 搬移到 ARF，功耗較大</li>
</ul>
</li>
</ul>
</li>
<li>
<p>擴充 ARF 來實做 register renaming</p>
<ul>
<li>優點：
<ul>
<li>如同使用 ROB 來實做 register renaming，只需將 architecture register 和 physical register 的映射關係記錄到 PRF 即可，其中 PRF 可以使用 FIFO 來實做，不須增加複雜的硬體控制邏輯電路</li>
</ul>
</li>
<li>缺點：
<ul>
<li>暫存器的值需要被搬移 (PRF → ARF)，功耗較大</li>
<li>由於暫存器的值有可能存在 PRF 或是 ARF，當指令 retire 時，需要額外的電路同步所有有使用到該暫存器的指令來告知該暫存器已經從 PRF 搬移到 ARF，功耗較大</li>
</ul>
</li>
</ul>
</li>
<li>
<p>使用統一的 PRF 來實做 register renaming</p>
<ul>
<li>
<p>優點：</p>
<ul>
<li>暫存器的值只需寫入一次，不需要再被搬移，功耗較小</li>
<li>暫存器的值只會存在一個地方，不需要</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>需要使用一個 free list 以及兩個 RAT，因此需要使用複雜的硬體控制邏輯電路</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="73---重命名映射表">7.3 - 重命名映射表</h1>
<ul>
<li>
<p>(以下皆採用統一的 PRF 來實做 register renaming 的方式)</p>
</li>
<li>
<p>RAT 的實做有以下幾種方式：</p>
<ul>
<li>基於 SRAM 來實做 (<strong>sRAT</strong>)</li>
<li>基於 CAM 來實做 (<strong>cRAT</strong>，實際上是基於 SRAM + CAM 來實做的)</li>
</ul>
</li>
<li>
<p>範例：32 個 architecture registers (<code>$r0</code> ~ <code>$r31</code>)，64 個 physical registers (<code>$p0</code> ~ <code>$p63</code>)</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%207.png"></p>
<ul>
<li>sRAT：
<ul>
<li>使用 architecture register 來 index，共 32 個</li>
<li>每個 RAT entry 記錄了 architecture register 所對應的 physical register (共 6 bits)</li>
</ul>
</li>
<li>cRAT：
<ul>
<li>CAM 是使用<strong>內容</strong>來 index 的，CAM 會將內容與每個 entry 做比較 (類似 fully-associative cache)，並回傳與內容相符的 entry indexes</li>
<li>因此 cRAT 是透過 architecture register 來 index (共 5 bits)，CAM 會回傳與其對應的 physical register index</li>
</ul>
</li>
</ul>
</li>
<li>
<p>雖然 SRAM 的存取速度比 CAM 來得快，而且 sRAT 比 cRAT 更省空間 (architecture registers 數量都是固定的，physical register 所佔的空間只為 log2 bits)，但是在現實的 CPU 中，仍然存在使用 cRAT 的設計</p>
<ul>
<li>這是因為 cRAT 在做 checkpoint 時，<strong>只需保存 valid bits 及 free list 的 read pointer (sRAT 同樣也需保存 free list 的 read pointer)，不需將整個 cRAT 做保存</strong>，大大減少 checkpoint 所需的電路面積
<ul>
<li>因此相較於 sRAT，cRAT 不會隨著 checkpoint 的數量增加而增加大量的硬體面積
<ul>
<li>當 checkpoints 的數量超過一定值，cRAT 就比 sRAT 有更大的優勢</li>
</ul>
</li>
<li>只需保存 free list 的 read pointer：
<ul>
<li>Write pointer + 1 代表有指令 retire，將 physical register 加回 free list 中
<ul>
<li>Write pointer 在 restore checkpoint 的時候不需要恢復，因為在 checkpoint restore 時，分支指令之前的指令有可能已經 retired 了，因此我們要保留其已經 free 掉的 physical registers</li>
</ul>
</li>
<li>Read pointer + 1 代表有新的指令被執行，並使用 physical register 來映射 architecture register
<ul>
<li>Read pointer 在 checkpoint 的當下需要被保存，因為會從 free list 中拿 physical register 來建立映射關係的指令，都是分支指令之後的指令，restore checkpoint 後這些指令也需要被 flushed，因此需要恢復 read pointer</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>現代處理器通常有較深 pipeline stages 及更多的 issue ways (可以處理的指令越多，代表一個 cycle 內出現的分支指令個數有可能也會增加)，因此需要更多的 checkpoints，此時 cRAT 就是一個比較好的選擇</li>
</ul>
</li>
</ul>
<h2 id="731---基於-sram-的重命名映射表">7.3.1 - 基於 SRAM 的重命名映射表</h2>
<ul>
<li>
<p>當需要對分支指令狀態進行 checkpoint 時，需要備份整個 sRAT，因此這個實做方法每個 checkpoint 會佔用大量的硬體面積，因此限制了 checkpoints 的個數</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%208.png"></p>
</li>
<li>
<p>對於一個 4-way 的 superscalar CPU 來說，每個 cycle 需要對 4 條指令做 register renaming，也就是 sRAT 需要支援 8 個 read ports 和 4 個 write ports (假設每條指令包含 2 個 source registers 和 1 個 destination register)；此外，還需要一個 free list 來記錄有哪些 physical registers 是 free 的</p>
</li>
<li>
<p>當有新的指令做 register renaming 時，有可能會發生 RAT entry 被蓋掉的問題，例如：</p>
<ul>
<li>此時不能直接把 RAT entry 給蓋掉，因為：
<ul>
<li>一條指令在 retire 時，要將其對應的 physical registers 改為 free 狀態，並加回 free list 中，如果其 RAT entry 被覆蓋，就沒辦法更新 physical register
<ul>
<li>例如：當發生 <code>WAW</code> 時，前後指令同樣的 destination register 會對應到不同的 physical registers，此時前面指令的 RAT entry 就會被後面指令的 RAT entry 給覆蓋</li>
</ul>
</li>
<li>當一條指令在 exception 或是 mis-prediction 的路徑上，這條指令最終會需要被 flushed 掉，同時也需要將該指令對 RAT 的修改給復原
<ul>
<li>如果舊的映射關係沒有被保存下來，就無法將該指令對 RAT 的修改給復原</li>
</ul>
</li>
</ul>
</li>
<li>因此，在一個 destination register 對 physical register 的映射關係被寫進 RAT 前，需要將其所覆蓋的 RAT entry (i.e. 舊的映射關係) 給寫進 <strong>ROB</strong> 中</li>
</ul>
</li>
<li>
<p>sRAT 相較于 cRAT，讀寫速度會快一點，設計複雜度也不會隨著 physical registers 的增加而變大，但其最大的缺點就是：<strong>Checkpoints 的數量有上限</strong></p>
<ul>
<li>但隨著處理器的並行度的提高，會有更多的分支指令存在 pipeline 中，也就需要使用更多個 checkpoints</li>
<li>不過當分支指令的預測正確時，checkpoint 是不會被使用的
<ul>
<li>因此可以對分支指令的預測正確度也進行預測，對於那些預測準確度很高的分支指令，可以不使用 checkpoint，可以把 sRAT 的 checkpoints 留給那些預測準確度較低的分支指令</li>
<li>但當這些未使用 checkpoint 的分支指令發生 mis-prediction 時，就需要使用速度較慢的方式來恢復 RAT
<ul>
<li>如果發生頻率的很低，就不會對性能造成影響</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="732---基於-cam-的重命名映射表">7.3.2 - 基於 CAM 的重命名映射表</h2>
<ul>
<li>
<p>在 cRAT 中，architecture register 就是每個 CAM entry 所保存的內容，physical register 則是最後的結果</p>
</li>
<li>
<p>cRAT 使用 architecture register 來索引 CAM，所有的 CAM entries 都會與欲索引的 architecture register 做比較，只有 CAM entry 內容與欲索引的 architecture register 相同時，其所對應的 physical register 才會被回傳；由於一個 architecture register 只會有一個對應的 physical register，因此只會有一個 CAM entry 相符</p>
<ul>
<li>相符的 entry 可以用 valid bit 來表示，i.e. <code>V = 1</code></li>
</ul>
</li>
<li>
<p>對 cRAT 做 checkpoint 時，只需 checkpoint valid bits 即可</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%209.png"></p>
</li>
<li>
<p>由於 cRAT 所需的 checkpoint 資源很少，因此可以將 checkpoints 個數做得很大</p>
<ul>
<li>E.g. Alpha 21264 使用 cRAT 做 register renaming，總共包含了 80 個 checkpoints，也就是最大允許 80 條分支指令存在 pipeline 中</li>
</ul>
</li>
<li>
<p>對於一個 4-way superscalar CPU 而言，使用 cRAT：</p>
<ul>
<li>cRAT 需要支援 8 個 read ports (2 source registers * 4) 及 4 個 write ports (4 destination registers)</li>
</ul>
</li>
<li>
<p>基於 cRAT 進行 register renaming 仍然需要使用 free list 來記錄哪幾個 physical registers 是 free 狀態的</p>
<ul>
<li>要等指令 retire 時，才可以將其 architecture register 所對應的 physical register 改為 free 狀態並加回 free list 中</li>
<li>因此，在 cRAT 中，並不是一個 physical register 的 valid bit 為 0 時，就代表其為 free 狀態，有可能是其映射關係剛被覆蓋而已
<ul>
<li>
<p>例如：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-2">2</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#a6e22e">addi</span> <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r0</span>, <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">addi</span> <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r3</span>, <span style="color:#ae81ff">3</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>第一條指令：<code>$p11</code> → <code>$r7</code> ⇒ <code>V = 1</code></li>
<li>第二條指令： <code>$p12</code> → <code>$r7</code> ⇒ <code>V = 1</code>；<code>$p11</code> → <code>$r7</code> ⇒ <code>V = 1</code> → <code>0</code>
<ul>
<li><code>$p11</code> → <code>$r7</code> 的映射關係被 <code>$p12</code> → <code>$r7</code> 取代，因此 <code>V = 1</code> → <code>0</code>，但不代表 <code>$p11</code> 就是 free 狀態的</li>
</ul>
</li>
</ul>
</li>
<li>
<p>指令實際在 pipeline 中的運算還是使用<strong>當初所被分配的 physical register</strong>，所以只有等到指令 retire 後，其 physical register 才可以被改為 free 狀態</p>
<ul>
<li>i.e. 第一條指令 <code>$r7</code> 所使用的 physical register 依然是 <code>$p11</code>，RAT 只是記錄<strong>最新的映射狀態</strong></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>當一條指令 retire，其 physical register 改為 <strong>free 狀態</strong>後，該 physical register 在 RAT 中的映射關係的 <strong>valid bit 也會被設為 0</strong></p>
<ul>
<li>也就是說，<code>V = 0</code> 同時表示了：
<ul>
<li>映射關係被取代</li>
<li>Physical register 已經被改為 free 狀態</li>
</ul>
</li>
</ul>
</li>
<li>
<p>cRAT checkpoint &amp; restore 範例 (只針對 destination register 的部份)：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-5">5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-6">6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-7">7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-8">8</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">A</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">addi</span> <span style="color:#66d9ef">$r5</span>, <span style="color:#66d9ef">$r0</span>, <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">B</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">addi</span> <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r0</span>, <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">C</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">addi</span> <span style="color:#66d9ef">$r6</span>, <span style="color:#66d9ef">$r7</span>, <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">D</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">add</span>  <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r1</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">E</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">add</span>  <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r2</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">F</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">beq</span>  <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r3</span>, <span style="color:#75715e">#TARGET1
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">G</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">add</span>  <span style="color:#66d9ef">$r9</span>, <span style="color:#66d9ef">$r5</span>, <span style="color:#66d9ef">$r6</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">H</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">add</span>  <span style="color:#66d9ef">$r5</span>, <span style="color:#66d9ef">$r9</span>, <span style="color:#66d9ef">$r9</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>
<p>當分支指令 F 做 register renaming 時，需要對 cRAT 做 checkpoint，此時 cRAT 的內容如下：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-1"> 1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-2"> 2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-3"> 3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-4"> 4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-5"> 5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-6"> 6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-7"> 7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-8"> 8</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-9"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-9"> 9</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-10"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-10">10</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-11"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-11">11</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-12"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-12">12</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-13"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-13">13</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-14"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-14">14</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-15"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-15">15</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-16"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-16">16</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-17"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-17">17</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-18"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-18">18</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-19"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-19">19</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-20"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-20">20</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-21"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-21">21</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-22"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-22">22</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-23"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-23">23</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-24"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-24">24</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-25"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-25">25</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-26"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-26">26</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-27"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-27">27</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-28"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-28">28</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-29"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-29">29</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>+--------------------------------------------+-------------------------------+
</span></span><span style="display:flex;"><span>|                   cRAT                     |          Checkpoints          |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|  Inst   |  Phys Regs  | Arch Reg  |   V    |  GC0  |  GC1  |  GC2  |  GC3  |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p0      |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|   ...   |     ...     |    ...    |  ...   |  ...  |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    A    |    $p10     |    $r5    |   1    |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    B    |    $p11     |    $r7    | 1 -&gt; 0 |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    C    |    $p12     |    $r6    |   1    |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    D    |    $p13     |    $r7    | 1 -&gt; 0 |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    E    |    $p14     |    $r7    |   1    |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p15     |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p16     |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p17     |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |     ...     |           |        |  ...  |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p63     |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>當指令 D 做 register renaming 時，由於指令 D 和指令 B 都存取 <code>$r7</code>，因此指令 B 的映射關係會被 invalid (<code>V = 0</code>)</li>
<li>當指令 E 做 register renaming 時，由於指令 E 和指令 D 都存取 <code>$r7</code>，因此指令 D 的映射關係會被 invalid (<code>V = 0</code>)</li>
<li>分支指令 F 做 register renaming 時：
<ul>
<li>Checkpoint - GC0 的內容會被更新為：<code>1</code>, <code>0</code>, <code>1</code>, <code>0</code>, <code>1</code></li>
</ul>
</li>
</ul>
</li>
<li>
<p>在指令 G、指令 H 也做完 register renaming 後，指令 F 發生 mis-prediction，因此需要使用 GC0 來恢復 cRAT；在使用 GC0 來恢復前的 cRAT 內容如下：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-1"> 1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-2"> 2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-3"> 3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-4"> 4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-5"> 5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-6"> 6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-7"> 7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-8"> 8</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-9"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-9"> 9</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-10"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-10">10</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-11"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-11">11</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-12"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-12">12</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-13"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-13">13</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-14"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-14">14</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-15"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-15">15</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-16"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-16">16</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-17"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-17">17</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-18"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-18">18</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-19"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-19">19</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-20"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-20">20</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-21"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-21">21</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-22"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-22">22</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-23"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-23">23</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-24"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-24">24</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-25"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-25">25</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-26"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-26">26</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-27"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-27">27</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-28"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-28">28</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-3-29"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-29">29</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>+--------------------------------------------+-------------------------------+
</span></span><span style="display:flex;"><span>|                   cRAT                     |          Checkpoints          |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|  Inst   |  Phys Regs  | Arch Reg  |   V    |  GC0  |  GC1  |  GC2  |  GC3  |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p0      |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|   ...   |     ...     |    ...    |  ...   |  ...  |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    A    |    $p10     |    $r5    |   1    |   1   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    B    |    $p11     |    $r7    | 1 -&gt; 0 |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    C    |    $p12     |    $r6    |   1    |   1   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    D    |    $p13     |    $r7    | 1 -&gt; 0 |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    E    |    $p14     |    $r7    | 1 -&gt; 0 |   1   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    G    |    $p15     |    $r9    |   0    |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    H    |    $p16     |    Rr7    |   1    |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p17     |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    ...      |           |        |  ...  |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p63     |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>當指令 H 做 register renaming 時，由於指令 H 和指令 E 都存取 <code>$r7</code>，因此指令 E 的映射關係會被 invalid (<code>V = 0</code>)</li>
<li>當恢復 GC0 後，<code>$p10</code> ~ <code>$p16</code> 的 <code>V</code> 值會被恢復成：<code>1</code>, <code>0</code>, <code>1</code>, <code>0</code>, <code>1</code>, <code>0</code>, <code>0</code>
<ul>
<li>在分支指令 F 之前的指令有可能在恢復 checkpoint - GC0 的當下已經 retired 了，此時有以下可能：
<ul>
<li>Physical register 改為 free 狀態，並加入 free list 中 ⇒ <code>V = 0</code>
<ul>
<li><code>V</code> 被 restored 回 <code>0</code> (如指令 B)
<ul>
<li>不會造成影響，因為本來 <code>V = 0</code> 本來也就可以表示 free 狀態</li>
</ul>
</li>
<li><code>V</code> 被 restored 回 <code>1</code> (如指令 C)
<ul>
<li>被恢復的指令已經 retired 了，其 physical register 已經被加入 free list 中，因此只要在存取 RAT 時有一併檢查 physical register 是否在 free list 中，就算 <code>V</code> 被 restored 回 <code>1</code> 也沒關係</li>
</ul>
</li>
</ul>
</li>
<li>Physical register 被其他指令重新映射 ⇒ <code>V = 1</code>
<ul>
<li><code>V</code> 被 restored 回 <code>0</code> (如指令 B)
<ul>
<li>不會造成影響，因為重新映射的指令是分支指令之後的指令，本來就應該被 flushed 掉 ⇒ <code>V = 0</code></li>
</ul>
</li>
<li><code>V</code> 被 restored 回 <code>1</code> (如指令 C)
<ul>
<li>重新映射的指令也應該被 flushed ⇒ <code>V = 0</code>，不過由於被恢復的指令已經 retired 了，其 physical register 已經被加入 free list 中，因此只要在存取 RAT 時有一併檢查 physical register 是否在 free list 中，就算 <code>V</code> 被 restored 回 <code>1</code> 也沒關係</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>一般情況下，pipeline 中允許存在的最多的分支指令數，和處理器最大可以支援的 checkpoints 數量是一樣的 (假設只有分支指令使用 checkpoint)</p>
</li>
<li>
<p>如果分支預測正確，就可以將分支指令對應的 checkpoint 給釋放掉，變成 free 狀態，後續分支指令可以繼續使用它</p>
</li>
<li>
<p>分支預測失敗，restore checkpoint 後，也會一併將該 checkpoint 給釋放掉，變成 free 狀態，後續分支指令也可以繼續使用它</p>
</li>
<li>
<p>cRAT checkpoint &amp; restore 兩條分支指令範例 (只針對 destination register 的部份)：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-1"> 1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-2"> 2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-3"> 3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-4"> 4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-5"> 5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-6"> 6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-7"> 7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-8"> 8</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-9"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-9"> 9</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-4-10"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-10">10</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">A</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">addi</span> <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r0</span>, <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">B</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">add</span>  <span style="color:#66d9ef">$r6</span>, <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r1</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">C</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">add</span>  <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r2</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">D</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">beq</span>  <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r3</span>, <span style="color:#75715e">#TARGET1
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">E</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">add</span>  <span style="color:#66d9ef">$r9</span>, <span style="color:#66d9ef">$r5</span>, <span style="color:#66d9ef">$r6</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">F</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">add</span>  <span style="color:#66d9ef">$r9</span>, <span style="color:#66d9ef">$r9</span>, <span style="color:#66d9ef">$r8</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">G</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">beq</span>  <span style="color:#66d9ef">$r9</span>, <span style="color:#66d9ef">$r3</span>, <span style="color:#75715e">#TARGET2
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">H</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">addi</span> <span style="color:#66d9ef">$r7</span>, <span style="color:#66d9ef">$r0</span>, <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">H</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">addi</span> <span style="color:#66d9ef">$r6</span>, <span style="color:#66d9ef">$r0</span>, <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">指令</span> <span style="color:#a6e22e">H</span><span style="color:#960050;background-color:#1e0010">：</span><span style="color:#66d9ef">sub</span>  <span style="color:#66d9ef">$r9</span>, <span style="color:#66d9ef">$r1</span>, <span style="color:#66d9ef">$r0</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>
<p>當分支指令 D 做 register renaming 時，需要對 cRAT 做 checkpoint，此時 cRAT 的內容如下：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-1"> 1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-2"> 2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-3"> 3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-4"> 4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-5"> 5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-6"> 6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-7"> 7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-8"> 8</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-9"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-9"> 9</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-10"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-10">10</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-11"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-11">11</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-12"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-12">12</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-13"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-13">13</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-14"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-14">14</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-15"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-15">15</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-16"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-16">16</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-17"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-17">17</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-18"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-18">18</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-19"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-19">19</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-20"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-20">20</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-21"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-21">21</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-22"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-22">22</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-23"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-23">23</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-24"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-24">24</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-25"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-25">25</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-26"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-26">26</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-27"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-27">27</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-28"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-28">28</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-5-29"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-29">29</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>+--------------------------------------------+-------------------------------+
</span></span><span style="display:flex;"><span>|                   cRAT                     |          Checkpoints          |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|  Inst   |  Phys Regs  | Arch Reg  |   V    |  GC0  |  GC1  |  GC2  |  GC3  |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p0      |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|   ...   |     ...     |    ...    |  ...   |  ...  |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    A    |    $p10     |    $r7    | 1 -&gt; 0 |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    B    |    $p11     |    $r6    |   1    |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    C    |    $p12     |    $r7    |   1    |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p13     |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p14     |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p15     |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p16     |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p17     |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |     ...     |           |        |  ...  |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p63     |           |        |   0   |       |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>當指令 C 做 register renaming 時，由於指令 C 和指令 A 都存取 <code>$r7</code>，因此指令 A 的映射關係會被 invalid (<code>V = 0</code>)</li>
<li>分支指令 D 做 register renaming 時：
<ul>
<li>Checkpoint - GC0 的內容會被更新為：<code>0</code>, <code>1</code>, <code>1</code></li>
</ul>
</li>
</ul>
</li>
<li>
<p>當分支指令 G 做 register renaming 時，需要對 cRAT 做 checkpoint，此時 cRAT 的內容如下：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-1"> 1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-2"> 2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-3"> 3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-4"> 4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-5"> 5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-6"> 6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-7"> 7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-8"> 8</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-9"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-9"> 9</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-10"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-10">10</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-11"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-11">11</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-12"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-12">12</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-13"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-13">13</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-14"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-14">14</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-15"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-15">15</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-16"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-16">16</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-17"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-17">17</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-18"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-18">18</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-19"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-19">19</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-20"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-20">20</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-21"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-21">21</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-22"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-22">22</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-23"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-23">23</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-24"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-24">24</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-25"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-25">25</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-26"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-26">26</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-27"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-27">27</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-28"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-28">28</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-6-29"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-29">29</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>+--------------------------------------------+-------------------------------+
</span></span><span style="display:flex;"><span>|                   cRAT                     |          Checkpoints          |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|  Inst   |  Phys Regs  | Arch Reg  |   V    |  GC0  |  GC1  |  GC2  |  GC3  |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p0      |           |        |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|   ...   |     ...     |    ...    |  ...   |  ...  |  ...  |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    A    |    $p10     |    $r7    | 1 -&gt; 0 |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    B    |    $p11     |    $r6    |   1    |   1   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    C    |    $p12     |    $r7    |   1    |   1   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    E    |    $p13     |    $r9    | 1 -&gt; 0 |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    F    |    $p14     |    $r9    |   1    |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p15     |           |        |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p16     |           |        |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p17     |           |        |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |     ...     |           |        |  ...  |  ...  |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p63     |           |        |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>當指令 F 做 register renaming 時，由於指令 F 和指令 E 都存取 <code>$r9</code>，因此指令 E 的映射關係會被 invalid (<code>V = 0</code>)</li>
<li>分支指令 G 做 register renaming 時：
<ul>
<li>Checkpoint - GC1 的內容會被更新為：<code>0</code>, <code>1</code>, <code>1</code>, <code>0</code>, <code>1</code></li>
</ul>
</li>
</ul>
</li>
<li>
<p>當所有指令都完成 register renaming 後，cRAT 的內容如下：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-1"> 1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-2"> 2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-3"> 3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-4"> 4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-5"> 5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-6"> 6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-7"> 7</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-8"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-8"> 8</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-9"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-9"> 9</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-10"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-10">10</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-11"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-11">11</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-12"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-12">12</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-13"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-13">13</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-14"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-14">14</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-15"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-15">15</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-16"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-16">16</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-17"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-17">17</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-18"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-18">18</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-19"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-19">19</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-20"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-20">20</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-21"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-21">21</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-22"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-22">22</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-23"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-23">23</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-24"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-24">24</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-25"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-25">25</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-26"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-26">26</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-27"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-27">27</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-28"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-28">28</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-7-29"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-29">29</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>+--------------------------------------------+-------------------------------+
</span></span><span style="display:flex;"><span>|                   cRAT                     |          Checkpoints          |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|  Inst   |  Phys Regs  | Arch Reg  |   V    |  GC0  |  GC1  |  GC2  |  GC3  |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p0      |           |        |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|   ...   |     ...     |    ...    |  ...   |  ...  |  ...  |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    A    |    $p10     |    $r7    | 1 -&gt; 0 |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    B    |    $p11     |    $r6    | 1 -&gt; 0 |   1   |   1   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    C    |    $p12     |    $r7    | 1 -&gt; 0 |   1   |   1   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    E    |    $p13     |    $r9    | 1 -&gt; 0 |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    F    |    $p14     |    $r9    | 1 -&gt; 0 |   0   |   1   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    H    |    $p15     |    $r7    |   1    |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    I    |    $p16     |    $r6    |   1    |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|    J    |    $p17     |    $r9    |   1    |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |     ...     |           |        |  ...  |  ...  |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span><span style="display:flex;"><span>|         |    $p63     |           |        |   0   |   0   |       |       |
</span></span><span style="display:flex;"><span>+---------+-------------+-----------+--------+-------+-------+-------+-------+
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>當指令 H 做 register renaming 時，由於指令 H 和指令 C 都存取 <code>$r7</code>，因此指令 C 的映射關係會被 invalid (<code>V = 0</code>)</li>
<li>當指令 I 做 register renaming 時，由於指令 I 和指令 B 都存取 <code>$r6</code>，因此指令 B 的映射關係會被 invalid (<code>V = 0</code>)</li>
<li>當指令 J 做 register renaming 時，由於指令 J 和指令 F 都存取 <code>$r9</code>，因此指令 F 的映射關係會被 invalid (<code>V = 0</code>)</li>
</ul>
</li>
<li>
<p>如果指令 D 發生 mis-prediction，就需要使用 GC0 來恢復 cRAT，並在恢復完 cRAT 後將 GC0 給釋放掉</p>
<ul>
<li>如果是 in-order core，則在 restore checkpoint 時，分支指令一定會是最後一條指令，GC1 一定會是在 mis-prediction 的路徑上，因此 GC1 也可以被釋放掉</li>
</ul>
</li>
<li>
<p>如果指令 D 預測正確，但指令 G 發生 mis-prediction，就需要使用 GC1 來恢復 cRAT，並在恢復完 cRAT 後將 GC1 給釋放掉</p>
</li>
<li>
<p>cRAT 並不負責管理哪些 physical registers 是 free 狀態的，這個功能是透過 ROB 和 free list 來實現的</p>
</li>
<li>
<p>基於 CAM 的重命名映射表最大的弊端就是其硬體面積會隨著 CPU 中 physical registers 的個數增大而變大，因為 CAM 的 entry 個數，就是 physical registers 的個數</p>
<ul>
<li>隨著處理器並行度的增加，需要更多的 physical registers，此時 cRAT 就需要更多的比較電路，進而拖慢 CPU 的速度</li>
<li>但是由於 cRAT checkpoint 時只需保存 valid bits (以及 free list 的 read pointer) 即可，大大降低了對硬體的需求，尤其是現在處理器中，會有更多的指令在 pipeline 中，需要更多的 checkpoints 配合，這時 cRAT 的優勢就體現出來了</li>
<li>因此在設計時需要權衡使用 cRAT 的優缺點</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="74---超標量處理器的暫存器重命名">7.4 - 超標量處理器的暫存器重命名</h1>
<ul>
<li>
<p>RAT 在初始化時，architecture registers 就已經有對應的 physical registers，因此 source register 可以直接讀取 RAT 取得對應的 physical register，例如：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-8-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-5">5</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>$r0 -&gt; $p0
</span></span><span style="display:flex;"><span>$r1 -&gt; $p1
</span></span><span style="display:flex;"><span>$r2 -&gt; $p1
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>$r31 -&gt; $p31
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>剩餘沒映射關係的 physical registers 會被加進 free list 中</li>
</ul>
</li>
<li>
<p>對於一條：<code>Dest = Src1 op Src2</code> 的指令，register renaming 的過程如下：</p>
<ol>
<li>
<p>從 RAT 找到 <code>Src1</code> 和 <code>Src2</code> 對應的 physical registers：<code>Psrc1</code>、<code>Psrc2</code></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%2010.png"></p>
</li>
<li>
<p>從 free list 中找到一個 free 狀態的 physical register：<code>Pdest</code></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%2011.png"></p>
</li>
<li>
<p>將 <code>Dest</code> → <code>Pdest</code> 的映射寫進 RAT 中</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%2012.png"></p>
</li>
</ol>
</li>
<li>
<p>由上述範例可以知道：</p>
<ul>
<li>
<p>RAT 需要支援 2 個 read ports (source registers) 和 1 個 write port (destination register)</p>
</li>
<li>
<p>(對於 sRAT) 為了將 physical register 釋放回 free list，因此還需要將每條指令之前的對應關係保存到 ROB 中 (參考：<a href="#previous-physical-register-mapping-in-rob">link</a>)，因此 RAT 還需要 1 個額外的 read port 來讀取 destination register 所對應的 physical register</p>
</li>
<li>
<p>對於 superscalar CPU 而言，N-way CPU 的 RAT 就提供 N 倍的 read ports 和 write ports：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%2013.png"></p>
</li>
</ul>
</li>
<li>
<p>除了 multi-port RAT 外，register renaming 還需考慮到每個 cycle 同時 rename 多條指令之間所存在的 dependencies，例如：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%2014.png"></p>
<ul>
<li>指令 A 和指令 B 有 <code>RAW</code> dependency
<ul>
<li>為 true dependency</li>
</ul>
</li>
<li>指令 A 、指令 B 和指令 D 有 <code>WAW</code> dependency
<ul>
<li>可以透過 register renaming 解決，但實際上仍無法忽略其存在：
<ol>
<li>
<p>在一個 cycle 內多條指令發生 <code>WAW</code>，只需將最新那條指令的映射關係寫進 RAT 即可</p>
<ul>
<li>舊的指令還是會被分配 physical registers，後續在 pipeline 中的運算也是使用被分配的 physical registers，只是不需要將其映射關係給寫進 RAT，不然只是浪費 RAT 的空間</li>
</ul>
</li>
<li>
<p>在將每條指令的舊映射關係寫進 ROB 時，如果發現一個 cycle 內有多條指令都使用同一個 destination register，那麼此時寫進 ROB 的舊映射關係，就不是來自 RAT，而是來自於與其發生 <code>WAW</code> 的那條指令 (參考：<a href="#previous-physical-register-mapping-in-rob">link</a>)</p>
<ul>
<li>例如：
<ul>
<li>指令 B 的 <code>$r0</code> → <code>$p31</code>，<code>$r0</code> 舊的映射關係是來自指令 A 的 <code>$p30</code>，而不是來自於 RAT 所讀出的結果</li>
<li>指令 D 的 <code>$r0</code> → <code>$p33</code>，<code>$r0</code> 舊的映射關係是來自指令 B 的 <code>$p31</code>，而不是來自於 RAT 所讀出的結果</li>
</ul>
</li>
<li>因此 superscalar CPU 在做 register renaming 時，仍需對指令間的 <code>WAW</code> dependency 做檢查</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
<li>指令 B 和指令 D 有 <code>WAR</code> dependency
<ul>
<li>可以透過 register renaming 解決</li>
</ul>
</li>
</ul>
</li>
<li>
<p>因此，superscalar CPU 在做 register renaming 時，需檢查 <code>RAW</code> 和 <code>WAW</code> 的 dependencies</p>
</li>
</ul>
<h2 id="741---解決-raw-相關性">7.4.1 - 解決 RAW 相關性</h2>
<ul>
<li>
<p>對 4-way superscalar CPU 來說，每個 cycle 可以處理 4 條指令，如果這 4 條指令之間不存在 RAW，則 register renaming 過程就相對單純，例如：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%2015.png"></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-9-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-4">4</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#a6e22e">$r1</span> <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">$r1</span> <span style="color:#960050;background-color:#1e0010">+</span> <span style="color:#66d9ef">$r2</span>    <span style="color:#960050;background-color:#1e0010">=&gt;</span> <span style="color:#66d9ef">$p31</span> <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">$p10</span> <span style="color:#960050;background-color:#1e0010">+</span> <span style="color:#66d9ef">$p11</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">$r3</span> <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">$r3</span> <span style="color:#960050;background-color:#1e0010">+</span> <span style="color:#66d9ef">$r4</span>    <span style="color:#960050;background-color:#1e0010">=&gt;</span> <span style="color:#66d9ef">$p40</span> <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">$p12</span> <span style="color:#960050;background-color:#1e0010">+</span> <span style="color:#66d9ef">$p13</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">$r5</span> <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">$r6</span> <span style="color:#66d9ef">x</span> <span style="color:#66d9ef">$r7</span>    <span style="color:#960050;background-color:#1e0010">=&gt;</span> <span style="color:#66d9ef">$p8</span>  <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">$p20</span> <span style="color:#66d9ef">x</span> <span style="color:#66d9ef">$p21</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">$r8</span> <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">Load</span> <span style="color:#ae81ff">9</span>[<span style="color:#66d9ef">$r9</span>]  <span style="color:#960050;background-color:#1e0010">=&gt;</span> <span style="color:#66d9ef">$p5</span>  <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">Load</span> <span style="color:#ae81ff">9</span>[<span style="color:#66d9ef">$p22</span>]
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>但當這 4 條指令有 RAW 時：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%2016.png"></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-10-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-4">4</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#a6e22e">$r1</span> <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">$r1</span> <span style="color:#960050;background-color:#1e0010">+</span> <span style="color:#66d9ef">$r2</span>    <span style="color:#960050;background-color:#1e0010">=&gt;</span> <span style="color:#66d9ef">$p31</span> <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">$p10</span> <span style="color:#960050;background-color:#1e0010">+</span> <span style="color:#66d9ef">$p11</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">$r3</span> <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">$r3</span> <span style="color:#960050;background-color:#1e0010">+</span> <span style="color:#66d9ef">$r4</span>    <span style="color:#960050;background-color:#1e0010">=&gt;</span> <span style="color:#66d9ef">$p40</span> <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">$p12</span> <span style="color:#960050;background-color:#1e0010">+</span> <span style="color:#66d9ef">$p13</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">$r5</span> <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">$r6</span> <span style="color:#66d9ef">x</span> <span style="color:#66d9ef">$r1</span>    <span style="color:#960050;background-color:#1e0010">=&gt;</span> <span style="color:#66d9ef">$p8</span>  <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">$p20</span> <span style="color:#66d9ef">x</span> <span style="color:#66d9ef">$p25</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">$r8</span> <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">Load</span> <span style="color:#ae81ff">9</span>[<span style="color:#66d9ef">$r9</span>]  <span style="color:#960050;background-color:#1e0010">=&gt;</span> <span style="color:#66d9ef">$p5</span>  <span style="color:#960050;background-color:#1e0010">=</span> <span style="color:#66d9ef">Load</span> <span style="color:#ae81ff">9</span>[<span style="color:#66d9ef">$p22</span>]
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>對於 <code>$r5 = $r6 x $r1</code> 這條指令而言，<code>$r1</code> 的 physical register 應該來自於 <code>$r1 = $r1 + $r2</code> 中的 <code>$r1</code> destination register 所對應的 physical register，也就是：<code>$p31</code>，而非 RAT 所輸出的 <code>$p25</code>
<ul>
<li>如果不加以處理，運算結果就會有誤</li>
</ul>
</li>
</ul>
</li>
<li>
<p>需要有一個檢查機制，對 1 個 cycle 內的所有做 register renaming 的指令進行 <code>RAW</code> 的檢查</p>
<ul>
<li>
<p>在 register renaming stage，指令之間還是 in-order 的，因此只需要將所有指令的 source registers 與它前面所有指令的 destination registers 做比較，如果 registers 相同，那麼這個 source register 的 physical register 來源就不是 RAT，而是來自 free list</p>
<ul>
<li>如果多個 registers 皆相同，則使用最新指令的 physical register</li>
</ul>
</li>
<li>
<p>硬體設計如下圖：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%2017.png"></p>
<ul>
<li>第一條指令的 source registers 所映射的 physical registers 一定只能來自於 RAT，也不需要進行 RAW 的檢查</li>
<li>第二條指令的 source registers 所映射的 physical registers 有可能來自於 RAT，或是第一條指令的 destination physical register</li>
<li>第三、第四條指令也是類似的</li>
<li>此外，最後一條指令的 destination physical register 一定不會作為前面指令的 source register</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%2018.png"></p>
</li>
</ul>
</li>
</ul>
<h2 id="742---解決-waw-相關性">7.4.2 - 解決 WAW 相關性</h2>
<ul>
<li><code>WAW</code> 影響 RAT 和 ROB 的寫入過程，因此也需要在 register renaming stage 對其進行檢查：
<ol>
<li>
<p>對寫 RAT 進行檢查：</p>
<ul>
<li>如果 1 個 cycle 內有多條指令的 destination registers 都相同，那麼只有<strong>最新的那條指令的映射關係會被寫入 RAT</strong></li>
<li>每條指令都必須比較其 destination register 與所有在其後面的指令的 destination registers，如果發現有相同的 destination registers，則代表該條指令的 destination register 映射關係就不該被寫進 RAT
<ul>
<li>例如：
<ul>
<li>
<p>4 條在同一個 cycle 進行 register renaming 的指令，destination registers 分別為 <code>dst0</code>, <code>dst1</code>, <code>dst2</code>, <code>dst3</code>：</p>
<ul>
<li><code>dst0</code> 需要與 <code>dst1</code>、<code>dst2</code>、<code>dst3</code> 比較</li>
<li><code>dst1</code> 需要與 <code>dst2</code>、<code>dst3</code> 比較</li>
<li><code>dst2</code> 需要與 <code>dst3</code> 比較</li>
<li><code>dst3</code> 由於是最後一條指令，因此此 <code>dst3</code> 的映射關係一定會被寫進 RAT</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%2019.png"></p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>對寫 ROB 進行檢查：</p>
<ul>
<li>
<p>為了能夠釋放那些不再使用的 physical register，同時又可以恢復處理器的狀態，每條指令的 destination register 都必須在做 register renaming 時，將舊的映射關係寫進 ROB 當中 (參考：<a href="#previous-physical-register-mapping-in-rob">link</a>)；如果在 1 個 cycle 內，有兩條以上的指令存在 <code>WAW</code>，那麼比較新的指令的 destination register 舊的映射關係就直接來自比較舊的那條指令，而不是來自於 RAT</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%2020.png"></p>
<ul>
<li>指令 D 的 <code>$r0</code> 之前的 physical register 應該來自於指令 B 的 <code>$p31</code>，而不是 RAT 讀出的值</li>
</ul>
</li>
<li>
<p>每條指令都必須比較其 destination register 與所有在其前面的指令的 destination registers，如果發現有相同的 destination registers，則該條指令的 destination register 舊的映射關係就是與其最相近指令的 destination register 所對應的 physical register</p>
</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch7/image%2021.png"></p>
<ul>
<li>第一條指令由於前面沒有其他的指令，其 destination register 舊的映射關係只能是來自於 RAT</li>
<li>第二條指令必須和第一條指令的 destination register 比較</li>
<li>第三條指令必須和第二條、第一條指令的 destination registers 比較</li>
<li>第四條指令必須和第三、第二、第一條指令的 destination registers 比較</li>
</ul>
</li>
</ol>
</li>
</ul>
<h1 id="75---暫存器重命名過程的恢復">7.5 - 暫存器重命名過程的恢復</h1>
<ul>
<li>當發生 mis-prediction 或 exception 時，需要把在錯誤路徑上的指令給 flushed 掉；如果這些要被 flushed 的指令已經經過了 register renaming stage，就代表這些指令也佔據了 RAT、ROB、Issue Queue 等資源；當指令被 flushed 時，也需要將這些被佔據的資源給恢復，這樣才能保證後續的指令可以在一個正確的 pipeline 中開始執行</li>
<li>Register renaming 的恢復包含了：
<ul>
<li>ROB 的恢復</li>
<li>Issue Queue 的恢復</li>
<li>RAT 的恢復
<ul>
<li>以下方法對前面介紹的三種 register renaming 的方式 (使用 ROB 來實做暫存器重命名、擴充 ARF 來實做暫存器重命名、使用統一的 PRF 來實做暫存器重命名) 都適用：
<ul>
<li>使用 Checkpoint 來恢復 RAT</li>
<li>使用 WALK 來恢復 RAT</li>
<li>使用 Architecture State 來恢復 RAT</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="751---使用-checkpoint">7.5.1 - 使用 Checkpoint</h2>
<ul>
<li>對於一個實現了 Checkpoint 的 RAT 來說，在 SRAM 的每個最小儲存單元：MBC (Main Bit Cell) 周圍都加入同樣的儲存單元：CBC (Checkpoint Bit Cell)，這些 CBC 就實現了 Checkpoint 的功能，可以快速的完成 RAT checkpoint &amp; restore
<ul>
<li>當要保存 RAT 狀態時，就將 MBC 的內容複製到 CBC</li>
<li>當要恢復 RAT 狀態時，就將 CMC 的內容複製到 MBC</li>
</ul>
</li>
<li>cRAT 只需要保存 valid bits</li>
<li>sRAT 則需要將整個 SRAM 都保存下來，checkpoint 所需的電路面積很大，對處理器的速度和面積都會造成不小的負面影響，在設計時需要有所權衡</li>
</ul>
<h2 id="752---使用-walk">7.5.2 - 使用 WALK</h2>
<ul>
<li>Checkpoint 電路會增加硬體的開銷，因此，還有一種比較廉價的方式來保存和恢復 RAT 狀態，那就是使用 <strong>ROB</strong>
<ul>
<li>在 ROB 中保存每條指令之前 architecture registers → physical registers 的映射關係，利用這個資訊，就可以將 RAT 的狀態逐步地”倒回去”，使那些在錯誤路徑上的指令，一個一個恢復其對 RAT 的修改</li>
<li>這種方式稱為 WALK</li>
</ul>
</li>
<li>WALK 對 RAT 的恢復是比較慢的，它首先需要 flush 錯誤路徑上的指令，同時還需要逐個指令恢復 RAT，消耗非常多的時間
<ul>
<li>對於分支指令而言，這樣的方法會增加分支預測失敗時的 mis-penalty</li>
</ul>
</li>
<li>WALK 的優點就是消耗的硬體資源比較少，因此在某些 CPU 中，例如 MIPS R10000，就使用這種方法來恢復發生 exception 時的狀態
<ul>
<li>Exception 發生的頻率比分支預測失敗的頻率來得低，因此這種相對比較慢的方式在某些情況下也是可以接受的</li>
</ul>
</li>
</ul>
<h2 id="753---使用-architecture-state">7.5.3 - 使用 Architecture State</h2>
<ul>
<li>當需要從 CPU 外部存取一個 architecture register 時 (e.g. debugger)，直接使用 register renaming stage 的 RAT 是很難做到的，因為它仍處在 speculative 的階段，因此一般都會在 pipeline 的 commit stage 也使用一個 RAT，所有正確 retire 的指令都會將其對應的 physical registers 更新到這個 RAT，因此這個 RAT 所記錄的映射狀態肯定是正確的；這個 RAT 稱為 <strong>aRAT (Architecture RAT)</strong>
<ul>
<li>只有從 aRAT 才可以找到 architecture registers 對應的正確狀態的 physical registers</li>
</ul>
</li>
<li>利用 aRAT 也可以用來恢復 register renaming stage 的 RAT
<ul>
<li>舉例來說，當一條分支指令發生 mis-prediction 時，並不馬上進行 RAT 的恢復，而是讓 pipeline 繼續執行 (分支指令之前的 pipeline 必須 stall)，當這條分支指令變成 pipeline 中最舊的指令時，此時 aRAT 即表示了分支指令所對應的正確狀態的 RAT，因為分支指令之前的指令都已經順利 retired，並更新了 CPU 的狀態了
<ul>
<li>此時，便可以將 aRAT 內容，直接複製到 register renaming stage 的 RAT，就完成了 RAT 的恢復</li>
</ul>
</li>
</ul>
</li>
<li>但當在 execution stage 發現分支指令 mis-prediction 時，可能 pipeline 中還存有很多比這條分支指令還舊的指令；如果這些指令中包含了 D-cache miss 的 load 指令，那麼這條分支指令就可能得等待一段時間才能變為最舊的指令，這會增大 mis-penalty，一定程度上影響了處理器的效能</li>
<li>使用 aRAT 的好處：
<ul>
<li>在 superscalar CPU 中，在分支指令之前的指令，如果有指令發生了 exception，那麼就必須等到這條指令變為 pipeline 中最舊的指令時，將 pipeline 中的指令給全部 flush 掉，其中也包含了分支指令；由於這條分支指令並不會被執行，因此即使在 register renaming stage 做了 checkpoint 也是浪費，使用 aRAT 在這種情況下就不會白作工</li>
</ul>
</li>
</ul>
<h1 id="76---分發">7.6 - 分發</h1>
<ul>
<li>Pipeline 中的 <strong>Dispatch stage</strong> 就是 in-order execution 和 out-of-order execution 的分界點；指令經過 register renaming stage 後，就會進到 dispatch stage
<ul>
<li>在這個階段，經過 register renaming 後的指令會被寫到不同的 buffers 中，為 out-of-order execution 做好準備</li>
</ul>
</li>
<li>Buffers 主要分為三大類：
<ul>
<li>Issue Queue (out-of-order)
<ul>
<li>大部分的 FU (Function Unit) 都可以 out-of-order 來執行指令</li>
<li>當指令進到 Issue Queue 中時，其 operands 有可能還沒完全準備好，那麼就必須先在 Issue Queue 中等待</li>
<li>只要有任一條指令的 operands 都準備好了，就可以將其送到 FU 來執行，不用理會這條指令在程式中原先的執行順序 (i.e. out-of-order)</li>
<li>由於 out-of-order execution 的關係，Issue Queue 中的 empty entries 分佈是沒有規律的，需要有特別的設計方法</li>
</ul>
</li>
<li>Issue Queue (in-order)
<ul>
<li>即使在 out-of-order core 中，也是有部份的指令是照著程式中的執行順序來執行的，例如分支指令和 store 指令
<ul>
<li>這些指令如果按照 out-of-order 的方式來執行，會帶來不少的硬體消耗，且在性能上也不一定能提昇多少，因此對這些指令一般採取 in-order 的方式來執行</li>
</ul>
</li>
<li>Issue Queue 本質上就是一個 FIFO，透過調整 write pointer 就可以找到 free entries，將 register renaming 過的指令放到其中
<ul>
<li>可以透過 interleaving 的方式來實現這種 Issue Queue</li>
</ul>
</li>
</ul>
</li>
<li>ROB
<ul>
<li>ROB 可以將 out-of-order execution 的指令拉回程式的執行順序；指令經過 register renaming 後會按照程式的執行順序寫到 ROB 中
<ul>
<li>即使有些指令很早就完成執行了，它仍必須等到 ROB 中在它前面的指令執行完成後，才能被 retired 並更新 CPU 的 architecture state (程式可見)</li>
</ul>
</li>
<li>ROB 本質上也是一個 FIFO，透過調整 write pointer 就可以找到 free entries，將 register renaming 過的指令放到其中</li>
</ul>
</li>
</ul>
</li>
<li>Pipeline 中的 <strong>dispatch stage</strong> 就是將 register renaming 後的指令寫到 Issue Queue 和 ROB 的過程，指令被 dispatched 到 Issue Queue 後，就可以以 out-of-order 的方式來執行了，而透過 ROB 可以將 out-of-order execution 的指令拉回程式的執行順序</li>
<li>Dispatch 可以跟 register renaming 放在同一個 cycle 內完成，但當 Issue Queue 和 ROB 的容量比較大時，向它們寫入資料會變得很慢，嚴重影響 CPU 的 cycle time，因此很多 CPU 都會將 dispatch 獨立為一個 stage</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>&lt;超標量處理器概覽&gt; 第 6 章 - 指令解碼</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch6/</link>
      <pubDate>Sun, 23 Mar 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch6/</guid>
      <description>&lt;ul&gt;
&lt;li&gt;在 pipeline 中，decode stage 的任務是將指令中的資訊提取出來，CPU 使用這些資訊控制後續的 pipeline 來執行這條指令&lt;/li&gt;
&lt;li&gt;影響 decode 的複雜度因素有：
&lt;ul&gt;
&lt;li&gt;指令集的複雜度：
&lt;ul&gt;
&lt;li&gt;CISC vs. RISC&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;每個 cycle 可以 decode 的指令個數：
&lt;ul&gt;
&lt;li&gt;每個 cycle 可以 decode &lt;em&gt;N&lt;/em&gt; 條指令，那就需要 &lt;em&gt;N&lt;/em&gt; 個 decode 電路&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;61---指令緩存&#34;&gt;6.1 - 指令緩存&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;現代處理器可以在 fetch stage 從 I-Cache 讀出大於每個 cycle 可以 decode 指令個數的指令，因此需要在 fetch stage 和 decode stage 之間加一個 buffer，用來將 I-Cache 讀出的所有指令保存起來，這個 buffer 就稱為 &lt;code&gt;Instruction Buffer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Fetch stage 最終會輸出兩個主要的內容給 Instruction Buffer：
&lt;ul&gt;
&lt;li&gt;從 I-Cache 讀出的 &lt;em&gt;N&lt;/em&gt; 條指令 (並非所有指令都是有效的)&lt;/li&gt;
&lt;li&gt;有效的指令個數
&lt;ul&gt;
&lt;li&gt;1 instruction fetch 位址不是 cache aligned 時，或是 fetch group 中包含預測為 taken 的指令，會導致 fetch stage 沒辦法寫入 &lt;em&gt;N&lt;/em&gt; 條指令進 Instruction Buffer&lt;/li&gt;
&lt;li&gt;此時需要告知 Instruction Buffer，有效的指令個數&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;現在 superscalar CPU 需要 Instruction Buffer 的原因：
&lt;ul&gt;
&lt;li&gt;Superscalar CPU 可以每個 cycle 可以 fetch 的指令個數大於每個 cycle 可以 decode 的指令個數，這樣即使在發生 I-Cache miss 時，Instruction Buffer 中仍有可能還有保存尚未 decode 的指令，因此不需要 stall pipeline，可以繼續 decode 指令，增加 CPU 的性能&lt;/li&gt;
&lt;li&gt;Superscalar CPU 中即使每個 cycle 可以 decode 的指令個數與每個 cycle 所 fetch 的指令個數相等，在 decode stage 仍會有一些特殊的指令需要處理，導致在 fetch stage 所 fetch 的指令沒有辦法全部被 decode
&lt;ul&gt;
&lt;li&gt;如 ARM 的 multiply-accumulate 指令 (&lt;code&gt;UMAAL RdLo, RdHi, Rn, Rm&lt;/code&gt;)，會有兩個 destination registers，為了減少對 register renaming 的影響，會將其拆分成兩條普通的指令，每條指令只有一個 destination register&lt;/li&gt;
&lt;li&gt;因此，如果在 decode stage 沒有特別的處理，會導致 decode 的指令個數大於 fetch 指令的個數，但後續的 pipeline 都是依照原先的指令個數來設計的，不可能因為這些不常見的指令而增加後續 pipeline 的處理能力 (因為會增加硬體面積，且使用率也不高)&lt;/li&gt;
&lt;li&gt;為了解決此問題，就需要加入 Instruction Buffer，讓 multiply-accumulate 後面的指令，可以等到下一個 cycle 再 decode&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;由於 Instruction Buffer 可以在一個 cycle 內寫入多條的指令，也可以讀出多條的指令，因此也是一個 multi-port 的 FIFIO；但在實際設計上，並不會使用真的 multi-port 的 SRAM 來實現這樣的 FIFO，而是會採用 interleaving 的方式 (參考：&lt;a href=&#34;../superscalar-overview-ch2/#231---true-multi-port&#34;&gt;2.3.1 - True Multi-port&lt;/a&gt;)，使用多個 single-port 的 SRAM 來實現，從而避免使用 multi-port SRAM 所導致的硬體速度上的限制&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;62---一般情況&#34;&gt;6.2 - 一般情況&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;ARM 的 CPSR，只有 4 個 bits (N、C、Z、V)，但其他的暫存器都是 32 bits 的；如果將 CPSR 跟其他的暫存器統一對待，會造成很多暫存器無法有效的被利用，因為 32 bits 的暫存器，只存了 4 bits 的資料
&lt;ul&gt;
&lt;li&gt;因此，一般都是將 CPSR 單獨處理，對 CPSR 單獨使用一套 register renaming 的流程，這樣就可以根據 CPSR 的特性來訂製 register renaming 的流程&lt;/li&gt;
&lt;li&gt;且考慮到條件執行的指令只是少部份，所以所使用的 register file 可以很小，例如只需只用 16 個 physical registers 就足夠了&lt;/li&gt;
&lt;li&gt;指令所攜帶的 source registers 和 destination registers 的個數直接決定了 register renaming 電路在實現上的難易度：
&lt;ul&gt;
&lt;li&gt;像是 register renaming mapping tables 的 ports 數、指令間相關性檢查電路的複雜度&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;由於 RISC 架構的指令比較整齊劃一，很容易解析出指令中的 opcode 和 operands，在 decode stage 產生的 pipeline 控制訊號也比較少，因此 RISC 架構的 instruction decoding 通常都可以在 1 個 cycle 完成&lt;/li&gt;
&lt;li&gt;一般情況下，RISC 處理器在 decode stage 完成的任務可以概括為：
&lt;ul&gt;
&lt;li&gt;What type：例如指令是算術指令還是分支指令&lt;/li&gt;
&lt;li&gt;What operation：例如當指令是算術指令時，是進行什麼運算；是分支指令時，它的跳轉條件是什麼樣的&lt;/li&gt;
&lt;li&gt;What resource：例如對算術指令時來說，其 source 和 destination registers 是哪些，有沒有 immediate value&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;63---特殊情況&#34;&gt;6.3 - 特殊情況&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;即使在 RISC 指令集中，也存在一些特殊的指令；這些指令不能按照一般的方法處理
&lt;ul&gt;
&lt;li&gt;例如：ARM 的 &lt;code&gt;LDM&lt;/code&gt; / &lt;code&gt;STM&lt;/code&gt; 指令，需要多個 cycles 才能完成；而且它們的 source 和 destination registers 有多個，如果在 superscalar CPU 中對它們跟普通指令一樣來處理的話，會需要增加 register renaming mapping table、issue queue 和 ROB 所需的 ports 數，增加硬體的面積，並降低處理效能&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;因此在 superscalar CPU 中，並不會直接處理 &lt;code&gt;LDM&lt;/code&gt; / &lt;code&gt;STM&lt;/code&gt; 這樣的指令，而是會將其轉換為多條普通的指令 (&lt;code&gt;µops&lt;/code&gt;)，每條普通的指令就是一般的 load / store 指令，這樣就可以用普通指令的方式來處理&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;631---分支指令的處理&#34;&gt;6.3.1 - 分支指令的處理&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;先前提到，採用 checkpoint 的方式對 mis-prediction 的分支指令恢復 CPU 的狀態，為了減少分支指令編號分配電路的複雜度，需要限制每個 cycle 能 decode 的分支指令個數，例如每個 cycle 只能 decode 一條分支指令 (參考：&lt;a href=&#34;../superscalar-overview-ch4-part2/#44---%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E5%A4%B1%E6%95%97%E6%99%82%E7%9A%84%E6%81%A2%E5%BE%A9&#34;&gt;4.4 - 分支預測失敗時的恢復&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;但是，每個 cycle 從 Instruction Buffer 讀取的指令中，有可能存在多條的分支指令，需要在 decode stage 做特別的處理
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;簡單的作法：&lt;/p&gt;</description>
      <content:encoded><![CDATA[<ul>
<li>在 pipeline 中，decode stage 的任務是將指令中的資訊提取出來，CPU 使用這些資訊控制後續的 pipeline 來執行這條指令</li>
<li>影響 decode 的複雜度因素有：
<ul>
<li>指令集的複雜度：
<ul>
<li>CISC vs. RISC</li>
</ul>
</li>
<li>每個 cycle 可以 decode 的指令個數：
<ul>
<li>每個 cycle 可以 decode <em>N</em> 條指令，那就需要 <em>N</em> 個 decode 電路</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="61---指令緩存">6.1 - 指令緩存</h1>
<ul>
<li>現代處理器可以在 fetch stage 從 I-Cache 讀出大於每個 cycle 可以 decode 指令個數的指令，因此需要在 fetch stage 和 decode stage 之間加一個 buffer，用來將 I-Cache 讀出的所有指令保存起來，這個 buffer 就稱為 <code>Instruction Buffer</code></li>
<li>Fetch stage 最終會輸出兩個主要的內容給 Instruction Buffer：
<ul>
<li>從 I-Cache 讀出的 <em>N</em> 條指令 (並非所有指令都是有效的)</li>
<li>有效的指令個數
<ul>
<li>1 instruction fetch 位址不是 cache aligned 時，或是 fetch group 中包含預測為 taken 的指令，會導致 fetch stage 沒辦法寫入 <em>N</em> 條指令進 Instruction Buffer</li>
<li>此時需要告知 Instruction Buffer，有效的指令個數</li>
</ul>
</li>
</ul>
</li>
<li>現在 superscalar CPU 需要 Instruction Buffer 的原因：
<ul>
<li>Superscalar CPU 可以每個 cycle 可以 fetch 的指令個數大於每個 cycle 可以 decode 的指令個數，這樣即使在發生 I-Cache miss 時，Instruction Buffer 中仍有可能還有保存尚未 decode 的指令，因此不需要 stall pipeline，可以繼續 decode 指令，增加 CPU 的性能</li>
<li>Superscalar CPU 中即使每個 cycle 可以 decode 的指令個數與每個 cycle 所 fetch 的指令個數相等，在 decode stage 仍會有一些特殊的指令需要處理，導致在 fetch stage 所 fetch 的指令沒有辦法全部被 decode
<ul>
<li>如 ARM 的 multiply-accumulate 指令 (<code>UMAAL RdLo, RdHi, Rn, Rm</code>)，會有兩個 destination registers，為了減少對 register renaming 的影響，會將其拆分成兩條普通的指令，每條指令只有一個 destination register</li>
<li>因此，如果在 decode stage 沒有特別的處理，會導致 decode 的指令個數大於 fetch 指令的個數，但後續的 pipeline 都是依照原先的指令個數來設計的，不可能因為這些不常見的指令而增加後續 pipeline 的處理能力 (因為會增加硬體面積，且使用率也不高)</li>
<li>為了解決此問題，就需要加入 Instruction Buffer，讓 multiply-accumulate 後面的指令，可以等到下一個 cycle 再 decode</li>
</ul>
</li>
</ul>
</li>
<li>由於 Instruction Buffer 可以在一個 cycle 內寫入多條的指令，也可以讀出多條的指令，因此也是一個 multi-port 的 FIFIO；但在實際設計上，並不會使用真的 multi-port 的 SRAM 來實現這樣的 FIFO，而是會採用 interleaving 的方式 (參考：<a href="../superscalar-overview-ch2/#231---true-multi-port">2.3.1 - True Multi-port</a>)，使用多個 single-port 的 SRAM 來實現，從而避免使用 multi-port SRAM 所導致的硬體速度上的限制</li>
</ul>
<h1 id="62---一般情況">6.2 - 一般情況</h1>
<ul>
<li>ARM 的 CPSR，只有 4 個 bits (N、C、Z、V)，但其他的暫存器都是 32 bits 的；如果將 CPSR 跟其他的暫存器統一對待，會造成很多暫存器無法有效的被利用，因為 32 bits 的暫存器，只存了 4 bits 的資料
<ul>
<li>因此，一般都是將 CPSR 單獨處理，對 CPSR 單獨使用一套 register renaming 的流程，這樣就可以根據 CPSR 的特性來訂製 register renaming 的流程</li>
<li>且考慮到條件執行的指令只是少部份，所以所使用的 register file 可以很小，例如只需只用 16 個 physical registers 就足夠了</li>
<li>指令所攜帶的 source registers 和 destination registers 的個數直接決定了 register renaming 電路在實現上的難易度：
<ul>
<li>像是 register renaming mapping tables 的 ports 數、指令間相關性檢查電路的複雜度</li>
</ul>
</li>
<li>由於 RISC 架構的指令比較整齊劃一，很容易解析出指令中的 opcode 和 operands，在 decode stage 產生的 pipeline 控制訊號也比較少，因此 RISC 架構的 instruction decoding 通常都可以在 1 個 cycle 完成</li>
<li>一般情況下，RISC 處理器在 decode stage 完成的任務可以概括為：
<ul>
<li>What type：例如指令是算術指令還是分支指令</li>
<li>What operation：例如當指令是算術指令時，是進行什麼運算；是分支指令時，它的跳轉條件是什麼樣的</li>
<li>What resource：例如對算術指令時來說，其 source 和 destination registers 是哪些，有沒有 immediate value</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="63---特殊情況">6.3 - 特殊情況</h1>
<ul>
<li>即使在 RISC 指令集中，也存在一些特殊的指令；這些指令不能按照一般的方法處理
<ul>
<li>例如：ARM 的 <code>LDM</code> / <code>STM</code> 指令，需要多個 cycles 才能完成；而且它們的 source 和 destination registers 有多個，如果在 superscalar CPU 中對它們跟普通指令一樣來處理的話，會需要增加 register renaming mapping table、issue queue 和 ROB 所需的 ports 數，增加硬體的面積，並降低處理效能</li>
</ul>
</li>
<li>因此在 superscalar CPU 中，並不會直接處理 <code>LDM</code> / <code>STM</code> 這樣的指令，而是會將其轉換為多條普通的指令 (<code>µops</code>)，每條普通的指令就是一般的 load / store 指令，這樣就可以用普通指令的方式來處理</li>
</ul>
<h2 id="631---分支指令的處理">6.3.1 - 分支指令的處理</h2>
<ul>
<li>先前提到，採用 checkpoint 的方式對 mis-prediction 的分支指令恢復 CPU 的狀態，為了減少分支指令編號分配電路的複雜度，需要限制每個 cycle 能 decode 的分支指令個數，例如每個 cycle 只能 decode 一條分支指令 (參考：<a href="../superscalar-overview-ch4-part2/#44---%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E5%A4%B1%E6%95%97%E6%99%82%E7%9A%84%E6%81%A2%E5%BE%A9">4.4 - 分支預測失敗時的恢復</a>)</li>
<li>但是，每個 cycle 從 Instruction Buffer 讀取的指令中，有可能存在多條的分支指令，需要在 decode stage 做特別的處理
<ul>
<li>
<p>簡單的作法：</p>
<ul>
<li>遇到分支指令時，就不在同個 cycle decode 這條分支指令後面的指令，而是將它們 stall 到下個 cycle</li>
<li>這個功能只需更新 Instruction Buffer 的 pointer 即可實現</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch6/image.png"></p>
<ul>
<li>原本從 Instruction Buffer 中讀取指令：<code>ADD</code> → <code>BR</code> → <code>SUB</code> → <code>BR</code></li>
<li><em>Cycle 1</em> 只 decode <code>ADD</code> 和 <code>BR</code>，Instruction Buffer pointer + 2</li>
<li><code>SUB</code> 和 <code>BR</code> stall 到 <em>Cycle 2</em> 才被 decoded，Instruction Buffer pointer + 2</li>
</ul>
</li>
<li>
<p>限制每個 cycle 最多只能 decode 一條分支指令，降低了分支指令編號分配電路的複雜度</p>
<ul>
<li>雖然效能會下降一點，但是比較容易實現，是一種折衷的方法</li>
</ul>
</li>
<li>
<p>P.S. 採用 ROB 來恢復發生 mis-prediction 時 CPU 的狀態，就不再需要分支指令編號分配電路了，因此也不用限制每個 cycle 最多只能 decode 一條分支指令</p>
</li>
</ul>
</li>
<li>在 decode stage 還有另外一項重要的任務，就是位分支預測是否正確進行初步的檢查
<ul>
<li>越早發現 mis-prediction，penalty 越小</li>
<li>對於直接跳轉類型的指令，由於在 decode stage 就可以計算出其要跳轉的位址，因此可以在 decode stage 對這些分支指令的目標位址是否預測正確進行檢查
<ul>
<li>如果發現 mis-prediction，可以直接使用正確的位址來 fetch instruction</li>
</ul>
</li>
</ul>
</li>
<li>分支指令在 decode stage 是無法得到實際跳轉的方向的 (除了如：<code>jmp</code> 這種 unconditional branch 的指令)，因此在 decode stage 無法對分支預測的方向進行檢查，需要等到後續的 pipeline stage 階段才能完成</li>
</ul>
<h2 id="632---乘累加乘法指令的處理">6.3.2 - 乘累加/乘法指令的處理</h2>
<ul>
<li>乘法和乘累加指令在 MIPS 架構中是一種特殊的指令；指令中包含兩個 destination registers (<code>Hi register</code>、<code>Low register</code>)
<ul>
<li>這兩個暫存器並不屬於通用暫存器，因此需要特別處理</li>
</ul>
</li>
<li>在 superscalar CPU 中，需要對每條指令都進行 register renaming：
<ul>
<li>將指令的 source registers 變為對應的 physical registers</li>
<li>並為 destination register 分配一個 physical register</li>
</ul>
</li>
<li>大部分的指令都只有一個 destination register，但這種指令確有兩個 destination registers；此外，如果直接將此乘累加/乘法指令直接加進 ROB 中，則 ROB 也需要能夠存放兩個 destination registers，增加了 ROB 的面積，但這些增加的部份大部分都是沒有被使用的，造成了資源的浪費</li>
<li>可以採用下列的兩種方式來處理：
<ol>
<li>將 <code>Hi register</code>、<code>Low register</code> 分配為 MIPS CPU 的第 33、34 個通用暫存器，這種分配過程只在 CPU 內部進行，register renaming mapping table 也同樣要支援新的通用暫存器</li>
<li>將乘法/乘累加指令拆成兩條指令：
<ul>
<li>乘法指令：<code>{Hi, Lo} = Rs x Rt</code>
<ul>
<li>可以拆解為如下的兩條指令：
<ul>
<li><code>Hi = Rs x Rt</code></li>
<li><code>Lo = Rs x Rt</code></li>
</ul>
</li>
<li>這兩條指令經過 register renaming 並寫進 ROB 中時，會佔用兩個 ROB entries</li>
<li>實際上，這兩條指令只使用一個乘法器就足夠了，只要這個乘法操作在 pipeline 的 execution stage 計算完畢，這兩條指令就同時完成了</li>
</ul>
</li>
<li>乘累加指令：<code>{Hi, Lo} = {Hi, Lo} + Rs x Rt</code>
<ul>
<li>需要讀取四個 source registers (<code>Rs</code>, <code>Rt</code>, <code>Hi</code>, <code>Lo</code>)，同時也有兩個 destination registers (<code>Hi</code>, <code>Lo</code>)，正好是兩倍的普通指令，則可以將乘累加指令拆分成兩條普通指令：
<ul>
<li><code>Lo = {Hi, Lo}</code></li>
<li><code>Hi = Rs x Rt</code></li>
</ul>
</li>
<li>在 CPU 內部，乘累加指令仍然是以一個完整的指令來完成運算，因此指令的拆分只是更有利於 register renaming，以及便於在 ROB 中的存放，每條被拆分的指令並不是單獨進行運算的</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>
<li>此外，也需要對 <strong>issue queue</strong> 做特殊的處理，將乘法指令和乘累加指令使用一個 <strong>FU (Function Unit)</strong>，這個 FU 的 issue queue 和其他的有所不同，它包含了四個 source registers、兩個 destination registers</li>
<li>在 decode stage 拆分的兩條指令，經過 register renaming 後，就要寫入 ROB 和 issue queue 中 (i.e. Dispatch)，此時：
<ul>
<li>寫進 ROB 中依舊是以<strong>兩條指令</strong>的方式寫入，佔據 ROB 兩個 entries</li>
<li>寫進 issue queue 時則是將兩條指令進行<strong>融合 (fusion)</strong>，這兩條指令在 issue queue 中又變為了<strong>一條完整的乘累加或乘法指令</strong>，這樣就能夠保證 FU 在執行時，能夠執行一個完整的乘累加或乘法指令</li>
</ul>
</li>
<li>在 <em>N-way</em> 的 superscalar CPU 中，每個 cycle 可以 decode 和 register rename <em>N</em> 條指令，但如果 decode 的 <em>N</em> 條指令中包含乘法和乘累加指令，那麼就會 decode 出 &gt; <em>N</em> 條的指令，register renaming 不可能特別為了極少出現的情況而浪費其硬體面積，此問題可以透過下列兩種方法來解決：
<ol>
<li>
<p>在 decode stage 和 register renaming stage 新增一個 buffer，暫存 decode stage 所產生的指令；在 register renaming stage，每個 cycle 都從此 buffer 讀取指令來處理即可</p>
<ul>
<li>指令經過 decode 後會得到很多的資訊，例如 pipeline 的控制訊號等，導致這個 buffer 需要的 bits 數會很大，在一定程度上增加了硬體的面積</li>
</ul>
</li>
<li>
<p>限制每個 cycle decode 的指令數，一旦在 decode stage 發現乘累加指令 (e.g. <code>MADD</code> 指令，會被拆分為 <code>MADD1</code> 及 <code>MADD2</code> 指令)，那麼只有 <code>MADD1</code> 以及在其之前的指令可以進行 decode，<code>MADD2</code> 以及在其之後的指令需要等到下個 cycle 才可以被 decoded</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch6/image%201.png"></p>
<ul>
<li>Cycle 1：<code>ADD</code>、<code>MADD1</code> (<code>MADD</code> 被拆分，因此<code>MADD2</code> 必須等待)</li>
<li>Cycle 2：<code>MADD2</code>、<code>SUB、MSUB1</code> (<code>MSUB</code> 被拆分，因此<code>MSUB2</code> 必須等待)</li>
<li>Cycle 3：<code>MSUB2</code></li>
<li>這樣的作法雖然會降低 CPU 的性能，但考慮到乘累加指令的使用頻率並不高，這種方式易於實現，因此是一種可以接受的折衷方案</li>
</ul>
</li>
</ol>
</li>
</ul>
<h2 id="633---前後變址指令的處理">6.3.3 - 前/後變址指令的處理</h2>
<ul>
<li>ARM 指令集中還有一種前/後變址 (pre-index/post-index) 指令，能夠在一條指令中完成兩個任務
<ul>
<li>例如：<code>ldr $r2, [$r1, #4]!</code>
<ul>
<li>Load <code>mem[$r1 + 4]</code> to <code>$r2</code></li>
<li><code>$r1 = $r1 + 4</code></li>
</ul>
</li>
<li>相當於此 load 指令有兩個 destination registers，會給 register renaming 以及後續的過程帶來麻煩</li>
<li>因此在 superscalar CPU 實現時，仍舊會在 decode stage 將這條 load 指令，拆成兩條普通的指令：
<ul>
<li><code>ldr $r2, [$r1, #4]</code></li>
<li><code>add $r1, $r1, 4</code></li>
</ul>
</li>
<li>經過拆分後，一樣會導致在 decode stage 得到的指令個數 &gt; <em>N</em> 條的指令，可以參考 <a href="#632---%E4%B9%98%E7%B4%AF%E5%8A%A0%E4%B9%98%E6%B3%95%E6%8C%87%E4%BB%A4%E7%9A%84%E8%99%95%E7%90%86">6.3.2 節</a>的解決方法</li>
</ul>
</li>
</ul>
<h2 id="634---ldmstm-指令的處理">6.3.4 - LDM/STM 指令的處理</h2>
<ul>
<li>ARM LDM/STM 指令：
<ul>
<li><code>LDM/STM &lt;Rn&gt;{!}, &lt;register list&gt;</code>
<ul>
<li><code>STM</code>：將多個暫存器的值，存到記憶體的一段連續位址</li>
<li><code>LTM</code>：將記憶體一段連續位址的值，載入到多個暫存器</li>
</ul>
</li>
<li>例如：<code>ldm $r5!, {$r0 ~ $r3}</code>
<ul>
<li>
<p>這條指令在 superscalar CPU 內部中，會被拆成四條普通的 <code>load</code> 指令和一條普通的 <code>add</code> 指令</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch6/image%202.png"></p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="635---條件執行指令的處理">6.3.5 - 條件執行指令的處理</h2>
<ul>
<li>
<p>ARM 的條件執行指令，會檢查 CPSR 的值決定該條指令是否執行；在 ARM CPU 中，本質就是將 CPSR 也當作一個 source / destination registers 來看待</p>
<ul>
<li>當一條指令會更新 CPSR 時，CPSR 就會被當作為一個 destination register
<ul>
<li>E.g. <code>subs</code> 指令</li>
</ul>
</li>
<li>當一條指令需要條件執行時，CPSR 就會被當作為一個 source register
<ul>
<li>E.g. <code>addeq</code> 指令</li>
</ul>
</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch6/image%203.png"></p>
</li>
<li>
<p>對於 out-of-order CPU 來說，由於指令是亂序執行的，在條件執行指令被執行的當下，CPSR 的值有可能不是最新的；如果不加以處理，就會發生錯誤，例如：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch6/image%204.png"></p>
<ul>
<li><code>inst4</code> 被提前到 <code>inst2</code> 之前執行並更新了 CPSR，那麼當 <code>inst2</code> 和 <code>inst3</code> 執行並讀取 CPSR 時，就會錯誤的使用到 <code>inst4</code> 所更新的狀態了</li>
<li><code>inst6</code> 被提前到 <code>inst4</code> 之前執行，此時讀取的 CPSR 值內容有可能是來自 <code>inst1</code>，而不是 <code>intn4</code></li>
</ul>
</li>
<li>
<p>因此，當一條指令要更新 CPSR 時 (e.g. 上述範例中的 <code>inst1</code> 和 <code>inst4</code>)，需要使用一個新的 CPSR 來保存這條指令的狀態，並給這個 CPSR 一個新的名字；與之對應的指令 (e.g. 上述範例中的 <code>inst2</code>, <code>inst3</code> 與 <code>inst5</code>, <code>inst6</code>)，都會使用新的 CPSR 來作為其 source register</p>
<ul>
<li>透過 register renaming 的方法，就可以消除 ARM CPU 中條件執行指令所帶來的問題</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>&lt;超標量處理器概覽&gt; 第 4 章 - 分支預測 (Part 2)</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch4-part2/</link>
      <pubDate>Fri, 14 Mar 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch4-part2/</guid>
      <description>&lt;h1 id=&#34;43---分支指令的目標位址預測&#34;&gt;4.3 - 分支指令的目標位址預測&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;分支指令的目標位址可以分為兩種：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;直接跳轉 (direct)：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;對於直接跳轉指令 (e.g. RISC-V 的 &lt;code&gt;beq&lt;/code&gt;、&lt;code&gt;jal&lt;/code&gt;、&lt;code&gt;lui&lt;/code&gt; 指令)，它的跳轉 offset 是以 immediate value 的方式 encode 在 opcode 中 (有可能是 PC-relative，也有可能不是)，所以它的目標位址是固定的，只要記錄這條分支指令目標位址即可&lt;/li&gt;
&lt;li&gt;當再次遇到這條分支指令時，如果分支預測結果是 taken，那麼目標位址就可以直接使用先前所記錄的值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;間接跳轉 (indirect)：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;對於間接跳轉指令 (e.g. RISC-V 的 &lt;code&gt;jalr&lt;/code&gt; 指令)，由於它的目標位址來自於暫存器，而暫存器的值是有可能一直變化的，所以對間接跳轉指令來說，要預測目標位址並不是一件容易的事情&lt;/li&gt;
&lt;li&gt;然而慶幸的是，程式中大部份的間接跳轉指令都是用來呼叫函式的，也就是 call 和 return；這種目標位址是有規律的，因此可以對其進行預測&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;431---直接跳轉類型的分支預測&#34;&gt;4.3.1 - 直接跳轉類型的分支預測&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;對於直接跳轉類型的分支指令 (假設是 PC-relative)，其目標位址有兩種情況：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;當分支指令 not taken 時：
&lt;ul&gt;
&lt;li&gt;目標位址 =  當前分支指令的 PC 值 + sizeof(fetch group)
&lt;ul&gt;
&lt;li&gt;e.g. PC + 4&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;當分支指令 taken 時：
&lt;ul&gt;
&lt;li&gt;目標位址 = 當前分支指令的 PC 值 + signed extended offset&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;e.g. PC + offset&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="43---分支指令的目標位址預測">4.3 - 分支指令的目標位址預測</h1>
<ul>
<li>分支指令的目標位址可以分為兩種：
<ul>
<li><strong>直接跳轉 (direct)：</strong>
<ul>
<li>對於直接跳轉指令 (e.g. RISC-V 的 <code>beq</code>、<code>jal</code>、<code>lui</code> 指令)，它的跳轉 offset 是以 immediate value 的方式 encode 在 opcode 中 (有可能是 PC-relative，也有可能不是)，所以它的目標位址是固定的，只要記錄這條分支指令目標位址即可</li>
<li>當再次遇到這條分支指令時，如果分支預測結果是 taken，那麼目標位址就可以直接使用先前所記錄的值</li>
</ul>
</li>
<li><strong>間接跳轉 (indirect)：</strong>
<ul>
<li>對於間接跳轉指令 (e.g. RISC-V 的 <code>jalr</code> 指令)，由於它的目標位址來自於暫存器，而暫存器的值是有可能一直變化的，所以對間接跳轉指令來說，要預測目標位址並不是一件容易的事情</li>
<li>然而慶幸的是，程式中大部份的間接跳轉指令都是用來呼叫函式的，也就是 call 和 return；這種目標位址是有規律的，因此可以對其進行預測</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="431---直接跳轉類型的分支預測">4.3.1 - 直接跳轉類型的分支預測</h2>
<ul>
<li>
<p>對於直接跳轉類型的分支指令 (假設是 PC-relative)，其目標位址有兩種情況：</p>
<ol>
<li>當分支指令 not taken 時：
<ul>
<li>目標位址 =  當前分支指令的 PC 值 + sizeof(fetch group)
<ul>
<li>e.g. PC + 4</li>
</ul>
</li>
</ul>
</li>
<li>當分支指令 taken 時：
<ul>
<li>目標位址 = 當前分支指令的 PC 值 + signed extended offset</li>
</ul>
</li>
</ol>
</li>
<li>
<p>e.g. PC + offset</p>
</li>
<li>
<p>由於直接跳轉的分支指令，其跳轉的 offset 以 immediate value 的方式 encode 在 opcode 中的，所以 offset 是不會發生變化的，因此對於此類型的指令進行分支預測是很容易的</p>
<ul>
<li>只需使用一個表格，記錄下每條分支指令的目標位址即可；當一條分支指令執行且預測為 taken 時，直接查詢該表格即可得知該分支指令的目標位址</li>
</ul>
</li>
<li>
<p>此表格也就是所謂的 <code>BTB (Branch Target Buffer)</code>，本質上就是一種 cache，使用 PC 值的一部分作為 index，PC 值的另外一部份作為 tag (i.e. partial-tag)</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image.png"></p>
<ul>
<li>BTB 中存放多條的 <code>BTA (Branch Target Address)</code></li>
<li>因為 index 部份相同的分支指令會 index BTB 中同一條 BTA，因此還需要使用 tag 來判斷是否 hit 或 miss</li>
<li>同樣的，由於 BTB 大小有限，當發生 conflict 時，就需要將原本的 BTA 給 evict 出去，然後將新的 BTA 寫入 BTB</li>
</ul>
</li>
<li>
<p>BTB 也可以採用 set-associative 的設計，當有多條 index 相同的分支指令時，可以將它們的 BTA 寫到不同的 ways 中</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%201.png"></p>
</li>
<li>
<p>Set-associative 的 BTB 會增加硬體設計的複雜度，使 BTB 的佔用的硬體面積變大，同時降低了 BTB 的訪問速度，因此現實的處理器中，BTB 的 way 數通常都會比較小</p>
</li>
<li>
<p>除了使用 PC 值的一部分作為 partial-tag，也可以採用其他的方法 (如：XOR) 來 encode tag，降低 tag 的 bits 數：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%202.png"></p>
<ul>
<li>此方法將 28 bits 的 PC 值，每相鄰的 4 個 bits 做 XOR，最後得到 14 bits 的 tag 值</li>
</ul>
</li>
<li>
<p>一般情況下，為了最大限度的利用 BTB 有限的空間，只會將發生 taken 的分支指令的目標位址存入 BTB 中；對於 not taken 的分支指令，其目標位址就是下一條指令的位址，因此不需要特別做處理</p>
</li>
<li>
<p>當一條分支指令發生 BTB miss 或是 BTB conflict 時，可以採用以下兩種方式來處理：</p>
<ol>
<li>
<p>停止執行：</p>
<ul>
<li>
<p>當發生 BTB miss 或是 BTB conflict 時，暫停 instruction fetch (i.e. 產生 bubbles，也就是 pipeline stall)，直到這條分支指令的目標位址被算出來為止</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%203.png"></p>
<ul>
<li>
<p>在 Cycle <em>i</em> (fetch stage) 的時候發現分支指令的 PC，但在 BTB 中找不到對應的 BTA，發生 BTB miss，那麼就得暫停後續的 instruction fetch，直到分支指令從 I-Cache 中讀取出來並 decode (Cycle <em>i + 2</em>)，因此會引入兩個 bubbles，在 Cycle <em>i + 3</em> 才可以再根據預測目標位址 fetch 下一道指令</p>
<ul>
<li>如果對 I-Cache 的訪問需要比較多的 cycles，就會引入更多的 bubbles</li>
</ul>
</li>
<li>
<p>對於直接跳轉的分支指令，其目標位址可以在 decode stage 就得知其跳轉的 offset，此時就可以將目標位址給計算出來：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%204.png"></p>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>繼續執行：</p>
<ul>
<li>發生 BTB miss 的時候，不將 pipeline 給停下來，而是直接繼續使用下一道指令的位址來 fetch instruction (e.g. PC + 4)</li>
<li>當最後分支指令的目標位址計算出來後，如果發現目標位址跟下一道的指令位址不同，則將所有分支指令之後的指令都從 pipeline 中給 flush 掉，並重新使用計算出來後的目標位址來 fetch instruction</li>
<li>由於分支指令也有可能是 not taken，所以直接繼續使用下一道指令的位址來 fetch instruction，而不停下 pipeline，也是有可能是正確的</li>
<li>但從功耗的觀點來看，這樣做是更浪費功耗的，所以如果是採用此方法，對於功耗比較敏感的設計並不是一個好的選擇</li>
</ul>
</li>
</ol>
</li>
</ul>
<h2 id="432---間接跳轉類型的分支預測">4.3.2 - 間接跳轉類型的分支預測</h2>
<ul>
<li>對於間接跳轉的分支指令來說，其目標位址是存在暫存器中的，且是經常變化的，所以無法透過 BTB 的方式來對目標位址進行準確的預測</li>
<li>所幸的是，大部分的間接跳轉的分支指令都是用來呼叫函式的，也就是 call 和 return；這種目標位址是有規律的，因此可以對其進行預測</li>
</ul>
<ol>
<li>Call 和 return 指令的分支預測：
<ul>
<li>
<p>對於程式中一條 call 指令，其每次呼叫的函式都是固定的，也代表其目標位址都是固定的，因此可以透過 BTB 來對 call 指令來進行預測：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%205.png"></p>
</li>
<li>
<p>但對於 return 指令而言，有可能會 return 到不同的目標位址</p>
<ul>
<li>E.g. <code>printf()</code> 被不同的函式呼叫</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%206.png"></p>
</li>
<li>
<p>根據 return 指令的特點，可以設計一個暫存器，保存最近執行的 call 指令的下一條指令的位址；這個暫存器是 LIFO 的，也就符合函式呼叫和返回的特點</p>
<ul>
<li>
<p>這個暫存器的工作原理和 stack 是一樣的，稱為：<code>RAS (Return Address Stack)</code></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%207.png"></p>
</li>
</ul>
</li>
<li>
<p>綜合起來，可以使用 <strong>BTB</strong> 對 <strong>call 指令</strong>進行預測，而使用 <strong>RAS</strong> 對 <strong>return 指令</strong>進行預測：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%208.png"></p>
<ul>
<li>要使 RAS 能夠正確工作，需要符合下面兩個條件：
<ol>
<li>當遇到 call 指令時，需要能夠將 call 指令的 return address 寫進 RAS，而這需要識別出哪條指令是 call 指令
<ul>
<li>正常狀態下，只有到了 pipeline 的 decode stage 才能得知指令是否為 call 指令</li>
<li>但是現代處理器都需要好幾個 cycles 才能從 I-Cache 中取得指令，因此當指令達到 pipeline 的 decode stage 時，在這條指令後面已經有很多條指令進到 pipeline 內了，如果當中包含 return 指令 (e.g. 假設函式很短)，那麼這個 return 指令將無法從 RAS 中取得正確的 return address，造成分支預測失敗，降低處理器的執行效率</li>
<li>如果能在分支預測階段 (e.g. <a href="../superscalar-overview-ch4-part1/#41---%E6%A6%82%E8%BF%B0">fetch stage</a>) 就可以得知一條指令是否為 call 指令，也就是透過 PC 值就可以知道是否為 call 指令，那麼就可以即時將 call 指令的 return address 寫進 RAS；即使 call 指令後面馬上接著一條 return 指令，這條 return 指令也可以透過 RAS 來預測正確的目標位址</li>
<li>要實現這個功能依舊需要借助 BTB，BTB 中保存了所有發生跳轉的指令，且 call 和 return 指令總是會跳轉的，因此它們都會被保存在 BTB 中
<ul>
<li>
<p>需要在 BTB 中新增一個欄位，用來標記分支指令的類型</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%209.png"></p>
</li>
<li>
<p>而後只需要透過查詢 BTB，就可以得知這條分支指令是否為一個 call / return 指令了</p>
</li>
</ul>
</li>
</ul>
</li>
<li>當預測 return 指令的目標位址時，需要能夠選擇 RAS 中的輸出作為目標位址，而不是選擇 BTB 的輸出值，因此仍需要在分支指令預測階段 (e.g. fetch stage) 就知道指令的類型
<ul>
<li>使用 1. 的方法，透過 BTB 中新增的指令類型欄位，得知該分支指令是否是一個 recall 指令</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
<li>
<p>但如果一個 recursive 函式有很多層，導致 RAS 的空間不夠存放所有 retrun 指令的目標位址時，要如何處理?</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2010.png"></p>
<ul>
<li>
<p>當 call SHR 的時候，RAS 已經沒有空間可以存放其 return address 了：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2011.png"></p>
</li>
</ul>
</li>
<li>
<p>有兩種方法可以處理這種情況：</p>
<ol>
<li>
<p>不對 call 指令進行處理：</p>
<ul>
<li>不修改 RAS，新的 call 指令的 return address 會被直接拋棄，這樣在下一次執行 return 指令時，就一定會發生 mis-prediction</li>
<li>除此之外，這樣的作法還要求 RAS 的 pointer 不能改變，否則後續的 return 指令都無法對應到自己的目標位址</li>
<li>顯然這是一個<strong>較差</strong>的作法</li>
</ul>
</li>
<li>
<p>繼續按照順序將 call 指令的 return address 寫入 RAS，最舊的 return address 會從 RAS 中被 evicted：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2012.png"></p>
<ul>
<li>
<p>這樣的方式，當要執行被 evicted 的 return address 對應的 return 指令時，勢必一定會發生 mis-prediction，但這樣的方式比 1. 要來得好的原因在其存在一定的正確性：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2013.png"></p>
<ul>
<li>
<p>針對這樣的 recursive function，其實寫入到 RAS 和被 evicted 的 return address 都是同樣的：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2014.png"></p>
</li>
<li>
<p>這樣即使較舊的 return address 被 evicted，RAS 還是有可能給出正確的 return address</p>
<ul>
<li>P.S. 這邊假設 RAS 應該是一個 circular buffer</li>
</ul>
</li>
</ul>
</li>
<li>
<p>不過像這樣 RAS 都被同一個 call 指令的 return address 所佔用，是非常浪費空間的；對於連續執行的同一條 call 指令來說，其 return address 可以只保留一份，並透過新增一個 counter 來標記 call 指令被執行的次數</p>
<ul>
<li>E.g. 使用一個 8 bits counter，就可以紀錄到最多 256 次的 recursive call 了</li>
</ul>
</li>
<li>
<p>RAS 的 pointer 只有在執行 return 指令，counter 值減 1 後為 0 時，才會將 RAS 的 pointer 指向下一個 return address</p>
</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
<li>其他預測方法：
<ul>
<li>
<p>對於其他 call 和 return 指令以外的間接跳轉類型的分支指令，雖然其目標位址理論上可以有很多種可能性，不過實際上只會有幾個固定的位址而已，例如：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-5">5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-6">6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-7">7</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">switch</span> (a) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">1</span><span style="color:#f92672">:</span> <span style="color:#75715e">/* Jump to address 1 */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">2</span><span style="color:#f92672">:</span> <span style="color:#75715e">/* Jump to address 2 */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">3</span><span style="color:#f92672">:</span> <span style="color:#75715e">/* Jump to address 3 */</span>
</span></span><span style="display:flex;"><span>    ...
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">9</span><span style="color:#f92672">:</span> <span style="color:#75715e">/* Jump to address 9 */</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>以這個例子為例，目標位址最多就 9 種而已 (address 1 ~ address 9)，也就有機會預測其目標位址</li>
</ul>
</li>
<li>
<p>對於間接跳轉類型的分支指令，其目標位址也可能是和過去的執行情況有關，因此可以利用基於局部歷史預測的分支預測方法中的 BHR 對目標位址進行預測：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2015.png"></p>
<ul>
<li>跟之前 <code>2-bit saturating counter</code> 分支預測方法唯一的差別在於將 PHT 換成了 <code>Target Cache</code></li>
</ul>
</li>
</ul>
</li>
</ol>
<ul>
<li>總結：
<ul>
<li>使用 <strong>BTB</strong> 對<strong>直接跳轉類型的分支指令</strong>，和 <strong>call 指令</strong>進行預測</li>
<li>使用 <strong>RAS</strong> 對 <strong>return 指令</strong>進行預測</li>
<li>使用 <strong>BHR</strong> + <strong>Target Cache</strong> 對<strong>其他間接跳轉類型的分支指令</strong>進行預測</li>
</ul>
</li>
<li>尤其是 <strong>BTB</strong> 和 <strong>RAS</strong>，幾乎是現代 superscalar CPU 必備的元件</li>
</ul>
<h2 id="433---小結">4.3.3 - 小結</h2>
<ul>
<li>
<p>預測分支指令的<strong>方向</strong>：使用 <strong>BHR (多個 BHRs 組成 BHT)</strong>、<strong>GHR</strong> 和 <strong>PHT</strong> (<code>2-bit saturating counter</code>)</p>
</li>
<li>
<p>預測分支指令的<strong>目標位址</strong>：使用 <strong>BTB</strong>、<strong>RAS</strong> 和 <strong>BHR</strong> + <strong>Target Cache</strong></p>
</li>
<li>
<p>綜合到目前為止，完整的分支預測方法如下：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2016.png"></p>
</li>
<li>
<p>在 superscalar CPU 中，有很多階段都可以得到分支指令的結果，例如：</p>
<ul>
<li><strong>Fetch stage</strong> 可以得到直接跳轉分支指令的結果</li>
<li><strong>Execution stage</strong> 可以得到間接跳轉分支指令的結果</li>
</ul>
</li>
<li>
<p>在得到分支指令結果後，就可以跟分支預測是否正確做檢查，如果發生 mis-prediction，就需要將在這條分支指令之後 fetch 進 pipeline 的指令都給 flush 掉，也就會造成多個 bubbles，降低處理器的性能，i.e. mis-prediction penalty</p>
</li>
<li>
<p>此外，這些要被 flushed 的指令有可能已經更改了 pipeline 中的元件的內容 (e.g. register renaming 的 mapping table 被更新)，因此需要一個機制來恢復被 mis-prediction 更改的內容</p>
</li>
</ul>
<h1 id="44---分支預測失敗時的恢復">4.4 - 分支預測失敗時的恢復</h1>
<ul>
<li>
<p>在 pipeline 中，有很多 stages 可以對分支預測<strong>是否正確</strong>進行檢查：</p>
<ul>
<li>Decode stage：
<ul>
<li>可以得到一個 PC 值對應的指令是否為分支指令，以及這條分支指令的類型</li>
<li>如果是直接跳轉 (direct) 類型的指令，還可以在這個階段得到其目標位址
<ul>
<li>例如：RISC-V 的 <code>jal</code> 指令，在 decode stage 就可以由 offset 得知其目標位址，如果這條指令是預測 not taken，那就代表發生 mis-prediction</li>
</ul>
</li>
<li>在 decode stage 發現預測失敗的 mis-prediction penalty 最小，可以馬上進行恢復</li>
<li>然而，由於間接跳轉 (indirect) 類型指令的目標位址是存在暫存器中，在 decode stage 仍無法得知其目標位址，因此如果發生 mis-prediction，CPU 也沒辦法在這個 stage 取得下一個 fetch 指令的位址
<ul>
<li><strong>只能暫停 instruction fetch</strong></li>
</ul>
</li>
</ul>
</li>
<li>讀取暫存器的 stage (有可能是在 register renaming stage 之後，也有可能在 issue stage 之後，取決於 CPU 的設計)：
<ul>
<li>如果此時讀到了暫存器的值，那麼就可以對間接跳轉的分支指令的目標位址進行檢查，如果發現目標位址預測錯誤，那麼就可以用正確的目標位址來 fetch instruction
<ul>
<li>此時仍需在將該分支指令之後進入到 pipeline stage 的指令給 flushed 掉
<ul>
<li>但這並不容易實現，因為這些指令有可能已經進到 Issue queue 中了；Issue queue 中也包含了分支指令之前的指令，這些指令不應該被 flushed，因此需要<strong>有條件的 flush 在 Issue queue 中的指令</strong></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Execution stage：
<ul>
<li>
<p>不管是什麼類型的分支指令都可以在這個 execution stage 取得其正確的目標位址，可以對分支指令是否正確 (包含方向和目標位址) 進行檢查</p>
</li>
<li>
<p>但此時發生 mis-prediction 的 penalty 也是最大的</p>
</li>
<li>
<p>當發生 mis-prediction 時，所有在此分支指令之後的指令都要被 flushed 掉</p>
<ul>
<li>然而，針對 out-of-order CPU，在此分支指令之前的指令，有可能在 execution stage，也有可能還在 Issue queue 中，這些指令不應該被 flushed</li>
</ul>
</li>
<li>
<p>指令之間的順序是被記錄在 <strong>ROB (Re-Order Buffer)</strong> 中的，因此可以透過 ROB 來恢復發生 mis-prediction 時 CPU 的狀態</p>
</li>
<li>
<p>當發生 mis-prediction 時，將此訊息記錄在此分支指令對應的 ROB entry 中，並<strong>暫停 instruction fetch</strong>；<strong>當這條分支指令在 pipeline 中變成最老的指令時，就代表在 pipeline 中，剩餘的指令都比這條分支指令還新，可以直接將整個 pipeline stage 中的指令都 flush 掉，並從正確的目標位址 fetch instruction</strong></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2017.png"></p>
<ul>
<li>缺點：
<ul>
<li>需要分支指令在 pipeline 中一定時間後才能進行處理</li>
<li>如果在此分支指令之前有一道 D-cache miss 的 load 指令，這個等待的時間就會變得非常長，i.e. mis-prediction 的 penalty 很大，降低了處理器的性能
<ul>
<li>這段時間內都沒辦法 fetch 新的指令來執行</li>
</ul>
</li>
</ul>
</li>
<li>優點：
<ul>
<li>這種方法容易實現，是在硬體複雜度和執行效率之間一個比較折衷的方案</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>除了基於 ROB 的方法來恢復 mis-prediction 的狀態，現在很多處理器都採用了基於 <strong>Checkpoint</strong> 的方法來恢復 mis-prediction 的狀態</p>
<ul>
<li>在分支指令之後的指令更改 CPU 的狀態前，先將 CPU 的狀態保存下來，包含：
<ul>
<li>Register renaming 的 mapping table</li>
<li>預測 taken 的分支指令其對應的下一條指令的 PC… etc</li>
</ul>
</li>
</ul>
</li>
<li>
<p>通常在分支指令進入到 register renaming stage 時，就需要保存 CPU 的狀態</p>
</li>
<li>
<p>相比於使用 ROB 的方式，使用 Checkpoint 的方式需要消耗更多的硬體資源，但是這種方式可以快速地將 CPU 的狀態給恢復，並馬上從正確的 PC 值開始 fetch instruction，因此執行效率更高</p>
</li>
<li>
<p>當發生 mis-prediction 時，只有在該分支指令之後的指令才能被 flushed，在分支指令之前的指令要保留下來，因此需要一個機制來正確地識別哪些指令處在 mis-prediction 的路徑上</p>
<ul>
<li>
<p>可以透過對每一條分支指令都分配一個編號，所有在分支指令之後的指令都會給予同一個編號，直到碰到下一個分支指令為止；該編號會被記錄在 ROB entry 中</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2018.png"></p>
<ul>
<li>當發生 mis-prediction 時，就可以利用分支指令所分配到的編號，將所有在該分支指令之後的指令都 flushed 掉，而不需要等到分支指令變成 pipeline 中最老的指令</li>
</ul>
</li>
</ul>
</li>
<li>
<p>分支指令的編號個數決定了最多可以存在 pipeline 中分支指令的個數</p>
<ul>
<li>例如：假設一 CPU 支援最多 128 條指令在流水線中，假設每 5 條指令就有 1 個分支指令，最多會有 128 / 5 = 26 條分支指令存在 pipeline 中，也就是說需要 26 個編號，因此需要 5 個 bits (2^5 = 32) 來存放編號</li>
</ul>
</li>
<li>
<p>這些編號值會被保存在一個 FIFO (<strong>tag list</strong>) 中，一旦這個 FIFO 滿了，就沒辦法再向 pipeline 中送入分支指令</p>
<ul>
<li>當 tag list 滿時，如果又 decode 出一條分支指令，就得暫停 decode stage 之前的 pipeline，直到 tag list 有空間為止</li>
<li>Tag list 中的 entries 是 in-order 的</li>
</ul>
</li>
<li>
<p>其中一個設計方法：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2019.png"></p>
<ul>
<li>Free tag list：用來存放還沒有被分配的編號</li>
<li>Tag list：用來存放已經被分配的編號</li>
<li>在 decode stage 解析出一條新的分支指令時，就從 free tag list 中取得一個編號，指定給該分支指令，並編號加入 tag list 中</li>
<li>當發生 mis-prediction 時，就可以透過這個編號來識別所有在分支指令後面的指令，並從 pipeline 中給 flushed，最後再將這些編號加回 free tag list 中</li>
</ul>
</li>
<li>
<p>對於 superscalar CPU 而言，當一條分支指令發生 mis-prediction 時並需將在其之後的指令都從 pipeline 中給 flush 掉，實際上包含了以下兩個部份：</p>
<ul>
<li>在 issue stage 前的指令：
<ul>
<li>在 issue stage 前，指令都還是 in-order 的，因此當在 issue stage 中發現一條 mis-prediction 的分支指令時，只需將直接將在 issue stage 前的指令給 flush 掉即可
<ul>
<li>可以在 1 個 cycle 內完成</li>
</ul>
</li>
</ul>
</li>
<li>在 issue stage 及 issue stage 之後的指令：
<ul>
<li>從 issue stage 開始，指令就是 out-of-order 了，一部分的指令可能是在 mis-prediction 的路徑上的，因此需要透過上述的 tag list 的方式，找出在 mis-prediction 分支指令之後的指令並從 pipeline 中給 flush 掉
<ul>
<li>在 1 個 cycle 內無法完成</li>
</ul>
</li>
<li>Flush ROB 中的指令：
<ul>
<li>
<p>ROB 中有每個指令的編號，因此可以透過 tag list 找出 mis-prediction 分支指令之後所有指令的編號，並透過 broadcast 的方式通知 ROB，並將對應的 ROB entry 給 invalidate：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2020.png"></p>
<ul>
<li>這無法在 1 個 cycle 內完成，因為如果想要在 1 個 cycle 內比較所有的 ROB entries (最慘的情況下)，會需要非常大的硬體面積和增加 latency</li>
</ul>
</li>
</ul>
</li>
<li>Flush Issue queue 中的指令：
<ul>
<li>可以採用類似 ROB 的方式，將 Issue queue 中相關的指令給 flush 掉</li>
</ul>
</li>
<li>Flush issue stage 之後的指令：
<ul>
<li>可以採用類似 ROB 的方式，將 issue stage 之後的指令給 flush 掉</li>
</ul>
</li>
<li>其實並不一定要在 1 個 cycle 內將相關指令給 flushed 掉，因為在 execution stage 發現 mis-prediction 後並開始從正確的位址開始 fetch instructions，這些新指令需要經過好幾個 stages 才會進到 dispatch stage (到了 dispatch stage 才需要使用到 ROB 和 Issue queue)，因此只要在這些新的指令進到 dispatch stage 前來得及將相關的指令給 flush 掉即可，就不需要 stall pipeline 了</li>
<li>因此，一個折衷的方式就是每個 cycle 只從 tag list 中 broadcast 一個或幾個編號，這樣當編號個數不是很多時 (大部分情況是如此)，使用幾個 cycles 就可以將相關的指令給 flush 掉，也就不用 stall pipeline</li>
<li>只有在少數的情況下，當編號個數很多時，才需要花費多個 cycles 來 flush 相關的指令
<ul>
<li>新的指令在進入 issue stage 前，必須等待所有在 mis-prediction 路徑上的指令都被 flushed 並恢復 CPU 狀態後，才可以進入 issue stage</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在把 pipeline 中相關的指令給 flush 掉後，就可以使用 Checkpoint 的方式回覆 CPU 的狀態</p>
<ul>
<li>主要是將 register renaming 的 mapping table 給恢復</li>
</ul>
</li>
<li>
<p>什麼時候對分支指令分配一個編號呢?</p>
<ul>
<li>Fetch stage：
<ul>
<li>Fetch stage 只有 PC 值，對於一個指令是否為分支指令只能用推測的方式，因此此時分配編號並不合適</li>
</ul>
</li>
<li>Decode stage：
<ul>
<li>只有在 decode stage 才可以真正判斷一條指令是否為分支指令，此時就可以對分支指令分配一個編號</li>
</ul>
</li>
</ul>
</li>
<li>
<p>由於 superscalar 一個 cycle 內會解析多個分支指令，因此最差的情況就是 N-ways 的 CPU，會有 N 條分支指令，也就代表 free tag list 和 tag list 都需要在一個 cycle 內提供 N 個編號，也就需要使用 multi-port FIFO 的設計，對硬體面積和功耗會有所影響</p>
<ul>
<li>實際上，絕大部分情況下，每個 cycle decode 的指令最多只會有一、兩條分支指令，如果使用 multi-port FIFO 的設計，大部分的 ports 都會是閒置狀態的，因此實際上並不會採用 multi-port FIFO 的設計</li>
<li>一種折衷的作法是限制每個 cycle 最多只能處理一條分支指令
<ul>
<li>如果在 decode stage 讀出來的 N 條指令中，有超過一條的分支指令，那麼只會從 free tag list 拿出一個編號，第二條以後的分支指令都必須 stall 到下一個 cycle 才能被處理</li>
<li>對大部分的程式來說分支指令都並不密集，因此這種方法不會對性能造成太大的負面影響</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在 execution stage 檢查分支指令預測正確性時，要如何得知該分支指令之前所預測的值?</p>
<ul>
<li>
<p>可以將每條分支指令的預測值，存在 <code>PTAB (Prediction Target Address Buffer)</code> 中，且此分支指令在 PTAB 的 index 會隨著分支指令在 pipeline 中流動，等到分支指令進入 execution stage 時就可以用來跟實際的跳轉結果做比較</p>
<ul>
<li>在實際的處理器中，可以優化為<strong>只存預測為 taken 的分支指令</strong>預測值即可</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2021.png"></p>
</li>
<li>
<p>分支指令預測值與實際的結果有以下的可能性：</p>
<ul>
<li>實際結果為 <strong>not taken</strong>，並在 PTAB 中沒有找到對應的內容 (i.e. <strong>not taken</strong>) ⇒ 預測正確</li>
<li>實際結果為 <strong>not taken</strong>，但在 PTAB 中有找到對應的內容 (i.e. <strong>taken</strong>) ⇒ 預測錯誤，需要使用 <strong>Next PC</strong> 來作為正確的目標位址</li>
<li>實際結果為 <strong>taken</strong>，但在 PTAB 中沒有找到對應的內容 (i.e. <strong>not taken</strong>) ⇒ 預測錯誤，需要使用<strong>實際的目標位址</strong></li>
<li>實際結果為 <strong>taken</strong>，但在 PTAB 中有找到對應的內容 (i.e. <strong>taken</strong>) ⇒ 預測方向正確，但此時仍需要比較 predict address 是否與實際的目標位址相符：
<ul>
<li>相符：預測正確</li>
<li>不相符：需使用<strong>實際的目標位址</strong></li>
</ul>
</li>
</ul>
</li>
<li>
<p>寫 PTAB 的過程可以在 <strong>fetch stage</strong> 完成，只要在 <a href="../superscalar-overview-ch4-part1/#41---%E6%A6%82%E8%BF%B0">fetch stage 預測到</a>一條發生跳轉的分支指令，就可以將其寫進 PTAB 中</p>
</li>
</ul>
</li>
</ul>
<h1 id="45---超標量處理器的分支預測">4.5 - 超標量處理器的分支預測</h1>
<ul>
<li>對於 superscalar CPU，在 fetch stage 給一個 PC 值，可以透過 I-Cache 同時取出多條的指令並組成 fetch group，CPU 亦會根據 fetch group 中的指令個數，調整下一個 cycle instruction fetch 的位址，因此 superscalar CPU 的 instruction fetch PC 值並不會是連續的，且每次送到 I-Cache 的 fetch address 也只會是 fetch group 中第一條指令的位址而已
<ul>
<li>因此，如果只是根據第一條指令的位址做分支預測是不夠的</li>
</ul>
</li>
<li>如果對 instruction fetch 的位址進行限制，例如針對一個 4-way issue 的 CPU，每個 cycle fetch 的 instructions 都是 4-word aligned (共 4 條指令) 的，那麼就可以使用 fetch group 中所有指令共同的部份：<code>PC[31:4]</code> 來做 branch predict；對於 branch predictor 而言，它也只需要記下每個 4-word aligned 的指令中，第一條預測跳轉分支指令資訊即可
<ul>
<li>
<p>在大多數的情況下，4-word aligned 的指令只會有一條分支指令</p>
</li>
<li>
<p>不過仍需在 BTB 中記錄下該分支指令在這 4 條指令中的 index：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2022.png"></p>
<ul>
<li>此範例中，分支指令的 index 在這四條指令的 index 為 <code>01</code>，但此 cycle 的 instruction fetch 的 PC 值在 <code>10</code>，因此在進行分支預測時，由於分支指令並不在 instruction fetch 的範圍內，因此不會使用其預測值</li>
</ul>
</li>
<li>
<p>如果這 4-word aligned 的指令中，包含超過一條分支指令，那麼這些分支指令就會忽相干擾，影響分支預測的準確度，不過在大部分的程式中，分支指令出現的頻率並不會這麼高</p>
</li>
</ul>
</li>
<li>但實際上，每個 cycle fetch 的 instructions 是可以不侷限於 N-word aligned 的 (參考：<a href="../superscalar-overview-ch2/#24---%E8%B6%85%E6%A8%99%E9%87%8F%E8%99%95%E7%90%86%E5%99%A8%E7%9A%84%E5%8F%96%E6%8C%87%E4%BB%A4">2.4 - 超標量處理器的取指令</a>)
<ul>
<li>如一個 4-way issue 的 superscalar CPU，其每個 cycle fetch 的 instructions 是可以不侷限於 4-word aligned 的範圍內的，那麼要達到最理想的結果，就需要對一個 cycle 內 fetch group 內的指令都進行分支預測，如果當中存在 taken 的分支指令，那麼變需要並將第一個預測為 taken 的分支指令目標位址作為下一個 cycle 的 PC 值</li>
<li>實務上，要在一個 cycle 對 fetch group 內的指令都進行分支預測是不可接受的，因為這代表需要 BTB 支援 multi-port (參考：<a href="../superscalar-overview-ch2/#231---true-multi-port">2.3.1 - True Multi-port</a>)，才能在一個 cycle 內同時讀出多條指令的目標位址；即使採用 interleaving (參考：<a href="../superscalar-overview-ch2/#233---multi-banking">2.3.3 - Multi-banking</a> ) 的方式來避免使用 multi-port，但考慮到在使用過程中，最多只會使用一個 port 的值 (i.e. 第一個預測為 taken 的分支指令)，所以這種方式對硬體使用效率是非常低的</li>
</ul>
</li>
<li>如果可以在分支指令的方向預測完畢後，再進行目標位址的預測，就可以避免 multi-port BTB 使用的需求
<ul>
<li>但是這樣變成分支方向和目標位址的預測是 serialized 的，有可能就沒辦法在一個 cycle 內完成，也就無法”連續地”預測每個 cycle 的 instruction fetch 的位址</li>
</ul>
</li>
<li>不過大部分的分支指令都是直接跳轉類型的，其分支指令的目標位址是可以很快地被計算出來；對於此類的指令，其實並不需要進行目標位址的預測，而是直接在 instruction fetch 的階段，就馬上進行目標位址的計算
<ul>
<li>雖然不一定能在一個 cycle 內完成分支預測 (還有分支方向需預測)，但不一定會比 serialized 的方式來得慢，而且還可以取得分支指令正確的目標位址</li>
<li>要實現此功能，必須在指令進入 I-Cache 之前，就先進行 pre-decode，並馬上識別出分支指令，從而進行快速的分支位址的計算</li>
<li>但對於間接跳轉類型的指令 (除了 return 指令外)，就無法採用此方式了</li>
</ul>
</li>
<li>對於 superscalar CPU，由於需要一個 cycle 內對多條指令進行方向預測，從而找到第一條 taken 的分支指令：
<ul>
<li>基於局部歷史的分支預測：
<ul>
<li>需要 PHT 和 BHT 都支援 multi-port，可以透過 interleaving 的方式來避免 multi-port 的設計 (參考：<a href="../superscalar-overview-ch2/#233---multi-banking">2.3.3 - Multi-banking</a> )
<ul>
<li>
<p>例如將 PHT 改成 interleaving：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part2/image%2023.png"></p>
<ul>
<li><code>Addr[1:0]</code> 用來作為 multi-bank 的 index</li>
<li><code>Addr[6:2]</code> 用來作為 bank 中 (sub-)PHT 的 index</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>基於全局歷史的分支預測：
<ul>
<li>並無法簡單地將 PHT 改成 multi-port 即可，因為多條指令對應的 GHT 有可能是不同的，需要進行特殊的處理</li>
</ul>
</li>
</ul>
</li>
<li>Interleaving 在 superscalar CPU 中是經常被使用的，大部分需要 multi-port 設計的元件，如 ROB、Issue Queue、Instruction Buffer… etc，都可以採用 interleaving 的方式來設計</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>&lt;超標量處理器概覽&gt; 第 4 章 - 分支預測 (Part 1)</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch4-part1/</link>
      <pubDate>Fri, 28 Feb 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch4-part1/</guid>
      <description>&lt;h1 id=&#34;41---概述&#34;&gt;4.1 - 概述&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;分支預測和 Cache 左右著 CPU 的效能，對於 superscalar CPU 來說，準確度高的分支預測更為重要&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在一般的 RISC 指令集中，分支指令包含兩個要素：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;方向：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;分支指令的方向只有兩種：跳轉 (taken)，或是不跳轉 (not taken)&lt;/li&gt;
&lt;li&gt;有些跳轉指令是無條件執行的 (e.g. &lt;code&gt;jmp&lt;/code&gt; 指令)，有些跳轉指令則是需要根據指令中攜帶的條件是否成立來決定是否發生跳轉 (e.g. &lt;code&gt;beq&lt;/code&gt; 指令)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目標位址：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;對於一般的 RISC 指令集，目標位址在指令中有兩種形式：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;直接跳轉 (direct)：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;目標位址 = 當前分支指令 (或分支指令的下一條指令) 的 PC 值 + immediate PC offset&lt;/li&gt;
&lt;li&gt;由於指令通常只有 32/64 bits，因此 immediate PC offset 的值範圍也就有限，因此也限制了直接跳轉能夠跳轉的範圍&lt;/li&gt;
&lt;li&gt;因為 immediate PC offset 值是 encode 在分支指令中，因此在 pipeline 的 &lt;strong&gt;decode stage&lt;/strong&gt; 就可以直接將 immediate PC offset 給解析出來，因此這種類型的指令是很容易進行分支預測的&lt;/li&gt;
&lt;li&gt;這種跳轉指令的執行效能也比較好&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;間接跳轉 (indirect)：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;目標位址是來自於 register 的內容，由於 register 是 32/64 bits，因此可以跳轉到任何位址&lt;/li&gt;
&lt;li&gt;由於跳轉位址來自於 register，因此此類的分支指令的目標位址需要等待一段時間才能獲得 (e.g. 要等到 pipeline 的 &lt;strong&gt;execute stage&lt;/strong&gt;)
&lt;ul&gt;
&lt;li&gt;在確認目標位址前進到 pipeline 的指令，有可能都會是無效的 (if taken)，因此增大了 mis-prediction penalty&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;且由於 register 的內容是會動態變化的，因此這類的分支指令很難對目標位址進行預測
&lt;ul&gt;
&lt;li&gt;不過慶幸的是，這類指令通常都是 call 或是 return 類型的指令，這類指令都有很強的規律性，是容易被預測的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MIPS R3000 只使用了 5 級的 pipeline，在第二個階段的 decode stage，就可以得到分支指令跳轉的目標位址，即使發生了跳轉 (taken)，也只需要丟棄一道仍在 fetch stage 的指令，misprediction penalty 並不高&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="41---概述">4.1 - 概述</h1>
<ul>
<li>
<p>分支預測和 Cache 左右著 CPU 的效能，對於 superscalar CPU 來說，準確度高的分支預測更為重要</p>
</li>
<li>
<p>在一般的 RISC 指令集中，分支指令包含兩個要素：</p>
<ol>
<li><strong>方向：</strong>
<ul>
<li>分支指令的方向只有兩種：跳轉 (taken)，或是不跳轉 (not taken)</li>
<li>有些跳轉指令是無條件執行的 (e.g. <code>jmp</code> 指令)，有些跳轉指令則是需要根據指令中攜帶的條件是否成立來決定是否發生跳轉 (e.g. <code>beq</code> 指令)</li>
</ul>
</li>
<li><strong>目標位址：</strong>
<ul>
<li>對於一般的 RISC 指令集，目標位址在指令中有兩種形式：
<ul>
<li><strong>直接跳轉 (direct)：</strong>
<ul>
<li>目標位址 = 當前分支指令 (或分支指令的下一條指令) 的 PC 值 + immediate PC offset</li>
<li>由於指令通常只有 32/64 bits，因此 immediate PC offset 的值範圍也就有限，因此也限制了直接跳轉能夠跳轉的範圍</li>
<li>因為 immediate PC offset 值是 encode 在分支指令中，因此在 pipeline 的 <strong>decode stage</strong> 就可以直接將 immediate PC offset 給解析出來，因此這種類型的指令是很容易進行分支預測的</li>
<li>這種跳轉指令的執行效能也比較好</li>
</ul>
</li>
<li><strong>間接跳轉 (indirect)：</strong>
<ul>
<li>目標位址是來自於 register 的內容，由於 register 是 32/64 bits，因此可以跳轉到任何位址</li>
<li>由於跳轉位址來自於 register，因此此類的分支指令的目標位址需要等待一段時間才能獲得 (e.g. 要等到 pipeline 的 <strong>execute stage</strong>)
<ul>
<li>在確認目標位址前進到 pipeline 的指令，有可能都會是無效的 (if taken)，因此增大了 mis-prediction penalty</li>
</ul>
</li>
<li>且由於 register 的內容是會動態變化的，因此這類的分支指令很難對目標位址進行預測
<ul>
<li>不過慶幸的是，這類指令通常都是 call 或是 return 類型的指令，這類指令都有很強的規律性，是容易被預測的</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>
<li>
<p>MIPS R3000 只使用了 5 級的 pipeline，在第二個階段的 decode stage，就可以得到分支指令跳轉的目標位址，即使發生了跳轉 (taken)，也只需要丟棄一道仍在 fetch stage 的指令，misprediction penalty 並不高</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image.png"></p>
<ul>
<li>MIPS CPU 甚至為了減少這一個 cycle 的浪費，還會特別找一條一定會被執行，不相關的指令放在分支指令之後；不論分支是否跳轉或不跳轉，該指令都一定會被執行
<ul>
<li>分支指令後面的那個位置就被 MIPS 稱為分支延遲槽 (branch delay slot)，需要透過 compiler 或 programmer 找到一條不相關一定會被執行的指令，將其放置分支延遲槽中</li>
<li>但當 CPU 的並行度提高，以及 pipeline 的加深，當發生 misprediction 的時候，pipeline 中已經有很多指令都錯誤地進到 pipeline 裡了；這些指令都必須被拋棄，此時技使使用 branch delay slot，也很難得到滿意的結果，因為已經無法從分支指令之前的程序中找到這麼多不相關的指令了</li>
</ul>
</li>
</ul>
</li>
<li>
<p>對於 superscalar CPU，要進行分支預測，首先要從 I-Cache 取出的指令組 (<code>fetch group</code>) 中，判斷哪些指令是分支指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%201.png"></p>
<ul>
<li>
<p>最容易的辦法是將 <code>fetch group</code> 中的指令，從 I-Cache 取出後，進行快速的 decode</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%202.png"></p>
<ul>
<li>從 I-Cache 取出指令後，只需辨別目前的指令是否為一個分支指令，如果是的話，就可以直接將分支指令對應的 PC 值送到 branch predictor 中，就可以對分支指令進行預測了</li>
<li>然而，當 cycle time 比較短的時候，I-Cache 的訪問可能需要好幾個 cycles 才能完成 (e.g. 可能需要訪問 L2 Cache、memory)；在這些 cycles 內由於無法獲得準確的預測結果，只能照順序讀取指令 (i.e. not taken)，降低的分支預測的準確度，進而影響 CPU 的效能</li>
<li>此外，在指令從 I-Cache 讀出來後，fast decode 和 branch predict 是放在同一個 cycle 內的，會嚴重影響 CPU 的 cycle time</li>
<li>為了解決以上的問題，可以在指令從 L2 Cache 寫入 I-Cache 之前進行快速的 decode，也就是 <code>pre-decode</code>，然後將指令是否是分支指令的資訊和指令一起寫進 I-Cache 中；雖然這樣會增加 I-Cache 的面積，但可以省略 fast decode 的電路，在一定程度上緩解 對 CPU cycle time 的影響
<ul>
<li>但是 instruction fetch 到得到分支指令的預測結果這段的間隔時間還是過長的，無法透過以上的方式來解決</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在 pipeline 中，分支預測越靠前面幾個 stages 是越好的，如果指令從 I-Cache 讀出後才進行分支預測，那麼由於 I-Cache 的訪問可能需要多於 1 個  cycle 才能完成，可能經過了好幾個 pipeline stages 了才能得到分支指令預測的結果，在此期間已經有很多的指令進入到了 pipeline，但如果發生 misprediction，這段期間進入到 pipeline 的指令都必須被 flushed，降低了CPU 的執行效能</p>
<ul>
<li>因此分支預測，最好是可以發生在一得到 instruction fetch 的 PC 值時，在 instruction fetch 的同時進行分支預測，這樣在下一個 cycle 的時候，根據分支預測的結果來 fetch instruction
<ul>
<li>在同一個 process 內，PC 的 VA 值都是固定的，因此可以直接拿 PC 值來進行預測</li>
<li>當發生 context-switch 時，需要將 branch predictor 的內容給清空，才能保證不同 processes 間的分支預測不會互相影響
<ul>
<li>如果有使用 ASID，那麼可以將 ASID 和 PC 值一起進行分支預測，如此在發生 context-switch 時，就不用清空 branch predictor</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在一得到 instruction fetch 的 PC 值時，根據 PC 值來預測當下 cycle 的 instruction fetch 中是否存在分支指令，以及分支指令的方向和目標位址：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%203.png"></p>
</li>
<li>
<p>分支預測是影響 CPU 效能的關鍵因素之一，需要在硬體面積及功耗、預測準確度和 latency 之間找到一個平衡點</p>
</li>
</ul>
<h1 id="42---分支指令的方向預測">4.2 - 分支指令的方向預測</h1>
<ul>
<li>
<p>最簡單的動態預測方法就是直接使用上一次分支的結果 (last-outcome prediction)：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%204.png"></p>
<ul>
<li>
<p>相比於靜態分支預測，使用 last-outcome prediction 在不同的情況下可以獲得更優的結果；然而當分支指令發生變化時，其準確度有可能比靜態分支預測還差：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%205.png"></p>
<ul>
<li>當分支每次都發生變化時，此方法的分支預測失敗率是 ~100%</li>
</ul>
</li>
</ul>
</li>
<li>
<p>因此，last-outcome prediction 的準確度在現在處理器是無法被接受的，因此也沒有被現代處理器所採納</p>
</li>
<li>
<p>現代處理器用得最廣泛的分支預測方法，都是基於 <code>2-bit saturating counter</code> 為基礎而衍生出的不同實做</p>
</li>
</ul>
<h2 id="421---基於兩位飽和計數器的分支預測">4.2.1 - 基於兩位飽和計數器的分支預測</h2>
<ul>
<li>
<p><code>2-bit saturating counter</code> 分支預測方法不會馬上使用分支指令上一次的結果，而是根據一條分支指令前兩次的執行結果來預測本次分支的方向，通常有四種 FSM 狀態：</p>
<ul>
<li>
<p>Strongly taken (編碼：<code>11</code>)：Counter 處於飽和狀態，預測此次分支會發生跳轉 (taken)</p>
</li>
<li>
<p>Weakly taken (編碼：<code>10</code>)：Counter 不處於飽和狀態，預測此次分支會發生跳轉 (taken)</p>
</li>
<li>
<p>Weakly not taken (編碼：<code>01</code>)：Counter 不處於飽和狀態，預測只次分支不會發生跳轉 (not taken)</p>
</li>
<li>
<p>Strongly not taken (編碼：<code>00</code>)：Counter 處於飽和狀態，預測此次分支不會發生跳轉 (not taken)</p>
</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%206.png"></p>
<ul>
<li>當分支指令總是朝著一個方向的時候，FSM 就會處於飽和狀態，此時預測的正確率會比較高</li>
<li>當分支指令的方向總是發生變化時，FSM 無法處於飽和狀態，此時預測的準確率比較低</li>
</ul>
</li>
<li>
<p>其他 <code>2-bit saturating counter</code> 的實現方式：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%207.png"></p>
<ul>
<li>上：Weakly not taken 只要分支指令一跳轉 (taken)，就將 FSM 設為 Strongly taken</li>
<li>下：Weakly taken 只要分支指令一不跳轉 (not taken)，就將 FSM 設為 Strongly not taken</li>
<li>不過實務上，這樣的設計並不會讓 benchmark 結果變得比較好，因此這種兩實做並沒有很常使用</li>
</ul>
</li>
<li>
<p>一般來說，都是使用 Strongly not taken 或 Weakly not taken 作為 FSM 的起始狀態</p>
</li>
<li>
<p>可以使用 Gray code 將 FSM 的狀態編碼，保證在狀態轉換時，只有一個 bit 會發生變化</p>
</li>
<li>
<p>考慮以下 for-loop：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-3">3</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> (i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; i <span style="color:#f92672">&lt;</span> m; i<span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>    ...
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>假設初始狀態是在 Weakly not taken：
<ul>
<li><code>i = 0</code>：Weakly not taken ⇒ 預測<strong>失敗</strong>，FSM 切換到 Weakly taken</li>
<li><code>i = 1</code>：Weakly taken ⇒ 預測b，FSM 切換到 Strongly taken</li>
<li><code>i = 2</code> ~ <code>i = m - 1</code>：Strongly taken ⇒ 預測<strong>成功</strong>，FSM 保持在 Strongly taken</li>
<li><code>i = m</code>，離開迴圈：Strongly ⇒ 預測<strong>失敗</strong>，FSM 切換到 Weakly taken</li>
</ul>
</li>
<li>整個迴圈失敗的比例：<code>2 / m</code>，如果 m 很大，那麼這樣的預測結果就是可以接受的</li>
</ul>
</li>
<li>
<p>對於絕大數的程式來說，使用 <code>2-bit saturating counter</code> 就可以獲得比較高的準確率了；增加 counter 的 bits 數 (e.g. 使用 <code>3-bit saturating counter</code>)，會讓硬體的複雜度上升和更多的空間需求，這些額外的開銷所帶來的負面影響可能大於實際可以獲得預測準確度的提高，因此主流 CPU 都是使用 <code>2-bit saturating counter</code></p>
</li>
<li>
<p>分支預測都是基於 PC 值為基礎運行的，正常來說，每一個 PC 值都應該對應一個 2-bit 的 saturating counter，因此 32-bit CPU 就需要 <code>2^30 * 2</code> 個 bits (假設 PC 值都是 32 bits，所以最低 2 bits 不考慮)，顯然實務上不可能使用一個這麼大的硬體空間</p>
</li>
<li>
<p>由於不是所有指令都是分支指令，因此一般都使用以下的方法來紀錄 2-bit saturating counters：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%208.png"></p>
<ul>
<li><code>PHT (Pattern History Table)</code> 是一個表格，用來存放每個 <code>k-bit</code> PC 值所對應的 <code>2-bit saturating counters</code></li>
<li>這個 PHT 總共儲存了 <code>2^k</code> 個 saturating counters，因此 PHT 的大小為：<code>2^k * 2 bits</code>；PHT entry 是透過 <code>k-bit</code> 的 PC 來索引的</li>
<li>使用 <code>k-bit</code> PC 來索引 PHT，必然就導致了有相同 <code>k-bit</code> 的 PC 都會索引到同一個 saturating counter，如果這些有相同 <code>k-bit</code> 的 PC 對應到的指令不只一條是分支指令，那麼這些分支指令彼此間就會相互影響，也就是造成 <code>aliasing</code> 的問題
<ul>
<li>Neutral aliasing：多條 aliasing 的指令跳轉的方向都相同</li>
<li>Destructive aliasing：多條 aliasing 的指令跳轉方向不相同</li>
</ul>
</li>
<li>雖然這樣的實做方法會有 aliasing 的問題，但考量到這樣的方法實做起來比較簡單，且佔用的硬體空間也不大，因此輕微的預測準確度下降也是可以接受的</li>
</ul>
</li>
<li>
<p>PHT 的大小對分支預測準確度的影響：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%209.png"></p>
<ul>
<li>PHT 的大小是 2 KB，準確度已經達到 93% 以上
<ul>
<li>k = log(2 * 1024 * 8 bits) / 2 bits = 13，也就是使用 PC 中的 13 bits 來索引 PHT entry</li>
</ul>
</li>
</ul>
</li>
<li>
<p>避免 PHT aliasing 的方法：</p>
<ul>
<li>
<p>將 PC 做 hash 後，再拿 <code>k-bit</code> 的 hash 值去索引 PHT entry：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2010.png"></p>
</li>
<li>
<p>hash 的實做方法可以很簡單，例如使用普通的 XOR 運算，或是設計得更複雜以取得更好的 hash 結果</p>
</li>
</ul>
</li>
<li>
<p><code>2-bit saturating counters</code> 需要根據跳轉指令的結果，更新對應的 PHT entry，有三個時間點可以更新 PHT：</p>
<ol>
<li>在 Fetch stage，分支預測時，更新 PHT entry</li>
<li>在 Execution stage，分支指令的方向被實際運算出來時，更新 PHT entry</li>
<li>在 Commit stage，當分支指令要離開 pipeline 時，更新 PHT entry</li>
</ol>
</li>
<li>
<p>第一種方法，在 Fetch stage 根據分支預測的結果，直接更新 PHT entry，是否不可靠的，因為分支預測的結果有可能是錯誤的</p>
</li>
<li>
<p>第二、第三種方法，由於 superscalar CPU 的 pipeline stages 都很深，且每個 cycles 會執行多條指令，導致一條分支指令可能在 PHT entry 被更新前就被 fetched 過很多次了 (e.g. 很小的 for-loop body)，這條分支指令在進行分支預測時，就沒有利用到它之前被執行的分支結果</p>
<ul>
<li>但考量到 saturating counter 的特性，只要 saturating counter 處於飽和的狀態，其分支預測的結果就會比較固定，即使更新 PHT entry 的時間點較晚，也不會改變 saturating counter 的狀態，所以對分支預測準確度並不會造成太大的負面影響</li>
<li>此外，雖然第二種在 Execution stage 更新 PHT entry 比第三種在 Commit stage 才更新 PHT entry 的時間點早，但在 out-of-order CPU，即使在 Execution stage 得到分支指令的結果，也不能保證它一定會被執行 (有可能這個分支指令是在 mis-prediction 的路徑上)，有可能會被 flushed 掉，因此不能利用這個分支指令在 Execution stage 的結果來更新 PHT entry</li>
<li>所以，只有第三種在 Commit stage 更新 PHT entry 的方式，才是萬無一失的</li>
</ul>
</li>
<li>
<p>這種基於 <code>2-bit saturating counter</code>  的分支預測方法，其預測準確度有一定的上限，很難達到 98% 以上的準確度，因此現代處理器都<strong>不會直接</strong>使用此方法</p>
</li>
</ul>
<h2 id="422---基於局部歷史的分支預測">4.2.2 - 基於局部歷史的分支預測</h2>
<ul>
<li>
<p>針對以下的情境，<code>2-bit saturating counter</code> 並不會有比較高的準確度：</p>
<pre><code>  ![image.png](image%2011.png)
</code></pre>
<ul>
<li>針對這樣的情境，<code>2-bit saturating counter</code> 並沒辦法達到飽和的狀態，始終在 Weakly not taken 和 Weakly taken 之間做切換
<ul>
<li>假設 FSM 的起始狀態是 Weakly not taken，此時的分支預測準確率是 0%</li>
<li>假設 FSM 的起始狀態是 Strongly not taken，此時的分支預測準確率是 50%</li>
</ul>
</li>
<li>不論是哪一種情況，這樣的分支預測準確度都是無法接受的</li>
</ul>
</li>
<li>
<p>可以使用 <code>BHR (Branch History Register)</code> 來紀錄一條分支指令過去的歷史狀態，這種預測方法稱為<strong>基於局部歷史 (Local History)</strong> 的預測方法</p>
<ul>
<li>透過將每次分支的結果 (1: Taken，0: Not taken)，從右 shift 進 BHR，就可以紀錄這條分支指令的歷史狀態了</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2012.png"></p>
<ul>
<li>一個寬為 <code>n</code> 位的 BHR 可以紀錄一條分支指令過去 <code>n</code> 次的分支預測結果 (taken or not taken)，並使用 BHR 去索引 PHT 的 entry</li>
<li>PHT 仍是一個表格，大小為 <code>2^n * 2</code> bits，每個 PHT entry 都存儲了 saturating counter 的值
<ul>
<li>FSM Update Logic 硬體仍是獨立於 PHT 之外，每個 PHT entry 都是透過這個 FSM Update Logic 來更新其 counter 值</li>
</ul>
</li>
<li>當一條分支指令得到其分支結果時，就將對應 PHT entry 的 counter 值讀出來，並根據其分支結果進行更新，然後將結果寫回 PHT entry</li>
</ul>
</li>
<li>
<p>假設有一條分支指令，其 BHR 的寬度為 <strong>2 bits</strong>，也就是這條分支指令的<strong>前 2 次</strong>分支歷史結果會被紀錄在 BHR 中：</p>
<ul>
<li>
<p>BHR 的值總共四種可能：</p>
<ul>
<li><code>00</code>：此分支指令前兩次都是 not taken</li>
<li><code>01</code>：此分支指令前兩次分別是：not taken → taken</li>
<li><code>10</code>：此分支指令前兩次分別是：taken → not taken</li>
<li><code>11</code>：此分支指令前兩次都是 taken</li>
</ul>
</li>
<li>
<p>假設這條分支指令有如下的執行順序：taken → not taken → taken → not taken → taken…，則此 BHR 的值依執行順序分別會是 (從右 shift 進 BHR，BHR 的初始值為 <code>00</code>)：</p>
<ul>
<li>taken：<code>01</code></li>
<li>not taken：<code>10</code></li>
<li>taken：<code>01</code></li>
<li>not taken：<code>10</code>
<ul>
<li>可以統整出：
<ul>
<li>當 BHR 的值為 <code>01</code> 時，下次 shift 進 BHR 的值一定會是 <code>0</code>，i.e. 下次是 not taken</li>
<li>當 BHR 的值為 <code>10</code> 時，下次 shift 進 BHR 的值一定會是 <code>1</code>，i.e. 下次是 taken</li>
</ul>
</li>
<li>BHR 值：<code>10</code> → 對應到 PHT <strong>entry 2</strong>，此 PHT entry 的 saturating counter 會是 Strongly taken 的飽和狀態 (因為每次都是 taken)</li>
<li>BHR 值：<code>01</code> → 對應到 PHT <strong>entry 1</strong>，此 PHT entry 的 saturating counter 會是 Strong not taken 的飽和狀態 (因為每次都是 not taken)</li>
<li>BHR 值：<code>00</code> 和 <code>11</code>，沒有用到，因此 PHT <strong>entry 0</strong>，<strong>entry 3</strong> 也就不會被使用到，也就是 4 個 PHT entries 中，只有 2 個 entries 是有起作用的</li>
</ul>
</li>
</ul>
</li>
<li>
<p>假設執行順序是：TTNNTTNNTTNN&hellip;，BTN 的序列值會是：<code>110011001100</code></p>
<ul>
<li><code>11</code> 之後必接 <code>0</code></li>
<li><code>10</code> 之後必接 <code>0</code></li>
<li><code>00</code> 之後必接 <code>1</code></li>
<li><code>01</code> 之後必接 <code>1</code></li>
<li>也就是說，序列中每 2 位數後跟著的值都是唯一的，稱這個序列的循環週期為 2
<ul>
<li><strong>entry 3</strong>、<strong>entry 2</strong> 最終會是 Strongly not taken</li>
<li><strong>entry 0</strong>、<strong>entry 1</strong> 最終會是 Strongly taken</li>
</ul>
</li>
</ul>
</li>
<li>
<p>對於一個最小循環週期為 <code>n</code> 的序列來說，任何<code>大於 n</code> 的值都可以作為這個序列的循環週期</p>
<ul>
<li>例如：<code>110011001100</code>
<ul>
<li><code>n = 2</code>：
<ul>
<li><code>11</code> 之後必接 <code>0</code></li>
<li><code>10</code> 之後必接 <code>0</code></li>
<li><code>00</code> 之後必接 <code>1</code></li>
<li><code>01</code> 之後必接 <code>1</code></li>
</ul>
</li>
<li><code>n = 3</code>：
<ul>
<li><code>110</code> 之後必接 <code>0</code>，與 <code>n = 2</code>，<code>10</code> 相同</li>
<li><code>100</code> 之後必接 <code>1</code>，與 <code>n = 2</code>，<code>00</code> 相同</li>
<li><code>001</code> 之後必接 <code>1</code>，與 <code>n = 2</code>，<code>01</code> 相同</li>
<li><code>011</code> 之後必接 <code>0</code>，與 <code>n = 2</code>，<code>11</code> 相同</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>如果一個序列中，連續相同的 taken 或 not taken <strong>最多</strong>有 <code>p</code> 個，那麼這個序列的循環週期就為 <code>p</code></p>
</li>
<li>
<p>例如，假設執行順序是：TTTNTTTN&hellip;，BTN 的序列值會是：<code>11101110</code>，其循環週期為 3</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2013.png"></p>
</li>
</ul>
</li>
<li>
<p>更寬的 BNT：</p>
<ul>
<li>優點：
<ul>
<li>可以紀錄到分支指令越多的歷史分支結果，提高分支預測的準確度</li>
</ul>
</li>
<li>缺點：
<ul>
<li>要讓此 BNT 達到飽和，需要更長的訓練時間；在達到飽和狀態前，分支預測的準確度是比較低的</li>
<li>需要的 PHT entries 數也更多，佔用硬體空間</li>
</ul>
</li>
</ul>
</li>
<li>
<p>將所有分支指令的 BHR 組合起來稱作 <code>BHT (Branch History (Register) Table)</code></p>
</li>
<li>
<p>在實務中，BHT 不可能照顧到所有的 PC (一個 <code>n</code> 位的 BHR，需要的 PHT 空間為 <code>2^n * 2</code>)</p>
</li>
<li>
<p>一般都是採用 <code>k-bit</code> PC 來索引 BHT 中的 BHR，<code>t-bit</code> PC 來索引 PHT，再根據 BHR (寬度為 <code>n-bit</code>) 的值，索引 PHT entry：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2014.png"></p>
<ul>
<li>BHR = <code>n</code> bits</li>
<li>BHT = <code>2^k * n</code> bits</li>
<li>PHT = <code>2^n * 2</code> bits</li>
<li>PHTs = <code>(2^n * 2) * 2^t</code> bits</li>
<li>一般來說：<code>t &lt; k</code></li>
</ul>
</li>
<li>
<p>由於使用了 PC 的一部分來索引 BHT 和 PHTs，有可能會碰到 aliasing 的問題；這些 aliased 的分支指令會使用同一個 BHR 及 PHT，彼此之間會互相影響，使分支預測的準確度下降</p>
</li>
<li>
<p>以下兩種情況會造成 PHT aliasing：</p>
<ol>
<li>兩條分支指令的 <code>k-bit</code> PC 相同，因此索引到同一個 BHR，也就對應到同一個 PHT entry</li>
<li>兩條分支指令的 <code>k-bit</code> PC 不同，索引到不同的 BHR，但 BHR 的內容相同，因此對應到w同一個 PHT entry</li>
</ol>
</li>
<li>
<p>為了避免上述的兩種狀況，可以將 PC 值和對應的 BHR 值進行一定的處理，使用處理後的值來索引 PHT：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2015.png"></p>
<ul>
<li>將 BHR 的值和分支指令的 PC 值的一部分拼接，用得到的新的值索引 PHT 來得到分支預測結果
<ul>
<li>這種方法將分支指令的 PC 值也考慮進來，一定程度上避免了 PHT aliasing 的問題，因此可以提高分支預測的準確度</li>
</ul>
</li>
</ul>
</li>
<li>
<p>上述 BHR 的值和分支指令的 PC 值的拼接方法其實效果並不是很理想，還有其他的方式可以對 BHR 的值和分支指令的 PC 值做處理，獲得的效果會更好一點，例如使用 XOR：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2016.png"></p>
</li>
<li>
<p>現實處理器會使用更複雜的演算法，盡量避免 PHT aliasing 的情況發生</p>
</li>
<li>
<p>現實情況中，當一條分支指令的循環週期太大時 (e.g. 比較大的 for-loop body)，就需要一個寬度很大的 BHR，這會導致過長的訓練時間，並且 PHT 也會隨之佔用更大的硬體空間，這在現實處理器是無法接受的</p>
<ul>
<li>現實處理器只能用有限寬度的 BHR，寬度不夠，就會導致就算是很有規律的分支指令 (e.g. 999 次的 taken → 1 次的 not taken)，也無法獲得最完美的分支預測結果</li>
</ul>
</li>
<li>
<p>基於 <code>2-bit saturating counter</code> 的分支預測方法可以稱為<strong>基於局部歷史 (Local History) 的分支預測</strong>，因為都是根據同一條分支指令過去的執行結果來預測，並沒有考量到其他條分支指令的執行結果對預測結果造成的影響</p>
<ul>
<li>有時候一條分支指令的執行結果並不取決於自身在過去的執行結果，而是和它前面的分支指令的結果息息相關，因此需要採用<strong>基於全局歷史 (Global History) 的分支預測</strong>方法</li>
</ul>
</li>
</ul>
<h2 id="423---基於全局歷史的分支預測">4.2.3 - 基於全局歷史的分支預測</h2>
<ul>
<li>
<p>當對一條分支指令進行分支預測時，如果同時考慮到它前面分支指令的執行結果，則稱這種分支預測方法為：<strong>基於全局歷史 (Global History) 的分支預測</strong></p>
</li>
<li>
<p>一條分支指令什麼時候會和它前面的分支指令的執行結果相關呢?</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-5">5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-6">6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-7">7</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (aa <span style="color:#f92672">==</span> <span style="color:#ae81ff">2</span>)    <span style="color:#75715e">// b1
</span></span></span><span style="display:flex;"><span>    aa <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (bb <span style="color:#f92672">==</span> <span style="color:#ae81ff">2</span>)    <span style="color:#75715e">// b2
</span></span></span><span style="display:flex;"><span>    bb <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (aa <span style="color:#f92672">!=</span> bb) { <span style="color:#75715e">// b3
</span></span></span><span style="display:flex;"><span>    ....
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>如果 <code>b1</code> 和 <code>b2</code> 都 taken 了，<code>b3</code> 一定是 not taken</li>
<li>只依靠 <code>b3</code> 分支指令的預測，是一定不會發現這樣的規律的</li>
<li>因此需要在預測 b3 時，將前面 b2 和 b1 的執行結果也考慮進去，這就是<strong>基於全局歷史 (Global History) 的分支預測</strong></li>
</ul>
</li>
<li>
<p>基於全局歷史分支預測方法也需要一個暫存器，用來記錄程式中所有分支指令在過去的執行結果，這個暫存器稱為：<code>GHR (Global History Register)</code></p>
</li>
<li>
<p>現實中，GHR 不可能記錄所有分支指令的執行結果，因為 GHR 的寬度是不可能無限大的，因此一般都是使用一個有限 bits 的 GHR，來紀錄最近分支指令的執行結果</p>
<ul>
<li>每當執行一條分支指令時，就將其分支結果 (1: Taken，0: Not taken)，從右 shift 進 GHR</li>
</ul>
</li>
<li>
<p>使用基於全局歷史分支預測時，每當要對一條分支指令進行預測，就會根據當前 GHR 的值來預測，此時仍需要借助 PHT (i.e. <code>2-bit saturating counter</code>)，e.g.</p>
<ul>
<li>當 <code>b3</code> 分支要執行時，如果 GHR 最低 2 bits 的值是 <code>11</code> (i.e. <code>b1</code> 和 <code>b2</code> 都是 taken, <code>b3</code> 一定會是 not taken)，其對應的 PHT 在經過一段訓練的時間後就會達到飽和狀態 (e.g. Strongly not take)，之後就可以準確的預測 <code>b3</code> 的分支結果為 not taken 了</li>
<li>但當 <code>b3</code> 分支要執行時，如果 GHR 最低 2 bits 的值不是 <code>11</code>，那麼 <code>b3</code> 有可能會跳轉 (e.g. <code>aa = 2</code> → <code>aa = 0</code>, <code>bb = 3</code>)，也可能不會跳轉 (e.g. <code>aa = 2</code> → <code>aa = 0</code>, <code>bb = 0</code>)，這種並沒有規律的情況，分支預測的準確度就沒辦法保證了</li>
</ul>
</li>
<li>
<p>基於全局歷史分支預測最理想的方法，就是所有的分支指令都對應一個 PHT，並根據 GHR 的值來索引 PHT entry：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2017.png"></p>
<ul>
<li>由於每個分支指令都有自己的 PHT，因此即使 GHR 的值相同，不同條的分支指令也不會索引到同一個 PHT</li>
<li>但很顯然的，這種設計方法太佔硬體空間，在現實中是不可能使用的</li>
</ul>
</li>
<li>
<p>一般都會使用 hash 將 PC 值進行處理後，得到較小 bits 數的 hash 值後，再用來索引 PHT：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2018.png"></p>
<ul>
<li>P.S. 當 BHT 中的 BHR 個數減少到只有一個 BHR 時，基於局部歷史 (Local History) 的分支預測法就跟基於全部歷史 (Global History) 的分支預測法一樣了
<ul>
<li>i.e. 此時 BHR 跟功能跟 GHR 一樣</li>
</ul>
</li>
</ul>
</li>
<li>
<p>上述方法中，其實有很多的 PHT entry 都沒有被使用到，因此可以採用一種更為簡單的設計方式：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2019.png"></p>
<ul>
<li>缺點：
<ul>
<li>如果兩條分支指令在執行時的 GHR 值相同，那麼就會對應到同一個 PHT entry，造成衝突
<ul>
<li>如果兩條分支指令的跳轉結果是不相同的，那就會對分支預測造成負面影響</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>為了解決這個問題，可以同樣將分支指令部份的 PC 值和 GHR 值做拼接或 XOR，得到的值再拿來索引 PHT entry，就不會對應到同一個 PHT entry 了：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2020.png"></p>
<ul>
<li>同樣的，現實處理器會使用更複雜的演算法，盡量避免 PHT aliasing 的情況發生</li>
</ul>
</li>
<li>
<p>使用基於全局歷史的分支預測法，並無法對分支指令：TNTNTN… 進行完美的預測</p>
<ul>
<li>因為此時分支指令反而會受到其他分支指令的影響</li>
<li>且每次遇到這條分支指令時，都需要重新對 PHT 進行訓練，進而降低了分支預測的準確度</li>
</ul>
</li>
<li>
<p>造成這樣的現象，就是因為有些分支指令適合使用基於局部歷史的分支預測法，但另外一些分支指令適合使用基於全局歷史的分支預測法，如果能根據實際狀況，對不同的分支指令使用不同的分支預測法，那就可以得到更高的預測準確度</p>
</li>
</ul>
<h2 id="424---競爭的分支預測">4.2.4 - 競爭的分支預測</h2>
<ul>
<li>
<p>由於有些分支指令適合使用基於局部歷史的分支預測法，但另外一些分支指令適合使用基於全局歷史的分支預測法，因此可以設定一種自我調整 (self-adapting) 的分支預測方法，根據不同的分支指令執行情況自動選擇是要基於局部歷史或是全局歷史的分支預測法，稱為<strong>競爭的分支預測</strong> (<code>Tournament Predictor</code>)，就像兩種分支預測方法在進行競爭一樣：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2021.png"></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2022.png"></p>
</li>
<li>
<p><code>CPHT (Choice PHT)</code> 是由分支指令 PC 值來索引的一個表格，類似於 PHT，也是由多個 <code>2-bit saturating counter</code> 所組成</p>
<ul>
<li>將分支指令部份的 PC 值，和 GHR 的值，做一定的處理 (e.g. XOR)，用來索引 CPHT</li>
</ul>
</li>
<li>
<p>CPHT 中每個 <code>2-bit saturating counter</code> 的 FSM 如下：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2023.png"></p>
<ul>
<li>P1 (Global) 預測正確，且 P2 (Local) 預測錯誤時，i.e. <code>1/0</code>，counter 值 <strong>- 1</strong></li>
<li>P1 (Global) 預測錯誤，且 P2 (Local) 預測正確時，i.e. <code>0/1</code>，counter 值 <strong>+ 1</strong></li>
<li>當 P1 (Global) 和 P2 (Local) 預測的結果一樣時，i.e. <code>0/0</code> 或 <code>1/1</code>，不管預測正確與否，counter 值都<strong>保持不變</strong></li>
<li><code>00</code> / <code>01</code>：使用 P1 (Global) 的預測結果</li>
<li><code>11</code> / <code>10</code>：使用 P2 (Local) 的預測結果</li>
<li>同一條分支指令，根據 CPHT 的值，有可能有時候使用基於局部歷史的分支預測法，有時候使用基於全局歷史的分支預測法</li>
</ul>
</li>
<li>
<p>考慮以下的程式：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-5">5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-2-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-6">6</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (aa <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>)  <span style="color:#75715e">// b1
</span></span></span><span style="display:flex;"><span>    a <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (bb <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>)  <span style="color:#75715e">// b2
</span></span></span><span style="display:flex;"><span>    b <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (aa <span style="color:#f92672">==</span> bb) <span style="color:#75715e">// b3
</span></span></span><span style="display:flex;"><span>    c <span style="color:#f92672">=</span> <span style="color:#ae81ff">3</span>;
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>如果 <code>b1</code> 和 <code>b2</code> 都 taken 了，<code>b3</code> 一定是 taken
<ul>
<li>此時 <code>b3</code> 使用基於全局歷史的分支預測法較為合適</li>
</ul>
</li>
<li>如果 <code>b1</code> 和 <code>b2</code> 中只有一個 taken，<code>b3</code> 一定是 not taken
<ul>
<li>此時 <code>b3</code> 使用基於全局歷史的分支預測法較為合適</li>
</ul>
</li>
<li>如果 <code>b1</code> 和 <code>b2</code> 都 not taken 了，此時 <code>b3</code> 是否 taken 很難預測 (i.e. <code>aa == bb</code> 是否成立無法預測)
<ul>
<li>此時 <code>b3</code> 使用基於局部歷史的分支預測法較為合適</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="425---分支預測的更新">4.2.5 - 分支預測的更新</h2>
<ul>
<li>在實際的 superscalar CPU 中，對於 branch predictor 的更新需要考量到對 pipeline 的影響
<ul>
<li>對於基於局部歷史和基於全局歷史兩種分支分支預測法，什麼時候更新 branch predictor 對分支預測的準確度是會有所影響的</li>
</ul>
</li>
<li>對分支指令的方向預測，需要更新的內容包含兩個方面：
<ol>
<li>歷史暫存器：
<ul>
<li>基於局部歷史的分支預測法：BHR</li>
<li>基於全局歷史的分支預測法：GHR</li>
</ul>
</li>
<li><code>2-bit saturating counter</code>：
<ul>
<li>不論是哪種分支預測法，都需要更新 PHT 中的 <code>2-bit saturating counter</code> (i.e. PHT entry)</li>
</ul>
</li>
</ol>
</li>
<li>更新歷史暫存器：
<ul>
<li>GHR：
<ul>
<li>有三個時間點可以更新 GHR：
<ol>
<li>
<p>Commit stage：當分支指令 commit，要準備 retire 時，根據分支結果更新 GHR</p>
<ul>
<li>最保守，但也是最正確的，此時更新 GHR 一定沒有問題</li>
<li>但 superscaler CPU 的 pipeline 都很深，等分支指令 commit 並準備 retire 時，該分支指令後面已經有很多條指令已經進入 pipeline 了，這些指令都沒辦法享受到正確的分支預測結果</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2024.png"></p>
<ul>
<li>分支指令 Br2 ~ Br5 都使用同一個 GHR 值，沒辦法享受到 Br1 的分支執行結果</li>
</ul>
</li>
<li>
<p>Execution stage：當分支指令方向被計算出來後，根據分支結果更新 GHR</p>
<ul>
<li>對於普通的 CPU 而言，其 pipeline 並不會很深 (e.g. IF, DC, EX, MEM, WB)，因此，在 execution stage 更新 GHR，大部分後續的分支指令都可以享受到此分支指令的執行結果</li>
<li>但對於 superscalar CPU 而言，在 EX 和 IF stages 之間會存在很多的指令</li>
<li>此外：
<ul>
<li>如果是 in-order CPU，如果發生了 exception，在 execution stage 的分支指令是需要被 flushed 的</li>
<li>如果是 out-of-order CPU，在 execution stage 的分支指令有可能是處在 mis-prediction 的錯誤路徑上</li>
</ul>
</li>
<li>因此，分支指令在 execution stage 的更新 GHR 並不一定是正確的，有可能該分支指令實際上根本不會被執行到</li>
</ul>
</li>
<li>
<p>Fetch stage：在 fetch instruction 時，直接進行分支預測並更新 GHR</p>
<ul>
<li><strong>綜合 1. 和 2. 的結果來說，在 fetch stage，直接進行分支預測並更新 GHR，是一個不錯的方法</strong>
<ul>
<li>可以使後續的分支指令都用到最新的 GHR 內容</li>
<li>當一條分支指令預測錯誤時，即使後續的分支指令都使用到了錯誤的 GHR，其實也是沒關係的，因為後續分支指令也會在 mis-prediction 的路徑上，都應該從 pipeline 中被 flushed 掉</li>
<li>在 fetch stage 進行分支預測是 <strong>speculative</strong> 的，當分支預測失敗時，需要有一種機制可以將 GHR 進行修復，使 GHR 可以恢復到正確的值</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>
<li>GHR 的修復，有兩種方法：
<ol>
<li>
<p>Commit stage 修復法：</p>
<ul>
<li>在 pipeline 中的 comit stage 也放一個 GHR，每當一條分支指令 commit / retire 時，就將正確的分支結果，更新到這個 GHR 中，這個 GHR 稱為：<code>Retired GHR</code></li>
<li>因此，在 pipeline 中就有兩個 GHRs：
<ul>
<li>Fetch stage 的 <code>Speculative GHR</code>，採用 speculative 分支預測並更新 GHR</li>
<li>Commit stage 的 <code>Retired GHR</code>，每當一條分支指令 commit / retire 時，就將正確的分支結果，更新到這個 GHR 中</li>
</ul>
</li>
<li>當一條分支指令發生 mis-prediction 時，Speculative GHR 的內容一定是錯誤的，因此需要等到該分支指令進到 commit stage，並更新 Retired GHR 後，將 Retired GHR 的內容，覆蓋掉 Speculative GHR 的內容，就完成了 Speculative GHR 的修復；然後再根據這條分支指令所跳轉的位址，重新 fetch instruction 即可</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2025.png"></p>
<ul>
<li>缺點：
<ul>
<li>會增大 mis-prediction 的 penalty，因為必須等到該分支指令 commit / retire 時，才能重新 fetch instruction
<ul>
<li>尤其是當該分支指令前有 D-cache miss 的 load 指令時，penalty 會更為嚴重，並降低處理器的性能</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Checkpoint 修復法：</p>
<ul>
<li>在 fetch stage 進行 GHR 更新前，其實就可以先將”舊的” GHR 值給保存起來，這個保存內容就稱為：<code>Checkpoint GHR</code></li>
<li>一旦該分支指令的結果被計算出來後 (e.g. 在 execution stage)，就可以檢查是否發生 mis-prediction；如果發生 mis-prediction，就可以直接用 Checkpoint GHR 的內容，覆蓋掉 Speculative GHR 的內容，並 flush pipeline；然後再根據這條分支指令所跳轉的位址，重新 fetch instruction 即可</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2026.png"></p>
<ul>
<li>有一個暫存器專門用來儲存 Checkpoint GHR 的內容</li>
<li>由於新的分支結果是從右邊 shift 進 GHR 的，因此只需將 speculative 的預測結果反轉 (taken → not taken / taken → not taken)，從右邊 shift 進 Checkpoint GHR，即為”舊的” Speculative GHR 的值</li>
<li>當 mis-prediction 發生時，就可以用 Checkpoint GHR 的內容，覆蓋掉 Speculative GHR 的內容，即完成 Speculative GHR 的修復</li>
<li>由於分支預測發生在 fetch stage，此時指令仍是 in-order 的，所以 Checkpoint GHR 的值只要使用 FIFO 的方式，寫進暫存器即可</li>
<li>但如果是 out-of-order CPU，在 execution stage 的時候，指令已經是亂序執行了，因此就無法直接照 FIFO 的順序讀取這個暫存器，增加了此暫存器的設計難度</li>
<li>甚至，對於 out-of-order CPU，分支指令的執行結果也有可能是 mis-predict 的，因為該分支指令有可能是在 mis-prediction 的路徑上 (i.e. 更早之前有條分支指令發生 mis-prediction 了，該分支指令根本不應該被執行)
<ul>
<li>所以依舊需要在 commit stage 時，對分支預測的結果進行檢查</li>
</ul>
</li>
<li>因此，在 commit stage，還是需要使用 <code>Retired GHR</code>，當分支指令 commit / retire 時，如果發現分支預測錯了，那就需要將 Retired GHR 的內容，覆蓋掉 Speculative GHR 的內容，並 flush pipeline；然後再根據這條分支指令所跳轉的位址，重新 fetch instruction</li>
<li>Checkpoint 方法除了在 execution stage 可以修復 Speculative GHR，也可以在 commit stage 修復 Speculative GHR，這樣就可以加快分支指令在發生 mis-prediction 時的修復時間，提高處理器的執行效率</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
<li>BHR：
<ul>
<li>
<p>BHR 的更新也可以是 speculative 的，也可以是 non-speculative 的</p>
</li>
<li>
<p>不過跟 GHR 不一樣的是，BHR 儲存的是當前分支指令自身在過去的執行情況，通常只有在循環體很短的情況下，才有可能出現一條分支指令在 pipeline 的 commit stage 更新 BHR 時，pipeline 中又已經出現這條分支指令並使用 BHR 進行分支預測</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2027.png"></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch4-part1/image%2028.png"></p>
<ul>
<li>假設 <code>bne</code> 指令對應的 BHR 和 PHT 都已經訓練好，也就是現在是 taken，且分支位址也已經存在 BTB 中</li>
<li>在第一個 <code>bne</code> 指令在 commit (retire) stage 更新 BHR 前，所有後續的 <code>bne</code> 指令都使用不到第一個 <code>bne</code> 指令的分支預測結果
<ul>
<li>但這並不會對性能造成太大的影響，因為經過一段的訓練時間後，branch predictor 對於 <code>bne</code> 這個指令都是預測 taken 的，只有在 for-loop 最後一個迴圈結束時會預測錯誤，其他預測結果都是正確的</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>綜合起來：
<ul>
<li><strong>在 fetch stage 更新 GHR 是最合適的</strong>，GHR 使用了指令間的相關性</li>
<li><strong>在 commit (retire) stage 更新 BHR 是最合適的</strong>，可以簡化設計，又不會對處理器的性能造成太大的影響</li>
</ul>
</li>
</ul>
</li>
<li>更新 PHT 的 saturating counter：
<ul>
<li>當一條分支指令比較有規律時，其對應的 saturating counter 總是處在飽和狀態，因此即使是在該分支指令 commit / retire 時更新 PHT 的 saturating counter，也不會對分支預測的準確度造成太大的影響
<ul>
<li>因此，不論是基於局部歷史的分支預測法，或是基於全局歷史的分支預測法，一般都是<strong>在 commit stage (指令 retire 時) 更新 PHT 的 saturating counter</strong></li>
</ul>
</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>&lt;超標量處理器概覽&gt; 第 3 章 - 虛擬存儲器</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch3/</link>
      <pubDate>Thu, 27 Feb 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch3/</guid>
      <description>&lt;h1 id=&#34;32---位址轉換&#34;&gt;3.2 - 位址轉換&lt;/h1&gt;
&lt;h2 id=&#34;323---page-fault&#34;&gt;3.2.3 - Page Fault&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;PTE (page table entry) 中包含：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;valid&lt;/code&gt; bit：標記這個 PTE 是否有效，當作業系統設定好 page table 後，就需要將對應 PTE 的 &lt;code&gt;valid&lt;/code&gt; bit 設成 1&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirty&lt;/code&gt; bit：當一個 page 內容被更新時 (e.g. 執行 store 指令)，硬體會自動將 &lt;code&gt;dirty&lt;/code&gt; bit 設成 1，代表這個 page 如果被選中要被替換時，需要將 page 的內容 swap 回硬碟&lt;/li&gt;
&lt;li&gt;&lt;code&gt;access&lt;/code&gt; bit：當一個 page 被訪問 (load/store) 時，硬體會自動將 &lt;code&gt;access&lt;/code&gt; bit 設成 1，作業系統則會定期的將 &lt;code&gt;access&lt;/code&gt; bit 清為 0&lt;/li&gt;
&lt;li&gt;當要替換 page table 時，就可以根據 access bit 來得知最近 page 是否有被訪問過，進而實現近似 LRU (Least Recently Used) 的替換策略&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;當 page 要被 swapped out 前：
&lt;ul&gt;
&lt;li&gt;要先將 D-Cache 的內容 flush 進 memory，以確保要被 swapped out 的 page 內容是最新的&lt;/li&gt;
&lt;li&gt;將 PTE 的 &lt;code&gt;valid&lt;/code&gt; bit 設成 0，以避免其他 CPU 或 MMU 在接下來 swap out 期間誤讀這個 page
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;valid&lt;/code&gt; bit 為 0 時，代表該 page 並不存在記憶體中，當 MMU 存取時會觸發 page fault 讓作業系統從硬碟讀取 page 進記憶體&lt;/li&gt;
&lt;li&gt;&lt;code&gt;valid&lt;/code&gt; bit 是由作業系統在把 page 讀進記憶體後設為 &lt;code&gt;1&lt;/code&gt; 的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;執行如 RISC-V 的 &lt;code&gt;sfence.vma&lt;/code&gt; 指令，確保 TLB 中對應的 entries 被清空，以避免 MMU 錯誤存取到被 swapped out page 的 cache 內容&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;34---加入-tlb-和-cache&#34;&gt;3.4 - 加入 TLB 和 Cache&lt;/h1&gt;
&lt;h2 id=&#34;341---tlb-的設計&#34;&gt;3.4.1 - TLB 的設計&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;TLB entry 中也會包含：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;valid&lt;/code&gt; bit：標記這個 TLB entry 是否有效；當 TLB entry 被 swap in 時，&lt;code&gt;valid&lt;/code&gt; bit 會被設為 1&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirty&lt;/code&gt; bit：當執行 store 指令時，如果 TLB hit，就不會再訪問 PTE，也就不會更新 PTE 中的 &lt;code&gt;dirty&lt;/code&gt; bit；只有當 TLB entry 要被替換時，才會同步至 PTE&lt;/li&gt;
&lt;li&gt;&lt;code&gt;access&lt;/code&gt; bit：當執行 load/store 指令時，如果 TLB hit，就不會再訪問 PTE，也就不會更新 PTE 中的 &lt;code&gt;access&lt;/code&gt; bit；只有當 TLB entry 要被替換時，才會同步至 PTE&lt;/li&gt;
&lt;li&gt;P.S. RISC-V 中，TLB entry 並沒有包含 &lt;code&gt;dirty&lt;/code&gt; 和 &lt;code&gt;access&lt;/code&gt; bits，作業系統需要自己確保 PTE 的更新
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sfence.vma&lt;/code&gt; 只會將 TLB 中對應的 entries 給 invalid 而已 (針對 TLB 的部份)&lt;/li&gt;
&lt;li&gt;如果 CPU 有支援 &lt;strong&gt;Svadu&lt;/strong&gt; extension，那麼硬體就會自動更新 PTE 中的 &lt;code&gt;dirty&lt;/code&gt; 和 &lt;code&gt;access&lt;/code&gt; bit
&lt;ul&gt;
&lt;li&gt;如果硬體支援 &lt;code&gt;access&lt;/code&gt; 和 &lt;code&gt;dirty&lt;/code&gt; bits 自動更新， 作業系統只需要讀取 PTE 即可獲得最新的 &lt;code&gt;access&lt;/code&gt; 和 &lt;code&gt;dirty&lt;/code&gt; bits 的狀態&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果硬體不支援 &lt;code&gt;access&lt;/code&gt; 和 &lt;code&gt;dirty&lt;/code&gt; bits 自動更新，當 &lt;code&gt;access=0&lt;/code&gt; 或 &lt;code&gt;dirty=0&lt;/code&gt; 時：
&lt;ol&gt;
&lt;li&gt;MMU 會觸發 page fault&lt;/li&gt;
&lt;li&gt;作業系統在 page fault handler 中設置 &lt;code&gt;access=1&lt;/code&gt; 或 &lt;code&gt;dirty=1&lt;/code&gt;
&lt;ol&gt;
&lt;li&gt;作業系統得先透過設定 PTE 的 &lt;code&gt;valid&lt;/code&gt; bit (追蹤 page 是否有被 accessed) 和 disable &lt;code&gt;write&lt;/code&gt; permission (追蹤 page 是否有被 stored) 來當 page fault 發生時，在 page fault handler 更新 &lt;code&gt;access&lt;/code&gt; 或 &lt;code&gt;dirty&lt;/code&gt; bit&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;重新執行造成 page fault 的那道指令&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;也就是讓作業系統來自行管理 PTE 中 &lt;code&gt;access&lt;/code&gt; 和 &lt;code&gt;dirty&lt;/code&gt; bits 的內容，效能較差，但硬體設計較簡單&lt;/li&gt;
&lt;li&gt;RISC-V privilege spec:
&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;pte.a=0&lt;/code&gt;, or if the original memory access is a store and &lt;code&gt;pte.d=0&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;If the &lt;strong&gt;Svade&lt;/strong&gt; extension is implemented, stop and raise a page-fault exception corresponding to the original access type.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;一般為了減少 TLB miss rate，會使用 &lt;code&gt;fully-associative&lt;/code&gt; 來設計 TLB
&lt;ul&gt;
&lt;li&gt;缺點：TLB 容量不能太大，不然會增加查找的時間&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;因此，有些架構也會使用 &lt;code&gt;set-associative&lt;/code&gt; 來設計容量比較大的 TLB&lt;/li&gt;
&lt;li&gt;因為是 &lt;code&gt;fully-associative&lt;/code&gt; 或 &lt;code&gt;set-associative&lt;/code&gt;，因此 TLB entry 中，還會包含 &lt;code&gt;tag&lt;/code&gt; 欄位 (VPN，Virtual Page Number)，用來比對 TLB 是否 hit
&lt;ul&gt;
&lt;li&gt;P.S. TLB entry 中的 data 是 PFN (Page Frame Number)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;現代處理器架構，通常都使用 2-level TLB
&lt;ul&gt;
&lt;li&gt;1st level 採用 Harvard 架構，分為 I-TLB (指令) 和 D-TLB (數據)，一般採用 &lt;code&gt;fully-associative&lt;/code&gt; 設計&lt;/li&gt;
&lt;li&gt;2nd level 採用 Von Neumann 架構，指令和數據共用，一般採用 &lt;code&gt;set-associative&lt;/code&gt; 設計&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;現代處理器中因應程式的 size 越來越大，因此還會支持容量更大的 page
&lt;ul&gt;
&lt;li&gt;E.g. 128 entries 的 TLB，只能映射到 128 * 4KB = 512 MB 大小的程式，顯然不夠用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;更大的 page：
&lt;ul&gt;
&lt;li&gt;優點：
&lt;ul&gt;
&lt;li&gt;降低 TLB miss rate&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;缺點：
&lt;ul&gt;
&lt;li&gt;當發生 page fault 的時候，需要花更多的時間才能將更大的 page 內容從硬碟搬至記憶體&lt;/li&gt;
&lt;li&gt;如果程式用不到這麼大的 page，那麼空間就被浪費了，且也會造成 page fragment，降低 page 的使用效率&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;總和以上原因，現代處理器都支持大小可變的 page，由作業系統負責管理，根據程式的特點選用不同大小的 page，最大程度地利用 TLB 有限的空間，並降低 page fragment
&lt;ul&gt;
&lt;li&gt;在 TLB 中會有相對應的設定可以調整映射的 page 大小&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;因為記憶體的存取速度相對於 CPU 的執行速度來說非常慢，因此 TLB 通常只會採用 &lt;strong&gt;Write-back&lt;/strong&gt; 的方式設計，且因為 TLB entry 中 &lt;code&gt;tag&lt;/code&gt; 和 &lt;code&gt;data&lt;/code&gt; 等欄位是不會變動的，因此當發生 TLB miss，需要將 TLB entry swap out 時，只需要將 &lt;code&gt;dirty&lt;/code&gt; bit (執行 store 指令時會被更新) 和 &lt;code&gt;access&lt;/code&gt; bit (執行 load/store 指令時會被更新) 寫回 PTE 中即可&lt;/li&gt;
&lt;li&gt;TLB miss 發生的情況：
&lt;ol&gt;
&lt;li&gt;Page 並不在記憶體中，因此也不在 TLB 中&lt;/li&gt;
&lt;li&gt;Page 在記憶體中，page table 中也有對應的 PTE，但這個 PTE 並沒有被 cache 在 TLB 中&lt;/li&gt;
&lt;li&gt;Page 在記憶體中，page table 中也有對應的 PTE，這個 PTE 也曾經存在 TLB 中，但是因為先前的 TLB miss，因此被替換出來了&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;TLB miss 時，TLB entry 的替換策略：
&lt;ul&gt;
&lt;li&gt;LRU (Least Recently Used)&lt;/li&gt;
&lt;li&gt;Random：如同 cache，使用一個 counter，每個 cycle 都會 + 1，每次 TLB miss 要替換 TLB entry 時，就根據當下 counter 的值決定是哪個 entry 要被替換&lt;/li&gt;
&lt;li&gt;LRU 比較難實現，所以通常會採用 Random 的替換策略，實做也比較簡單&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果 TLB 採用 &lt;strong&gt;Write-back&lt;/strong&gt;，TLB entry 中的 &lt;code&gt;dirty&lt;/code&gt; 和 &lt;code&gt;access&lt;/code&gt; bits 就有可能跟 PTE 的內容不同步，如果 page 要被 swap out 的時候，作業系統會無法即時得知究竟 page 是否 dirty，以及是否有被 accessed 過
&lt;ul&gt;
&lt;li&gt;直觀解法：每次發生 page fault 要 swap page 的時候，先將 TLB entries flush 至 PTE
&lt;ul&gt;
&lt;li&gt;缺點：需要耗費額外的時間 flush TLB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;另類解法：作業系統可以認為，有在 TLB 中被 cached 對應的 pages 都是正在使用的，因此不能將其 swap out
&lt;ul&gt;
&lt;li&gt;需要作業系統自行維護一張表，紀錄哪些 PTE 有被 cached 在 TLB 中，且為 valid 的
&lt;ul&gt;
&lt;li&gt;不過這種設計並不常見&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果系統中有 D-Cache，page 也是 dirt 的，那麼 page 被更動的內容也有可能還存在 D-Cache 中，因此在 page swap out 前也必須先 flush D-Cache&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;342---cache-的設計&#34;&gt;3.4.2 - Cache 的設計&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;兩種 caches：&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="32---位址轉換">3.2 - 位址轉換</h1>
<h2 id="323---page-fault">3.2.3 - Page Fault</h2>
<ul>
<li>PTE (page table entry) 中包含：
<ul>
<li><code>valid</code> bit：標記這個 PTE 是否有效，當作業系統設定好 page table 後，就需要將對應 PTE 的 <code>valid</code> bit 設成 1</li>
<li><code>dirty</code> bit：當一個 page 內容被更新時 (e.g. 執行 store 指令)，硬體會自動將 <code>dirty</code> bit 設成 1，代表這個 page 如果被選中要被替換時，需要將 page 的內容 swap 回硬碟</li>
<li><code>access</code> bit：當一個 page 被訪問 (load/store) 時，硬體會自動將 <code>access</code> bit 設成 1，作業系統則會定期的將 <code>access</code> bit 清為 0</li>
<li>當要替換 page table 時，就可以根據 access bit 來得知最近 page 是否有被訪問過，進而實現近似 LRU (Least Recently Used) 的替換策略</li>
</ul>
</li>
<li>當 page 要被 swapped out 前：
<ul>
<li>要先將 D-Cache 的內容 flush 進 memory，以確保要被 swapped out 的 page 內容是最新的</li>
<li>將 PTE 的 <code>valid</code> bit 設成 0，以避免其他 CPU 或 MMU 在接下來 swap out 期間誤讀這個 page
<ul>
<li><code>valid</code> bit 為 0 時，代表該 page 並不存在記憶體中，當 MMU 存取時會觸發 page fault 讓作業系統從硬碟讀取 page 進記憶體</li>
<li><code>valid</code> bit 是由作業系統在把 page 讀進記憶體後設為 <code>1</code> 的</li>
</ul>
</li>
<li>執行如 RISC-V 的 <code>sfence.vma</code> 指令，確保 TLB 中對應的 entries 被清空，以避免 MMU 錯誤存取到被 swapped out page 的 cache 內容</li>
</ul>
</li>
</ul>
<h1 id="34---加入-tlb-和-cache">3.4 - 加入 TLB 和 Cache</h1>
<h2 id="341---tlb-的設計">3.4.1 - TLB 的設計</h2>
<ul>
<li>TLB entry 中也會包含：
<ul>
<li><code>valid</code> bit：標記這個 TLB entry 是否有效；當 TLB entry 被 swap in 時，<code>valid</code> bit 會被設為 1</li>
<li><code>dirty</code> bit：當執行 store 指令時，如果 TLB hit，就不會再訪問 PTE，也就不會更新 PTE 中的 <code>dirty</code> bit；只有當 TLB entry 要被替換時，才會同步至 PTE</li>
<li><code>access</code> bit：當執行 load/store 指令時，如果 TLB hit，就不會再訪問 PTE，也就不會更新 PTE 中的 <code>access</code> bit；只有當 TLB entry 要被替換時，才會同步至 PTE</li>
<li>P.S. RISC-V 中，TLB entry 並沒有包含 <code>dirty</code> 和 <code>access</code> bits，作業系統需要自己確保 PTE 的更新
<ul>
<li><code>sfence.vma</code> 只會將 TLB 中對應的 entries 給 invalid 而已 (針對 TLB 的部份)</li>
<li>如果 CPU 有支援 <strong>Svadu</strong> extension，那麼硬體就會自動更新 PTE 中的 <code>dirty</code> 和 <code>access</code> bit
<ul>
<li>如果硬體支援 <code>access</code> 和 <code>dirty</code> bits 自動更新， 作業系統只需要讀取 PTE 即可獲得最新的 <code>access</code> 和 <code>dirty</code> bits 的狀態</li>
</ul>
</li>
<li>如果硬體不支援 <code>access</code> 和 <code>dirty</code> bits 自動更新，當 <code>access=0</code> 或 <code>dirty=0</code> 時：
<ol>
<li>MMU 會觸發 page fault</li>
<li>作業系統在 page fault handler 中設置 <code>access=1</code> 或 <code>dirty=1</code>
<ol>
<li>作業系統得先透過設定 PTE 的 <code>valid</code> bit (追蹤 page 是否有被 accessed) 和 disable <code>write</code> permission (追蹤 page 是否有被 stored) 來當 page fault 發生時，在 page fault handler 更新 <code>access</code> 或 <code>dirty</code> bit</li>
</ol>
</li>
<li>重新執行造成 page fault 的那道指令</li>
</ol>
<ul>
<li>也就是讓作業系統來自行管理 PTE 中 <code>access</code> 和 <code>dirty</code> bits 的內容，效能較差，但硬體設計較簡單</li>
<li>RISC-V privilege spec:
<ul>
<li>If <code>pte.a=0</code>, or if the original memory access is a store and <code>pte.d=0</code>:
<ul>
<li>If the <strong>Svade</strong> extension is implemented, stop and raise a page-fault exception corresponding to the original access type.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>一般為了減少 TLB miss rate，會使用 <code>fully-associative</code> 來設計 TLB
<ul>
<li>缺點：TLB 容量不能太大，不然會增加查找的時間</li>
</ul>
</li>
<li>因此，有些架構也會使用 <code>set-associative</code> 來設計容量比較大的 TLB</li>
<li>因為是 <code>fully-associative</code> 或 <code>set-associative</code>，因此 TLB entry 中，還會包含 <code>tag</code> 欄位 (VPN，Virtual Page Number)，用來比對 TLB 是否 hit
<ul>
<li>P.S. TLB entry 中的 data 是 PFN (Page Frame Number)</li>
</ul>
</li>
<li>現代處理器架構，通常都使用 2-level TLB
<ul>
<li>1st level 採用 Harvard 架構，分為 I-TLB (指令) 和 D-TLB (數據)，一般採用 <code>fully-associative</code> 設計</li>
<li>2nd level 採用 Von Neumann 架構，指令和數據共用，一般採用 <code>set-associative</code> 設計</li>
</ul>
</li>
<li>現代處理器中因應程式的 size 越來越大，因此還會支持容量更大的 page
<ul>
<li>E.g. 128 entries 的 TLB，只能映射到 128 * 4KB = 512 MB 大小的程式，顯然不夠用</li>
</ul>
</li>
<li>更大的 page：
<ul>
<li>優點：
<ul>
<li>降低 TLB miss rate</li>
</ul>
</li>
<li>缺點：
<ul>
<li>當發生 page fault 的時候，需要花更多的時間才能將更大的 page 內容從硬碟搬至記憶體</li>
<li>如果程式用不到這麼大的 page，那麼空間就被浪費了，且也會造成 page fragment，降低 page 的使用效率</li>
</ul>
</li>
</ul>
</li>
<li>總和以上原因，現代處理器都支持大小可變的 page，由作業系統負責管理，根據程式的特點選用不同大小的 page，最大程度地利用 TLB 有限的空間，並降低 page fragment
<ul>
<li>在 TLB 中會有相對應的設定可以調整映射的 page 大小</li>
</ul>
</li>
<li>因為記憶體的存取速度相對於 CPU 的執行速度來說非常慢，因此 TLB 通常只會採用 <strong>Write-back</strong> 的方式設計，且因為 TLB entry 中 <code>tag</code> 和 <code>data</code> 等欄位是不會變動的，因此當發生 TLB miss，需要將 TLB entry swap out 時，只需要將 <code>dirty</code> bit (執行 store 指令時會被更新) 和 <code>access</code> bit (執行 load/store 指令時會被更新) 寫回 PTE 中即可</li>
<li>TLB miss 發生的情況：
<ol>
<li>Page 並不在記憶體中，因此也不在 TLB 中</li>
<li>Page 在記憶體中，page table 中也有對應的 PTE，但這個 PTE 並沒有被 cache 在 TLB 中</li>
<li>Page 在記憶體中，page table 中也有對應的 PTE，這個 PTE 也曾經存在 TLB 中，但是因為先前的 TLB miss，因此被替換出來了</li>
</ol>
</li>
<li>TLB miss 時，TLB entry 的替換策略：
<ul>
<li>LRU (Least Recently Used)</li>
<li>Random：如同 cache，使用一個 counter，每個 cycle 都會 + 1，每次 TLB miss 要替換 TLB entry 時，就根據當下 counter 的值決定是哪個 entry 要被替換</li>
<li>LRU 比較難實現，所以通常會採用 Random 的替換策略，實做也比較簡單</li>
</ul>
</li>
<li>如果 TLB 採用 <strong>Write-back</strong>，TLB entry 中的 <code>dirty</code> 和 <code>access</code> bits 就有可能跟 PTE 的內容不同步，如果 page 要被 swap out 的時候，作業系統會無法即時得知究竟 page 是否 dirty，以及是否有被 accessed 過
<ul>
<li>直觀解法：每次發生 page fault 要 swap page 的時候，先將 TLB entries flush 至 PTE
<ul>
<li>缺點：需要耗費額外的時間 flush TLB</li>
</ul>
</li>
<li>另類解法：作業系統可以認為，有在 TLB 中被 cached 對應的 pages 都是正在使用的，因此不能將其 swap out
<ul>
<li>需要作業系統自行維護一張表，紀錄哪些 PTE 有被 cached 在 TLB 中，且為 valid 的
<ul>
<li>不過這種設計並不常見</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>如果系統中有 D-Cache，page 也是 dirt 的，那麼 page 被更動的內容也有可能還存在 D-Cache 中，因此在 page swap out 前也必須先 flush D-Cache</li>
</ul>
<h2 id="342---cache-的設計">3.4.2 - Cache 的設計</h2>
<ul>
<li>
<p>兩種 caches：</p>
<ul>
<li>
<p>Physical cache：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image.png"></p>
</li>
<li>
<p>Virtual cache：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%201.png"></p>
</li>
</ul>
</li>
<li>
<p>Virtual cache 會有 aliasing (synonyms，同義) 問題：</p>
<ul>
<li>
<p>多個 VA 對應到同一個 PA</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%202.png"></p>
</li>
<li>
<p>缺點：</p>
<ul>
<li>造成 cache 空間的浪費，因為可能會有兩個 cache sets (不同 VA) 都是對應到同一個 PA 的資料</li>
<li>當執行 store 指令更新 cache 的內容時，只有一個 cache set 的內容會被更新，但實際上，所有對應到同一個 PA 的 cache sets 內容都應該被更新，否則其他 CPU 讀到的 cache 內容就會不同步</li>
</ul>
</li>
<li>
<p>並不是所有情況都會有 aliasing (synonyms) 問題，例如：</p>
<ul>
<li>當 page 為 4 KB，VA 轉成 PA 時，low 12 bits 的位址是不會發生變化的，因此當一個 direct-mapped 的 cache 容量 ≤ 4KB 時，由於 cache index 最多只會用到 page offset 內的 12 bits (i.e. cache sets 數量 ≤ page offset 可表達的範圍)，即使兩個不同 VA 對應到同一個 PA，還是會 mapping 到同一個 cache set，也就沒有 aliasing (synonyms) 問題
<ul>
<li>
<p>只有在 cache 容量 &gt; 4KB 時，才會導致 cache index bits &gt; 12 bits (i.e. cache sets 的數量 &gt; page offset 可表達的範圍，也就是 cache index bits 包含了 VPN 的部份)，造成兩個不同 VA 對應到同一個 PA，有可能會 mapping 到不同個的 cache sets，導致 aliasing (synonyms) 問題</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%203.png"></p>
<ul>
<li>E.g. 即使 VA：<code>0x800</code> 和 <code>0x1800</code> 都是對應到同一個 PA，但因為 VA 的 <code>bits[12]</code> 也是 cache index bits 的一部分，因此還是會被 mapping 到不同的 cache sets</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>使用 bank 解決 aliasing 問題：</p>
<ul>
<li>
<p>最簡單的方式：在寫 cache 時，將 aliased 的 cache sets 都進行更新</p>
<ul>
<li>需要將 PA 的一部分作為 cache tag 來使用，並需要使用兩個 banks</li>
<li>缺點：浪費 cache 使用空間</li>
</ul>
</li>
<li>
<p>如何在不浪費 cache 使用空間的情況下，透過 bank 的方式解決 aliasing 問題：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%204.png"></p>
<ul>
<li>範例：8 KB 切成兩個 4 KB 的 cache banks
<ul>
<li>讀取 cache 時，使用 <code>VA[11:0]</code> 同時查詢兩個 banks，輸出的 cache sets 再透過 <code>PA[12]</code> 控制的 Mux 來選取是哪個 cache set 要被讀出
<ul>
<li>由於 PA 需要透過 TLB 轉換才能得知，處理時間較長，因此會增加 CPU 的 cycle time</li>
<li>Cache tag 也必須是 PA，才能跟 TLB 轉換出來的 PFN 做比對</li>
</ul>
</li>
<li>寫 cache 時，因為只有被 retired 的指令才會把數據寫入 cache，此時 PA 已經得到了，所以直接拿 <code>PA[12]</code> 來判斷要寫入到哪個 bank 中即可</li>
<li>缺點：
<ul>
<li>增加硬體的複雜度</li>
<li>由於讀取 cache 時必須同時查找所有的 banks，因此也會增加功耗</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Virtual cache 會有 homonyms (同名) 問題：</p>
<ul>
<li>一個 VA 對應到多個 PA
<ul>
<li>不同 processes，同一個 VA 可能對應到的 PA 不同</li>
</ul>
</li>
<li>在 context switch 時，需要把 virtual indexed cache 的 cache 內容都 invalidate</li>
<li>同樣的，在 context switch 時，也需要把所有的 TLB entries 都 invalidate，以避免存取到錯誤的 VA -&gt; PA mappings</li>
<li>可以透過 ASID (Address Space Identifier) 來只 invalidate 被 context switched 的 cache 內容和 TLB entries</li>
<li>可以再透過引入 <code>global</code> bit 來標記該 page 是被多個 processes 共享的 (e.g. Linux kernel space)</li>
</ul>
</li>
<li>
<p>DMA 搬 memory 的資料前，必須先 flush D-Cache，確保記憶體中的內容是最新的</p>
</li>
<li>
<p>CPU 在讀取 DMA 搬完的 memory 資料前，必須先 invalidate D-Cache，確保 CPU 一定會從 memory 讀取最新的資料</p>
</li>
<li>
<p>當發生 page fault，並需要將 page swap out 至硬碟前，如果該 page 是 <code>dirty</code> 的，那就需要先 flush D-Cache，確保要被 swapped out 的 page 內容是最新的</p>
</li>
<li>
<p>在執行完 self-modifying codes 的指令後，必須先 flush D-Cache，確保指令的修改有正確的寫進 memory；然後要再 invalid I-Cache，確保 CPU 可以讀取到 self-modified 完後最新的指令</p>
</li>
<li>
<p>Cache 在上電時，大部分都是關閉的，所以起始程式通常都是放在 non-cacheable 的 memory region；等到初始化完 cache 後，cache 才可以被開啟及使用</p>
</li>
</ul>
<h2 id="343---將-tlb-和-cache-放入流水線">3.4.3 - 將 TLB 和 Cache 放入流水線</h2>
<ul>
<li>
<p><strong>PIPT Cache (Physically-indexed, Physically-tagged)：</strong></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%205.png"></p>
<ul>
<li>Cache index 和 tag 都是 PA</li>
<li>缺點：
<ul>
<li>TLB lookup 需要消耗一定的時間，為了不影響 CPU 的 cycle time，可能會需要將 TLB lookup 獨立成一個獨立的 pipeline stage
<ul>
<li>對 I-Cache 來說，branch mis-prediction 的 penalty 會增加</li>
<li>對 D-Cache 來說，load 指令的 latency 會變長
<ul>
<li>且 load 指令通常都是其他指令的 dependency，因此也會影響其他指令可以被 issued 的時間</li>
</ul>
</li>
</ul>
</li>
<li>真實世界的 CPU 很少 L1 Cache 是採用 PIPT cache 的設計，因為這樣將 cache 訪問和 TLB lookup 給綁定在一起了，會影響 pipeline
<ul>
<li>實際上也沒有必要，如果 page offset 就可以作為 cache 的 index，VA 的 page offset 和 PA 的 page offset 都是一樣的，可以直接使用 VA 的 page offset，沒有必要再透過 TLB 做轉換</li>
<li>不過 L2/L3 Cache 通常就是使用 PIPT cache 的設計，因為 L2/L3 Cache 被訪問時，TLB lookup 有可能已經完成</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>VIPT Cache (Virtually-indexed, Physically-tagged)：</strong></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%206.png"></p>
<ul>
<li>Cache index 是 VA，tag 是 PA</li>
<li>VIPT 是目前最多被使用的 cache</li>
<li>優點：
<ul>
<li>Cache 訪問和 TLB lookup 可以同時進行</li>
</ul>
</li>
<li>缺點：
<ul>
<li>Cache 的大小會有所限制，因為要避免 aliasing 的問題</li>
</ul>
</li>
<li>假設一 direct-mapped cache，cache line：2^<code>b</code> bytes，cache sets 個數：2^<code>L</code> ⇒ Cache size = 2^(<code>L + b</code>)；Page size：2^<code>k</code> bytes，會有以下三種情況：
<ul>
<li>
<p>Page size (<code>k</code>) &gt; Cache size (<code>L + b</code>)：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%207.png"></p>
</li>
<li>
<p>Page size (<code>k</code>) = Cache size (<code>L + b</code>)：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%208.png"></p>
</li>
<li>
<p>Page size (<code>k</code>) &lt; Cache size (<code>L + b</code>)：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%209.png"></p>
</li>
<li>
<p>針對 Page size (<code>k</code>) &gt; Cache size (<code>L + b</code>) 和 Page size (<code>k</code>) = Cache size (<code>L + b</code>)：</p>
<ul>
<li>
<p>有機會 cache 訪問和 TLB lookup 在同一個 stage 完成</p>
</li>
<li>
<p>為了要避免 aliasing 的問題，因此 cache size 被限制最大只能是一個 page 的大小</p>
</li>
<li>
<p>如果 cache size 要超過一個 page，就只能使用 set-associative 的設計：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%2010.png"></p>
</li>
</ul>
</li>
<li>
<p>針對 Page size (<code>k</code>) &lt; Cache size (<code>L + b</code>)：</p>
<ul>
<li>
<p>如果已經增加 ways 數 (ways 數不可能無限制的增加)，但還想再增加 cache 的 size，當 cache size &gt; page size 時，就會造成 aliasing 的問題</p>
</li>
<li>
<p>除了透過先前介紹 bank 的方式來解決 aliasing 的問題，還可以透過 inclusive L2 cache 來解決 aliasing 的問題：</p>
<ul>
<li>L2 cache 包含所有 L1 I-Cache 和 L1 D-Cache 的內容</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%2011.png"></p>
<ul>
<li>L2 Cache 中的 <code>a1</code> (VA) 是 VA1 的，所以如果跟 VA2 的 <code>a2</code> (VA) 部份不相同的話，就代表有 aliasing 的問題</li>
<li>透過 <code>{a1, offset}</code> 的組合，可以找到 VA1 在 L1 Cache 中的 cache line：
<ul>
<li>如果是 <code>dirty</code>，將其 flush 回 L2 Cache 後，再 invalidate cache line</li>
<li>如果沒有 <code>dirty</code>，直接 invalidate cache line</li>
</ul>
</li>
<li>此時就可以將 VA2 的內容從 L2 Cache 讀回 L1 Cache 中，且因為只有 VA2 在 L1 Cache 的 cache line 是 valid 的，也就不存在 aliasing 的問題</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>VIVT Cache (Virtually-indexed, Virtually-tagged)：</strong></p>
<ul>
<li>Cache index 和 tag 都是 VA</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch3/image%2012.png"></p>
<ul>
<li>優點：
<ul>
<li>Cache hit 的話，完全不用 TLB lookup</li>
</ul>
</li>
<li>缺點：
<ul>
<li>同樣會有 aliasing 的問題
<ul>
<li>可以同樣用跟 VIPT 的方式查找 VA1 在 L1 Cache 的 cache line，並將其 invalidate</li>
<li>不過此時 L2 Cache 沒辦法只存 <code>a1</code> (VA)，必須存整個 <code>VA1</code> (VA)，因為 L1 Cache 是 virtually-tagged 的，會使用 VA 的一部分作為 tag，只用 <code>{a1, offset}</code> 是不夠的
<ul>
<li>L2 Cache 中的 <code>VA1</code> (VA) 是 VA1 的，所以如果跟 VA2 的 <code>VA2</code> (VA) 部份不相同的話，就代表有 aliasing 的問題</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>&lt;超標量處理器概覽&gt; 第 2 章 - Cache</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch2/</link>
      <pubDate>Thu, 13 Feb 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch2/</guid>
      <description>&lt;h1 id=&#34;21---cache-的一般設計&#34;&gt;2.1 - Cache 的一般設計&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;L1 Cache 通常分為 I-Cache 和 D-Cache，為 private cache
&lt;ul&gt;
&lt;li&gt;I-Cache 需要能夠在一個 cycle 內，讀取多條的指令&lt;/li&gt;
&lt;li&gt;D-Cache 需要能在一個 cycle 內，處理多條 load/store 指令的訪問，因此需要使用 multi-port 的設計&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Instruction fetch 的時候，會向 L1 Cache 嘗試讀取指令，因此 L1 Cache 也是 pipeline 的一部分&lt;/li&gt;
&lt;li&gt;L1 Cache 必須保持跟 CPU 相近的速度，因此通常是透過 SRAM 實現的&lt;/li&gt;
&lt;li&gt;L2 Cache 通常都是指令和資料一起 share 整個 cache，容量通常以 MB 為單位&lt;/li&gt;
&lt;li&gt;現在大部份的 multi-core 都是 shared 同一個 L3 Cache&lt;/li&gt;
&lt;li&gt;L2 Cache 被訪問的頻率通常不是很高 (L1 Cache miss 才會訪問 L2 Cache)，因此不需要使用 multi-port 的設計&lt;/li&gt;
&lt;li&gt;需要盡可能的提昇 L2 Cache 的命中率，因為如果 L2 Cache miss，且沒 L3 Cache 的話，就需要去訪問 DRAM 了，訪問時間會很長&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;211---cache-的組成方式&#34;&gt;2.1.1 - Cache 的組成方式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Set-associative cache：&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="21---cache-的一般設計">2.1 - Cache 的一般設計</h1>
<ul>
<li>L1 Cache 通常分為 I-Cache 和 D-Cache，為 private cache
<ul>
<li>I-Cache 需要能夠在一個 cycle 內，讀取多條的指令</li>
<li>D-Cache 需要能在一個 cycle 內，處理多條 load/store 指令的訪問，因此需要使用 multi-port 的設計</li>
</ul>
</li>
<li>Instruction fetch 的時候，會向 L1 Cache 嘗試讀取指令，因此 L1 Cache 也是 pipeline 的一部分</li>
<li>L1 Cache 必須保持跟 CPU 相近的速度，因此通常是透過 SRAM 實現的</li>
<li>L2 Cache 通常都是指令和資料一起 share 整個 cache，容量通常以 MB 為單位</li>
<li>現在大部份的 multi-core 都是 shared 同一個 L3 Cache</li>
<li>L2 Cache 被訪問的頻率通常不是很高 (L1 Cache miss 才會訪問 L2 Cache)，因此不需要使用 multi-port 的設計</li>
<li>需要盡可能的提昇 L2 Cache 的命中率，因為如果 L2 Cache miss，且沒 L3 Cache 的話，就需要去訪問 DRAM 了，訪問時間會很長</li>
</ul>
<h2 id="211---cache-的組成方式">2.1.1 - Cache 的組成方式</h2>
<ul>
<li>
<p>Set-associative cache：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image.png"></p>
<ul>
<li>
<p>TLB 和 Victim Cache 通常使用 fully-associative 架構</p>
</li>
<li>
<p>I-Cache 和 D-Cache 通常使用 set-associative 架構</p>
</li>
<li>
<p>Compulsory miss 可以透過 prefetching 來降低其發生的頻率</p>
</li>
<li>
<p>Capacity miss 沒有方法可以降低其發生的頻率</p>
</li>
<li>
<p>Conflict miss 可以透過 Victim Cache 來降低其發生的頻率</p>
</li>
<li>
<p>Set-associative：</p>
<ul>
<li>優點：
<ul>
<li>可以降低 cache miss rate</li>
</ul>
</li>
<li>缺點：
<ul>
<li>需要比對多個 cache line，因此 latency 會比較高</li>
</ul>
</li>
</ul>
</li>
<li>
<p>補充：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%201.png"></p>
<ul>
<li>4-way set associative cache：
<ul>
<li>共 256 個 sets，透過 (Set) Index (8 bits) 來選取</li>
<li>每個 set 有 4 個 ways (i.e. 共 4 條 cache line)</li>
<li>同時間這四個 ways 中對應的 cache line 都會被選中，然後透過比對 Tag 來決定是哪個 way hit</li>
<li>最後再透過 Word + Byte (= Block Offset)，決定 way hit 的那條 cache line 中，要被讀/寫的 word (一條 cache line 通常包含了好幾個 words)</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在實際的實做上，Cache Tag 和 Data 是分開的，也就是：Tag SRAM 和 Data SRAM</p>
</li>
<li>
<p>Tag SRAM 和 Data SRAM 有兩種存取方式：</p>
<ul>
<li>
<p>同時訪問 Tag SRAM 和 Data SRAM</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%202.png"></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%203.png"></p>
<ul>
<li>Address Calculation stage 先計算出 memory 的位址，Disambiguation stage 再透過 LSU (Load-Store Unit) 檢查 load/store 指令之間的 memory disambiguation，確保 load/store 指令之間依賴關係有正確的被處理 (因為指令是 Out-of-Order 執行)，Cache Access stage 再根據 tag 的比較結果，決定存取哪一個 cache set；Tag hit 的 way 就可以同時間訪問其 Tag SRAM 和 Data SRAM，最後在 Result Drive stage 根據 block offset 從 cache line 上取得資料</li>
<li>優點：
<ul>
<li>比先訪問 Tag SRAM，再訪問 Data SRAM 的設計，少了一個 pipeline stage</li>
<li>較適合 In-Order CPU</li>
</ul>
</li>
<li>缺點：
<ul>
<li>較低的 CPU frequency (因為 pipeline stage 比較少)</li>
<li>較大的功耗 (因為多了 Way Mux)</li>
<li>較不適合 Out-of-Order CPU</li>
</ul>
</li>
</ul>
</li>
<li>
<p>先訪問 Tag SRAM，根據 Tag 的比較結果，再訪問 Data SRAM</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%204.png"></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%205.png"></p>
<ul>
<li>Address Calculation stage 先計算出 memory 的位址，Disambiguation stage 再透過 LSU (Load-Store Unit) 檢查 load/store 指令之間的 memory disambiguation，確保 load/store 指令之間依賴關係有正確的被處理 (因為指令是 Out-of-Order 執行)，Tag access stage 可以直接比對 tag 是否 hit，而不需要使用 Way Mux 決定是哪條 way (因為 Data SRAM 還沒有被存取)。Tag hit 的 way 就可以在 Data Access stage 訪問其 Data SRAM，最後在 Result Drive stage 根據 block offset 從 cache line 上取得資料</li>
<li>優點：
<ul>
<li>較高的 CPU frequency (因為 pipeline stage 比較多)</li>
<li>較小的功耗 (因為少了 Way Mux)</li>
<li>較適合 Out-of-Order CPU</li>
</ul>
</li>
<li>缺點：
<ul>
<li>比同時訪問 Tag SRAM 和 Data SRAM 的設計，多了一個 pipeline stage
<ul>
<li>不過 Out-of-Order CPU 可以將訪問 cache 這段時間，透過 reorder 其他的指令來填補
<ul>
<li>因此比較不適合 In-Order CPU</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Fully-associative cache：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%206.png"></p>
<ul>
<li>
<p>所有的 cache lines 的 Tag 都會被比較</p>
</li>
<li>
<p>因此通常都是使用 CAM (Content Address Memory) 來儲存 Tag，SRAM 來儲存 Data</p>
<blockquote>
<p>CAM 是一種類型的記憶體，它允許透過<strong>內容</strong>（而非位址）來存取儲存的資料。這與傳統的 RAM（Random Access Memory，隨機存取記憶體）不同，因為 RAM 透過記憶體位址來讀取或寫入資料，而 CAM 則根據輸入的內容來搜尋對應的儲存單元，並返回匹配的結果。</p>
</blockquote>
</li>
<li>
<p>優點：</p>
<ul>
<li>cache miss rate 最低 (因為只要有空的 cache line 就可以使用)</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>Latency 也是最長的 (因為有大量的 Tags 需要被比較)</li>
</ul>
</li>
<li>
<p>通常 TLB 會使用 fully-associative 來實現</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="212---cache-的寫入">2.1.2 - Cache 的寫入</h2>
<ul>
<li>Self-modifying codes 的執行流程：
<ul>
<li>Self-modified 的 codes 存至 D-Cache → 將 D-Cache 的內容 flush 至 L2/L3 Cache 或是 memory→ Invalidate I-Cache → 下次 CPU fetch 該指令的時候，就會從 L2/L3 Cache 或是 memory 取得並存進 I-Cache 中了</li>
</ul>
</li>
<li>當執行 store 指令的時候：
<ul>
<li>如果 cache line 存在：
<ul>
<li>Write-through：
<ul>
<li>資料會一路寫進 D-Cache → L2/L3 Cache → Memory</li>
<li>優點：
<ul>
<li>設計較簡單，不須額外的 dirty bit 紀錄來哪條 cache line 是 dirty 的</li>
<li>Cache 和 Memory 的資料是一致的，適合 <strong>multi-core</strong> 的系統</li>
</ul>
</li>
<li>缺點：
<ul>
<li>CPU 的效率會降低，因為 D-Cache 同步至 L2/L3 Cache, Memory 的速度很慢</li>
<li>如果寫入了資料只會用過一次，那麼同步至 L2/L3 Cache, Memory 就是多餘的開銷</li>
</ul>
</li>
</ul>
</li>
<li>Write-back：
<ul>
<li>當執行 store 指令的時候，資料只會寫進 D-Cache，並標記該 cache line 為 dirty；只有在該 cache line 要被 evicted 的時候，才會將 dirty cache line 同步至 L2 Cache or L3 Cache or Memory (只需同步至下一級)</li>
<li>優點：
<ul>
<li>資料只須寫入 D-Cache，效率較佳</li>
</ul>
</li>
<li>缺點：
<ul>
<li>不同層級的 caches 資料有可能不一致，會需要處理一致性的問題</li>
<li>需要額外的 dirty bit 來紀錄哪些 cache line 是 dirty 的</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>如果 cache line 不存在 (i.e. Write miss)：
<ul>
<li>Non-write-allocate：
<ul>
<li>直接將資料寫至下一級的 cache 或 memory，不寫進 D-Cache</li>
</ul>
</li>
<li>Write-allocate：
<ul>
<li>如果搭配 Write-through：
<ul>
<li>從下一級的 cache 讀出對應的 cache line 並寫回 D-cache 中，再將 store 的資料更新至該 cache line 中對應的 block</li>
<li>該 D-Cache 的 cache line 會再同步回 L2/L3 Cache, Memory</li>
</ul>
</li>
<li>如果搭配 Write-back：
<ul>
<li>如果該 D-Cache 的 cache line 已經是 dirty 了，需先將該 cache line flush 至下一級的 cache 或 memory</li>
<li>從下一級的 cache 讀出對應的 cache line 並寫回 D-cache 中，再將 store 的資料更新至該 cache line 中對應的 block</li>
<li>只須將該 D-Cache 的 cache line 標記為 dirty</li>
</ul>
</li>
<li>為何需要先從下一級的 cache 讀出對應的 cache line，而不是直接把資料寫進 D-Cache?
<ul>
<li>因為通常 store 只會寫入一個 word 的值 (e.g. 32 bits)，但 cache line 可能是 512 bits，如果直接該 word 寫進 cache，會導致整條 cache line 都變 dirty，當 cache line 被 evicted 的時候，整條 cache line 都會同步至下一級的 cache 或 memory，導致資料不一致
<ul>
<li>因為有可能 cache line 其他 words 在 store 指令執行時，是 invalid 的，例如：第一次寫入該 cache line</li>
<li>除非一條 cache line 有多個 dirty bits 可以 track 各個 word 的 dirty 狀態，不過這樣會需要耗費多餘的硬體，一致性的問題也會更難處理</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>實做通常搭配：
<ul>
<li>
<p>Write-through + Non-write-allocate</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%207.png"></p>
<ul>
<li>Write-through 和 Non-write-allocate 都會將資料寫至下一級的 cache 或 memory</li>
<li>如果是搭配 Write-allocate，需要先從下一級的 cache 讀出對應的 cache line 並寫回 D-cache 中，但 Write-through 又會將資料寫至下一級的 cache 或 memory，前面從下一級 cache 的讀取就是多餘的</li>
</ul>
</li>
<li>
<p>Write-back + Write-allocate</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%208.png"></p>
<ul>
<li>Write-back 搭配 Write-allocate 只須將 cache line 標記為 dirty 就好，不用再同步回L2/L3 Cache, Memory</li>
<li>如果是搭配 Non-write-allocate，資料是直接寫至下一級的 cache 或 memory，不會寫進 D-Cache 的，因此下一次存取的時候 D-Cache 還是會 miss</li>
<li>Write-back + Write-allocate 相較於 Write-through + Non-write-allocate：
<ul>
<li>優點：
<ul>
<li>同步至下一級的 cache 或 memory 的次數較少，效能較好</li>
</ul>
</li>
<li>缺點：
<ul>
<li>設計較為複雜</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="213---cache-的替換策略">2.1.3 - Cache 的替換策略</h2>
<ul>
<li>LSU (Least Recently Used)，近期最少使用法：
<ul>
<li>選擇最近被使用次數最少的 cache line 來替換</li>
<li>每個 cache line 都有個 age 的欄位紀錄被使用的次數，每被使用一次，age 就會 + 1</li>
<li>age 最小的 cache line，就是最近被使用次數最少的，也就是會被替換的 cache line</li>
<li>Directed-mapped cache 不須使用 LSU，因為會被替換的 cache line 都是固定的</li>
<li>Set-associative cache 可以針對 way 來紀錄 age
<ul>
<li>例如：2-way set-associative cache 每個 way 的每條 cache line 可以各自用 1 個 bit 的 age 來紀錄 way 的 cache line 是 LSU
<ul>
<li>當 way 0 被使用時，將 way 0 的 age 設成 1，way 1 的 age 設成 0，代表最近 way 0 有被使用</li>
<li>反之，當 way 1 被使用時，將 way 1 的 age 設成 1，way 0 的 age 設成 0，代表最近 way 1 有被使用</li>
<li>如此，只要找到 age 是 0 的那個 way，就可以將該 way 的 cache line 給取代了</li>
</ul>
</li>
<li>然而，當 ways 數增加時，精準的 LSU 實做會非常昂貴，因此實務上都是使用 pseudo-LSU 的方法來實現：
<ul>
<li>
<p>將所有的 way 分組並分級，每一組 ways 使用 1 個 bit 的 age：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%209.png"></p>
<ul>
<li>共需要 <strong>1 + 2 + 4 = 7 個 bits</strong></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Random Replacement，隨機替換法：
<ul>
<li>不再需要紀錄每個 way 的 age，而是隨機挑一個 way 來替換</li>
<li>優點：
<ul>
<li>設計較 LSU 來得簡單</li>
</ul>
</li>
<li>缺點：
<ul>
<li>相較於 LSU，cache miss rate 會比較高，但隨著 cache 的容量增大，這個差距會越來越小</li>
</ul>
</li>
<li>實務上很難實做出嚴格的隨機，一般是採用時鐘算法 (clock algorithm) 來實現近似的隨機：
<ul>
<li>本質上為一個 counter，每個 cycle 都會 + 1，counter 的 bits 由 ways 數決定 (如 8 ways 就需要 3 個 bits)，每次 cache line 要被替換的時候，就根據當下 counter 的值決定是哪個 way 的 cache line 要被替換</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="22-提高-cache-的性能">2.2. 提高 Cache 的性能</h1>
<h2 id="221--write-buffer">2.2.1  Write Buffer</h2>
<ul>
<li>
<p>當 D-Cache miss 時，需要從下一級的 cache 或 memory 讀取資料回 D-Cache 並寫入 cache line；如果 D-Cache 的 cache line 是 dirty 的，那還需要先將 dirty cache line 寫至下一級的 cache 或 memory，才能再從下一級的 cache 或 memory 讀取資料回 D-Cache</p>
</li>
<li>
<p>由於訪問下一級的 cache 或 memory 的 latency 很長，會影響 cache miss 的處理時間，因此可以引入 write buffer 來解決此問題：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2010.png"></p>
</li>
<li>
<p>Dirty cache line 會先被寫進 write buffer，等到下一級的 cache 或 memory 有空閒的時候，才會將 dirty cache line 寫入</p>
</li>
<li>
<p>Dirty cache line 寫入的時間會因此被隱藏，D-Cache 在將 dirty cache line 寫進 write buffer 後，就可以開始從下一級的 cache 或 memory 讀取資料回 D-Cache</p>
<ul>
<li>對於 write-through 類型的 D-Cache，write buffer 是非常必要的元件</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>會增加系統的設計複雜度：
<ul>
<li>當發生 cache miss 的時候，不只要查詢下一級的 cache 或 memory，同時間也要查詢 write buffer
<ul>
<li>因此 write buffer 通常也是使用 CAM 來實做</li>
<li>當 write buffer 中有 cache miss 所需的數據時，會優先採用 write buffer 的數據</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="222-流水線">2.2.2 流水線</h2>
<ul>
<li>讀 D-Cache：
<ul>
<li>Tag SRAM 和 Data SRAM 可以同時讀取，因此可以在一個 cycle 內完成</li>
</ul>
</li>
<li>寫 D-Cache：
<ul>
<li>讀 Tag SRAM 和 寫 Data SRAM 只能依序完成，因為必須先讀取 tag 並比較，確認要寫的位址在 cache 中後才能將數據寫進 Data SRAM</li>
<li>這些操作很難在一個 cycle 內完成，因此需要對寫 D-Cache 的操作採用 pipeline 的架構</li>
</ul>
</li>
<li>比較經典的設計方式是：
<ul>
<li>
<p>將 tag 的讀取和比較放在同一個 cycle</p>
</li>
<li>
<p>寫 D-Cache 放在下一個 cycle</p>
</li>
<li>
<p>對一個 store 指令而言，就算 cache hit，至少也需要 2 個 cycles 才能完成；但如果是連續的 store 指令，那麼還是可以透過 pipeline，每個 cycle 執行一道 store 指令 (假設沒有 hazard)</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2011.png"></p>
<ul>
<li>Load 指令在 D-Cache hit 的情況下，只需要 1 個 cycle 就可以完成</li>
<li>Store 指令在 D-Cache hit 的情況下，需要 2 個 cycles (讀 tag 並比較 + 寫 data) 才可以完成</li>
<li>當 load 指令執行時，有可能其所需的數據存在 store 指令的 register (Delayed Store Data) 中，而不是來自 Data SRAM，因此需要將 load 指令的位址，和 store 指令的位址 (Delayed Store Addr) 做比較；如果相等，那麼就可以直接將 store 指令的 register (Delayted Store Data) 內容，直接 forward 給 load 指令
<ul>
<li>
<p>E.g.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-2">2</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#a6e22e">sw</span> <span style="color:#66d9ef">a0</span>, <span style="color:#ae81ff">0</span>(<span style="color:#66d9ef">a1</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">lw</span> <span style="color:#66d9ef">a2</span>, <span style="color:#ae81ff">0</span>(<span style="color:#66d9ef">a1</span>)  <span style="color:#75715e"># Store 指令的 $a0 可以直接 forward 給 load 指令的 $a2
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="223---多級結構">2.2.3 - 多級結構</h2>
<ul>
<li>
<p>一般處理器設計中：</p>
<ul>
<li>L1 Cache 可以採用 <strong>Write-through</strong> 或 <strong>Write-back</strong> 的設計
<ul>
<li><strong>Write-through</strong> 可以簡化 pipeline 的設計，尤其是針對 multi-core，需要處理 coherence 的問題，Write-through 的設計會比較簡單一點</li>
</ul>
</li>
<li>(Shared) L2 Cache 會採用 <strong>Write-back</strong> 的設計</li>
</ul>
</li>
<li>
<p>Inclusive cache vs. Exclusive cache：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2012.png"></p>
<ul>
<li>Inclusive cache：L2 Cache 包含了所有 L1 Cache 的內容
<ul>
<li>優點：
<ul>
<li>可以將數據直接寫進 L1 Cache，因為被 evicted 的 cache line 在 L2 Cache 中也一定存在；直接將數據寫進 L1 Cache 不會引起任何的問題 (假設被 evicted 的 cache line 並不是 <code>dirty</code> 的)</li>
<li>簡化了 cache coherence 的管理
<ul>
<li>在 multi-core 的平台，當其中一個 CPU 更新了其 private cache (e.g. L1 D-Cache) 某個位址的數據 (e.g. 執行 store 指令)，如果其他 CPU 也有相同位址的數據，那就必須將其設為 invalid，以免其他 CPU 存取到舊的值</li>
<li>如果是採用 inclusive cache，那麼只需檢查 last-level (shared) cache (e.g. L2 Cache) 中，是否有該位址的數據，如果 last-level cache 中沒有，那就代表 private cache 中也一定沒有，如此就不用打擾其他 CPU 的 private cache，影響 pipeline 的執行</li>
</ul>
</li>
</ul>
</li>
<li>缺點：
<ul>
<li>浪費了 cache 的空間，實做成本比較高</li>
</ul>
</li>
</ul>
</li>
<li>Exclusive cache：L2 Cache 和 L1 Cache 的內容完全不相同 (i.e. 互斥)
<ul>
<li>優點：
<ul>
<li>避免了 cache 空間的浪費，實做成本比較低</li>
</ul>
</li>
<li>缺點：
<ul>
<li>如果是採用 exclusive cache，那麼在 multi-core 的平台，當某一個 CPU 更新了其 private cache (e.g. L1 D-Cache) 某個位址的數據  (e.g. 執行 store 指令)，那麼每個 CPU 都必須檢查其每個 cache 是否也有包含其位址的數據，如果有的話，就必須將其設為 invalid，也因此會影響 pipeline 的執行</li>
<li>同時，當要讀取的數據不在 L1 Cache，而是在 L2 Cache 時，除了將 L2 Cache 的數據拉回 L1 Cache 外，還需要將 L1 Cache 中被 evicted 的值，寫進 L2 Cache，這種交換過程會降低 CPU 的執行效率</li>
</ul>
</li>
</ul>
</li>
<li>目前大部分的處理器，都還是採用 Inclusive cache 的設計</li>
</ul>
</li>
</ul>
<h2 id="224---victim-cache">2.2.4 - Victim Cache</h2>
<ul>
<li>
<p>有時候，被 evicted 的 cache line 有可能又會馬上被使用，例如：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2013.png"></p>
<ul>
<li>A、B、C 都被分配在同一個 cache set，如果同時間都被頻繁使用 (e.g. A -&gt; C -&gt; B)，那就有可能被 evicted 後，又會馬上被讀回 cache，然後又被 evicted，而且會一直 cache miss</li>
<li>而且 cache 的 ways 數有上限，沒辦法無上限的透過增加 ways 數來解決這個問題；其他的 cache sets 也未必會有同樣的狀況，為此增加 ways 數可能只是浪費空間</li>
</ul>
</li>
<li>
<p>可以引入 Victim cache 來解決這樣的問題，Victim cache 是用來保存最近被 evicted 的 cache lines，因此所有的 cache sets 都可以透過 Victim cache 來”增加 ways 數”</p>
</li>
<li>
<p>Victim cache 通常採用 <code>fully-associative</code> 設計，容量也會比較小 (一般可以存 4 ~ 16 條 cache lines)</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2014.png"></p>
</li>
<li>
<p>一般情況下 Victim cache 的數據跟 L1 D-Cache 的數據是互斥的，CPU 可以同時讀取它們，只要有其中一個 hit，就可以直接使用</p>
</li>
<li>
<p>如果 L1 D-Cache 中沒有找到數據，但有在 Victim cache 中找到，那麼該數據會被讀進 L1 D-Cache，同時間 L1 D-Cache 中被 evicted 的數據，會被寫進 Victim cache，也就是互換了兩邊的數據 (因為 L1 D-Cache 和 Victim cache 是互斥的)</p>
</li>
<li>
<p>Victim cache 可以降低 cache miss rate</p>
</li>
<li>
<p>現代大多數處理器都有採用 Victim cache</p>
</li>
<li>
<p>Filter cache 則是另外一種類似 Victim cache 的設計，它會被放置在 L1 D-Cache 前：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2015.png"></p>
</li>
<li>
<p>當一個數據第一次被使用時，它不會被馬上放進 cache，而是會先放到 Filter cache；只有等到這個數據再次被使用時，才會真的搬進 cache</p>
</li>
<li>
<p>使用 Filter cache 可以防止那些只有偶爾才會被使用的數據佔據 cache 的空間，進而提高 cache 的使用效率</p>
</li>
</ul>
<h2 id="225---預取-prefetch">2.2.5 - 預取 (Prefetch)</h2>
<ul>
<li>硬體預取：
<ul>
<li>指令的 prefetch 是相對容易的，只要在取一個指令進 I-Cache 時，同時間也取其後的幾條指令進 I-Cache 即可</li>
<li>因為有分支指令，所以有可能會 prefetch 錯指令，浪費 I-Cache 的空間
<ul>
<li>
<p>可以將 prefetch 的指令，放到一個單獨的 buffer 中，來避免這個問題</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2016.png"></p>
<ul>
<li>如果 fetch 指令時 I-Cache miss，但有在 Stream Buffer 中找到該指令，那麼 CPU 可以直接從 Stream Buffer 中讀取指令，且該指令會被搬進 I-Cache，Stream Buffer 也會繼續從 L2-Cache prefetch 後面的指令，並搬進 Stream Buffer 中</li>
</ul>
</li>
</ul>
</li>
<li>數據的 prefetch 就相對困難許多，因為數據的使用是沒有一定規律的</li>
<li>一般情況下，當 D-Cache miss 時，除了將數據從 L2 cache 中讀出來外，也會同時 prefetch 下一個數據
<ul>
<li>然後，這種方法並不準確，因為有可能接下來要使用的數據，並不是 prefetch 的數據，prefetch 只是做白工</li>
<li>這種方法目前被廣泛得使用在現代處理器中</li>
</ul>
</li>
<li>Intel Pentium 4 和 IBM Power 5 的處理器中，採用了 Strided Prefetch 的機制
<ul>
<li>Prefetcher 可以自行觀察程式中數據使用的規律，例如第一個數據位於位址 <code>a</code>，第二個數據位於位址 <code>a + 128</code>，第三個數據位於 <code>a + 256</code>，那麼 Prefetcher 就可以預測接下來的數據在 <code>a + 384</code>、<code>a + 512</code>、<code>a + 640</code>… etc</li>
</ul>
</li>
<li>Prefetcher 是否實用，還是得根據實際跑的程式而定
<ul>
<li>Prefetch 錯，就只是增加功耗而已</li>
</ul>
</li>
</ul>
</li>
<li>軟體預取：
<ul>
<li>
<p>如果有支援 prefetch 指令，則軟體可以利用 prefetch 指令來提前將指令 prefetch 進 cache，e.g.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-5">5</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> (i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; i <span style="color:#f92672">&lt;</span> N; i<span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">prefetch</span>(<span style="color:#f92672">&amp;</span>a[i <span style="color:#f92672">+</span> P)]);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">prefetch</span>(<span style="color:#f92672">&amp;</span>b[i <span style="color:#f92672">+</span> P)]);
</span></span><span style="display:flex;"><span>    sum <span style="color:#f92672">=</span> sum <span style="color:#f92672">+</span> a[i] <span style="color:#f92672">+</span> b[i];
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>不過 prefetch 指令有可能會引起 exception，此時有兩種處理方法：
<ul>
<li>處理 exception</li>
<li>不處理 exception，直接將此 prefetch 指令轉為 NOP
<ul>
<li>現代處理器大多採用此方式</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="23---多端口-cache">2.3 - 多端口 Cache</h1>
<ul>
<li>為了提昇效能，處理器必須要能夠在 1 個 cycle 內同時執行多條 load/store 指令，這需要一個 multi-port 的 D-Cache，以便能夠支持多條 load/store 指令的同時訪問</li>
<li>不只 D-Cache，在 superscalar 的架構，還有很多元件都是 multi-port 的，e.g. register file、issue queue、ROB… etc</li>
<li>D-Cache 由於原本的容量就很大，因此採取 multi-port 的設計會對晶片的面積和速度帶來負面的影響，因此需要再採用其他的方法來實現 multi-port D-Cache：
<ul>
<li>True Multi-port</li>
<li>Multiple Cache copies</li>
<li>Multi-banking</li>
</ul>
</li>
</ul>
<h2 id="231---true-multi-port">2.3.1 - True Multi-port</h2>
<ul>
<li>實務上，很難對 D-Cache 採用 multi-port 的設計，因為所有在 Cache 中的控制電路和 data path都需要進行複製</li>
<li>如果要讓 D-Cache 採用 multi-port 的設計，就代表需要：
<ul>
<li>兩套的 Address Decoder，使兩個 ports 可以同時存取 Tag SRAM 和 Data SRAM</li>
<li>兩套的 Way Mux，用來讀取兩個 ports 的數據</li>
<li>兩套的 Tag Comparators，用來判斷兩個 ports 的命中情況</li>
<li>兩套的 Aligners，用來做 word 或 half-word 的讀取</li>
</ul>
</li>
<li>Tag SRAM 和 Data SRAM 並不需要複製一份，但它們當中的每個 cells 都需要同時支持兩個並行的讀取操作 (對於一個 SRAM cell 來說，不需要兩個 write ports，因為無法同時對 SRAM cell 同時寫 0 又寫 1)</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2017.png"></p>
<ul>
<li>由於 multi-port D-Cache 的設計需要將很多電路都複製一份，增大了面積；同時 multi-port SRAM cell 需要驅動多個 read ports，功耗也會隨之增大；因此實務上並不會採用 multi-port 的設計</li>
</ul>
<h2 id="232---multiple-cache-copies">2.3.2 - Multiple Cache Copies</h2>
<ul>
<li>
<p>這種設計方法直接將 Tag SRAM 和 Data SRAM 複製一份：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2018.png"></p>
</li>
<li>
<p>透過將 Tag SRAM 和 Data SRAM 複製一份，SRAM 就不需要採用 multi-port 的設計，雖然可以消除 multi-ports 對處理器效能的影響，但是這種方法消耗了很多的面積，而且需要保持兩邊 caches 的同步：</p>
<ul>
<li>執行 store 指令時，需要同步到兩邊的 caches</li>
<li>其中一個 cache 發生 replacement 的時候也需要對另一個 cache 做同樣的 replacement</li>
</ul>
</li>
<li>
<p>也因此，實務上並不會採用這樣的設計</p>
</li>
</ul>
<h2 id="233---multi-banking">2.3.3 - Multi-banking</h2>
<ul>
<li>Multi-banking 將 cache 分成很多很小的 banks，每個 bank 都只有一個 port
<ul>
<li>
<p>如果在一個 cycle 內，對 cache 的訪問位址是位於不同的 banks 之中，那麼就不會有任何的問題</p>
</li>
<li>
<p>只有當兩個甚至多個對 cache 的訪問位址是位於同一個 bank 之中，才會引起衝突，也就是 bank conflict</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2019.png"></p>
<ul>
<li>Cache line 被分成了兩個 banks</li>
<li>在同一個 cycle 內，如果兩個訪問位址落在不同的 banks 之中，那這兩個訪問就可以同時存取 cache (e.g. Port 0 訪問 Cache bank 1、Port 1 訪問 Cache bank 0)</li>
</ul>
</li>
</ul>
</li>
<li>使用 Multi-banking 仍然需要：
<ul>
<li>兩套的 Address Decoder，使兩個 ports 可以同時存取 Tag SRAM 和 Data SRAM</li>
<li>兩套的 Way Mux，用來讀取兩個 ports 的數據</li>
<li>兩套的 Tag Comparators，用來判斷兩個 ports 的命中情況</li>
<li>兩套的 Aligners，用來做 word 或 half-word 的讀取</li>
</ul>
</li>
<li>但使用 Multi-banking，Data SRAM 便不需要採用 multi-port 的設計，可以提高存取速度，並降低面積</li>
<li>然而由於需要判斷 cache 的每個 port 是否 hit，因此對於 Tag SRAM 來說，仍然需要採用 multi-port 的設計，以便提供多個 ports 同時讀取的功能，或是直接將 single-port 的 Tag SRAM 複製一份</li>
<li>Bank conflicts 是 Multi-banking 的關鍵性能影響因素，解決 bank conflicts 的方法有：
<ul>
<li>使用更多的 banks</li>
<li>提高 banks 的使用效率，使數據不會集中在同一個 bank
<ul>
<li>通常需要 compiler 的配合才能實現</li>
</ul>
</li>
</ul>
</li>
<li>使用 Multi-banking 來實現 multi-port 的功能可以使 cache 的總面積降低，而且不會對處理器的 cycle time 有太大的影響，因此現代處理器通常都使用 Multi-banking 的設計</li>
</ul>
<h2 id="234----真實的例子amd-opteron-的-multi-port-cache">2.3.4  - 真實的例子：AMD Opteron 的 multi-port cache</h2>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2020.png"></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2021.png"></p>
<ul>
<li>Data SRAM 共 8 個 banks，所以使用了 <code>VA[5:3]</code> 來選取 bank
<ul>
<li>每個 bank 都是 single-port 的 SRAM</li>
</ul>
</li>
<li>採用將 Tag SRAM 複製的方式，每個 Tag SRAM 都是 single-port 的</li>
<li>共需要：
<ul>
<li>兩個 TLB</li>
<li>兩個 Tag Comparators</li>
<li>兩個 single-port 的 Tag SRAM
<ul>
<li>亦可以使用 multi-port 的 Tag SRAM，但面積可能不會減少多少，而且速度還有可能會變慢</li>
</ul>
</li>
<li>除了 Data SRAM 沒有被複製外，基本上其他的電路都被複製了一份</li>
</ul>
</li>
</ul>
<h1 id="24---超標量處理器的取指令">2.4 - 超標量處理器的取指令</h1>
<ul>
<li>
<p>針對 n-ways 的 superscalar CPU，每個 cycle I-Cache 都應該至少能送出 n 條的指令，這 n 條的指令為一組 <code>fetch group</code></p>
</li>
<li>
<p>最簡單的方法就是 I-Cache 每個 data block 為 n-words，每個 cycle 都把整條 cache line 給讀出：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2022.png"></p>
</li>
<li>
<p>如果指令的位址是 n-word aligned 的，那就可以每個 cycle 都從 I-Cache 讀出 n 條指令</p>
<ul>
<li>實際上由於有 branch 指令、exception 等情況，所以指令位址不可能永遠都是 n-word aligned 的</li>
</ul>
</li>
<li>
<p>如果指令的位址不是 n-word aligned 的，那就代表該 fetch group 有可能會落在兩條不同的 cache lines 上，這種情況會導致後續的 pipeline 沒辦法得到充足的指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2023.png"></p>
</li>
<li>
<p>透過 Instruction Buffer，我們可以一次讀取超過 n words 條的指令，並將其存至 Instruction Buffer 中，後續的 decoder 可以從 Instruction Buffer 中讀取指令，這樣就算 I-Cache 沒辦法在一個 cycle 內提供足夠的 n words 條的指令，也可以保證後續的 pipeline stages 可以獲得充足的指令</p>
</li>
<li>
<p>實際上，只有第一個 cycle 沒辦法讀取 n words 條的指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2024.png"></p>
<ul>
<li>在 cycle 2 後，只要後續沒有 branch 指令或 exception，還是可以一次讀出 n words 條的指令</li>
</ul>
</li>
<li>
<p>此外，也可以將 cache lines 長度變長，超過 n words：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2025.png"></p>
<ul>
<li>N = 4，cache line = 8 words，只有在指令的位址落在 cache line 最後 3 個 blocks 的情況，才沒辦法一次讀出 4 條指令
<ul>
<li>缺點：
<ul>
<li>如果 cache size 是固定的，增加 cache line 的 size，會減少 cache sets 的個數，增加 cache miss rate</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>延續上述的範例，實務上也不會使用 8 個 32-bit 的 SRAM 來實做 8 words 的 cache line：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2026.png"></p>
<ul>
<li>
<p>因為 SRAM 周圍也需要放保護電路，如果 SRAM 的個數過多，會導致保護電路也佔用過多的面積</p>
</li>
<li>
<p>要從 8 個 SRAM 選出要輸出的 4 words 也是一件很浪費電路的事情</p>
</li>
<li>
<p>因此，實務上，還是會使用 4 個 SRAM 來實現 8 words 的 cache line</p>
<ul>
<li>
<p>當 tag hit 時，每個 SRAM 的兩行數據都是有效的，可以透過以下的方式重新排序，並選出正確 4 個 words 的指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2027.png"></p>
</li>
</ul>
</li>
<li>
<p>同樣的，如果指令的位址落在 cache line 最後 3 個 blocks，是沒辦法在一個 cycle 內讀出 4 條指令的，因此需要重新排序電路還需要加入指示哪些 words 是 valid 的 signal，才能夠將 valid 的指令寫入 Instruction Buffer 中</p>
</li>
</ul>
</li>
<li>
<p>如果有 branch predictor，I-Cache 在取指令的時候便可得知 fetch group 中哪條指令是 branch 指令；如果預測是 branch taken，那麼 branch 指令後面的指令就不會被讀進 Instruction Buffer 和 pipeline 了</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2028.png"></p>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>&lt;超標量處理器概覽&gt; 第 1 章 - 超標量處理器概覽</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch1/</link>
      <pubDate>Wed, 12 Feb 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch1/</guid>
      <description>&lt;h1 id=&#34;12---普通處理器的流水線&#34;&gt;1.2 - 普通處理器的流水線&lt;/h1&gt;
&lt;h2 id=&#34;122---流水線的劃分&#34;&gt;1.2.2 - 流水線的劃分&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Pipeline CPU，其 cycle time 由最長 cycle time 的 pipeline stage 所決定
&lt;ul&gt;
&lt;li&gt;因此，最好每個 pipeline stage 的 cycle time 都是差不多長的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;解決各個 pipeline stage cycle time 不平衡的方法：
&lt;ul&gt;
&lt;li&gt;合：
&lt;ul&gt;
&lt;li&gt;將多個 pipeline stages 合併成一個 stage，例如：
&lt;ul&gt;
&lt;li&gt;Fetch (7 ns) &amp;amp; Decode (3 ns) | Operand fetch (8 ns) &amp;amp; Execute (5 ns) | Memory (10 ns) &amp;amp; Write back (3 ns)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;此方法將 pipeline stages 從 5 個降為 3 個，原本各個 pipeline stage cycle time 不平衡的情況也變成：10 ns | 13 ns | 13 ns&lt;/li&gt;
&lt;li&gt;適用於對於性能要求不高的 CPU，例如：ARM7、ARM9、Cortex-M0、Cortex-M3
&lt;ul&gt;
&lt;li&gt;因為 pipeline stage 的 cycle time 增加了，從原本的 &lt;code&gt;max(7 ns, 3 ns, 8 ns, 5 ns, 10 ns, 3 ns) ⇒ 10 ns&lt;/code&gt;，增加為 &lt;code&gt;max(10 ns, 13 ns, 13 ns) ⇒ 13 ns&lt;/code&gt;，cycle time 增加，就代表 CPU 的 frequency 會降低&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;拆：
&lt;ul&gt;
&lt;li&gt;將 pipeline stage 拆成更小的 stages，例如：
&lt;ul&gt;
&lt;li&gt;Fetch (7 ns)  ⇒ Fetch1 (3.5 ns) &amp;amp; Fetch2 (3.5 ns)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;適用於高性能 CPU，因為可以提昇 CPU 的 frequency&lt;/li&gt;
&lt;li&gt;缺點：
&lt;ul&gt;
&lt;li&gt;增加所需的硬體元件，例如：需要多個 pipeline registers&lt;/li&gt;
&lt;li&gt;功耗會增大&lt;/li&gt;
&lt;li&gt;較深的 pipeline 也會增加 branch misprediction 的 penalty&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;123---指令間的相依性&#34;&gt;1.2.3 - 指令間的相依性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Instructions hazard：&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="12---普通處理器的流水線">1.2 - 普通處理器的流水線</h1>
<h2 id="122---流水線的劃分">1.2.2 - 流水線的劃分</h2>
<ul>
<li>Pipeline CPU，其 cycle time 由最長 cycle time 的 pipeline stage 所決定
<ul>
<li>因此，最好每個 pipeline stage 的 cycle time 都是差不多長的</li>
</ul>
</li>
<li>解決各個 pipeline stage cycle time 不平衡的方法：
<ul>
<li>合：
<ul>
<li>將多個 pipeline stages 合併成一個 stage，例如：
<ul>
<li>Fetch (7 ns) &amp; Decode (3 ns) | Operand fetch (8 ns) &amp; Execute (5 ns) | Memory (10 ns) &amp; Write back (3 ns)</li>
</ul>
</li>
<li>此方法將 pipeline stages 從 5 個降為 3 個，原本各個 pipeline stage cycle time 不平衡的情況也變成：10 ns | 13 ns | 13 ns</li>
<li>適用於對於性能要求不高的 CPU，例如：ARM7、ARM9、Cortex-M0、Cortex-M3
<ul>
<li>因為 pipeline stage 的 cycle time 增加了，從原本的 <code>max(7 ns, 3 ns, 8 ns, 5 ns, 10 ns, 3 ns) ⇒ 10 ns</code>，增加為 <code>max(10 ns, 13 ns, 13 ns) ⇒ 13 ns</code>，cycle time 增加，就代表 CPU 的 frequency 會降低</li>
</ul>
</li>
</ul>
</li>
<li>拆：
<ul>
<li>將 pipeline stage 拆成更小的 stages，例如：
<ul>
<li>Fetch (7 ns)  ⇒ Fetch1 (3.5 ns) &amp; Fetch2 (3.5 ns)</li>
</ul>
</li>
<li>適用於高性能 CPU，因為可以提昇 CPU 的 frequency</li>
<li>缺點：
<ul>
<li>增加所需的硬體元件，例如：需要多個 pipeline registers</li>
<li>功耗會增大</li>
<li>較深的 pipeline 也會增加 branch misprediction 的 penalty</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="123---指令間的相依性">1.2.3 - 指令間的相依性</h2>
<ul>
<li>
<p>Instructions hazard：</p>
<ul>
<li>RAW (Read After Write)，為 true hazard，無法避免；可以透過 forwarding 的方式來緩解</li>
<li>WAR (Write After Read)，可以透過 register renaming 避免</li>
<li>WAW (Write After Write)，可以透過 register renaming 避免</li>
</ul>
</li>
<li>
<p>Control hazard：</p>
<ul>
<li>需要等到 branch target address，或是是否會 branch 的結果計算出來後，才知道要去哪裡 fetch 下一道指令來執行</li>
<li>在結果出來之前，只能依據 branch predict 的結果 fetch 指令</li>
</ul>
</li>
<li>
<p>Load/store address dependency：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-2">2</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#a6e22e">sw</span> <span style="color:#66d9ef">r1</span>, <span style="color:#ae81ff">0</span>(<span style="color:#66d9ef">r5</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ld</span> <span style="color:#66d9ef">r2</span>, <span style="color:#ae81ff">0</span>(<span style="color:#66d9ef">r6</span>)
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>如果上述的 <code>r5</code>, <code>r6</code> 的值相同，那麼就會產生 RAW dependency</li>
</ul>
</li>
</ul>
<h1 id="13---superscalar-處理器的流水線">1.3 - Superscalar 處理器的流水線</h1>
<ul>
<li>In-Order vs. Out-of-Order：
<table>
	<thead>
			<tr>
					<th></th>
					<th>Frontend</th>
					<th>Issue</th>
					<th>Write back</th>
					<th>Commit</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>In-Order Superscalar</td>
					<td><strong>In-Order</strong></td>
					<td><strong>In-Order</strong></td>
					<td><strong>In-Order</strong></td>
					<td><strong>In-Order</strong></td>
			</tr>
			<tr>
					<td>Out-of-order Superscalar</td>
					<td><strong>In-Order</strong></td>
					<td><strong>Out-of-Order</strong></td>
					<td><strong>Out-of-Order</strong></td>
					<td><strong>In-Order</strong></td>
			</tr>
	</tbody>
</table>
<ul>
<li>Frontend ⇒ Fetch + Decode，很難 Out-of-Order</li>
<li>Issue，只要 operands 有準備好且有多個 FU (Function Unit) 可用，就可以 Out-of-Order</li>
<li>Write back，可以透過 register renaming 實現 Out-of-Order</li>
<li>Commit，一定要 In-Order</li>
</ul>
</li>
</ul>
<h2 id="131---in-order">1.3.1 - In-Order</h2>
<ul>
<li>
<p>Scoreboard：</p>
<ul>
<li>
<p>用來紀錄每個指令的執行狀況，例如：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch1/image.png"></p>
</li>
</ul>
</li>
<li>
<p>In-Order pipeline (Dual Issue Superscalar)：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch1/image%201.png"></p>
<ul>
<li>
<p>因為 Write back stage 是 In-Order 的，因此所有的 FU 都必須擁有相同數量的 pipeline stages，會以最長的那個為主 (X0, X1, X2)</p>
</li>
<li>
<p>Pipeline 範例：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch1/image%202.png"></p>
<ul>
<li>指令 A depends on 指令 C, 指令 C depends on 指令 E</li>
<li>指令 B depends on 指令 D 和指令 E</li>
<li>因為要等到 Write back stage 才能 forwarding 結果，因此：
<ul>
<li>指令 C 和指令 D 必須等到 cycle 6 才能進到 Execute stage</li>
<li>指令 E 要等到 cycle 9 才能進到 Execute stage</li>
</ul>
</li>
<li>且因為是 In-Order pipeline，因此縱使指令 F 沒有任何的 dependency，還是得等到 cycle 8 才能被 issued</li>
<li>共需要 <strong>12 個 cycles</strong> 才可以完成所有的指令</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="132---out-of-order">1.3.2 - Out-of-Order</h2>
<ul>
<li>
<p>Out-of-Order pipeline (Dual Issue Superscalar)：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch1/image%203.png"></p>
<ul>
<li>
<p>在 Decode stage 可以做 register renaming 來解決 WAR 和 WAW hazards</p>
<ul>
<li>將 ARF (architecture register file)，rename 成 PRF (physical register file)</li>
</ul>
</li>
<li>
<p>直到 Issue stage 才可以 Out-of-Order Issue</p>
<ul>
<li>指令會先存在 Issue queue，一旦指令的 operands 準備好了，就可以被 issued 到 FU</li>
</ul>
</li>
<li>
<p>由於 Write back stage 是 Out-of-Order 的，因此 FU 不需擁有相同數量的 pipeline stages</p>
<ul>
<li>一旦指令計算完畢，便可以將結果寫回 PRF</li>
</ul>
</li>
<li>
<p>不過由於 branch misprediction 及 exception，PRF 的結果不一定會被寫回 ARF 中</p>
</li>
<li>
<p>為了保持指令的 commit 順序是 In-Order 的，所有的指令都會被存進 ROB (Re-Order Buffer) 中</p>
</li>
<li>
<p>Commit stage 會將指令依據 ROB 中的排列順序，一一 commit 並將結果從 PRF 寫回 ARF 中，一定 commit 成功 (離開 commit stage)，該指令便被 retired 了</p>
</li>
<li>
<p>由於 store 指令會更新 register，如果在 write back stage 就先將 store 指令的結果寫進 register，一旦發生 branch misprediction 或是 exception，就必須將寫入的結果給還原</p>
<ul>
<li>為了解決這問題，store 指令在 write back stage 會先將結果寫進 SB (store buffer)，來紀錄尚未 retire 的 store 指令結果</li>
<li>只有當 store 指令真的 retire 的時候，才會將 store 的結果更新至 register 中</li>
</ul>
</li>
<li>
<p>Pipeline 範例：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch1/image%204.png"></p>
<ul>
<li>只需要 <strong>9 個 cycles</strong> 就可以完成所有的指令</li>
</ul>
</li>
<li>
<p>Register renaming stage 需要比較長的執行時間，因此通常都會單獨使用一個 pipeline stage，而不是跟 Decode stage 放在一起</p>
</li>
<li>
<p>Dispatch stage 負責將 renaming 後的指令依照 program order 放入 Issue queue、ROB 和 SB</p>
<ul>
<li>如果 Issue queue、ROB、SB 沒有多餘的空間存放該指令，那麼就必須 stall pipeline</li>
<li>Dispatch stage 可以跟 Register name stage 一起放在同一個 stage；如果對 CPU frequency 有要求，也可以單獨使用一個 pipeline stage</li>
</ul>
</li>
<li>
<p>Issue stage 負責將 dispatched 的指令依據指令需求，issue 到對應的 Issue queue 中；Select （仲裁) 電路會再從 Issue queue 中找出合適的指令，issue 到對應的 FU 中來執行</p>
<ul>
<li>Issue stage 是 Out-of-Order CPU，從 In-Order 轉回 Out-of-Order 的分界點</li>
</ul>
</li>
<li>
<p>被 Select 電路選中的指令會需要讀取 PRF，或是透過 forwarding 獲取所需的 operands，這部份就是在 Register file read stage 中執行</p>
<ul>
<li>由於 superscalar CPU 一個 cycle 內會執行多個指令，會需要使用 multi-port register file；而由於 multi-port register file 的存取速度通常不快，Register file read stage 需要比較長的執行時間，因此通常都會單獨使用一個 pipeline stage</li>
</ul>
</li>
<li>
<p>Write back stage 會將 FU 的運算結果，更新回 PRF；同時間也會將運算結果 forwarding 給 FU 的輸入端，讓其他的指令可以使用</p>
<ul>
<li>Forwarding 的電路延遲時間會嚴重影響 CPU 的 cycle time，因此現代 CPU 使用了 cluster 架構，將 FU 分成不同的組
<ul>
<li>同一個 cluster 內的 forwarding 通常可以在一個 cycle 內完成</li>
<li>但跨 clusters 的 forwarding 就需要兩個以上的 cycles 才能完成了</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Commit stage 會依據 ROB 中的 order 來 retire 指令</p>
<ul>
<li>Commit stage 也會負責處理 exception
<ul>
<li>指令在很多的 pipeline stages 都有可能會發生 exception，但所有的 exceptions 都必須等到 Commit stage 的時候才能被處理，才能保證 exception handler 也是依照 program order 被執行的</li>
<li>指令在從 ROB 中離開後，就會被 retired 了</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在 Out-of-Order cores 中，狀態恢復電路也是非常重要的</p>
<ul>
<li>Branch misprediction 和發生 exception 的時候需要恢復正確的狀態</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
