<?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>Execute on 0xc0de</title>
    <link>https://0xc0de.xyz/tags/execute/</link>
    <description>Recent content in Execute 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/execute/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; 第 9 章 - 執行</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch9/</link>
      <pubDate>Mon, 28 Apr 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch9/</guid>
      <description>&lt;h1 id=&#34;91---概述&#34;&gt;9.1 - 概述&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;FU 的個數決定了每個 cycle 最大可以同時執行的指令個數，也就是 issue width&lt;/li&gt;
&lt;li&gt;Execute stage 另一個重要的部份就是 bypassing network，其負責將 FU 的運算結果送到其他需要它的地方，像是 physical register file (PRF)、其他 FU 的輸入端、store buffer 等
&lt;ul&gt;
&lt;li&gt;隨著每個 cycle 可以同時執行的指令個數的增多，bypassing network 變得越來越複雜，已經是處理器中速度提昇的一個關鍵部份&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在不使用 bypassing network 的情況下，指令的 operands 值可以來自於 PRF (for &lt;a href=&#34;../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture&#34;&gt;&lt;em&gt;non-data-capture&lt;/em&gt;&lt;/a&gt;)，或是 payload RAM (for &lt;a href=&#34;../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture&#34;&gt;&lt;em&gt;data-capture&lt;/em&gt;&lt;/a&gt;)，每個 FU 都是透過 select 電路將 PRF 或是 payload RAM 串連起來的
&lt;ul&gt;
&lt;li&gt;每個 select 電路和 PRF (或是 payload RAM) 的 read ports 是一一對應的，每個 FU 和 PRF (或是 payload RAM) 的 read ports 也是一一對應的&lt;/li&gt;
&lt;li&gt;因此，PRF 總共需要的 read ports 個數與 issue width 是直接相關的；如果對處理器追求更大的 issue width，就代表著 PRF 需要更多的 read ports，反而會限制了處理器的執行速度，現代處理器多會採用 cluster 架構來解決這個矛盾
&lt;ul&gt;
&lt;li&gt;Payload RAM 由於需要與 Issue Queue 綁定在一起，使用 &lt;a href=&#34;../superscalar-overview-ch8-part1/#811----%E9%9B%86%E4%B8%AD%E5%BC%8F-centralized-vs-%E5%88%86%E4%BD%88%E5%BC%8F-distributed&#34;&gt;distributed Issue Queue&lt;/a&gt; 可以減少 payload RAM read ports 的需求&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;92---fu-的類型&#34;&gt;9.2 - FU 的類型&lt;/h1&gt;
&lt;h2 id=&#34;921---alu&#34;&gt;9.2.1 - ALU&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ALU (Arithmetic and Logic Unit) 通常負責：整數加減法、shift、簡單的乘除法、資料搬移 (e.g. mov、byte-swap 指令)、分支指令目標位址的計算、記憶體存取位址的計算&lt;/li&gt;
&lt;li&gt;很多處理器在 ALU 中也實現了比較簡單的乘法功能，用來支持乘法指令
&lt;ul&gt;
&lt;li&gt;不過為了追求比較高的處理效能，高性能處理器通常選擇將乘法器單獨使用一個 FU 來實現，並在這個 FU 中實現乘累加的功能&lt;/li&gt;
&lt;li&gt;有些處理器基於功耗和成本的考量，會將整數類型的乘法交由浮點數運算的 FU 來運算
&lt;ul&gt;
&lt;li&gt;整數轉成浮點數 → 浮點數乘法 → 浮點數轉為整數&lt;/li&gt;
&lt;li&gt;指令的 latency 會變長，但較省面積和功耗，Intel Atom CPU 就採用了此設計&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;922---agu&#34;&gt;9.2.2 - AGU&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;AGU (Address Generate Unit) 用來計算記憶體存取位址，如 load/store 指令
&lt;ul&gt;
&lt;li&gt;在一般 pipeline 處理器中，記憶體的存取位址都是交由 ALU 來計算，但是在 superscalar CPU 中，由於需要同時執行多條的指令，記憶體存取指令的執行效率直接影響了處理器的性能，所以通常會單獨使用一個 FU 來計算其位址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;923---bru&#34;&gt;9.2.3 - BRU&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;BRU (Branch Unit) 負責處理 control flow 類型的指令，如 branch、jump、call 和 return 類型的指令，BRU 負責將這些指令所攜帶的目標位址計算出來，並根據一定的條件決定是否使用這些位址；同時，在這個 FU 中還會對分支預測與否正確進行檢查，一旦發現 mis-prediction，就需要啟動相對硬的恢復機制&lt;/li&gt;
&lt;li&gt;ARM 和 PowerPC 等處理器在指令中的編碼加上了 condition code，根據 condition code 來決定指令是否執行
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;優點：&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="91---概述">9.1 - 概述</h1>
<ul>
<li>FU 的個數決定了每個 cycle 最大可以同時執行的指令個數，也就是 issue width</li>
<li>Execute stage 另一個重要的部份就是 bypassing network，其負責將 FU 的運算結果送到其他需要它的地方，像是 physical register file (PRF)、其他 FU 的輸入端、store buffer 等
<ul>
<li>隨著每個 cycle 可以同時執行的指令個數的增多，bypassing network 變得越來越複雜，已經是處理器中速度提昇的一個關鍵部份</li>
</ul>
</li>
<li>在不使用 bypassing network 的情況下，指令的 operands 值可以來自於 PRF (for <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture"><em>non-data-capture</em></a>)，或是 payload RAM (for <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture"><em>data-capture</em></a>)，每個 FU 都是透過 select 電路將 PRF 或是 payload RAM 串連起來的
<ul>
<li>每個 select 電路和 PRF (或是 payload RAM) 的 read ports 是一一對應的，每個 FU 和 PRF (或是 payload RAM) 的 read ports 也是一一對應的</li>
<li>因此，PRF 總共需要的 read ports 個數與 issue width 是直接相關的；如果對處理器追求更大的 issue width，就代表著 PRF 需要更多的 read ports，反而會限制了處理器的執行速度，現代處理器多會採用 cluster 架構來解決這個矛盾
<ul>
<li>Payload RAM 由於需要與 Issue Queue 綁定在一起，使用 <a href="../superscalar-overview-ch8-part1/#811----%E9%9B%86%E4%B8%AD%E5%BC%8F-centralized-vs-%E5%88%86%E4%BD%88%E5%BC%8F-distributed">distributed Issue Queue</a> 可以減少 payload RAM read ports 的需求</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="92---fu-的類型">9.2 - FU 的類型</h1>
<h2 id="921---alu">9.2.1 - ALU</h2>
<ul>
<li>ALU (Arithmetic and Logic Unit) 通常負責：整數加減法、shift、簡單的乘除法、資料搬移 (e.g. mov、byte-swap 指令)、分支指令目標位址的計算、記憶體存取位址的計算</li>
<li>很多處理器在 ALU 中也實現了比較簡單的乘法功能，用來支持乘法指令
<ul>
<li>不過為了追求比較高的處理效能，高性能處理器通常選擇將乘法器單獨使用一個 FU 來實現，並在這個 FU 中實現乘累加的功能</li>
<li>有些處理器基於功耗和成本的考量，會將整數類型的乘法交由浮點數運算的 FU 來運算
<ul>
<li>整數轉成浮點數 → 浮點數乘法 → 浮點數轉為整數</li>
<li>指令的 latency 會變長，但較省面積和功耗，Intel Atom CPU 就採用了此設計</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="922---agu">9.2.2 - AGU</h2>
<ul>
<li>AGU (Address Generate Unit) 用來計算記憶體存取位址，如 load/store 指令
<ul>
<li>在一般 pipeline 處理器中，記憶體的存取位址都是交由 ALU 來計算，但是在 superscalar CPU 中，由於需要同時執行多條的指令，記憶體存取指令的執行效率直接影響了處理器的性能，所以通常會單獨使用一個 FU 來計算其位址</li>
</ul>
</li>
</ul>
<h2 id="923---bru">9.2.3 - BRU</h2>
<ul>
<li>BRU (Branch Unit) 負責處理 control flow 類型的指令，如 branch、jump、call 和 return 類型的指令，BRU 負責將這些指令所攜帶的目標位址計算出來，並根據一定的條件決定是否使用這些位址；同時，在這個 FU 中還會對分支預測與否正確進行檢查，一旦發現 mis-prediction，就需要啟動相對硬的恢復機制</li>
<li>ARM 和 PowerPC 等處理器在指令中的編碼加上了 condition code，根據 condition code 來決定指令是否執行
<ul>
<li>
<p>優點：</p>
<ul>
<li>不須使用分支指令，因此可以降低分支指令使用的頻率，也就降低了分支預測錯誤的風險，可以獲得更好的執行性能</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>Condition code 佔據了指令 encoding 的一部分，例如 ARM 使用了 4 bits 的 condition code (<code>NCZV</code>)，扣除掉 opcode、immediate… etc，最後只剩 4 bits 可以用來 encode registers，因此 ARM 指令集支援的 registers 個數只有 16 個 (<code>$r0</code> ~ <code>$r15</code>)</li>
<li>使用更多的 registers 可以降低指令存取記憶體的頻率，增加處理器的執行效率</li>
<li>當 pipeline 中存在很多條件不符合，因此不會被執行的條件指令，就會造成 pipeline 中存在著大量無效的指令，反而降低了處理器的執行效率</li>
</ul>
</li>
<li>
<p>此外，條件執行指令也會對 register renaming 帶來額外的麻煩：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image.png"></p>
<ul>
<li>
<p>如果 <code>ADDEQ</code> 指令會被執行，但 <code>SUBNE</code> 指令不會被執行，那麼 <code>ADD</code> 指令應該使用 <code>P6</code> 而非 <code>P7</code></p>
</li>
<li>
<p>最簡單的方法就是 stall pipeline：一旦在 register renaming 的時候碰到條件執行指令，就 stall pipeline，直到這條指令的執行條件被計算出來，就可以決定後續的 register rename 的結果</p>
<ul>
<li>缺點：效能非常差</li>
</ul>
</li>
<li>
<p>另外一個方法就是對條件執行指令也進行預測：可以先假設所有條件執行指令都會被執行，等到後續指令的執行條件被計算出來，並發現預測錯誤時，再恢復執行前的狀態：</p>
<ul>
<li>將在這條條件執行指令之後的指令從 pipeline 中 flush</li>
<li>恢復這些指令對 RAT 的修改</li>
<li>重新將這些指令 fetch 進 pipeline，重新做 register renaming 時就會使用正確的結果</li>
<li>缺點：
<ul>
<li>條件指令相較於分支指令，比較沒有固定的 pattern (i.e. 執行 or 不執行)，因此 mis-prediction 的機率會高得多，效率不高</li>
</ul>
</li>
</ul>
</li>
<li>
<p>(Intel x86) 硬體插入額外的 <code>select-uOP</code> 指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%201.png"></p>
<ul>
<li><code>select-uOP</code> 指令可以針對兩個條件執行指令的結果做選擇，所以需要兩個條件執行指令才可以使用</li>
<li>需要編譯器的搭配，手寫 assembly 的時候也需要特別的處理硬體才會插入 <code>select-uOP</code> 指令</li>
<li>例如：
<ul>
<li><code>ADDEQ</code> 指令會執行：<code>P8</code> = <code>P6</code></li>
<li><code>SUBNE</code> 指令會執行：<code>P8</code> = <code>P7</code></li>
<li>P.S. <code>ADDEQ</code> 指令和 <code>SUBNE</code> 指令都不會被執行的時候???</li>
</ul>
</li>
</ul>
</li>
<li>
<p>硬體在每個條件指令後面都插入一條 <code>select-uOP</code> 指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%202.png"></p>
<ul>
<li>不用侷限於一定要兩個條件指令才能使用</li>
<li>缺點：
<ul>
<li>指令變多，執行效能會降低</li>
</ul>
</li>
<li>也可以先每兩條條件指令插入一條 <code>select-uOP</code> 指令，直到最後只剩一條條件執行指令時，再額外插入一條 <code>select-uOP</code> 指令</li>
</ul>
</li>
</ul>
</li>
<li>
<p>ARM 以 <strong>s</strong> 結尾的指令，在執行完畢後，也會修改 CPSR，CPSR 就相當於這條指令的另一個 destination register；所有在這條指令之後的條件執行指令，都會將 CPSR 視為其中一個的 source register (i.e. 相當於使用該指令的 destination register)</p>
<ul>
<li>因此，CPSR 也可以當作一個普通的 register，也可以對其做 register renaming</li>
<li>不過由於 CPSR 的寬度小於一般的暫存器，因此一般會為 CPSR 使用一個獨立的 PRF</li>
<li>由此可見，使用 CPSR 增加了 register renaming 的複雜度，功耗也會有所增加，各有利弊</li>
</ul>
</li>
</ul>
</li>
<li>對於實現分支預測的處理器來說，BRU 還必須負責檢查分支預測是否正確；如果發現 mis-prediction，就必須依照先前介紹分支預測失敗狀態恢復的方法 (參考：<a href="../superscalar-overview-ch4-part2/#44---%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E5%A4%B1%E6%95%97%E6%99%82%E7%9A%84%E6%81%A2%E5%BE%A9">4.4 - 分支預測失敗時的恢復</a>)，恢復 pipeline 的狀態</li>
</ul>
<h2 id="924---其他-fu">9.2.4 - 其他 FU</h2>
<ul>
<li>處理器中還包含其他很多類型的 FU：
<ul>
<li>浮點數運算 FU</li>
<li>SIMD 類型指令的 FU</li>
</ul>
</li>
</ul>
<h1 id="93---旁路網路">9.3 - 旁路網路</h1>
<ul>
<li>
<p>一條指令可以在 execute stage 才需要 operands，且在 execute stage 末端就可以得到它的結果，因此只需在 FU 的輸入端和 FU 的輸出端之間搭建一個通路，就可以提前將 FU 的計算結果送到所有 FU 的輸入端</p>
<ul>
<li>處理器內部也有其他的元件需要 FU 的計算結果：PRF、payload RAM 等，因此也需要將 FU 的計算結果送到這些地方</li>
<li>這個通路就稱為：<code>Bypassing network</code></li>
</ul>
</li>
<li>
<p>在處理器中，為了能夠 back-to-back 的執行指令，bypassing network 都是必須的：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%203.png"></p>
<ul>
<li>指令 A 的計算結果，需要提前在 EX stage 末端，bypass 計算結果給指令 B</li>
</ul>
</li>
<li>
<p>在 superscalar CPU 中，指令在被 select 電路選中的那個 stage，wake up pipeline 中相關的指令，仍舊可以 back-to-back 的執行指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%204.png"></p>
<ul>
<li>指令 A 在 Select stage wake up 指令 B，並提前在 Execute stage 末端，bypass 計算結果給指令 B</li>
</ul>
</li>
<li>
<p>一個更真實的 superscalar CPU，有可能會切分更細的 pipeline stages：</p>
<ul>
<li>Source operands 從 PRF 讀出來，需要經過一段很長的線路，才能抵達 FU 的輸入端，而且 FU 的輸入端還有大量的 multiplexer，用來選擇從不同的 bypassing network 或 PRF 的輸出中選擇合適的 operands
<ul>
<li>為了降低對 CPU cycle time 的影響，source operands 從 PRF 讀出來後，還需要經過一個 <code>Source Drive</code> stage (≥ 1 個 cycle)，才能抵達 FU 的輸入端</li>
</ul>
</li>
<li>FU 計算完後，也需要經過複雜的 bypassing network，才能抵達 FU 或是 PRF 的輸入端
<ul>
<li>為了降低對 CPU cycle time 的影響，FU 的結果被計算出來後，還需要經過一個 <code>Result Drive</code> stage (≥ 1 個 cycle)，才能 FU 或 PRF 的輸入端</li>
</ul>
</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%205.png"></p>
</li>
</ul>
<h2 id="931---簡單設計的旁路網路">9.3.1 - 簡單設計的旁路網路</h2>
<ul>
<li>
<p>當 FU 的個數比較少，對 CPU 頻率的要求不高時，bypassing network 可以採用比較簡單的設計</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%206.png"></p>
<ul>
<li>FU 的 operands 直接來自於 PRF</li>
<li>FU 的計算結果也直接寫入 PRF</li>
<li>一個 FU 想使用另一個 FU 的計算結果只能直接從 PRF 讀取</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%207.png"></p>
<ul>
<li>FU 的 operands 可以來自於 PRF、自身 FU 的結果、其他 FU 的結果
<ul>
<li>每個 FU 都需要一個 3-to-1 的 multiplexer 來選擇 operands 來源</li>
</ul>
</li>
<li>FU 的計算結果會透過 bypassing network，寫入 PRF 或是 FU 的輸入端</li>
</ul>
</li>
<li>
<p>很多 FU 都有多個功能，也就是有多個計算單元；然而，由於 FU 所支援的指令的 latency 不一定都相同，如加減法指令只需 1 個 cycle 即可完成，乘法指令需要 32 個 cycles 才能完成，此時如果使用採用正常的執行，就可能出現一個 FU 中兩個以上計算單元的結果在同一個 cycle 被計算出，並搶用 FU 的 bypassing network，造成衝突</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%208.png"></p>
<ul>
<li>最簡單的解法可以採用類似 <a href="../superscalar-overview-ch8-part2/#853---%E6%8E%A8%E6%B8%AC%E5%96%9A%E9%86%92">8.5.3 - 推測喚醒</a>的方法，先假設不會發生衝突，等到一條指令到達 FU 中被執行之前，檢查目前 FU 是否可以被使用，例如：
<ul>
<li>
<p>一個 FU 在上個 cycle 執行了 latency = 3 的指令，那麼這個 cycle 就沒辦法執行 latency = 2 的指令了</p>
</li>
<li>
<p>如果這個 cycle 不幸到達 FU 的指令的 latency = 2，那就必須將這條指令重新放回 Issue Queue 中參與仲裁</p>
</li>
<li>
<p>缺點：</p>
<ul>
<li>
<p>浪費了原本可以執行其他指令的機會，如果可以事先得知無法執行 latency = 2 的指令，那 select 電路就可以選擇其他 latency ≠ 2 的指令來執行</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%209.png"></p>
</li>
</ul>
</li>
<li>
<p>因此，select 電路在進行仲裁時，應該要將某些 latency 的指令給排除，那就可以在不降低性能的情況下解決 bypassing network 衝突的問題</p>
</li>
<li>
<p>例如：</p>
<ul>
<li>假設一個 FU 可以執行三種類型的指令，也就是該 FU 有三種計算單元，其對應的 latency 分別為：1 個 cycle、2 個 cycles、3 個 cycles，那麼：
<ul>
<li>當這個 cycle 執行 latency = 3 的指令時：
<ul>
<li>下個 cycle 不允許執行 latency = 2 的指令</li>
<li>下下個 cycle 不允許執行 latency = 1 的指令</li>
</ul>
</li>
<li>當這個 cycle 執行 latency = 2 的指令時：
<ul>
<li>下個 cycle 不允許執行 latency = 1 的指令</li>
</ul>
</li>
<li>當這個 cycle 執行 latency = 1 的指令時，沒有限制</li>
<li>對於 latency = 3 的指令，沒有限制 (假設 FU 是 pipeline 的)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Select 電路在仲裁時，需要考慮指令所對應的 FU 是否可以被使用，不會產生 bypassing network 衝突</p>
</li>
<li>
<p>從簡化設計的考量，最好使一個 FU 所執行所有類型的指令的 latency 都是相同的，就不用引入上述的判斷電路</p>
</li>
</ul>
<h2 id="932---複雜設計的旁路網路">9.3.2 - 複雜設計的旁路網路</h2>
<ul>
<li>
<p>真實的 superscalar CPU 中，execute stage 有可能會切分更細的 pipeline stages：<code>Source Drive</code> stage 以及 <code>Result Drive</code> stage，這也導致了 bypassing network 需要做對應的變化：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2010.png"></p>
<ul>
<li>
<p>指令 B 只能在 Execute stage 從指令 A 的 Result drive stage 獲得 operands</p>
</li>
<li>
<p>指令 C 可以在 Source drive stage 從指令 A 的 Result drive stage 獲得 operands</p>
</li>
<li>
<p>指令 C 也可以在 Execute stage 從指令 A 的 Write back stage 獲得 operands</p>
</li>
<li>
<p>指令 D 可以在 Source drive stage 從指令 A 的 Write back stage 獲得 operands</p>
</li>
<li>
<p>指令 E 在 RF read stage 讀去 PRF 時，就可以得到指令 A 寫入 PRF 的結果了，因此不需要透過 bypassing network</p>
<ul>
<li>假設 PRF write 可以在前半個 cycle 完成，PRF read 可以在後半個 cycle 完成</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在複雜的 pipeline 中加入 bypassing network，會使設計的複雜度變大，對 CPU cycle time 造成一定的影響</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2011.png"></p>
<ul>
<li>相較於先前簡單的設計，只增加了 Source Drive 和 Result Drive stages，不使用 bypassing network 並不會增加設計的複雜度</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2012.png"></p>
<ul>
<li>相較於先前簡單的設計，增加了 Source Drive 和 Result Drive stages 會導致在使用 bypassing network 時，設計的複雜度大幅度的增加</li>
</ul>
</li>
<li>
<p>每新增一個新的 pipeline stage，bypassing network 的複雜度會大幅地增加，因此無法無上限的新增 pipeline stage</p>
</li>
</ul>
<h1 id="94---操作數的選擇">9.4 - 操作數的選擇</h1>
<ul>
<li>
<p>FU 的輸入端來源包括：PRF、bypassing network、還有指令的 immediate value；而這些來源是透過 multiplexer 來選擇的，需要有對應的訊號來控制這個 multiplexer，決定要選擇的來源</p>
</li>
<li>
<p>所有的 physical register 可以保存在一個表格：<code>Scoreboard</code> 中，scoreboard 記錄了一個 physical register 生命週期經過的地方：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2013.png"></p>
<ul>
<li>真實的 scoreboard 原本圖片所示來得複雜</li>
<li>scoreboard 中對於每個 physical register，都記錄了：
<ul>
<li><code>FU #</code>：這個 physical register 會在哪個 FU 被計算出來
<ul>
<li>當需要透過 bypassing network 取得 physical register 的值時，需要知道它來自於哪個 FU，以控制 multiplexer 選擇對應的值</li>
<li>當一條指令被 select 電路選中時，就會將這條指令的 destination register 在哪個 FU 中執行的資訊記錄到 scoreboard 中</li>
</ul>
</li>
<li><code>R</code>：表示這個 physical register 已經被 FU 計算出來並寫進 PRF 中
<ul>
<li>只需使用一個 bit 即可：
<ul>
<li><code>0</code>：這個 physical register 還沒被寫進 PRF 中，需要透過 bypassing network 來取得值</li>
<li><code>1</code>：這個 physical register 已經被寫進 PRF 中了，可以直接讀取 PRF 來取得值</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在 pipeline 中加入 scoreboard 會對 pipeline 的性能造成一定的影響：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2014.png"></p>
<ul>
<li>指令 A 在 select stage 會將 <code>$r1</code> 的 physical register 在哪個 FU 中執行的資訊寫進 scoreboard 的 <code>FU #</code> 中</li>
<li>指令 A 在 write-back stage 除了會將 FU 的結果寫入 PRF，也會將 <code>$r1</code> 的 physical register 在 scoreboard 中的 <code>R</code> 設成 <code>1</code></li>
<li>指令 B 在 execute stage 時，可以讀取 scoreboard，得知其所需的 <code>$r1</code> 需透過 bypassing network 來取得</li>
<li>指令 C 在 execute stage 時，可以讀去 scoreboard，得知其所需的 <code>$r1</code> 可以直接讀取 PRF 來取得</li>
</ul>
</li>
<li>
<p>Scoreboard 的讀取如果放在 execute stage，會對 CPU 的 cycle time 造成一定的影響</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2015.png"></p>
<ul>
<li>
<p>當 CPU 頻率的需求比較高時，這種作法可能就無法滿足要求</p>
</li>
<li>
<p>如果需要提高 CPU 的頻率，可以將 scoreboard 的讀取放到 RF read stage，使 scoreboard 的讀取和 PRF 的讀取可以同時進行，”隱藏” scoreboard 的讀取時間：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2016.png"></p>
<ul>
<li>
<p>然而，這樣會導致新的問題：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2017.png"></p>
<ul>
<li>指令 C 原本可以在 execute stage 直接讀取 PRF 獲得 <code>$r1</code> 的值，但由於指令 C 讀取 scoreboard 時，指令 A 尚未完成 scoreboard <code>R</code> bit 的更新，因此會讓指令 C 錯誤地認為 <code>$r1</code> 必須透過 bypassing network 才能獲得</li>
</ul>
</li>
<li>
<p>可以在讀取和寫入 scoreboard 的地方加入新的邏輯：</p>
<ul>
<li>當寫入 scoreboard 所使用的 physical register 編號和讀取 scoreboard 的 physical register 編號相同時，就將 multiplexer 設為從 PRF 中獲得 operands</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>補充：</p>
<ul>
<li>
<p>實際上的 scoreboard 範例：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-3">3</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#a6e22e">MUL</span> <span style="color:#66d9ef">F2</span>, <span style="color:#66d9ef">F0</span>, <span style="color:#66d9ef">F4</span>    <span style="color:#75715e">; F2 = F0 * F4
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">ADD</span> <span style="color:#66d9ef">F6</span>, <span style="color:#66d9ef">F2</span>, <span style="color:#66d9ef">F8</span>    <span style="color:#75715e">; F6 = F2 + F8
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">SUB</span> <span style="color:#66d9ef">F10</span>, <span style="color:#66d9ef">F6</span>, <span style="color:#66d9ef">F12</span>  <span style="color:#75715e">; F10 = F6 - F12
</span></span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-5">5</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-6"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-6">6</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-7"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-7">7</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>+------+-------+-----+-----+-----+-----+-------+-------+-----+-----+
</span></span><span style="display:flex;"><span>|  FU  | Busy  | Op  | Fi  | Fj  | Fk  |  Qj   |  Qk   | Rj  | Rk  |
</span></span><span style="display:flex;"><span>+------+-------+-----+-----+-----+-----+-------+-------+-----+-----+
</span></span><span style="display:flex;"><span>| MUL1 |   Y   | MUL | F2  | F0  | F4  |   -   |   -   |  T  |  T  |
</span></span><span style="display:flex;"><span>| ADD1 |   Y   | ADD | F6  | F2  | F8  | MUL1  |   -   |  F  |  T  |
</span></span><span style="display:flex;"><span>| ADD2 |   Y   | SUB | F10 | F6  | F12 | ADD1  |   -   |  F  |  T  |
</span></span><span style="display:flex;"><span>+------+-------+-----+-----+-----+-----+-------+-------+-----+-----+
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li><code>FU</code>：FU 的名稱</li>
<li><code>Busy</code>：FU 是否正在忙</li>
<li><code>Op</code>：這個 FU 正在執行的 operation</li>
<li><code>Fi</code>：這個 FU 要寫的 destination register</li>
<li><code>Fj</code>, <code>Fk</code>：FU 所需的 source registers</li>
<li><code>Qj</code>, <code>Qk</code>：如果 <code>Fj</code>/<code>Fk</code> 尚未 ready，哪個 FU 會提供它</li>
<li><code>Rj</code>, <code>Rk</code>：來源是否 ready</li>
</ul>
</li>
<li>
<p>現代處理器，scoreboard 幾乎已經被 Tomasulo (Issue Queue + Register Renaming + Bypassing Network) 給取代了</p>
</li>
</ul>
</li>
</ul>
<h1 id="95---cluster">9.5 - Cluster</h1>
<ul>
<li>現在處理器隨著 pipeline stages 級數的提高，會導致硬體的設計越來越複雜，例如需要更多的 read/write ports、PRF，更複雜的 bypassing network，不僅增加了硬體的面積，也增加了硬體的功耗，限制了 CPU 的頻率，需要採用 <strong>Cluster</strong> 架構來解決這個問題</li>
</ul>
<h2 id="951---cluster-issue-queue">9.5.1 - Cluster Issue Queue</h2>
<ul>
<li>
<p>除了 Issue Queue 外，由於 register file 與 Issue Queue 彼此是緊密相關的，因此 register file 同時也會採用 cluster 架構</p>
</li>
<li>
<p><a href="../superscalar-overview-ch8-part1/#811----%E9%9B%86%E4%B8%AD%E5%BC%8F-centralized-vs-%E5%88%86%E4%BD%88%E5%BC%8F-distributed">8.1.1 -  集中式 (Centralized) vs. 分佈式 (Distributed)</a> 介紹的 distributed Issue Queue 就是將 Issue Queue 採用了 cluster 架構</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2018.png"></p>
<ul>
<li>每個 cluster Issue Queue 都對應一個 payload RAM (採用 <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture">data-capture</a> 架構) 和 FU</li>
<li>優點：
<ul>
<li>減少每個 Issue Queues 的 ports 數</li>
<li>每個 Issue Queue 對應的 select 電路只需從少量的指令中做仲裁，因此可以加快 select 電路的速度</li>
<li>由於 Issue Queue 的容量較小，因此 wake up 指令的速度也比較快</li>
<li>Payload RAM 也可以採用 cluster 架構，因此每個 payload RAM 的容量也會比較小，而且也不需要支援這麼多的 read/write ports</li>
</ul>
</li>
<li>缺點：
<ul>
<li>被 select 電路選中的指令如果要 wake up 的指令存在另外的 Issue Queue 中，那麼需要走比較長的 wake up 電路
<ul>
<li>
<p>對頻率有要求的 CPU，跨 cluster 的 wake up 有可能會需要額外新增一個 pipeline stage</p>
</li>
<li>
<p>當兩條存在相關性的指令存在不同 cluster 的 Issue Queue 時，有可能就會有 bubble，無法 back-to-back 的執行指令</p>
</li>
<li>
<p>例如：一個 2-way issue 的 CPU，執行指令 A ~ E，假設指令 A ~ E 的 dependency 如下：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2019.png"></p>
<ul>
<li>
<p>如果指令 A、B、E 被分配到同一個 cluster，指令 C、D 被分配到另外一個 cluster，共需 5 個 cycles 才能執行完畢：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2020.png"></p>
<ul>
<li>指令 A 需要多 1 個 cycle 才能 wake up 指令 C</li>
<li>指令 B 需要多 1 個 cycle 才能 wake up 指令 D</li>
<li>指令 D 需要多 1 個 cycle 才能 wake up 指令 E</li>
</ul>
</li>
<li>
<p>如果指令 A、C 被分配到同一個 cluster，指令 B、D、E 被分配到另一個 cluster，只需 3 個 cycles 才能執行完畢：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2021.png"></p>
<ul>
<li>指令 A 可以 back-to-back wake up 指令 C</li>
<li>指令 B 可以 back-to-back wake up 指令 D</li>
<li>指令 D 可以 back-to-back wake up 指令 E</li>
</ul>
</li>
<li>
<p>因此想使處理器有比較高的執行效率，指令 <a href="../superscalar-overview-ch8-part1/#83---%E5%88%86%E9%85%8D-allocation">allocation</a> 的演算法就必須有適當的設計</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>對於採用 <a href="../superscalar-overview-ch8-part1/#812---%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-data-capture-vs-%E9%9D%9E%E6%95%B8%E6%93%9A%E6%8D%95%E6%8D%89-non-data-capture">non-data-capture</a> 架構的 CPU 來說，指令在被 select 電路選中後，會先去讀 PRF，因此就需要 PRF 支援 multiple read ports，因此也會增大硬體面積，執行效率也會比較慢，因此，也可以對 PRF 採用 cluster 架構</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2022.png"></p>
<ul>
<li>每個 cluster 都有”完整”的 PRF：每個 FU 在更新自己 cluster 內部 PRF 的同時，也同時需要更新其他 clusters 的 PRF</li>
<li>每個 FU 都只能在自己的 cluster 內使用 bypassing network</li>
<li>優點：
<ul>
<li>減少 PRF 所需的 read ports
<ul>
<li>4 個 FU → 每個 cluster 2 個 FU，因此 read ports 可以減少，進而減少硬體面積</li>
<li>但無法減少 write ports，因為 FU 仍需要更新其他 clusters 的 PRF</li>
</ul>
</li>
<li>減少 bypassing network 的複雜度</li>
</ul>
</li>
<li>缺點：
<ul>
<li>Bypassing network 無法跨 cluster，當兩個存在相關性的連續指令處於不同的 clusters 時，後面的指令需要等到前面的指令更新完 PRF 後，才能從 PRF 中讀取 operands
<ul>
<li>雖然硬體可以嘗試找其他不相關的指令到這兩條指令中間來執行，但當 pipeline 較深的時候，硬體可能沒辦法找到這麼多不相關的指令，引進較多的 bubbles</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="952---cluster-bypassing-network">9.5.2 - Cluster Bypassing Network</h2>
<ul>
<li>
<p>為了減少 bypassing network 的複雜度，也可以讓 bypassing network 採用 cluster 的架構</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2023.png"></p>
<ul>
<li>採用 cluster 架構的 bypassing network 後，每個 FU 就沒辦法將其計算結果送到其他 clusters 的 FU
<ul>
<li>當兩條相關的指令都使用同一個 FU 時，還是可以透過 bypassing network，back-to-back 的執行
<ul>
<li>跨 cluster 只能透過 PRF 來傳遞 operands</li>
</ul>
</li>
<li>但當兩條相關的指令使用不同 clusters 的 FU 時，就沒辦法 back-to-back 的執行了</li>
</ul>
</li>
<li>由於 bypassing network 的複雜度降低了，因此 pipeline 中的 Source Drive stage 和 Result Drive stage 就可以被移除了
<ul>
<li>
<p>雖然跨 cluster 只能透過 PRF 來傳遞 operands，但由於 pipeline 少了 Source Driver stage 以及 Result Driver stage，因此一條指令的執行效率是有被提高的，兩條跨 cluster 的指令只需間隔 1 個 cycle 即可</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2024.png"></p>
<ul>
<li>Out-of-order CPU 可以找一條不相關的指令插到這個 cycle 中來執行
<ul>
<li>然而，隨著 CPU 同時可以執行的指令數越來越多，PRF 的 ports 數量也會隨著增加，加上 CPU 的頻率的提高 (i.e. cycle time 的減少)，有可能會導致 PRF 沒辦法在同一個 cycle 中做 write &amp; read，兩條相關的指令就需要間隔更多的 cycles，Out-of-order CPU 有可能沒辦法找到這麼多條不相關的指令插到這幾個 cycles 中來執行，影響到 CPU 的執行效率</li>
</ul>
</li>
<li>In-order CPU 由於沒辦法調度不相干的指令來執行，只能產生 bubbles，因此通常 in-order CPU 都會採用”完全”的 bypassing network，而非採用 cluster 架構的 bypassing network
<ul>
<li>例如：Intel Atom 就有複雜的 bypassing network</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>對於 Cluster Issue Queue 和 cluster bypassing network，當兩條相關的指令分處於不同的 cluster 時，都會需要 delay 1 個 cycle，但這兩個 delays 其實並不會被累加：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2025.png"></p>
<ul>
<li>指令 A 需要 delay 1 個 cycle 才能 wake up 指令 B</li>
<li>由於已經 delay 1 個 cycle 才 wake up 指令 B，指令 A bypass 其 FU 的計算結果的 delay 剛好被隱藏起來了；當指令 B 進到 execute stage 時，指令 B 的 FU 剛好可以接收到來自指令 A 的 FU 的計算結果，不需要額外的 delay</li>
<li>總共只需要 delay 1 個 cycle 即可</li>
</ul>
</li>
</ul>
<h1 id="96---記憶體指令的加速">9.6 - 記憶體指令的加速</h1>
<h2 id="961---memory-disambiguation">9.6.1 - Memory Disambiguation</h2>
<ul>
<li>暫存器的相關性 (RAW、WAW、WAR)，都是可以在 register renaming stage 被解決；然而，針對記憶體存取的相關性 (i.e. load、store 指令)，由於記憶體存取位址是在 execute stage 才被計算出來的，才可以得知兩條記憶體存取的指令，是否存在相關性
<ul>
<li>記憶體存取指令間也存在 RAW、WAW、WAR 相關性：</li>
</ul>
</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2026.png"></p>
<ul>
<li>
<p>記憶體存取的 WAW、WAR 是沒辦法像暫存器可以用 register renaming 來消除的，因此記憶體存取的 RAW、WAW、WAR 都是真的相關性，需要特別處理</p>
</li>
<li>
<p>為了降低設計難度，大部分的處理器：</p>
<ul>
<li><strong>Store 指令都是 in-order 的</strong>，可以避免 WAW 相關性</li>
<li>Load 指令則有不同的實現方式：
<ul>
<li><strong>完全 in-order：</strong>
<ul>
<li>Load 指令和 store 指令都完全依照程式的順序來執行</li>
</ul>
</li>
<li><strong>部份 out-of-order：</strong>
<ul>
<li>In-order 的 store 指令將程式劃分成了不同的 blocks，當一條 store 指令的存取位址被計算出來後，這條 store 指令和它後面的 store 指令之間的 load 指令可以以 out-of-order 的方式來執行：</li>
<li>這種設計可以降低 RAW 相關性的檢查難度</li>
</ul>
</li>
<li><strong>完全 out-of-order：</strong>
<ul>
<li>Load 指令不受 store 指令的限制，只要 load 指令的 operands 都準備好了，它就可以被送到 FU 中執行</li>
<li>這種設計 WAR 和 RAW 相關性都必須在 pipeline 中被處理</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>完全 in-order：</p>
<ul>
<li>Load 指令和 store 指令都是嚴格得照程式的執行順序來執行，為最保守的一種方法</li>
<li>由於 load 指令通常處於相關性的頂端，這種方法沒辦法使 load 指令盡可能地提前被執行，導致所有相關的指令都必須等待 load 指令才能被執行，性能較差</li>
<li>在 superscalar CPU 中基本上不會採用此方法</li>
</ul>
</li>
<li>
<p>部份 out-of-order：</p>
<ul>
<li>
<p>Store 指令仍是 in-order 執行，但兩條 store 指令之間的所有 load 指令都可以以 out-of-order 的方式來執行</p>
</li>
<li>
<p>可以使 load 指令盡可能地提早被執行</p>
</li>
<li>
<p>當一條 store 指令的存取位址被計算出來後，在它之後進入到的所有 load 指令就可以判斷 RAW 的相關性了，每條 load 指令的存取位址被計算出來後，需要和前面所有已經執行的 store 指令的存取位址進行比較</p>
</li>
<li>
<p>因此，需要使用 <code>store buffer</code> 來儲存還沒 retire 的 store 指令，如果 load 指令在 store buffer 中發現了相同存取位址的 store 指令，則說明了存在 RAW 的相關性，此時 load 指令就可以直接從 store buffer 拿到 store 指令的值；如果在 store buffer 中沒有找到相同存取位址的 store 指令，那 load 指令就必須去存取 D-Cache 或是 memory 來取得值</p>
</li>
<li>
<p>實際上，當 store 指令被 select 電路選中後，就可以 wake up 後面的 load 指令了，無須等到 store 指令的存取位址真的被計算出來；當 load 指令的存取位址被計算出來時，其前面 store 指令的存取位址一定也早就被計算出來了</p>
<ul>
<li>
<p>然而除了比較<strong>存取位址</strong>外，還需要比較<strong>指令的先後順序</strong>，例如：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2027.png"></p>
<ul>
<li>即使 store 指令 E 和 store 指令 A 的存取位址相同，load 指令 B、C、D 也不會跟 store 指令 E 有 RAW 相關性，如果只比對 store buffer 中的存取位址，有可能就會判斷錯誤 (如果 load 指令 B、C、D 執行時，store 指令 E 已經存入 store buffer 中了)
<ul>
<li>RAW 相關性檢查只需檢查 load 指令之前的 store 指令即可，因此需要對 load 指令和 store 指令前後順序進行編號</li>
</ul>
</li>
<li>可以在 decode stage 替每條 load 指令和 store 指令進行編號，這個編號的 bits 寬度由 pipeline 中最多可容納的 load 指令和 store 指令的個數來決定
<ul>
<li>編號的比較方法可以參考 <a href="../superscalar-overview-ch8-part1/#841---1-of-m-%E7%9A%84%E4%BB%B2%E8%A3%81%E9%9B%BB%E8%B7%AF">ROB 編號的比較方式</a></li>
<li>由於 ROB 中儲存了所有的指令，使用 ROB 編號作為 load 指令和 store 指令的編號會變得非常的稀疏，浪費比較器的 bits 寬，增加硬體面積，不過在真實處理器中也有採用</li>
</ul>
</li>
</ul>
</li>
<li>
<p>P.S. 一條 load 指令是有可能會與多條 store 指令存在 RAW 相關性的</p>
<ul>
<li>例如：兩條 store 指令各 store 1 byte 的資料，但另外一條 load 指令 load 的 4 bytes 資料中，其中 2 bytes 是從這兩條 stores 而來，此時這條 load 指令就與這兩條 store 指令存在 RAW 相關性</li>
<li>因此不能單單只比較位址，還需比較<strong>存取的資料範圍</strong>
<ul>
<li>i.e. <code>addr + size</code></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>另外一種設計方法，可以限制後面的 store 指令只有在前面所有的 load 指令全部都被 select 電路選中後，才允許該 store 指令向 select 電路發出 request</p>
<ul>
<li>這樣每一條 load 指令在比對 store buffer 時，只會遇到在自己老的 store 指令，而不會遇到比自己年輕的 store 指令，就不需要比對指令的先後順序，簡化 RAW 相關性的檢查</li>
<li>例如上例中，store 指令 E 只能在 load 指令 B、C、D 都被 select 電路選中後，才會向 select 電路發出 request</li>
</ul>
</li>
<li>
<p>然而這種部份 out-of-order 的設計，仍然無法最大限度的同步執行指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2028.png"></p>
<ul>
<li>Load 指令 F 與 store 指令 B 沒有相關性，但卻沒辦法在 store 指令 B 之前被執行</li>
</ul>
</li>
</ul>
</li>
<li>
<p>完全 out-of-order：</p>
<ul>
<li>Store 指令仍是 in-order 執行，但只要 load 指令的 operands 都準備好了，就可以向 select 電路發出 request</li>
<li>可以採用 load 指令和 store 指令共用同一個 Issue Queue，每個 cycle 只 issue 一條 load 指令或 store 指令的設計，或是每個 cycle 同時 issue 一條 load 指令和 store 指令的設計
<ul>
<li>Load 指令和 store 指令在一般程式中佔的比例比較大，所以最好每個 cycle 都盡可能的增加每個 cycle 可以同時執行的 load 指令和 store 指令的個數</li>
<li>不過由於 load 指令和 store 指令的處理過程不同，即使使用同一個 Issue Queue，仍需要使用互相獨立的 select 電路
<ul>
<li>
<p>對於 store 指令的 select 電路來說：</p>
<ul>
<li>當指令被寫入 Issue Queue 中時，需根據指令的年齡資訊，找到最舊的那條 store 指令來 issue；如果還沒有準備好，則必須一直等待 ⇒ In-order</li>
</ul>
</li>
<li>
<p>對於 load 指令的 select 電路來說：</p>
<ul>
<li>只需要比對準備好的 load 指令的年齡資訊即可，找到最舊，且準備好的 load 指令來 issue ⇒ Out-of-order</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>也可以採用 load 指令和 store 指令各自使用獨立的 Issue Queue 的設計
<ul>
<li>Store 指令的 Issue Queue 可以直接使用 FIFO 來實做，select 電路不用比較年齡資訊，只需判斷 FIFO 中最舊的那條 store 指令是否準備好就好</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>Store/load 指令違例：</strong></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2029.png"></p>
<ul>
<li>第二條 load 指令被提前到 store 指令之前被執行，結果發現 store 指令與第二條 load 指令的存取位址相同，彼此之間存在 RAW 相關性，因此必須恢復 pipeline 的狀態
<ul>
<li>除此之外，不僅第二條 load 指令的結果有錯，所有與這條 load 指令有直接關係或間接關係的指令，都需要從 pipeline 中被 flushed 掉，恢復 pipeline 的狀態，並重新參與仲裁並執行，因此會影響處理器的執行效能</li>
</ul>
</li>
<li>需要採用 load/store 相關性的預測，來預測哪些 load 指令和前面的 store 指令存在 RAW 相關性而不能提前被執行：
<ul>
<li>透過 load/store 相關性的預測，一旦發現一條 load 指令和它前面的 store 指令存在 RAW 相關性，就將其記錄下來，之後再遇到這條 load 指令時，就不提前執行，而且需要從 store buffer 中獲取 load 指令所需要的值
<ul>
<li>額外好處：只有那些與 store 指令存在 RAW 相關性的 load 指令，才需要存取 store buffer，因此可以減少 store buffer 所需的 ports 數及比較電路的個數，並降低設計的複雜度和功耗</li>
</ul>
</li>
<li>P.S. 預測失敗並不需要恢復 pipeline 的狀態， 因為 load 指令只是比較晚被執行而已</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="962---non-blocking-cache">9.6.2 - Non-blocking Cache</h2>
<ul>
<li>
<p>I-Cache 由於 fetch 指令需要照 program order，不能簡單地使用 non-blocking cache，因此 non-blocking cache 主要會用在 D-Cache</p>
<ul>
<li>
<p>然而對於使用了 branch prediction 的 CPU 來說，是可以根據當前指令的位址來對下一條指令的位址做預測的；當使用一個 PC address 讀取 I-Cache 發生 I-Cache miss，且同時預測這個 PC address 所的指令是一條分支指令時，如果不採用 non-blocking I-Cache，那 CPU 就得等這條分支指令從 I-Cache 讀出來後，才可以繼續使用預測的 PC address 來 fetch 下一條指令，此時再次發生 I-Cache miss 的機率是很高的：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2030.png"></p>
<ul>
<li>有可能連續發生兩次 miss penalties</li>
</ul>
</li>
<li>
<p>如果使用 non-blocking I-Cache，就可以隱藏一部分的 miss penalty，提高處理器的執行效率：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2031.png"></p>
<ul>
<li>Non-blocking I-Cache 不用等到所預測的分支指令從 I-Cache 讀出來後，才開始 fetch 下一條指令 (有可能又會再發生一次 I-Cache miss)</li>
</ul>
</li>
<li>
<p>然而，由於指令的特性，就算後面的指令比前面一個指令先被 fetched 進來，也沒辦法先執行後面的指令；因此通常不用使用太大的 <a href="#962---non-blocking-cache"><strong>MSHR</strong></a></p>
</li>
</ul>
</li>
<li>
<p>對於 load 指令來說 (假設 L1 D-Cache 下一級就是記憶體)：</p>
<ul>
<li>如果需要的資料不在 D-Cache 中，就會發生 D-Cache miss，需要從下一級的記憶體中讀取data block (一條 cache line 的 size)，並找到一條 cache line 將該 data block 寫入
<ul>
<li>如果被寫入的 cache line 是 dirty 的，那在寫入之前，還需要先將 dirty 的 cache line flush 回記憶體後，才可以將讀到的 data block 寫入 cache line</li>
</ul>
</li>
</ul>
</li>
<li>
<p>對於 store 指令來說 (假設 L1 D-Cache 下一級就是記憶體)：</p>
<ul>
<li>如果 store 的位址不在 D-Cache 中，對於 “write back + write allocate” 類型的 D-Cache 來說，首先需要從記憶體中找到這個位址對應的 data block，讀出來後與 store 指令的資料合併，並找到一條 cache line 將合併後的資料寫入
<ul>
<li>如果被替換的 cache line 是 dirty 的，那在寫入之前，還需要先將 dirty 的 cache line flush 回記憶體後，才可以將合併後的資料寫入 cache line</li>
</ul>
</li>
</ul>
</li>
<li>
<p>不管是 load 指令還是 store 指令，當發生 D-Cache miss 時，D-Cache 和記憶體都是需要交換資料的，通常需要好幾個 cycles 才能完成；如果在這幾個 cycles 內，又發生了 D-Cache miss，該如何處理?</p>
<ul>
<li>
<p>最簡單的方法就是在發生 D-Cache miss 並未被解決前，鎖定 D-Cache 和記憶體之間的 bus，只處理目前發生 D-Cache miss 的指令</p>
<ul>
<li>缺點：
<ul>
<li>
<p>D-Cache miss 通常處理的時間都比較長，如果這段時間 block 了 load 指令和 store 指令的執行，在一般情況下，load 指令都是處於相關性的最頂端，這樣的 block 就會大大降低處理器可以同步執行指令的數量，降低處理效能</p>
</li>
<li>
<p>這種 cache 設計方法稱為：<code>Blocking Cache</code></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2032.png"></p>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>如果在發生 D-Cache miss 時，不用 block load 指令和 store 指令的執行，這種 cache 的設計方法稱為：<code>Non-blocking Cache</code></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2033.png"></p>
<ul>
<li>Non-blocking cache 可以在一定程度上掩蓋 D-Cache miss 所佔用的時間 (Miss Penalty)</li>
<li>採用 non-blocking cache 後，load 指令和 store 指令 D-Cache miss 被處理完的時間有可能就跟原始的程式執行順序不一樣了</li>
<li>為了支援 non-blocking cache，必須將產生 D-Cache miss 的 load 指令和 store 指令給記錄起來，用來保存這些資訊的硬體稱為：<code>MSHR (Miss Status/information Holding Register)</code></li>
</ul>
</li>
</ul>
</li>
<li>
<p>D-Cache miss 可以分為兩種：</p>
<ul>
<li><strong>Primary miss：</strong>
<ul>
<li>對於一位址來說，存取 D-Cache 時第一次發生的 miss 稱為：<em>primary miss</em></li>
</ul>
</li>
<li><strong>Secondary miss：</strong>
<ul>
<li>對於一位址來說，primary miss 還沒被處理完前，後續的 load 指令或 store 指令再次存取了<strong>同一條 cache line</strong> 並發生 D-Cache miss，此時的 miss 便稱為：<em>secondary miss</em></li>
</ul>
</li>
</ul>
</li>
<li>
<p>MSHR 和 Load/store Table 的架構如下：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2034.png"></p>
<ul>
<li>MSHR：
<ul>
<li>MSHR 記錄了所有發生 <strong>primary miss</strong> 的指令</li>
<li><code>V</code>：
<ul>
<li>Valid bit，用來表示此 MSHR entry 是否被佔用</li>
<li>當發生 primary miss 時，會將該指令存進一 MSHR entry 和 Load/store Table entry，並將 <code>V</code> 設成 1</li>
<li>當從下一級的記憶體讀回資料後，就會將對應的 MSHR entry 和 Load/store Table entry 清掉，並將 <code>V</code> 設成 0</li>
</ul>
</li>
<li><code>Block address</code>：
<ul>
<li>發生 primary miss 位址的 cache line address
<ul>
<li>例如：
<ul>
<li>Cache line = 64 bytes ⇒ 需要 6 bits 來選擇所存取的資料位址是在哪個 byte</li>
<li>假設存取位址是 32 bits ⇒ Block address = 32 - 6 = 26 bits</li>
</ul>
</li>
</ul>
</li>
<li>每條 load 指令或是 store 指令發生 D-Cache miss 時，都會查詢 MSHR 是否有同一條 cache line 正在被讀取的過程中，這需要比較 MSHR 每個 <code>V = 1</code> 的 entries
<ul>
<li>如果有發現同一條 cache line 正在被讀取的過程，那麼就不需要再發一次 request</li>
</ul>
</li>
<li>P.S. DDR3 SDRAM 的 burst size 為 64 bytes，因此處理器通常也會將 cache line size 設為 64 bytes</li>
</ul>
</li>
<li><code>Issued</code>：
<ul>
<li>表示發生 primary miss 的 load 指令或是 store 指令是否已經開始處理，i.e. 是否已經開始從下一級的記憶體讀取 data block
<ul>
<li>由於 bus 寬度有限，每個 MSHR entry 並不一定會馬上被處理，因此需要透過 <code>Issued</code> bit 來表示此 primary miss 是否已經在處理的過程中</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Load/store Table：
<ul>
<li>發生 <strong>primary miss</strong> 或是 <strong>secondary miss</strong> 的 load 指令或是 store 指令都會被記錄到 Load/store Table 中</li>
<li><code>V</code>：
<ul>
<li>Valid bit，用來表示此 Load/store Table entry 是否被佔用</li>
</ul>
</li>
<li><code>MSHR entry</code>：
<ul>
<li>發生 miss 的 load 指令或 store 指令所對應的 MSHR entry
<ul>
<li>不同的 load 指令或 store 指令有可能會存取到同一條 cache line，此時 MSHR entry 只會有一個，但 Load/store Table 會記錄所有發生 miss 的指令，以確保在 data block 讀回來後，可以在 Load/store Table 中找到哪些 load 指令和 store 指令屬於這條 cache line</li>
</ul>
</li>
</ul>
</li>
<li><code>Dest. register</code>：
<ul>
<li>對於 load 指令，<code>Dest. register</code> 欄位記錄 load 指令的 destination register 編號 (physical register)</li>
<li>對於 store 指令，<code>Dest. register</code> 欄位記錄 store 指令在 store buffer 中的編號
<ul>
<li>Store 指令在 retire 前會將其資訊記錄在 store buffer 中，當 store 指令變成 pipeline 中最老的指令時，需要將其資料寫進 D-Cache 中，如果此時發生了 D-cache miss，store buffer 中對應的 entry 就不會馬上被釋放，需要等到 D-Cache miss 解決並將合併後的資料寫進 D-Cache 後，才可以釋放 store buffer 中的 entry</li>
</ul>
</li>
</ul>
</li>
<li><code>Type</code>：
<ul>
<li>記錄指令的類型，如：Load word、Load half word、Load byte、Store word、Store half word、Store byte</li>
</ul>
</li>
<li><code>Offset</code>：
<ul>
<li>Load 指令或 store 指令所存取的位址在 data block 中的 offset
<ul>
<li>例如：64 bytes 共需要 6 bits 來找出 data block 中所對應的 byte(s)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>當 load 指令或 store 指令發生 D-Cache miss 時，會先搜尋 MSHR 中是否有對應的 entry</p>
<ul>
<li>如果沒有的話，代表是 primary miss，需將該指令寫入 MSHR 及 Load/store Table 中，並開始處理 D-Cache miss</li>
<li>如果有的話，代表同樣的 D-Cache miss 已經在被處理了 (<code>Issued = 1</code>)，或是即將要被處理了 (<code>Issued = 0</code>)，此時只需將指令寫入 Load/store Table 中即可</li>
</ul>
</li>
<li>
<p>如果 MSHR 或 Load/store Table 滿了，那就無法再處理任何的 load 指令或 store 指令，此時得暫停 pipeline 繼續處理 load 指令或 store 指令，直到有 D-Cache miss 被處理完畢，並釋放 MSHR 和 Load/store Table entries</p>
</li>
<li>
<p>當一個 D-Cache miss 被解決後，會釋放一個 MSHR entry，以及一或多個 Load/store Table entries</p>
<ul>
<li>對於 load 指令，在 refill 完 cache line 後，load 指令所需的資料會被送到對應的 destination register</li>
<li>對於 store 指令，store buffer 中的資料會與讀回來的 data block 合併，並寫回 D-Cache 中；Store buffer 中對應的 entry 也會被釋放</li>
</ul>
</li>
<li>
<p>在現實處理器中，基於硬體面積和功耗的考量，MSHR 和 Load/store Table 容量通常不會很大，一般多為 4 ~ 8 個 entries，也就是同時可以處理 4 ~ 8 個 D-Cache misses</p>
</li>
<li>
<p>對於 out-of-order CPU，由於採用了 branch prediction 及 out-of-order execution，發生 D-Cache miss 的指令有可能是處在 mis-prediction 路徑上的，除了需要 flush 這些指令外，也需要一種機制能夠選擇性的放棄正在被處理的 D-Cache miss，以及將 mis-prediction 指令所對應的 MSHR 和 Load/store Table entries 給釋放，此外，如果已經從下一級記憶體讀取出 data block，該 data block 也不能寫回 D-Cache</p>
</li>
</ul>
<h2 id="963---關鍵字優先">9.6.3 - 關鍵字優先</h2>
<ul>
<li>
<p>當執行一條 load 指令或 store 指令而發生 D-Cache miss 時，會將這條指令所需的整條 data block (e.g .64 bytes) 都從下一級的記憶體讀出來並寫進 D-Cache 中，如果考慮到 prefetching，還需要將相鄰的下一個 data block 也一起讀出來，而且這些 data blocks 的讀取過程是照順序的</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2035.png"></p>
</li>
<li>
<p>如果等到所有的數據都寫進 D-Cache 後才將所需的資料送給 CPU，可能就會讓 CPU 等待一段時間；為了加快執行速度，可以對 D-Cache 下一級的記憶體進行”改造”，使資料的讀取順序發生改變：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2036.png"></p>
<ul>
<li>如果 load 指令或 store 指令所需的資料在 data block 中的第 6 個 word，那麼可以從第 6 個 word 開始讀取記憶體，當讀到 data block 的尾端時，再從頭開始將第 0 ~ 第 5 個 words 給讀出來</li>
<li>當記憶體第一個讀取的 word (i.e. 第 6 個 word) 返回時，CPU 就可以得到所需的資料並繼續執行，D-Cache 會繼續完成剩餘資料的 refill</li>
<li>下一級的記憶體需要額外增加硬體才能支援這種特性，會一定程度上增加硬體的面積及功耗</li>
</ul>
</li>
<li>
<p>這種方式稱為：<code>關鍵字優先 (Critical Word First)</code></p>
</li>
</ul>
<h2 id="964---提前開始">9.6.4 - 提前開始</h2>
<ul>
<li>Critical Word First 由於需要改變資料讀取的順序，因此需要額外增加硬體才能支援此特性；如果不想增加硬體成本，可以改採用<code>提前開始 (Early Restart)</code> 的方法
<ul>
<li>
<p>這種方法不會改變硬體讀取資料的順序，因此不需要增加額外的硬體，當發生 D-Cache miss  的指令所需要的資料被讀出來後，CPU 就可以馬上繼續執行</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch9/image%2037.png"></p>
<ul>
<li>當指令所需的第 6 個 word 被讀取出來後，就可以讓 CPU 繼續執行了，D-cache 會繼續完成剩餘資料的讀取</li>
<li>由於沒有改變資料讀取的順序，因此若是所需的資料位於 data block 比較後面的位置時，這種方法就沒有比較明顯的優勢</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
