<?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>Commit on 0xc0de</title>
    <link>https://0xc0de.xyz/tags/commit/</link>
    <description>Recent content in Commit 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/tags/commit/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>
  </channel>
</rss>
