[TOC] # SOCStudy # Lab ## Lab2 ### Block Design #### ![](https://hackmd.io/_uploads/HJ1VdZjxp.png) #### ZYNQ M_AXI_GP0: PL側的IP core透過AXI-Lite傳輸狀態透過中斷傳達到PS的中斷控制器。 S_AXI_HP0: DMA的資料傳輸到這個接口,由於每個HP接口都具備有Ctrl和FIFO,很適合作為burst傳輸的理想接口。 #### AXI DMA DMA作用:負責搬運資料,期間不會浪費CPU時間,搬運完成再送中斷通知CPU。一般流程: 1.CPU或是外部設備 向DMA發送傳輸資料的請求並設置好參數(data、source/destination address、R/W......) 2.DMA 開始傳輸資料,若destination是記憶體的話,DMA需要先去向CPU發出請求,讓DMA能夠去存取記憶體的匯流排 3.完成傳輸後,DMA會再向CPU發出中斷,表示傳輸已經完成。 M_AXI_MM2S:DMA的讀取通道,從DDR讀取資料。 M_AXI_S2MM:DMA的寫入通道,將資料寫入DDR中。 M_AXIS_MM2S:DMA將資料傳送至具有Stream的IP。 S_AXIS_S2MM:DMA將資料從具有Stream的IP中將資料讀入。 (AXI後面有S的是面向具有Stream的IP) #### AXI Interconnect Interconnect 的作用: 用於確定數據傳輸的控制和方向。這種模型有助於確保數據的有效交換,同時提供了對資源共享和管理的更多控制。 **ZYNQ 在連接週邊的時候 都會需要有Interconnect去負責傳輸, 當有多個週邊時,則需要去配置 Interconnect 使用多個Port。 Port: (Port Name 前面通常都會去標示M or S) Master: 通常是發起數據傳輸的一方,它控制數據的發送和操作。主要負責發出數據讀取或寫入的請求。 Slave: 通常是接受來自Master Port的命令和數據傳輸的一方,它根據Master Port的指示進行操作。 ### Overlay [參考資料連結](https://pynq.readthedocs.io/en/v2.6.1/pynq_libraries/allocate.html#pynq-libraries-allocate) #### 1. Read Write read(0x00) 讀取0x00的值 (限制address必須是4的倍數) (read(0x00) & 0x4) == 0x0 抓出特定的bit去比較 因為address被限定為4的倍數 若要比較特定的bit 需要用這種方法 write(0x00, 0x11) 寫入0x11 到 Address 0x00 ### IO Mapping #### 1. 位置 solution1/impl/misc/drivers/xxx_0/src/xxx_hw.h ** xxx 是你的design name #### 2. python 知道IO Mapping後 就能透過Overlay提供的read, write去操作 ### C++函數參數類型 合成結果 #### 介紹 函數參數的合成結果 跟 它本身是什麼類型有關,Interface並不會去影響 #### 1. Pointer 如果C++函數的參數是用pointer方式給的話,那在給資料的時候,也要給Address EX: 像是用Interface = m_axi傳大量的Data的話,一般會用pointer的方式去傳, 那這樣在jupyter上要給它資料時,需要使用下面的方法 首先,先去建立一個buffer 要用Overlay的API去allocate inBuffer0 = allocate(shape=(numSamples,), dtype=np.int32) 再來,把buffer的device_address寫到IO Mapping所對應的位址 write(0x1c, inBuffer0.device_address) #### 2. Array 合成出一塊Memory ### HLS Interface #### 1. depth HLS Interface的depth 與 testbench 測試的數量 相關 若testbench 宣告了一個600的array 那在HLS設計時 就需要去assign一個depth=600的Interface給它(depth不是每個Interface都會有) EX HLS.cpp void func(int* a, int* b){ #pragma HLS INTERFACE m_axi depth=600 port=a //這個pointer 在testbench中 是要傳遞一個大小為600 array的pointer } HLSTester.cpp int main(){ int a[600], b[600]; func(a, b); } #### 2. block level protocol with port level protocol 問題: #pragma HLS INTERFACE ap_ctrl_hs port=return 這樣會產生port for ap_ctrl_hs 不知道應該由誰去驅動這個block protocol 解決方法: 可以用Interconnect 去連接ZYNQ的M_AXI_GP來去控制該Design or #pragma HLS INTERFACE s_axilite port=return #pragma HLS INTERFACE ap_ctrl_hs port=return //猜測: 可能是因為直接被整合進Control Register裡面(?) 總之 s_axilite 除了可以給port level以外 還能給block level ### C++ 語法 #### 1. system() 代表去執行系統命令 EX: int return_val = system("ls"); //執行該cpp檔後 就會在終端上 執行ls指令 if(return_val == 0) //成功 else //失敗 #### 2. volatile 告知Compiler 這個變數再執行的時候,有可能會被其他program或其他原因給更改, 它是屬於揮發性的數值,因此在每次讀取該變數的時候,都要去讀該位置的數值,不能 去優化它。 例如: 在MMIO中,它的數值有可能會被其他硬體給更改掉,而Compiler只會看到C program 的情況,並不知道該變數還會被更改,就有可能會直接拿之前的值來去餵給下個指令。 ### linux 命令 #### 1. diff 去比較兩個檔案有什麼不同 #### 2. 絕對路徑:路徑的寫法『一定由根目錄 / 寫起』, 例如: /usr/share/doc 這個目錄。 **注意 在寫絕對路徑的時候 前面不需要加上 "." 相對路徑:路徑的寫法『不是由 / 寫起』,例如由 /usr/share/doc 要到 /usr/share/man 底下時, 可以寫成: 『cd ../man』這就是相對路徑的寫法啦!相對路徑意指『相對於目前工作目錄的路徑!』 #### 3. pwd 可以顯示它目前所在的絕對路徑 ### Error #### 1. HLS設計的Input 並沒有適合的port可以連接 #### ![](https://hackmd.io/_uploads/HyLJhQBxa.png) #### ![](https://hackmd.io/_uploads/S17RoQBgp.png) #### 2. RunTimeError : Unable to find metadata for bitstream .hwh 和 .bit 需要取相同的名稱 和 放在相同的folder內 EX: xxx.hwh, xxx.bit # 講義 ## Kernel IO Interface ### 介紹 在Top Module中,函數和函數參數各自都需要有Interface去讓該函數 能夠與其他Module溝通,其他Module可能是自己寫的Module 也可能是 系統的其他硬體資源,而溝通則代表的是函數何時會被呼叫到,對於信號就是 何時會被觸發。 ### Block IO #### 介紹 用於提供訊號來控制函數何時開始操作 當Block IO觸發了Module的start訊號後,Port IO將會負責接下來資料的輸入與輸出 #pragma HLS INTERFACE <type> port=return #### ap_ctrl_none no handshake on block level #### ap_ctrl_hs No Pipeline Serve : one execution request a time. #### ap_ctrl_chain Protocol to support pipeline chains: Pipeline ### Port IO #### 介紹 針對函數參數的Interface #### 描述![](https://hackmd.io/_uploads/BykM6vVlp.png) #### 預設![](https://hackmd.io/_uploads/ByxyqzeHlp.png) **在Output, Inout 的部分 有些C語言實參類型在接口協議中為不適用 代表在寫HLS.cpp時,Output, Inout的部分 不能宣告為該C實參類型 EX call by value的參數 不能被宣告為Output or Inout. #### ap_none 若指名為ap_none的話 會被合成出port #### ap_stable #### ap_hs ap_ack: ap_vld: ap_ovld: #### ap_memory, bram #### ap_fifo #### ap_bus #### s_axilite AXI4 Lite : 適用於少量資料 #### m_axi AXI4 Master : 適用於大量資料, array、pointer 允許在全域記憶體(DDR)中讀取和寫入資料, 代表這將能夠在不同資源間進行資料的共享 例如:主機與Kernel之間 ##### Max burst length <= 256 Max transfer size <= 4kB Bus width :power of 2 between 32 and 512 ##### offset offset=off Vitis HLS會為m_axi設定base address,而在運行時間內無法更改base address. **在使用offset=off時,一定要再去外掛一個Interface 用來控制讀寫請求, 否則m_axi Adaptor會不知道什麼時候要讀寫,只有在offset=slave時 才不需要。 offset=direct Vitis HLS會在IP上產生連接埠用於設定位址,它支援在運行時間內更新位址,即可以透過同一個m_axi介面來讀取和寫入不同位置 offset=salve IP將由主機透過s_axilite來控制,當變成從模式時,讀寫請求將不再由該信號所決定,而是由主機(Host)來決定 bundle Vitis HLS會將具有相容性的函數綑綁到單一m_axi介面適配器上, 這可以減少AXI邏輯來節省資源,但也會限制了Kernel的效能, 因為所有記憶體傳輸都必須經過同一個連接埠 **若不特別指名綑綁名稱的話,它們將會被綑綁在一起 #### ![](https://hackmd.io/_uploads/rk_qOu4g6.png) #### ![](https://hackmd.io/_uploads/B1JnOuNe6.png) HLS 命名型態 需要將該函數變數型態應該為 Array, Pointer Overlay庫使用方法 1.先去建立一個buffer 要用Overlay的API去allocate inBuffer0 = allocate(shape=(numSamples,), dtype=np.int32) 2.將buffer的device_address寫到IO Mapping所對應的位址 write(0x1c, inBuffer0.device_address) 3.對inBuffer 做讀寫操作 inBuffer[:] = 123 #### axis AXI4 Stream(axis) 適用於大量資料, array, input, output 但不適用於inout HLS 命名型態 命名型態為 hls::stream<value_t>,其中value_t是函數型態, 而在函數間的傳遞時,應該使用 Reference或Pointer的方式去傳遞。 Function value_t valTemp = pstrmInput->read(); //讀取Input的資料進來 pstrmOutput->write(valTemp); //輸出資料到Output (Blocking) val = pstrmOutput->write_nb(valTemp) //輸出資料到Output (non Blocking ) //如果有傳輸成功返回1,反之 返回0 Overlay庫使用方法 1.先去建立一個buffer 要用Overlay的API去allocate一個buffer inBuffer0 = allocate(shape=(numSamples,), dtype=np.int32) 2.對inBuffer 做讀寫操作 inBuffer[:] = 123 3.Call DMA 的function ipDMAIn 是block design裡面DMA的實體 dma = overlay.axi_dma ipDMAIn.sendchannel.transfer(inBuffer0) //傳送資料到HLS Design ipDMAOut.recvchannel.transfer(outBuffer0) //從HLS Design接收資料 ipDMAIn.sendchannel.wait() //確保傳輸完成 ipDMAOut.recvchannel.wait() //確保傳輸完成 ## HLS Array ### Pipeline #### ![image](https://hackmd.io/_uploads/rknxvybVa.png) Initiation Interval(ii): 下一筆資料須要等多久 才能開始計算 讀、寫、算 由於它是使用不同資源的關係,因此它們之間能夠做pipeline 來加速完成的時間。 ### Roll(HLS Default) #### ![image](https://hackmd.io/_uploads/ByHwd1-4a.png) 它會把累加的計算,用一個ACC Reg 的方式去實現。 這樣做會需要很長的clock cycle 去完成計算, 但它的Fmax 能夠增加,且它的Gate Count 也會比較低。 ### Unroll #### ![image](https://hackmd.io/_uploads/ByntO1b46.png) #pragma UNROLL [factor = n] : factor 代表 它要用幾層迭代 做為一個單位 它會將累加的部分 用Combinational logic 去實現, 速度上會比較快,只要1個clock cycle 就能完成, 但它的Fmax 會非常糟,且它的Gate Count 也會非常大, 除此之外,還有一個重點是 **所有迭代 所需要的資料有沒有辦法在同一時刻抵達 如圖所示,a[4] - a[0] 要在同一時刻抵達,輸出才能正確輸出。 ## Amdahl's Law ### ![image](https://hackmd.io/_uploads/Bk02u6xVT.png) Fraction(enhanced): 代表"優化的那部分" 它的執行時間佔全部執行時間的比例 Speedup(enhanced): 代表"優化的那部分" 它優化前的執行時間 比起 優化完後的執行時間 縮短了1/s 倍 Speedup :優化前執行的總時間(分子) / 優化後執行的總時間(分母)。 代表整體的系統一共Speedup 了多少倍 ## DRAM #### ![image](https://hackmd.io/_uploads/S1Fci3lVT.png) WL 負責選擇哪個word line 會被選擇到,而BL 則是哪個Bit 會被選擇到 ### DRAM Organization #### ![image](https://hackmd.io/_uploads/ry4N22xNT.png) Bank group 組成一個Device, Device Group 組成一個Rank, Rank 也是與BUS 對接的單元 其中Rank 的輸出頻寬與Channel 的頻寬相符。 #### ![image](https://hackmd.io/_uploads/rJ1CHoeE6.png) ### Channel Channel 之間互相獨立。 一個Channel 可以放兩個DIMM module,但這樣會造成兩個DIMM 要去競爭且兩個會互相干擾。 ### Capacity row addr bits: 15 bits -> Total number of row = 2^15 = 32K col addr bits: 10 bits -> Total number of col = 2^10 = 1K Width of each column = 8 bits Number of Bank Group = 4 Number of Banks = 4 32K * 1K * 8 * 4 * 4 = 4Gbits ### Page size 每次Row Access時,載入到Sense Amp Array 的位元數量 DIMM with 64bits(代表一個col 共有64bits) Channel Num. Columns * 64bits = 1K * 8Bytes = 8kBytes ### Access Sequence #### ![image](https://hackmd.io/_uploads/Bybpdse46.png) 第一步: 送Row Access Command、Col Select 第二步: DRAM Array會將該Row 的Data 送到 Sense amp array(Row buffer) 第三步: 將Sense amp array 的Data 搬到Data out register 第四步: 將Data 傳送出去 ##### Row Access Command(Activate) ###### ![image](https://hackmd.io/_uploads/rkhl9ixNa.png) 去Memory Array 搬運被選擇的Row Data 到Sense amp array 然後 還要將被選擇到的Row Data 去Restore 回去,因為DRAM 一旦讀取完後,電容的電(DRAM 儲存資料的方法)就會消失,需要重新充電才能保持原有的Data。 ###### Trcd (Row to column command delay) 從Memory Array 搬運資料到Sense Amp Array 所需要花的時間。 ###### Tras (Row Access Strobe) Activate 之間 最短的間隔時間。 因為每次讀完後 都需要先重新充電,因此每次讀取的時間不能太接近,否則會來不及充電(Restore)。 ##### Precharge 每次讀取資料之前,都需要對Sense Amp 做Precharge 的動作 ##### Refresh 由於DRAM 的電容會有洩漏的情況,因此需要週期性的對它做充電,也就是每隔一段時間就去讀取資料再寫回去。 #### Preamble 由於一開始沒有讀寫的時候,Data Bus 可能會是在High Impedence(Z), 這時候 去Drive 它傳送資料,第一筆會因為從0開始到1 or -1,只需要half swift 而接下來的資料就要從 1 > -1 、 -1 > 1 (FULL Swift),這樣它們的轉態時間就會不一樣 因此 在傳送資料前,需要有一個cycle去做Preamble 先將Data Bus Drive 到H or L ### Write to Read Turn-around(Twtr) DRAM 在write 時在寫入後,還需要再去檢查是否有正確寫入,因此就會很多IDLE 的時間 利用率下降。 ### Page Management Policies #### Open row 每筆Row 讀出來後,就不會再去更動它 如果下一筆剛好在該Row 能夠直接去R/W(快) 如果下一筆不在該Row上 就要先去Precharge -> ACTIVATE -> R/W(久) #### Closed row 每筆Row 讀出來後,就去Precharge 如果下一筆不在該Row上 需要去ACTIVATE -> R/W(中) 如果下一筆剛好在該Row 也需要去ACTIVATE -> R/W(中) #### Adaptive row 設定Timer 如果長時間沒有R/W 就Precharge #### Application Optimization ##### ![image](https://hackmd.io/_uploads/BJl-Uhe4p.png) 1. DRAM Latency 太長了,可以將Outstanding Request 拉長一點 2. 避免 Read Write Turn Around的情況發生 3. 增加一些local buffer ,減少Random short Access、讀取次數 ##### ![image](https://hackmd.io/_uploads/SkR-DnxE6.png) 將不同的BANK 當作是一個連續的記憶體,這樣DRAM 在讀取一個ROW 後, 就能把散在不同的BANK 的資料 一次讀取出來 ### SRAM #### ![image](https://hackmd.io/_uploads/HkBO_3xNT.png) #### Read Operation ##### ![image](https://hackmd.io/_uploads/SJc1YhgNp.png) 先去選擇想要的Select ,之後存在SRAM 的資料就會透過M5 M6 傳到BL 上。 #### Write Operation ##### ![image](https://hackmd.io/_uploads/rk3l93eEp.png) 先將BL、~BL Charge 到想要的值(BL),然後再打開M5 M6,就能寫入資料 **** M5 M6 的Drive 能力要大於其他的MOS 這樣才有辦法去修改裡面的值。 #### Access SRAM ##### ![image](https://hackmd.io/_uploads/BkdJjhg46.png) Address bus 去做Row Decode,之後Data 就會寫入Bit Line Amplifier ## Peripheral ### Interrupt #### Interrupt(硬體產生) Asynchronous: 外面來的Interrupt。有可能發生在任何一Stage. 但是它會先讓當前指令執行完畢(到write back Stage), 才會去處理中間發生的Interrupt。 #### Exception(軟體產生) Synchronous: 會在執行指令時,才會發生Exception. #### Timer Interrupt 由計時器生成的,用於在特定的時間間隔內中斷正在執行的程序。 這種中斷通常是用來執行定期的任務或確保某些操作在特定時間內完成。 #### Trigger ##### Level-sensitive interrupt: 描述: trigger 線會一直assert 直到Interrupt處理完畢。 當Interrupt 被trigger 後,就會去執行Interrupt Service Routine, 當服務處理完後,就會去叫它將Interrupt 復位, 如果服務完後 Interrupt還是被trigger的狀態,代表還有其他裝置依舊拉著Interrupt,ISR 就會在執行一次Interrupt Service Routine 能夠支持 多個Device共用 Interrupt 線。 ##### Edge sensitive interrupt: 描述: trigger 線會去產生一個rise edge, 之後就會復位, 等待Interrupt Service Routine 去處理。 #### Preemption Interrupt之間有優先權之分,如果高、低優先的Interrupt 被同時觸發, 低優先度的會被高優先度的給蓋掉,ISR會先執行高優先度的。 #### Handler ##### ![image](https://hackmd.io/_uploads/H1vgJQJ4T.png) ##### Hardware 要做的事 1. 紀錄curr PC 到MEPC 2. 紀錄Privilage 到MPP 3. 紀錄MIE(Interrupt enable) 到MPIE 4. 跳到 PC = mtvec(查表 看是發生什麼事 對應的ISR是什麼) 5. 在軟體上定義ISR 應該怎麼處理,處理完後再跳回原PC 6. 存回原本的PC ##### ![image](https://hackmd.io/_uploads/SJTy-Qk4T.png) ##### Software 要做的事 1. 儲存好Register file的值 2. 呼叫Trap Handler 3. 載回Register file的值 4. Return #### Interrupt response latency 由於發生Interrupt以後,會先等到當前指令執行完畢 才會去處理Interrupt, 而x86的指令一般都會比較久,如果要等到該指令執行完畢,可能要非常久以後, 因此有些指令會被abandon,直到處理完Interrupt再重新執行該指令。 #### Max Interrupt rate ##### ![image](https://hackmd.io/_uploads/rJ_wQmyV6.png) ## Pipeline ### Hazard 為了要能夠加速處理,會希望指令能夠同時處理,然而指令之間可能會有所衝突(Hazard) #### Structural Hazard 如果不同運算指令之間 需要使用到相同資源的時候,就會發生互相競爭使用 可能就會有衝突(Hazard)發生。 ##### Data Hazard ###### ![image](https://hackmd.io/_uploads/BJJ-Bbb4a.png) RAW:Read after write. 當前一筆指令還沒有執行完畢寫回去記憶體時, 下一個指令又跑去讀取該register,這樣就會造成資料還沒寫入 後面就開始讀了。 解法1: 在指令中間 塞入NOP(No operation) 解法2: Data forwarding 如果指令的結果已經產生了,只是單純還沒有寫入Data Reg file,那這樣就能夠在產生的地方直接拉去下一筆指令的運算單元或是需要的地方,不必等到寫回Data Reg file 然後再去Data Reg file讀取。 解法3: load to use. 從Memory load進去的值,直接從memory拉到其他指令需要的地方。 **** 在做Data forwarding的時候,不要做串聯的動作,像是Data Memory的output 拉到 Reg file 的input ,這樣它的critical path會太長,寧願多塞一個stall 也不要將critical path拉長。 ###### ![image](https://hackmd.io/_uploads/B1M6EWZN6.png) WAW: Write after Write. 兩個指令都要對同一個地方做寫入,結果後一個指令因為比較快(執行cycle短) 就先被寫進去, 而前面的指令則晚一個CLOCK寫入,導致前面的指令覆蓋掉了後一個指令的數值,最後 記憶體所存的數值就不是正確的數值. 解法: 其實這個只是Register Name 的問題,只要將兩次寫入的Register 換成是不同的Register 就能夠解決。 WAR: Write after read. 後面的指令搶先前一個指令寫入,導致前一個指令所讀取到的數值是後面指令所寫入的數值. 解法: 其實這個只是Register Name 的問題,只要將兩次寫入的Register 換成是不同的Register 就能夠解決。 ##### Load to Use Hazard 由於lw 指令一定要在第四個clock 才會從Memory 中將值輸出出來,並在下個clk 存回Register file 中,如果此時下個指令要使用到該Register 的話,就一定會來不及, 此時就一定要用Stall 去避免Hazard 的發生。 ##### Control Hazard 在branch指令中,它需要等待到執行完運算後,才能夠知道是否要跳,因此我們會先在結果出來前,先去猜測它是否會跳,如果猜對的話 就能加快一些cycle 只是預測branch時,有的時候可能會猜錯,如果猜錯的話,需要將偷做的指令做清除flush的動作,這樣的overhead會很大。 ## SuperScalar ![image](https://hackmd.io/_uploads/rJBW663Xp.png) ### Front end 在執行指令的時候,能夠去多個指令一起去擷取,因此會有一個Fetch Queue 讓Instruction Cache能夠去接收instruction,接下來就會進入到instruction buffer 準備輸入到 Decode 去做 Rename, 然後在到excution core 去做運算執行,而在運算執行是能夠以"out of order"的方式去執行,但是最後到了commit時,一定要in order,因為如果commit完後,才發現出了問題,就不能夠再做修改了,就會造成錯誤執行指令。 #### Register renaming 在core 的reg file map 中,程式只能看到32個register,但實際上 在core裡面 其實是有128個register可以使用的, 也因此在執行程式的時候,core能夠利用rename的方式,去盡量避免dependency的情況,而少了dependency也就意味著它在pipeline上 就能夠更容易地提高core utilization。 ##### ![image](https://hackmd.io/_uploads/Hka0e0nm6.png) raw instruction 與 renamed instruction 就明顯少很多dependency (然而這只對運算reg有效 如果是對physical memory 的話 就沒辦法用rename的方式去加速 因為它一定要寫入同一個記憶體(SRAM)上) ##### ![image](https://hackmd.io/_uploads/HJlTWC2X6.png) 這後面的read register 就是使用那128個register,因此在issue之前就需要先做好rename的動作。 #### Out of order(在issue中 透過獲取write back時間(write back時間是固定的,因此能夠得知哪些reg 在什麼時間會avaliable)、commit的結果,得知哪些指令能夠立即執行) ##### ![image](https://hackmd.io/_uploads/H12PMR37a.png) 隨著time的增加 有些operand會轉變成ready,能夠馬上執行,即使中間還有一些operand 還處於not avaliable的狀態 當operand 都ready的時候,還要再去等function unit(乘法器... 等)是否avaliable。 #### Memory Load and Store ##### ![image](https://hackmd.io/_uploads/rkKKHR3QT.png) **** 對於store 必須要謹慎,因為如果store一個錯誤的值的話 需要另外花很多cycle去彌補這個錯誤。 **** Issue window 因為是out of order,因此寫入store queue 可能會是out of order,然而執行store queue的時候,就一定要按照program order的順序。 **** load 的執行優先度一定要高於 Store,因為core才能夠及時去得到他想要的operand,加快運算速度。 Store to load forwarding : load 需要去snoop Store queue裡面有沒有它目前想要的值,如果有的話 要從裡面拿,因為那才會是最新的值。 #### Mispredicted branches ##### ![image](https://hackmd.io/_uploads/SJxJFChQa.png) ##### ![image](https://hackmd.io/_uploads/H1hZq0hQa.png) 要寫回Arch Reg file(32 reg)時,一定要按照program 順序,因此需要有一個reorder buffer去按照順序寫進去(綠色代表完成),如果有發生Exception的話 就從那個operand 往後flush就好。 #### Multithread 在一個core 中,能夠去一次儲存多個thread的資料(reg file、PC、CSR ...), 好處是 content switch 快速。 ## AXI ### 介紹 支援多個outstanding address. AXI協議共有五個通道 並且通道之間都是獨立的: (1)寫入地址通道(AW): 傳輸寫入操作的位址與對應的控制資訊 AWADDR: 下一次Burst傳輸的寫入地址 AWBURST: 選擇Burst的類型,FIXED, INCR, WRAP, REVERSED AWSIZE[2:0]: Burst傳輸中,每個資料傳輸(transfer)的大小 ** 000->1(2^0) bytes in a transfer, ...... 111->128(2^8) bytes in a transfer AWLEN: Burst傳輸中,資料傳輸(transfer) 的全部數量 ** 整個傳輸大小: AWLEN * AWSIZE (2)寫入資料通道(W): 傳送寫入資料相關資訊 WDATA: 寫入數據(可以是1, 2, 4, 8, ......128bytes) WLAST: 代表該次傳輸為最後一次 (3)寫入應答頻道(B): slave回傳寫入回應訊息 BRESP: 寫入狀態回應,OKAY, EXOKAY, SLVERR, DECERR (4)讀取位址通道(AR): 傳輸讀取操作的位址與對應控制資訊 與AW一樣 (5)讀取資料頻道(R): 傳送讀取資料同時也傳送 slave 的回應資訊 RDATA: 讀取數據(可以是8 16 32 64 … 1028bits) RLAST: 代表該次傳輸為最後一次 RRESP: 讀取狀態回應,OKAY, EXOKAY, SLVERR, DECERR **其中寫入是由Master端寫入資料到Slave端,而讀取則是Master端去讀取Slave端的資料 **每個通道都具備有”各自”的Valid-Ready 握手協議,也就是說通道是依照握手協議來去判定通道內的資料是否為有效的 **只有列出一部分資訊,若想要完整的資訊,請到參考網址。 參考網址: https://zhuanlan.zhihu.com/p/422561722 https://zhuanlan.zhihu.com/p/46538028 https://xilinx.eetrend.com/blog/2021/100067273.html ### Transfer & Transaction Transfer 是只有單一組的Handshake(valid & ready) (必須要in order 同一個Slave) Transaction 是整個 write address、write data、write response的handshake.(不同Slave之間可以是out of order) ### Write Dependency #### ![image](https://hackmd.io/_uploads/HyQDKQkVp.png) (有雙箭頭的代表有先後順序) AW、W 之間並不一定要誰先傳,可以是AW 先傳,也可以是W 先傳, 但是AW 的order 一定要跟W 的order一樣。 ### Read Dependency #### ![image](https://hackmd.io/_uploads/rydei71NT.png) RVALID 一定是在AR 接收完資料後,才會去assert RVALID ### Burst AXI 是一個burst-based 協議,AXI 傳輸事務中的資料傳輸以burst 形式組織,稱為AXI Burst。 每個傳輸事務包括一至多個Burst。 每個Burst 中傳輸一至多個數據,每個數據傳輸稱為AXI Transfer。 #### Burst Type ##### Fixed 在burst中,它的傳輸位址是FIXED的,適用於FIFO ##### INCR 在burst中,每次傳輸的位址比前一個變大,適用於Bloxk TRANSFER、Memory讀寫。 ##### WRAP 類似於INCR,每次傳輸的位址都會比前一個大,但不同的是當它達到limit時, 會返回低addr 繼續傳輸。 對於WRAP 模式,突發傳輸長度僅能為2,4,8,16 在一次突發傳輸中,位址不能跨越一個4KB 分區 **** Critical Word First 在Cache中,Cache Line一次傳入的大小為64bytes 而AXI 一次傳輸大小為64bit,意味著要傳入完整的Cache Line需要有八次的burst, 而此時,如果CPU是要第7個burst 的資料,就需要等待7*4個CPU週期,這樣會非常慢。 事情並不必然非得如此。記憶體控制器能夠以不同的順序隨意請求快取行的字組。 處理器能夠傳達程式正在等待哪個字組––即關鍵字組,而記憶體控制器能夠先請求這個字組。 最後,AXI透過WRAP模式去傳輸,先將Critical 的先傳輸進CPU(指定第七個位址),剩下的它會在循環傳輸(8-1-2-3-4-5-6)。 ##### ERROR 當在AXI 傳輸時返回ERROR,這時Interrupt 就會被觸發進入ISR。 ##### Limitation burst length : 256 (AxLEN) burst data size: 128b Max transfer size : 4KBytes (256 * 128b) #### Outstanding Transfer ##### 關於 ###### ![image](https://hackmd.io/_uploads/S1y418JVp.png) Outstanding 是代表Master 不需要去等待前一筆資料回應,就能直接傳送下一筆資料 而這對於Memory Access很重要,因為CPU從發Request 到DRAM,再從DRAM 回傳到CPU 這中間可能會經過非常多cycle,如果每一筆資料都要去等的話,那它的Utilization 就會變得非常低。 ##### 如何去提高Utilization ###### ![image](https://hackmd.io/_uploads/SJ7-RHJ46.png) 假設CPU 發出Request 到從DRAM 得到資料之間的時間為 L(latency), 而 每次Transfer 的length 為 B ,那這就表示 我的outstanding 數量K 要滿足 K * B = L ,因為這樣就能夠在每筆資料接收的時間,剛好下一筆就馬上進來了。 只是你的buffer 也需要有相對應的大小(K * B),因為你發出的Request 如果來不及收的話,就會造成資料溢出的情況發生。 ##### HLS HLS 觸發條件: For-loop + PIPELINE Only one read and one write is allowed in a for loop Assign_loop : for(i=0; i<20; i++){ #pragma HLS PIPELINE a[i] = buff[i]; } // 建議每個Loop 都要有一個名稱 在m_axi介面下,要如何去定義FIFO buffer的大小,讓Bus utilization能夠最高: 1. max_read_burst_length, max_write_burst_length 在HLS設計停止前 在沒有回應的情況下 可以向AXI4匯流排發出多少的讀取/寫入請求 2. num_read_outstanding, num_write_outstanding 在burst transfer期間所讀取資料的最大數量 3. FIFO buffer size = num_read_outstanding * max_read_burst_length * word_size ### FIFO Buffer #### ![image](https://hackmd.io/_uploads/HkxSLRfPT.png) ## Device Address Decoding ### ![image](https://hackmd.io/_uploads/rkkIDI1E6.png) 在High Order 的Address 一般會放Device Address 而Low-Order 的Address 則是放 Internal register (write, read ...) ### MMIO ### ![image](https://hackmd.io/_uploads/BJ0XdL146.png) 一般PROCESS 在Write出來,都是用broadcast 的方式, 而Read 則需要有Address Decoder 去分析是哪個Device 輸出的資料, 不過Process 一般都會有自己的Address Decoder 去告知現在Processor 要去讀哪個Device 的資料, 因此不必自己在做一次Device Address Decode,只需要去做Internal Register 的Address Decode 就好。 ## Cache ### Physical Address Physical Address 是程式實際存在Main Memory 中的地址(到Main Memory 讀取Data 時的位址)。 ### Virtual Address Virtual Address 是在同一個程式中的相對位址(CPU 在執行時所使用的位址)。 **** CPU 在執行時 是使用Virtual Address,因為CPU 所執行的Assembly Code 都是用相對位址去表示Address 的 這是因為OS 沒辦法保證當程式被執行時,它會存放在Main Memory 的哪個位址,因此如果程式在編譯時,只能用Virtual Address 來去標示位址,因為這是程式本身能夠確定的資訊。 ### TLB TLB存儲Virtual Page 和對應的Physical Page Number之間的映射。當程式訪問一個Virtual 位址時,CPU首先查詢TLB。 如果TLB中有這個Virtual 位址的映射,那麼CPU直接使用TLB中的Physical 位址; 否則,CPU需要進行一次Page Table 查詢,將Virtual 位址轉換為Physical 位址。 ### Cache 的結構 #### ![image](https://hackmd.io/_uploads/HJJ4SfEVyg.png) #### ![image](https://hackmd.io/_uploads/HJszGMVNkg.png) 每個 Cache Set(index) 內可能包含多個Cache Line(way) one-way set associative(Direct mapped): 一組set 中只有一個cache line two-way set associative:一組set 中有兩個cache line ... Address 只要符合{tag, 000, xxx} 它就會被映射到set_index=000 中 然而,是放到set_index=000 的哪條cache line 則不一定要看當時replacement policy 所選擇的 每個cache line 都會有一個tag 來標示該cache line 是哪個address 的 cache line 的大小可以超過1 word(4 bytes),當大小超過1 word 時,就會需要block offset 來分辨在該set 中的cache line 中的偏移量 此時的address 就會被切分成 {tag, set_index, block_offset, byte_offset} **** cache 在載入時都是以cache line 作為單位 ### Memory mapping ##### Direct mapped(one-way associative) ##### ![image](https://hackmd.io/_uploads/S1iCN3JNp.png) 每個Main Memory 位址只能映射到緩存中的唯一一個位置,而不是有多個可能的位址。 每個set 中只有一個cache line,因此,Memory 的地址想要印射到cache 時, 會先透過set index 來區分,然而,由於它只有一個cache line,所以不需要額外的bit 來去判斷是在哪個cache line 中 這也導致相同set index 的Memory 地址只能映射到單一個set 中 EX: 以上圖為例8 個set, 1 word cache line 在Memory 中,所有的{xxx, 000, 00(word offset)} 的地址只能映射到cache 中的set0 的cache line 中 在Memory 中,所有的{xxx, 001, 00(word offset)} 的地址只能映射到cache 中的set1 的cache line 中 #### Set associative cache ##### ![image](https://hackmd.io/_uploads/rJ9eK2JNp.png) n-way associative 每個set 中含有n 個cache line,位於該set 的Memory 可以隨機放入其中一個cache line 中 EX:以上圖為例 4組set,2 cache line/set 在Memory 中,所有的{xxx, 00, xxx} 的地址能夠映射到cache 中的set0 的兩個cache line 的其中一個 在Memory 中,所有的{xxx, 01, xxx} 的地址能夠映射到cache 中的set1 的兩個cache line 的其中一個 #### Fully associative cache 它只有一個set,也就是說Memory 的每個位址可以映射到任一cache line 中 EX: Memory = {Tag, set, block_offset} 變成 Memory = {Tag, block_offset} 也就是說,Memory 不再分set,Memory 的每個位址可以映射到任一cache line 中 缺點: 就是它必須要一直比對每一個Tag 來看是否有Hit 因為它沒有分組,所以每個cache line 都比較過一遍 如果有分組的話(set associative ),只要先查看是哪一組的,再去尋找那組中Tag 就好了 #### Scratchpad Memory(SPM) 跟L1 cache 一樣,離CPU非常近,access 只要一個cycle 而它跟L1 不同的是,它能夠由Software 來決定存放的Data, 這樣做 最大的好處是 能夠預知timing,不會有cache miss之類的去干擾, 在real-time application 是蠻重要的。 ### Virtually Indexed Virtually Tagged Cache ###### ![image](https://hackmd.io/_uploads/ryedg6kVT.png) cache Address的Tag 和Index 都是Virtual address 優點是不需要轉換Virtual Address 到Physical Address 就能夠進行cache 查找。 這減少了cache 訪問的延遲,因為不需要等待Address 轉換的結果。 ### Virtually Indexed Physically Tagged cache ###### ![image](https://hackmd.io/_uploads/Bk5AeayEa.png) ###### ![image](https://hackmd.io/_uploads/rJOk-pJNp.png) cache Address的Tag 是Physical address,而Index 則是Virtual address ### Physically Indexed and Physically Tagged cache ###### ![image](https://hackmd.io/_uploads/rJeExa1Va.png) cache Address的Tag 和Index 都是Physical address ### Replacement ##### Direct mapped cache 由於它的記憶體只能存放在一個特定的cache line中,因此它只有一個選擇。 #### LRU 最少被使用到的,就會先被替換掉 ###### ![image](https://hackmd.io/_uploads/rywX5hyET.png) 沒有被使用到的 就會加一,如果要替換的話,最大的會被優先替換。 ### Cache Policies #### Write through 當Cache 被修改掉後,Main Memory 就會被同步修改 #### Write back 當Cache 被修改掉後,會先去設dirty flag 代表該cache line 有被修改過,當它被replace 掉後,才會寫回Main Memory #### Write Once Write through on the first write, write back on all subsequent write(cache Coherence) ### Multi-level Caches #### ![image](https://hackmd.io/_uploads/Sy6532JN6.png) L3 cache: 主要目的是能夠去snoop L1, L2 cache #### Inclusive cache L1有的資料,L2 也會有。 #### Exclusive cache L1有的資料,L2 沒有。 #### Performance Average memory access time(AMAT) Cache hit time + cache miss rate * cache miss penalty #### ![image](https://hackmd.io/_uploads/Hkvh0nyNT.png) #### Reduce cache miss ##### In software 1. Prefetching instruction 2. array access ###### ![image](https://hackmd.io/_uploads/Hk5HkT1Na.png) 每次array 存取都是跳著,如果array很大的話,有可能每次都會cache miss 3. Blocking to fit into cache size, e.g. matmul ##### Reduce Hit Time #### MESI protocol #### ![image](https://hackmd.io/_uploads/rJDf8a1V6.png) #### ![image](https://hackmd.io/_uploads/ByCQIayEa.png) #### ![image](https://hackmd.io/_uploads/Hy3VUpyNa.png) #### ![image](https://hackmd.io/_uploads/By-K8aJV6.png) #### ![image](https://hackmd.io/_uploads/rk-3UpJET.png) #### 狀態定義 Modified(修改): 表示該緩存行已經被修改並且與主內存不同步。這意味著它包含了在其他處理器的緩存中可能不存在的數據。如果其他處理器需要訪問這個內存位置,擁有修改狀態的緩存必須首先將數據寫回主內存。 Exclusive(獨占): 表示該緩存行僅在本地緩存中存在,並且與主內存同步。這意味著沒有其他處理器持有相同的內存行。 Shared(共享): 表示該緩存行與主內存同步,並且可能存在其他處理器的緩存中。這表示多個處理器可以共享相同的內存行而不需要修改。 Invalid(無效): 表示該緩存行是無效的,不能被使用。這可能是因為它被標記為無效,或者它包含的數據已經過時。 #### 操作 讀取(Read): 當一個Core 試圖讀取一個cache 位址時,它首先檢查該cache 位址的狀態。如果是 Exclusive 或 Shared,那麼該Core 可以直接進行讀取。如果是 Modified,需要將Data 寫回主cache 並將狀態切換為 Shared。 寫入(Write): 當一個Core 試圖寫入一個cache 位址時,如果是 Exclusive,那麼它可以直接進行寫入。如果是 Shared,那麼需要將狀態切換為 Modified,並更新其他Core 的相應緩存行。如果是 Invalid,需要將狀態切換為 Modified 或 Exclusive,並更新其他Core 。 ## STA ### Clock Uncertainty clock 作用在每個Register 時,中間有很多原因造成每個Reg 在收到Clock 的時候,並不會完美的跟Clock Generator 所產生的Clock 一模一樣,兩者之間可能會有一點點的差異。 set_clock_uncertainty 0.250 -setup [get_clocks BZCLK] set_clock_uncertainty 0.100 -hold [get_clocks BZCLK] #### Clock Jitter and Uncertainty #### Timing Arcs and Unateness #### Skew between Signals ### Clock Domain #### False path (該path 在理論上不應該被檢查的,因為該path 去檢查有沒有violation 是沒有意義的) Synchronizer logic: logic 橫跨兩個clock domain,目的是為了要讓兩個clock domain 的信號能夠同步,而實際上它是並不受constrain 限制,因此正常來說要先在STA tool中,把它設為一個False Path ,STA 才不會將它作為Violation 報錯。 在STA tool上,設定false path : set_false_path -from [get_clocks USBCLK] -to [get_clocks MEMCLK] ## Operating Conditions ### PVT Process, Voltage, Temperature #### Enviroment WCS (Worst-Case Slow): Process-slow, temperature-highest, voltage-lowest (-10%) TYP (Typical) – Process typical, temperature nominal, voltage nominal (1.2V) BCF (Best-Case Fast) – Process is fast, temperature, voltage highest(+10%) ### Power Analysis ML (Maximal Leakage): Process-fast, temperature-highest, voltage-highest TL (Typical Leakage): Process-typical, temperature-highest, voltage-nominal ### Wire Load Model ## Delay ### Slew(轉態時間) Mergeing 如果有多個信號帶有各自的slew 匯聚在同一個logic 時,該logic 在slew 就會有不一樣的表現。 #### Max path 選擇最差的Slew 作為判定標準,這樣才符合Max path #### Min path 選擇最快的Slew 作為判定標準 ## Setup STA Environment ### Create Clok #### ![image](https://hackmd.io/_uploads/HkrP_7EHa.png) ### Clock Uncertainty #### ![image](https://hackmd.io/_uploads/SJlWtQEHa.png) ### Clock Skew ### Clock Jitter Clock Generator 可能因為各種因素,導致它所產生的Clock Rising edge、Falling edge 之間的間隔可能會有一點點的誤差,而且這個誤差是隨機的無法去預測。 #### ![image](https://hackmd.io/_uploads/r1QUtXEra.png) ### Clock latency #### Source latency Clock 從Clock Generator 到目標Register 之間的latency #### Network latency Design 本身的latency set_clock_latency 1 [get_clocks CLK] # network latency, an estimate value before layout set_clock_latency 3 –source [get_clocks CLK] # source latency set_clock_latency 0.7 -source -min [get_clocks CLK] set_clock_latency 0.3 -source -max [get_clocks CLK] #### ![image](https://hackmd.io/_uploads/By3PqQ4H6.png) ### Generated Clock #### Divided Clock 如果要去產生兩個Clock Source ,而其中一個Clock B是由另一個Clock A去Driven 的話,就要使用create_generated_clock 這樣STA 才會去考慮到Clock B的latency,否則它就從Clock B 開始計算,而不是Clock A -> Clock B ##### ![image](https://hackmd.io/_uploads/Bk_ioQVBa.png) #### Gate Clock ##### ![image](https://hackmd.io/_uploads/ry2W3mEBp.png) ##### ![image](https://hackmd.io/_uploads/Bkn4nQVSa.png) 如果Gate 其中一個是由CLK 直接去Driven 的話,那就需要使用create_generated_clock ,若Gate 的輸入全都是由CLK Driven 的Register 去Drien 的話,則使用create_clk 就好 ### Edge Shift #### ![image](https://hackmd.io/_uploads/B1cG0Q4rT.png) #### ![image](https://hackmd.io/_uploads/Byz_TXNHp.png) ### Asic Clock Distribution #### ![image](https://hackmd.io/_uploads/HJ_CTm4ST.png) # Reference Youtube Channel: SoC LAB https://www.youtube.com/@soclab-2023