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