<?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>Cache on 0xc0de</title>
    <link>https://0xc0de.xyz/tags/cache/</link>
    <description>Recent content in Cache 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/cache/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; 第 2 章 - Cache</title>
      <link>https://0xc0de.xyz/posts/superscalar-overview-ch2/</link>
      <pubDate>Thu, 13 Feb 2025 00:00:00 +0800</pubDate>
      <guid>https://0xc0de.xyz/posts/superscalar-overview-ch2/</guid>
      <description>&lt;h1 id=&#34;21---cache-的一般設計&#34;&gt;2.1 - Cache 的一般設計&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;L1 Cache 通常分為 I-Cache 和 D-Cache，為 private cache
&lt;ul&gt;
&lt;li&gt;I-Cache 需要能夠在一個 cycle 內，讀取多條的指令&lt;/li&gt;
&lt;li&gt;D-Cache 需要能在一個 cycle 內，處理多條 load/store 指令的訪問，因此需要使用 multi-port 的設計&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Instruction fetch 的時候，會向 L1 Cache 嘗試讀取指令，因此 L1 Cache 也是 pipeline 的一部分&lt;/li&gt;
&lt;li&gt;L1 Cache 必須保持跟 CPU 相近的速度，因此通常是透過 SRAM 實現的&lt;/li&gt;
&lt;li&gt;L2 Cache 通常都是指令和資料一起 share 整個 cache，容量通常以 MB 為單位&lt;/li&gt;
&lt;li&gt;現在大部份的 multi-core 都是 shared 同一個 L3 Cache&lt;/li&gt;
&lt;li&gt;L2 Cache 被訪問的頻率通常不是很高 (L1 Cache miss 才會訪問 L2 Cache)，因此不需要使用 multi-port 的設計&lt;/li&gt;
&lt;li&gt;需要盡可能的提昇 L2 Cache 的命中率，因為如果 L2 Cache miss，且沒 L3 Cache 的話，就需要去訪問 DRAM 了，訪問時間會很長&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;211---cache-的組成方式&#34;&gt;2.1.1 - Cache 的組成方式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Set-associative cache：&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="21---cache-的一般設計">2.1 - Cache 的一般設計</h1>
<ul>
<li>L1 Cache 通常分為 I-Cache 和 D-Cache，為 private cache
<ul>
<li>I-Cache 需要能夠在一個 cycle 內，讀取多條的指令</li>
<li>D-Cache 需要能在一個 cycle 內，處理多條 load/store 指令的訪問，因此需要使用 multi-port 的設計</li>
</ul>
</li>
<li>Instruction fetch 的時候，會向 L1 Cache 嘗試讀取指令，因此 L1 Cache 也是 pipeline 的一部分</li>
<li>L1 Cache 必須保持跟 CPU 相近的速度，因此通常是透過 SRAM 實現的</li>
<li>L2 Cache 通常都是指令和資料一起 share 整個 cache，容量通常以 MB 為單位</li>
<li>現在大部份的 multi-core 都是 shared 同一個 L3 Cache</li>
<li>L2 Cache 被訪問的頻率通常不是很高 (L1 Cache miss 才會訪問 L2 Cache)，因此不需要使用 multi-port 的設計</li>
<li>需要盡可能的提昇 L2 Cache 的命中率，因為如果 L2 Cache miss，且沒 L3 Cache 的話，就需要去訪問 DRAM 了，訪問時間會很長</li>
</ul>
<h2 id="211---cache-的組成方式">2.1.1 - Cache 的組成方式</h2>
<ul>
<li>
<p>Set-associative cache：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image.png"></p>
<ul>
<li>
<p>TLB 和 Victim Cache 通常使用 fully-associative 架構</p>
</li>
<li>
<p>I-Cache 和 D-Cache 通常使用 set-associative 架構</p>
</li>
<li>
<p>Compulsory miss 可以透過 prefetching 來降低其發生的頻率</p>
</li>
<li>
<p>Capacity miss 沒有方法可以降低其發生的頻率</p>
</li>
<li>
<p>Conflict miss 可以透過 Victim Cache 來降低其發生的頻率</p>
</li>
<li>
<p>Set-associative：</p>
<ul>
<li>優點：
<ul>
<li>可以降低 cache miss rate</li>
</ul>
</li>
<li>缺點：
<ul>
<li>需要比對多個 cache line，因此 latency 會比較高</li>
</ul>
</li>
</ul>
</li>
<li>
<p>補充：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%201.png"></p>
<ul>
<li>4-way set associative cache：
<ul>
<li>共 256 個 sets，透過 (Set) Index (8 bits) 來選取</li>
<li>每個 set 有 4 個 ways (i.e. 共 4 條 cache line)</li>
<li>同時間這四個 ways 中對應的 cache line 都會被選中，然後透過比對 Tag 來決定是哪個 way hit</li>
<li>最後再透過 Word + Byte (= Block Offset)，決定 way hit 的那條 cache line 中，要被讀/寫的 word (一條 cache line 通常包含了好幾個 words)</li>
</ul>
</li>
</ul>
</li>
<li>
<p>在實際的實做上，Cache Tag 和 Data 是分開的，也就是：Tag SRAM 和 Data SRAM</p>
</li>
<li>
<p>Tag SRAM 和 Data SRAM 有兩種存取方式：</p>
<ul>
<li>
<p>同時訪問 Tag SRAM 和 Data SRAM</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%202.png"></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%203.png"></p>
<ul>
<li>Address Calculation stage 先計算出 memory 的位址，Disambiguation stage 再透過 LSU (Load-Store Unit) 檢查 load/store 指令之間的 memory disambiguation，確保 load/store 指令之間依賴關係有正確的被處理 (因為指令是 Out-of-Order 執行)，Cache Access stage 再根據 tag 的比較結果，決定存取哪一個 cache set；Tag hit 的 way 就可以同時間訪問其 Tag SRAM 和 Data SRAM，最後在 Result Drive stage 根據 block offset 從 cache line 上取得資料</li>
<li>優點：
<ul>
<li>比先訪問 Tag SRAM，再訪問 Data SRAM 的設計，少了一個 pipeline stage</li>
<li>較適合 In-Order CPU</li>
</ul>
</li>
<li>缺點：
<ul>
<li>較低的 CPU frequency (因為 pipeline stage 比較少)</li>
<li>較大的功耗 (因為多了 Way Mux)</li>
<li>較不適合 Out-of-Order CPU</li>
</ul>
</li>
</ul>
</li>
<li>
<p>先訪問 Tag SRAM，根據 Tag 的比較結果，再訪問 Data SRAM</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%204.png"></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%205.png"></p>
<ul>
<li>Address Calculation stage 先計算出 memory 的位址，Disambiguation stage 再透過 LSU (Load-Store Unit) 檢查 load/store 指令之間的 memory disambiguation，確保 load/store 指令之間依賴關係有正確的被處理 (因為指令是 Out-of-Order 執行)，Tag access stage 可以直接比對 tag 是否 hit，而不需要使用 Way Mux 決定是哪條 way (因為 Data SRAM 還沒有被存取)。Tag hit 的 way 就可以在 Data Access stage 訪問其 Data SRAM，最後在 Result Drive stage 根據 block offset 從 cache line 上取得資料</li>
<li>優點：
<ul>
<li>較高的 CPU frequency (因為 pipeline stage 比較多)</li>
<li>較小的功耗 (因為少了 Way Mux)</li>
<li>較適合 Out-of-Order CPU</li>
</ul>
</li>
<li>缺點：
<ul>
<li>比同時訪問 Tag SRAM 和 Data SRAM 的設計，多了一個 pipeline stage
<ul>
<li>不過 Out-of-Order CPU 可以將訪問 cache 這段時間，透過 reorder 其他的指令來填補
<ul>
<li>因此比較不適合 In-Order CPU</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Fully-associative cache：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%206.png"></p>
<ul>
<li>
<p>所有的 cache lines 的 Tag 都會被比較</p>
</li>
<li>
<p>因此通常都是使用 CAM (Content Address Memory) 來儲存 Tag，SRAM 來儲存 Data</p>
<blockquote>
<p>CAM 是一種類型的記憶體，它允許透過<strong>內容</strong>（而非位址）來存取儲存的資料。這與傳統的 RAM（Random Access Memory，隨機存取記憶體）不同，因為 RAM 透過記憶體位址來讀取或寫入資料，而 CAM 則根據輸入的內容來搜尋對應的儲存單元，並返回匹配的結果。</p>
</blockquote>
</li>
<li>
<p>優點：</p>
<ul>
<li>cache miss rate 最低 (因為只要有空的 cache line 就可以使用)</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>Latency 也是最長的 (因為有大量的 Tags 需要被比較)</li>
</ul>
</li>
<li>
<p>通常 TLB 會使用 fully-associative 來實現</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="212---cache-的寫入">2.1.2 - Cache 的寫入</h2>
<ul>
<li>Self-modifying codes 的執行流程：
<ul>
<li>Self-modified 的 codes 存至 D-Cache → 將 D-Cache 的內容 flush 至 L2/L3 Cache 或是 memory→ Invalidate I-Cache → 下次 CPU fetch 該指令的時候，就會從 L2/L3 Cache 或是 memory 取得並存進 I-Cache 中了</li>
</ul>
</li>
<li>當執行 store 指令的時候：
<ul>
<li>如果 cache line 存在：
<ul>
<li>Write-through：
<ul>
<li>資料會一路寫進 D-Cache → L2/L3 Cache → Memory</li>
<li>優點：
<ul>
<li>設計較簡單，不須額外的 dirty bit 紀錄來哪條 cache line 是 dirty 的</li>
<li>Cache 和 Memory 的資料是一致的，適合 <strong>multi-core</strong> 的系統</li>
</ul>
</li>
<li>缺點：
<ul>
<li>CPU 的效率會降低，因為 D-Cache 同步至 L2/L3 Cache, Memory 的速度很慢</li>
<li>如果寫入了資料只會用過一次，那麼同步至 L2/L3 Cache, Memory 就是多餘的開銷</li>
</ul>
</li>
</ul>
</li>
<li>Write-back：
<ul>
<li>當執行 store 指令的時候，資料只會寫進 D-Cache，並標記該 cache line 為 dirty；只有在該 cache line 要被 evicted 的時候，才會將 dirty cache line 同步至 L2 Cache or L3 Cache or Memory (只需同步至下一級)</li>
<li>優點：
<ul>
<li>資料只須寫入 D-Cache，效率較佳</li>
</ul>
</li>
<li>缺點：
<ul>
<li>不同層級的 caches 資料有可能不一致，會需要處理一致性的問題</li>
<li>需要額外的 dirty bit 來紀錄哪些 cache line 是 dirty 的</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>如果 cache line 不存在 (i.e. Write miss)：
<ul>
<li>Non-write-allocate：
<ul>
<li>直接將資料寫至下一級的 cache 或 memory，不寫進 D-Cache</li>
</ul>
</li>
<li>Write-allocate：
<ul>
<li>如果搭配 Write-through：
<ul>
<li>從下一級的 cache 讀出對應的 cache line 並寫回 D-cache 中，再將 store 的資料更新至該 cache line 中對應的 block</li>
<li>該 D-Cache 的 cache line 會再同步回 L2/L3 Cache, Memory</li>
</ul>
</li>
<li>如果搭配 Write-back：
<ul>
<li>如果該 D-Cache 的 cache line 已經是 dirty 了，需先將該 cache line flush 至下一級的 cache 或 memory</li>
<li>從下一級的 cache 讀出對應的 cache line 並寫回 D-cache 中，再將 store 的資料更新至該 cache line 中對應的 block</li>
<li>只須將該 D-Cache 的 cache line 標記為 dirty</li>
</ul>
</li>
<li>為何需要先從下一級的 cache 讀出對應的 cache line，而不是直接把資料寫進 D-Cache?
<ul>
<li>因為通常 store 只會寫入一個 word 的值 (e.g. 32 bits)，但 cache line 可能是 512 bits，如果直接該 word 寫進 cache，會導致整條 cache line 都變 dirty，當 cache line 被 evicted 的時候，整條 cache line 都會同步至下一級的 cache 或 memory，導致資料不一致
<ul>
<li>因為有可能 cache line 其他 words 在 store 指令執行時，是 invalid 的，例如：第一次寫入該 cache line</li>
<li>除非一條 cache line 有多個 dirty bits 可以 track 各個 word 的 dirty 狀態，不過這樣會需要耗費多餘的硬體，一致性的問題也會更難處理</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>實做通常搭配：
<ul>
<li>
<p>Write-through + Non-write-allocate</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%207.png"></p>
<ul>
<li>Write-through 和 Non-write-allocate 都會將資料寫至下一級的 cache 或 memory</li>
<li>如果是搭配 Write-allocate，需要先從下一級的 cache 讀出對應的 cache line 並寫回 D-cache 中，但 Write-through 又會將資料寫至下一級的 cache 或 memory，前面從下一級 cache 的讀取就是多餘的</li>
</ul>
</li>
<li>
<p>Write-back + Write-allocate</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%208.png"></p>
<ul>
<li>Write-back 搭配 Write-allocate 只須將 cache line 標記為 dirty 就好，不用再同步回L2/L3 Cache, Memory</li>
<li>如果是搭配 Non-write-allocate，資料是直接寫至下一級的 cache 或 memory，不會寫進 D-Cache 的，因此下一次存取的時候 D-Cache 還是會 miss</li>
<li>Write-back + Write-allocate 相較於 Write-through + Non-write-allocate：
<ul>
<li>優點：
<ul>
<li>同步至下一級的 cache 或 memory 的次數較少，效能較好</li>
</ul>
</li>
<li>缺點：
<ul>
<li>設計較為複雜</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="213---cache-的替換策略">2.1.3 - Cache 的替換策略</h2>
<ul>
<li>LSU (Least Recently Used)，近期最少使用法：
<ul>
<li>選擇最近被使用次數最少的 cache line 來替換</li>
<li>每個 cache line 都有個 age 的欄位紀錄被使用的次數，每被使用一次，age 就會 + 1</li>
<li>age 最小的 cache line，就是最近被使用次數最少的，也就是會被替換的 cache line</li>
<li>Directed-mapped cache 不須使用 LSU，因為會被替換的 cache line 都是固定的</li>
<li>Set-associative cache 可以針對 way 來紀錄 age
<ul>
<li>例如：2-way set-associative cache 每個 way 的每條 cache line 可以各自用 1 個 bit 的 age 來紀錄 way 的 cache line 是 LSU
<ul>
<li>當 way 0 被使用時，將 way 0 的 age 設成 1，way 1 的 age 設成 0，代表最近 way 0 有被使用</li>
<li>反之，當 way 1 被使用時，將 way 1 的 age 設成 1，way 0 的 age 設成 0，代表最近 way 1 有被使用</li>
<li>如此，只要找到 age 是 0 的那個 way，就可以將該 way 的 cache line 給取代了</li>
</ul>
</li>
<li>然而，當 ways 數增加時，精準的 LSU 實做會非常昂貴，因此實務上都是使用 pseudo-LSU 的方法來實現：
<ul>
<li>
<p>將所有的 way 分組並分級，每一組 ways 使用 1 個 bit 的 age：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%209.png"></p>
<ul>
<li>共需要 <strong>1 + 2 + 4 = 7 個 bits</strong></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Random Replacement，隨機替換法：
<ul>
<li>不再需要紀錄每個 way 的 age，而是隨機挑一個 way 來替換</li>
<li>優點：
<ul>
<li>設計較 LSU 來得簡單</li>
</ul>
</li>
<li>缺點：
<ul>
<li>相較於 LSU，cache miss rate 會比較高，但隨著 cache 的容量增大，這個差距會越來越小</li>
</ul>
</li>
<li>實務上很難實做出嚴格的隨機，一般是採用時鐘算法 (clock algorithm) 來實現近似的隨機：
<ul>
<li>本質上為一個 counter，每個 cycle 都會 + 1，counter 的 bits 由 ways 數決定 (如 8 ways 就需要 3 個 bits)，每次 cache line 要被替換的時候，就根據當下 counter 的值決定是哪個 way 的 cache line 要被替換</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="22-提高-cache-的性能">2.2. 提高 Cache 的性能</h1>
<h2 id="221--write-buffer">2.2.1  Write Buffer</h2>
<ul>
<li>
<p>當 D-Cache miss 時，需要從下一級的 cache 或 memory 讀取資料回 D-Cache 並寫入 cache line；如果 D-Cache 的 cache line 是 dirty 的，那還需要先將 dirty cache line 寫至下一級的 cache 或 memory，才能再從下一級的 cache 或 memory 讀取資料回 D-Cache</p>
</li>
<li>
<p>由於訪問下一級的 cache 或 memory 的 latency 很長，會影響 cache miss 的處理時間，因此可以引入 write buffer 來解決此問題：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2010.png"></p>
</li>
<li>
<p>Dirty cache line 會先被寫進 write buffer，等到下一級的 cache 或 memory 有空閒的時候，才會將 dirty cache line 寫入</p>
</li>
<li>
<p>Dirty cache line 寫入的時間會因此被隱藏，D-Cache 在將 dirty cache line 寫進 write buffer 後，就可以開始從下一級的 cache 或 memory 讀取資料回 D-Cache</p>
<ul>
<li>對於 write-through 類型的 D-Cache，write buffer 是非常必要的元件</li>
</ul>
</li>
<li>
<p>缺點：</p>
<ul>
<li>會增加系統的設計複雜度：
<ul>
<li>當發生 cache miss 的時候，不只要查詢下一級的 cache 或 memory，同時間也要查詢 write buffer
<ul>
<li>因此 write buffer 通常也是使用 CAM 來實做</li>
<li>當 write buffer 中有 cache miss 所需的數據時，會優先採用 write buffer 的數據</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="222-流水線">2.2.2 流水線</h2>
<ul>
<li>讀 D-Cache：
<ul>
<li>Tag SRAM 和 Data SRAM 可以同時讀取，因此可以在一個 cycle 內完成</li>
</ul>
</li>
<li>寫 D-Cache：
<ul>
<li>讀 Tag SRAM 和 寫 Data SRAM 只能依序完成，因為必須先讀取 tag 並比較，確認要寫的位址在 cache 中後才能將數據寫進 Data SRAM</li>
<li>這些操作很難在一個 cycle 內完成，因此需要對寫 D-Cache 的操作採用 pipeline 的架構</li>
</ul>
</li>
<li>比較經典的設計方式是：
<ul>
<li>
<p>將 tag 的讀取和比較放在同一個 cycle</p>
</li>
<li>
<p>寫 D-Cache 放在下一個 cycle</p>
</li>
<li>
<p>對一個 store 指令而言，就算 cache hit，至少也需要 2 個 cycles 才能完成；但如果是連續的 store 指令，那麼還是可以透過 pipeline，每個 cycle 執行一道 store 指令 (假設沒有 hazard)</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2011.png"></p>
<ul>
<li>Load 指令在 D-Cache hit 的情況下，只需要 1 個 cycle 就可以完成</li>
<li>Store 指令在 D-Cache hit 的情況下，需要 2 個 cycles (讀 tag 並比較 + 寫 data) 才可以完成</li>
<li>當 load 指令執行時，有可能其所需的數據存在 store 指令的 register (Delayed Store Data) 中，而不是來自 Data SRAM，因此需要將 load 指令的位址，和 store 指令的位址 (Delayed Store Addr) 做比較；如果相等，那麼就可以直接將 store 指令的 register (Delayted Store Data) 內容，直接 forward 給 load 指令
<ul>
<li>
<p>E.g.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-0-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-2">2</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-asm" data-lang="asm"><span style="display:flex;"><span><span style="color:#a6e22e">sw</span> <span style="color:#66d9ef">a0</span>, <span style="color:#ae81ff">0</span>(<span style="color:#66d9ef">a1</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">lw</span> <span style="color:#66d9ef">a2</span>, <span style="color:#ae81ff">0</span>(<span style="color:#66d9ef">a1</span>)  <span style="color:#75715e"># Store 指令的 $a0 可以直接 forward 給 load 指令的 $a2
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="223---多級結構">2.2.3 - 多級結構</h2>
<ul>
<li>
<p>一般處理器設計中：</p>
<ul>
<li>L1 Cache 可以採用 <strong>Write-through</strong> 或 <strong>Write-back</strong> 的設計
<ul>
<li><strong>Write-through</strong> 可以簡化 pipeline 的設計，尤其是針對 multi-core，需要處理 coherence 的問題，Write-through 的設計會比較簡單一點</li>
</ul>
</li>
<li>(Shared) L2 Cache 會採用 <strong>Write-back</strong> 的設計</li>
</ul>
</li>
<li>
<p>Inclusive cache vs. Exclusive cache：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2012.png"></p>
<ul>
<li>Inclusive cache：L2 Cache 包含了所有 L1 Cache 的內容
<ul>
<li>優點：
<ul>
<li>可以將數據直接寫進 L1 Cache，因為被 evicted 的 cache line 在 L2 Cache 中也一定存在；直接將數據寫進 L1 Cache 不會引起任何的問題 (假設被 evicted 的 cache line 並不是 <code>dirty</code> 的)</li>
<li>簡化了 cache coherence 的管理
<ul>
<li>在 multi-core 的平台，當其中一個 CPU 更新了其 private cache (e.g. L1 D-Cache) 某個位址的數據 (e.g. 執行 store 指令)，如果其他 CPU 也有相同位址的數據，那就必須將其設為 invalid，以免其他 CPU 存取到舊的值</li>
<li>如果是採用 inclusive cache，那麼只需檢查 last-level (shared) cache (e.g. L2 Cache) 中，是否有該位址的數據，如果 last-level cache 中沒有，那就代表 private cache 中也一定沒有，如此就不用打擾其他 CPU 的 private cache，影響 pipeline 的執行</li>
</ul>
</li>
</ul>
</li>
<li>缺點：
<ul>
<li>浪費了 cache 的空間，實做成本比較高</li>
</ul>
</li>
</ul>
</li>
<li>Exclusive cache：L2 Cache 和 L1 Cache 的內容完全不相同 (i.e. 互斥)
<ul>
<li>優點：
<ul>
<li>避免了 cache 空間的浪費，實做成本比較低</li>
</ul>
</li>
<li>缺點：
<ul>
<li>如果是採用 exclusive cache，那麼在 multi-core 的平台，當某一個 CPU 更新了其 private cache (e.g. L1 D-Cache) 某個位址的數據  (e.g. 執行 store 指令)，那麼每個 CPU 都必須檢查其每個 cache 是否也有包含其位址的數據，如果有的話，就必須將其設為 invalid，也因此會影響 pipeline 的執行</li>
<li>同時，當要讀取的數據不在 L1 Cache，而是在 L2 Cache 時，除了將 L2 Cache 的數據拉回 L1 Cache 外，還需要將 L1 Cache 中被 evicted 的值，寫進 L2 Cache，這種交換過程會降低 CPU 的執行效率</li>
</ul>
</li>
</ul>
</li>
<li>目前大部分的處理器，都還是採用 Inclusive cache 的設計</li>
</ul>
</li>
</ul>
<h2 id="224---victim-cache">2.2.4 - Victim Cache</h2>
<ul>
<li>
<p>有時候，被 evicted 的 cache line 有可能又會馬上被使用，例如：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2013.png"></p>
<ul>
<li>A、B、C 都被分配在同一個 cache set，如果同時間都被頻繁使用 (e.g. A -&gt; C -&gt; B)，那就有可能被 evicted 後，又會馬上被讀回 cache，然後又被 evicted，而且會一直 cache miss</li>
<li>而且 cache 的 ways 數有上限，沒辦法無上限的透過增加 ways 數來解決這個問題；其他的 cache sets 也未必會有同樣的狀況，為此增加 ways 數可能只是浪費空間</li>
</ul>
</li>
<li>
<p>可以引入 Victim cache 來解決這樣的問題，Victim cache 是用來保存最近被 evicted 的 cache lines，因此所有的 cache sets 都可以透過 Victim cache 來”增加 ways 數”</p>
</li>
<li>
<p>Victim cache 通常採用 <code>fully-associative</code> 設計，容量也會比較小 (一般可以存 4 ~ 16 條 cache lines)</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2014.png"></p>
</li>
<li>
<p>一般情況下 Victim cache 的數據跟 L1 D-Cache 的數據是互斥的，CPU 可以同時讀取它們，只要有其中一個 hit，就可以直接使用</p>
</li>
<li>
<p>如果 L1 D-Cache 中沒有找到數據，但有在 Victim cache 中找到，那麼該數據會被讀進 L1 D-Cache，同時間 L1 D-Cache 中被 evicted 的數據，會被寫進 Victim cache，也就是互換了兩邊的數據 (因為 L1 D-Cache 和 Victim cache 是互斥的)</p>
</li>
<li>
<p>Victim cache 可以降低 cache miss rate</p>
</li>
<li>
<p>現代大多數處理器都有採用 Victim cache</p>
</li>
<li>
<p>Filter cache 則是另外一種類似 Victim cache 的設計，它會被放置在 L1 D-Cache 前：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2015.png"></p>
</li>
<li>
<p>當一個數據第一次被使用時，它不會被馬上放進 cache，而是會先放到 Filter cache；只有等到這個數據再次被使用時，才會真的搬進 cache</p>
</li>
<li>
<p>使用 Filter cache 可以防止那些只有偶爾才會被使用的數據佔據 cache 的空間，進而提高 cache 的使用效率</p>
</li>
</ul>
<h2 id="225---預取-prefetch">2.2.5 - 預取 (Prefetch)</h2>
<ul>
<li>硬體預取：
<ul>
<li>指令的 prefetch 是相對容易的，只要在取一個指令進 I-Cache 時，同時間也取其後的幾條指令進 I-Cache 即可</li>
<li>因為有分支指令，所以有可能會 prefetch 錯指令，浪費 I-Cache 的空間
<ul>
<li>
<p>可以將 prefetch 的指令，放到一個單獨的 buffer 中，來避免這個問題</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2016.png"></p>
<ul>
<li>如果 fetch 指令時 I-Cache miss，但有在 Stream Buffer 中找到該指令，那麼 CPU 可以直接從 Stream Buffer 中讀取指令，且該指令會被搬進 I-Cache，Stream Buffer 也會繼續從 L2-Cache prefetch 後面的指令，並搬進 Stream Buffer 中</li>
</ul>
</li>
</ul>
</li>
<li>數據的 prefetch 就相對困難許多，因為數據的使用是沒有一定規律的</li>
<li>一般情況下，當 D-Cache miss 時，除了將數據從 L2 cache 中讀出來外，也會同時 prefetch 下一個數據
<ul>
<li>然後，這種方法並不準確，因為有可能接下來要使用的數據，並不是 prefetch 的數據，prefetch 只是做白工</li>
<li>這種方法目前被廣泛得使用在現代處理器中</li>
</ul>
</li>
<li>Intel Pentium 4 和 IBM Power 5 的處理器中，採用了 Strided Prefetch 的機制
<ul>
<li>Prefetcher 可以自行觀察程式中數據使用的規律，例如第一個數據位於位址 <code>a</code>，第二個數據位於位址 <code>a + 128</code>，第三個數據位於 <code>a + 256</code>，那麼 Prefetcher 就可以預測接下來的數據在 <code>a + 384</code>、<code>a + 512</code>、<code>a + 640</code>… etc</li>
</ul>
</li>
<li>Prefetcher 是否實用，還是得根據實際跑的程式而定
<ul>
<li>Prefetch 錯，就只是增加功耗而已</li>
</ul>
</li>
</ul>
</li>
<li>軟體預取：
<ul>
<li>
<p>如果有支援 prefetch 指令，則軟體可以利用 prefetch 指令來提前將指令 prefetch 進 cache，e.g.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-1"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-1">1</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-2"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-2">2</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-3"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-3">3</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-4"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-4">4</a>
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f" id="hl-1-5"><a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-5">5</a>
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-c" data-lang="c"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> (i <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; i <span style="color:#f92672">&lt;</span> N; i<span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">prefetch</span>(<span style="color:#f92672">&amp;</span>a[i <span style="color:#f92672">+</span> P)]);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">prefetch</span>(<span style="color:#f92672">&amp;</span>b[i <span style="color:#f92672">+</span> P)]);
</span></span><span style="display:flex;"><span>    sum <span style="color:#f92672">=</span> sum <span style="color:#f92672">+</span> a[i] <span style="color:#f92672">+</span> b[i];
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>不過 prefetch 指令有可能會引起 exception，此時有兩種處理方法：
<ul>
<li>處理 exception</li>
<li>不處理 exception，直接將此 prefetch 指令轉為 NOP
<ul>
<li>現代處理器大多採用此方式</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="23---多端口-cache">2.3 - 多端口 Cache</h1>
<ul>
<li>為了提昇效能，處理器必須要能夠在 1 個 cycle 內同時執行多條 load/store 指令，這需要一個 multi-port 的 D-Cache，以便能夠支持多條 load/store 指令的同時訪問</li>
<li>不只 D-Cache，在 superscalar 的架構，還有很多元件都是 multi-port 的，e.g. register file、issue queue、ROB… etc</li>
<li>D-Cache 由於原本的容量就很大，因此採取 multi-port 的設計會對晶片的面積和速度帶來負面的影響，因此需要再採用其他的方法來實現 multi-port D-Cache：
<ul>
<li>True Multi-port</li>
<li>Multiple Cache copies</li>
<li>Multi-banking</li>
</ul>
</li>
</ul>
<h2 id="231---true-multi-port">2.3.1 - True Multi-port</h2>
<ul>
<li>實務上，很難對 D-Cache 採用 multi-port 的設計，因為所有在 Cache 中的控制電路和 data path都需要進行複製</li>
<li>如果要讓 D-Cache 採用 multi-port 的設計，就代表需要：
<ul>
<li>兩套的 Address Decoder，使兩個 ports 可以同時存取 Tag SRAM 和 Data SRAM</li>
<li>兩套的 Way Mux，用來讀取兩個 ports 的數據</li>
<li>兩套的 Tag Comparators，用來判斷兩個 ports 的命中情況</li>
<li>兩套的 Aligners，用來做 word 或 half-word 的讀取</li>
</ul>
</li>
<li>Tag SRAM 和 Data SRAM 並不需要複製一份，但它們當中的每個 cells 都需要同時支持兩個並行的讀取操作 (對於一個 SRAM cell 來說，不需要兩個 write ports，因為無法同時對 SRAM cell 同時寫 0 又寫 1)</li>
</ul>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2017.png"></p>
<ul>
<li>由於 multi-port D-Cache 的設計需要將很多電路都複製一份，增大了面積；同時 multi-port SRAM cell 需要驅動多個 read ports，功耗也會隨之增大；因此實務上並不會採用 multi-port 的設計</li>
</ul>
<h2 id="232---multiple-cache-copies">2.3.2 - Multiple Cache Copies</h2>
<ul>
<li>
<p>這種設計方法直接將 Tag SRAM 和 Data SRAM 複製一份：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2018.png"></p>
</li>
<li>
<p>透過將 Tag SRAM 和 Data SRAM 複製一份，SRAM 就不需要採用 multi-port 的設計，雖然可以消除 multi-ports 對處理器效能的影響，但是這種方法消耗了很多的面積，而且需要保持兩邊 caches 的同步：</p>
<ul>
<li>執行 store 指令時，需要同步到兩邊的 caches</li>
<li>其中一個 cache 發生 replacement 的時候也需要對另一個 cache 做同樣的 replacement</li>
</ul>
</li>
<li>
<p>也因此，實務上並不會採用這樣的設計</p>
</li>
</ul>
<h2 id="233---multi-banking">2.3.3 - Multi-banking</h2>
<ul>
<li>Multi-banking 將 cache 分成很多很小的 banks，每個 bank 都只有一個 port
<ul>
<li>
<p>如果在一個 cycle 內，對 cache 的訪問位址是位於不同的 banks 之中，那麼就不會有任何的問題</p>
</li>
<li>
<p>只有當兩個甚至多個對 cache 的訪問位址是位於同一個 bank 之中，才會引起衝突，也就是 bank conflict</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2019.png"></p>
<ul>
<li>Cache line 被分成了兩個 banks</li>
<li>在同一個 cycle 內，如果兩個訪問位址落在不同的 banks 之中，那這兩個訪問就可以同時存取 cache (e.g. Port 0 訪問 Cache bank 1、Port 1 訪問 Cache bank 0)</li>
</ul>
</li>
</ul>
</li>
<li>使用 Multi-banking 仍然需要：
<ul>
<li>兩套的 Address Decoder，使兩個 ports 可以同時存取 Tag SRAM 和 Data SRAM</li>
<li>兩套的 Way Mux，用來讀取兩個 ports 的數據</li>
<li>兩套的 Tag Comparators，用來判斷兩個 ports 的命中情況</li>
<li>兩套的 Aligners，用來做 word 或 half-word 的讀取</li>
</ul>
</li>
<li>但使用 Multi-banking，Data SRAM 便不需要採用 multi-port 的設計，可以提高存取速度，並降低面積</li>
<li>然而由於需要判斷 cache 的每個 port 是否 hit，因此對於 Tag SRAM 來說，仍然需要採用 multi-port 的設計，以便提供多個 ports 同時讀取的功能，或是直接將 single-port 的 Tag SRAM 複製一份</li>
<li>Bank conflicts 是 Multi-banking 的關鍵性能影響因素，解決 bank conflicts 的方法有：
<ul>
<li>使用更多的 banks</li>
<li>提高 banks 的使用效率，使數據不會集中在同一個 bank
<ul>
<li>通常需要 compiler 的配合才能實現</li>
</ul>
</li>
</ul>
</li>
<li>使用 Multi-banking 來實現 multi-port 的功能可以使 cache 的總面積降低，而且不會對處理器的 cycle time 有太大的影響，因此現代處理器通常都使用 Multi-banking 的設計</li>
</ul>
<h2 id="234----真實的例子amd-opteron-的-multi-port-cache">2.3.4  - 真實的例子：AMD Opteron 的 multi-port cache</h2>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2020.png"></p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2021.png"></p>
<ul>
<li>Data SRAM 共 8 個 banks，所以使用了 <code>VA[5:3]</code> 來選取 bank
<ul>
<li>每個 bank 都是 single-port 的 SRAM</li>
</ul>
</li>
<li>採用將 Tag SRAM 複製的方式，每個 Tag SRAM 都是 single-port 的</li>
<li>共需要：
<ul>
<li>兩個 TLB</li>
<li>兩個 Tag Comparators</li>
<li>兩個 single-port 的 Tag SRAM
<ul>
<li>亦可以使用 multi-port 的 Tag SRAM，但面積可能不會減少多少，而且速度還有可能會變慢</li>
</ul>
</li>
<li>除了 Data SRAM 沒有被複製外，基本上其他的電路都被複製了一份</li>
</ul>
</li>
</ul>
<h1 id="24---超標量處理器的取指令">2.4 - 超標量處理器的取指令</h1>
<ul>
<li>
<p>針對 n-ways 的 superscalar CPU，每個 cycle I-Cache 都應該至少能送出 n 條的指令，這 n 條的指令為一組 <code>fetch group</code></p>
</li>
<li>
<p>最簡單的方法就是 I-Cache 每個 data block 為 n-words，每個 cycle 都把整條 cache line 給讀出：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2022.png"></p>
</li>
<li>
<p>如果指令的位址是 n-word aligned 的，那就可以每個 cycle 都從 I-Cache 讀出 n 條指令</p>
<ul>
<li>實際上由於有 branch 指令、exception 等情況，所以指令位址不可能永遠都是 n-word aligned 的</li>
</ul>
</li>
<li>
<p>如果指令的位址不是 n-word aligned 的，那就代表該 fetch group 有可能會落在兩條不同的 cache lines 上，這種情況會導致後續的 pipeline 沒辦法得到充足的指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2023.png"></p>
</li>
<li>
<p>透過 Instruction Buffer，我們可以一次讀取超過 n words 條的指令，並將其存至 Instruction Buffer 中，後續的 decoder 可以從 Instruction Buffer 中讀取指令，這樣就算 I-Cache 沒辦法在一個 cycle 內提供足夠的 n words 條的指令，也可以保證後續的 pipeline stages 可以獲得充足的指令</p>
</li>
<li>
<p>實際上，只有第一個 cycle 沒辦法讀取 n words 條的指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2024.png"></p>
<ul>
<li>在 cycle 2 後，只要後續沒有 branch 指令或 exception，還是可以一次讀出 n words 條的指令</li>
</ul>
</li>
<li>
<p>此外，也可以將 cache lines 長度變長，超過 n words：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2025.png"></p>
<ul>
<li>N = 4，cache line = 8 words，只有在指令的位址落在 cache line 最後 3 個 blocks 的情況，才沒辦法一次讀出 4 條指令
<ul>
<li>缺點：
<ul>
<li>如果 cache size 是固定的，增加 cache line 的 size，會減少 cache sets 的個數，增加 cache miss rate</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>延續上述的範例，實務上也不會使用 8 個 32-bit 的 SRAM 來實做 8 words 的 cache line：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2026.png"></p>
<ul>
<li>
<p>因為 SRAM 周圍也需要放保護電路，如果 SRAM 的個數過多，會導致保護電路也佔用過多的面積</p>
</li>
<li>
<p>要從 8 個 SRAM 選出要輸出的 4 words 也是一件很浪費電路的事情</p>
</li>
<li>
<p>因此，實務上，還是會使用 4 個 SRAM 來實現 8 words 的 cache line</p>
<ul>
<li>
<p>當 tag hit 時，每個 SRAM 的兩行數據都是有效的，可以透過以下的方式重新排序，並選出正確 4 個 words 的指令：</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2027.png"></p>
</li>
</ul>
</li>
<li>
<p>同樣的，如果指令的位址落在 cache line 最後 3 個 blocks，是沒辦法在一個 cycle 內讀出 4 條指令的，因此需要重新排序電路還需要加入指示哪些 words 是 valid 的 signal，才能夠將 valid 的指令寫入 Instruction Buffer 中</p>
</li>
</ul>
</li>
<li>
<p>如果有 branch predictor，I-Cache 在取指令的時候便可得知 fetch group 中哪條指令是 branch 指令；如果預測是 branch taken，那麼 branch 指令後面的指令就不會被讀進 Instruction Buffer 和 pipeline 了</p>
<p><img alt="image.png" loading="lazy" src="/posts/superscalar-overview-ch2/image%2028.png"></p>
</li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
