tag:blogger.com,1999:blog-81421133348155762312024-03-19T03:12:18.948-07:00網路黑貓 BlackCat on Net / Champ Yen我的生活雜記, 包含了心得感想, 電腦新聞和技術探討等網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.comBlogger226125tag:blogger.com,1999:blog-8142113334815576231.post-57105709764095902722023-11-12T04:16:00.000-08:002023-11-12T04:44:17.012-08:00Chisel 學習筆記 - Scala 與 Chisel 基礎語法<p>標題為筆記, 但這篇比較屬於心得</p><p>延續<a href="https://champyen.blogspot.com/2023/11/chisel.html" target="_blank">上一篇</a>的環境建立, 這次計劃藉由 Jserv 最新的<a href="https://hackmd.io/@sysprog/r1mlr3I7p" target="_blank">課程安排</a>來學習 Chisel, 當然個人目標是能夠按照 Jserv 的課程規劃在<a href="https://hackmd.io/@sysprog/2023-arch-homework3" target="_blank">期限之內</a>完成 Lab 3, 由於個人並非 digital designer (現在這年紀也算老貓學新把戲...), 因此對於 chisel 的一切也是從基本開始, 目前計劃於一週內按照 <a href="https://github.com/freechipsproject/chisel-bootcamp" target="_blank">Chisel Bootcamp</a> 的章節完成 Chap 0 ~ 3 的所有內容. (Chap 4 部份在後續會依照進階需求選擇回來看)<br /></p><p>週末花了點時間把 Chap 0 ~ 2 的說明研讀完, code block 的操作也跑過一次流程, 另外是在 try and error 下完成當中的所有 exercise & test. 個人覺得 Chisel Bootcamp 多少是面向俱備 Verilog / HDL 有些許基礎的人. 儘管無基礎並不會造成學習的困難, 但多少難以體會 Chisel 相較之下的優點與強項.<br /></p><p>基本上Chap 0 ~ 2 的重點其實並不多</p><ul style="text-align: left;"><li>Chisel 的特性</li><li>Scala 的基本語法</li><li>建構於 Scala 之上的 Chisel DSL <br /></li><li>電路的基本: module, combinational, control & sequential logic</li><li>除錯與測試方式 </li></ul><p>由於並非第一次學習建構在另一語言的 Domain Specific Language. 過往學習 Halide 的過程就是這樣的一個經驗. 然而與先前 Halide 不一樣的地方是 Halide 所依附的 C++ 個人有所基礎, 但是對於 Scala / Chisel 這樣的組合卻是完全從頭學起. 而這大概有幾個面向是需要反覆思考與刻意練習的.<br /></p><p>第一個主要的點在於 Value 與 Typing System, 類似於 C++ 與 Halide 有著各自的 value & typing system, C++ 主要用在撰寫與控成 Halide code 的流程, Halide 體系著重在產生 target code 上. Scala 與 Chisel 也有各自的 value & typing system. Scala 也是用在撰寫 Chisel 主體與流程上, Chisel 是用以定義 hardware 使用到的 module 與產生 instance. Chap 1 ~ 2.1 強調分清楚 Scala 與 Chisel 的 value & typing system 確實是很重要的一件事. <br /></p><p>第二個是 Chisel 的實作撰寫必需了解的 3 個階段 - circuit generation, circuit simulation 與 testing. (Halide 也有著類似的階段, 甚至都使用 Generator 這樣的名稱) Scala 與 Chisel 在使用上容易搞混的是 generation, Scala 是讓 code generation 能透過參數化來產生對應不同的 circuit (按照教學說法, 這是 Chisel 強大的地方). 因此這些都是在 compile time 決定的, 而 Chisel 的撰寫則是決定了 circuit generation 的結果. 對於一些 if / else 的思考必須區隔希望影響的是 compile time 的行為抑或者是 runtime 時的所需. (習題上來說, 觀察前後輸入的 data type 就可以快速反應) <br /></p><p> 儘管這些並不是什麼艱深難懂的心得, 然而困難的點是在熟悉與快速應用 Scala / Chisel. 重要的還是需要刻意練習來讓思維方式有著正確的對應. 若儘只是研讀一番並不足以深刻體會, 或是習得實務上的撰寫能力. Chisel Bootcamp 的作業與測試非常值得一一花時間下去練習. 藉由這些能夠一次次找到認知錯誤的地方或是盲點. 個人認為能嘗試完成 2.2 Arbiter, 2.3 Finite State Machine 與 2.5 FIR Filter Generator 這三個 exercise 應符合了基本要求. ( 2.2 中提供的 <a href="https://github.com/freechipsproject/chisel-cheatsheet/releases/latest/download/chisel_cheatsheet.pdf" target="_blank">Cheatsheet</a> 第一頁對於閱讀內容與作業相當有幫助)<br /></p><p><br /></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-138288922237084572023-11-10T21:22:00.004-08:002023-11-10T21:22:57.090-08:00Chisel 學習筆記 - 環境建立<p>近日 Jserv 在期 2023 年度課程中仿效 UC Berkeley 的課程, 計劃透過課程 Lab 引導學生學習 Chisel 來實作 RISC-V 核心</p><p>為此他高度簡化了原本 <a href="https://inst.eecs.berkeley.edu/~cs152/sp23/" target="_blank">UC Berkeley 的教程</a>, 建立一個<a href="https://www.facebook.com/groups/system.software2023/posts/754714589814031/" target="_blank">新的 Lab</a>, 目標是帶領學生實作 5-stage pipeline RISC-V processor</p><p>在 <a href="https://hackmd.io/@sysprog/r1mlr3I7p" target="_blank">Lab 3 的 Hackmd 頁面</a>上可以看到幾個章節:</p><ul style="text-align: left;"><li>Install sbt</li><li>Chisel Tutorial</li><li>Chisel Bootcamp & Chisel 介紹<br /></li><li>Single-cycle RISC-V CPU</li></ul><p>這篇文章的重點是放在建立可攜式的 Scala Build Tool 環境, 由於並非每個人都想在日常使用環境與這類開發環境混在一起, 希望儘可能保持系統的單純與乾淨程度. 所以還是選擇透過 <a href="https://apptainer.org/" target="_blank">Apptainer</a> 這個 rootless 的 container 技術來建立環境. Apptainer 的安裝相當簡單, 在官網的 <a href="https://apptainer.org/docs/admin/main/installation.html" target="_blank">Installation 頁面</a>可以找到個別 Linux Distro 的安裝方式. 至於 Apptainer 的使用方式則可以參考個人先前的<a href="https://champyen.blogspot.com/2020/10/linux-developer-container-singularity.html" target="_blank">介紹文</a>.<br /></p><p>對於 <a href="https://www.scala-sbt.org/release/docs/Installing-sbt-on-Linux.html" target="_blank">SBT 於 Linux 上的安裝</a>, 在官網提供了兩個方式, 分別是透過 SDKMAN 與 debian-based repository 的方裝方式, 由於使用 apptainer, 所以我們可以選擇透過 debian-based repository 的方式.</p><p>經嘗試後, 下列為建立 apptainer sandbox 的 script<br /></p><p></p><blockquote><p>#!/bin/sh
export SANDBOX=chisel_env<br />rm -rf $SANDBOX
<br />rm -rf $SANDBOX-overlay
<br />apptainer build --fakeroot --sandbox $SANDBOX docker://ubuntu:latest
<br />apptainer exec --fakeroot --writable $SANDBOX apt update
<br />apptainer exec --fakeroot --writable $SANDBOX apt dist-upgrade -yqq
<br />apptainer exec --fakeroot --writable $SANDBOX apt install openssh-client openjdk-11-jre-headless openjdk-11-jdk-headless ca-certificates-java apt-transport-https curl gnupg -yqq
<br />apptainer exec --fakeroot --writable $SANDBOX sh -c "echo 'deb https://repo.scala-sbt.org/scalasbt/debian all main' | tee /etc/apt/sources.list.d/sbt.list"<br />apptainer exec --fakeroot --writable $SANDBOX sh -c "echo 'deb https://repo.scala-sbt.org/scalasbt/debian /' | tee /etc/apt/sources.list.d/sbt_old.list"
<br />apptainer exec --fakeroot --writable $SANDBOX sh -c "curl -sL 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823' | gpg --no-default-keyring --keyring gnupg-ring:/etc/apt/trusted.gpg.d/scalasbt-release.gpg --import"
<br />apptainer exec --fakeroot --writable $SANDBOX chmod 644 /etc/apt/trusted.gpg.d/scalasbt-release.gpg
apptainer exec --fakeroot --writable $SANDBOX apt update
<br />apptainer exec --fakeroot --writable $SANDBOX apt install sbt graphviz -yqq
mkdir $SANDBOX-overlay
<br /># testing sbt
<br />apptainer exec --overlay $SANDBOX-overlay $SANDBOX sbt --version</p><p></p></blockquote><p>完成後可以看到如下的字串</p><p></p><blockquote>sbt version in this project: 1.9.7<br />sbt script version: 1.9.7 </blockquote><p></p><p>後續使用方式並不難, 只要提供 overlay 與 sandbox 的路徑即可, 所以可以宣告絕對路徑來使用, 假設 sandbox 與 overlay 的目錄為 ~/workspace/ca2023/ 下, 那麼可以如下先建立環境變數:</p><p></p><blockquote><p>$ export SANDBOX_PATH=~/workspace/ca2023/chisel_env<br />$ export SANDBOX_OVERLAY=~/workspace/ca2023/chisel_env-overlay</p><p></p></blockquote><p>如此可以透過下列指令在 chisel-tutorial 來執行 sbt run<br /></p><p></p><blockquote><p>$ apptainer exec --overlay $SANDBOX_OVERLAY $SANDBOX_PATH <b>sbt run</b></p></blockquote><p>當然也可以先執行下列指令, 得到建立環境的 shell 後切到 chisel-tutorial 目錄執行 sbt run</p><p></p><blockquote><p><b>$ apptainer shell --overlay $SANDBOX_OVERLAY $SANDBOX_PATH<br />Apptainer> cd chisel-tutorial/<br />Apptainer> sbt run </b><br />[info] Loading project definition from /home/champ/workspace/ca2023/chisel-tutorial/project<br />[info] Loading settings for project chisel-tutorial from build.sbt ...<br />[info] Set current project to chisel-tutorial (in build file:/home/champ/workspace/ca2023/chisel-tutorial/)<br />[info] running hello.Hello <br />[info] [0.001] Elaborating design...<br />[info] [0.054] Done elaborating.<br />Computed transform order in: 208.3 ms<br />Total FIRRTL Compile Time: 261.5 ms<br />End of dependency graph<br />Circuit state created<br />[info] [0.000] SEED 1699679987261<br />test Hello Success: 1 tests passed in 6 cycles taking 0.008084 seconds<br />[info] [0.002] RAN 1 CYCLES PASSED<br />[success] Total time: 2 s, completed Nov 11, 2023, 5:19:48 AM<br />Apptainer><br /></p></blockquote><p>如此就建立好可以使用的 container, 需要清除也很簡單, 刪除 chisel_env 與 chisel_env-overlay 即可</p><p><br /></p><p>最後對於 Chisel 語言的學習提供的材料是 <a href="https://github.com/freechipsproject/chisel-bootcamp" target="_blank">Chisel Bootcamp</a>, Lab 3 中提供的是 GitHub 上面的兩個方式 - Docker 與 Local Install. </p><p>然而 Chisel Bootcamp 的安裝設定, 並不是 Lab 的主要重點, 個人最建議的方式是直接使用在其 GitHub 主頁面上的 Get Started 一段中提供的 "Try it out <a href="https://mybinder.org/v2/gh/freechipsproject/chisel-bootcamp/master" rel="nofollow">HERE</a>! No local installation required!", 如此可以省下安裝設置 Docker 或是 local 安裝 jupyter 碰到各種問題的時間.<br /></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-54277334485748594802023-11-05T23:54:00.004-08:002023-11-05T23:54:44.405-08:00OpenCL 1.2 之後(個人觀點中)重要的 feature 與 extension<p></p><p>最近因工作緣故, 而看到了使用 <a href="https://github.com/KhronosGroup/OpenCL-Docs/blob/main/ext/cl_khr_subgroups.asciidoc" target="_blank">subgroup</a> 特性的實作, 在第一時間個人難以解讀程式碼的目的與原意, 後來看了原始實作與擴充功能的說明文件才得知 subgroup 的功能特性為何. 主要因為個人對於 OpenCL 2.x 印象僅停留在 Shared Virtual Memory. 然而事實上 OpenCL 2.0 到 OpenCL 3.0 這段期間依然有數個實用的功能特性與擴充 對於 OpenCL 3.0 而言, 這些 2.0 以後加入的新 features 與 extensions 都屬於 extension. 因此可以到 <a href="https://registry.khronos.org/OpenCL/specs/3.0-unified/html/OpenCL_Ext.html" target="_blank">The OpenCL™ Extension Specification</a> 頁面一一查看. 以下列出個人覺得實用的 extensions:<br /></p><h4 style="text-align: left;"><a href="https://registry.khronos.org/OpenCL/specs/3.0-unified/html/OpenCL_Ext.html#cl_khr_image2d_from_buffer" target="_blank">Creating a 2D Image From A Buffer</a></h4><p>許多時候人們很希望能透過 vload / vstore 來存取 buffer, 又希望能夠使用 GPU 的 texturing unit. 在 OpenCL 1.x 的時代, 通常是透過 ION buffer 來重複 mapping 到 Buffer 與 Image, 並且傳入對應的 kernel 中使用. 基本上這需要 GPU vendor 確認 cache coherency 的問題, 甚至提供對應的 driver 支援. 在 OpenCL 2.0 中將類似功能成為 core feature, 也就是 OpenCL 2.0 中是必要的存在. 透過 cl_khr_image2d_from_buffer 這個 extension, 可以先配置 Buffer 後再以此 Buffer 來建立 Image. 一開始提到相同目的.</p><h4 style="text-align: left;"><a href="https://registry.khronos.org/OpenCL/specs/3.0-unified/html/OpenCL_Ext.html#cl_khr_subgroups" target="_blank">Subgroups</a><br /></h4><p>儘管 OpenCL 中提供了 Global 與 Local work size 來對整體工作做切割, 而 Local work size 另一個重要的意義是對應的 workgroup size 是能作同步的最小單位. 在 OpenCL 2.1 中新增了這個 core feature. 在 Workgroup 中能夠再切分一個 1D 的 subgroup, 除了 subgroup 層及的 barrier 做同步外, 也提供了 3 個種類的 kernel function: reduce_op, scan_exclusive_op 與 scan_inclusive_op. 這些 operation 對於 reduction 與 integral 這兩類原本 OpenCL 不適合應付的計算提供了有效的介面, 讓 device 能夠使用硬體支援來處理這類的計算.<br /></p><h4 style="text-align: left;"><a href="https://registry.khronos.org/OpenCL/specs/3.0-unified/html/OpenCL_Ext.html#cl_khr_mipmap_image" target="_blank">Mipmaps</a></h4><p>在 OpenGL 中支援以硬體產生 Mipmaps 來做 texturing. 可以避免 aliasing. 這裡應該是同等的結構. 但除了做原本 antialiasing 用途外, 不知是否有機會利用這個 pyramid 結構來做 CV 應用.</p><h4 style="text-align: left;"><a href="https://registry.khronos.org/OpenCL/specs/3.0-unified/html/OpenCL_Ext.html#cl_khr_integer_dot_product" target="_blank">Integer Dot Product</a></h4><p>OpenCL 中原本只支援 float / double 型別的內積 (dot product), 然而現今 NN 應用中 8-bit integer dot product 的需求相當大. 這個 extension 有效的提供了對於 8-bit dot product 硬體加速的介面. 理論上透過 multiplication 搭配 subgroup 就可以得到相同結果, 但若支援此 extension 且俱備硬體加速, 可以得到更高的 computation throughput.</p><p><br /></p><p>當然 OpenCL 2.x 系列還是有其他像是 SVM, device-side enqueue 與 Pipe 的功能, 有時間再來寫程式並撰文說明這些功能的實際的應用.</p><p><br /></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-52865451065410410472023-10-14T07:26:00.005-07:002023-10-15T18:58:40.124-07:00OpenCL 在 2023 相關的研讀資料 / 工具<p><span style="font-family: arial;">儘管近年已經沒有密集使用 OpenCL</span></p><p><span style="font-family: arial;">但最近同仁因工作而起的 OpenCL 相關實務討論還是令我相當感興趣與熱衷</span></p><p><span style="font-family: arial;">雖然 OpenCL 在現今已不是各家密集發展的重點技術標準</span></p><p><span style="font-family: arial;">然而在有實際應用場景需求, 但沒有更好的新標準, 儘管各家計算硬體開發商缺乏合作默契的情況之下(特別是 Mobile 平台), 繼續良好支援 OpenCL 似乎是實務上的一個共識了</span></p><p><span style="font-family: arial;">這篇主要的目的是統整一下在 2023 的今日要著手 OpenCL 有哪些資料可以研讀</span></p><h3 style="text-align: left;"><span style="font-family: arial;">書籍<br /></span></h3><p><span style="font-family: arial;">由於 OpenCL 3.0 標準是以 OpenCL 1.2 為基底, 因此過往的 OpenCL 圖書內容都還是相當實用. 至於談到 OpenCL 2.0 標準後新增的部份 (個人覺得現今 OpenCL runtime 逐漸完備下 device-side enqueue, subgroup 以及 pipe 都可以把玩一番), 而目前僅有一本參考書目可以研讀 - <a href="https://play.google.com/store/books/details/Benedict_Gaster_Heterogeneous_Computing_with_OpenC?id=qa73rNA7drYC" target="_blank">"Heterogeneous Computing with OpenCL 2.0"</a>, 這本書被標為 3rd edition 的原因是前面有著兩版談論 1.1 與 1.2 的兩個版本<br /></span></p><h3 style="text-align: left;"><span style="font-family: arial;">標準與相關資訊<br /></span></h3><p><span style="font-family: arial;">OpenCL 的制定組織統整了 OpenCL 相關的資源連結 - <a href="https://www.khronos.org/opencl/resources" target="_blank">OpenCL Resource Guide</a></span></p><p><span style="font-family: arial;">若要查找標準相關的資訊, 另外是相關的程式資源, 這裡算是參考資料翻找的入口<br /></span></p><h3 style="text-align: left;"><span style="font-family: arial;">各 GPU vendor 提供的 OpenCL Programming Guide<br /></span></h3><p><span style="font-family: arial;">由於各家還是有自己的 OpenCL runtime 與 compiler 實作, 因此在使用與實作 OpenCL 程式還是有各家平台在 Host 與 Device 中需要注意的一些細節. 這裡整理了各家目前提供的文件.</span></p><h4 style="text-align: left;"><span style="font-family: arial;">Intel</span></h4><p><span style="font-family: arial;"><a href="https://www.intel.com/content/www/us/en/content-details/774834/opencl-developer-guide-for-intel-processor-graphics.html" target="_blank">OpenCL Developer Guide for Intel® Processor Graphics</a><span><br /></span></span></p><h4 style="text-align: left;"><span style="font-family: arial;">AMD </span></h4><p><span style="font-family: arial;"><a href="https://web.archive.org/web/20221205102916/https://rocmdocs.amd.com/en/latest/Programming_Guides/Opencl-programming-guide.html" target="_blank">ROCm OpenCL Programming Guide</a> (這是 archive.org 在去年底的備份, 其實不清楚為何 AMD 再最新的文件中將 OpenCL 的部份移除)<br /></span></p><h4 style="text-align: left;"><span style="font-family: arial;">Nvidia</span></h4><p><a href="https://www.nvidia.com/content/cudazone/cudabrowser/downloads/papers/nvidia_opencl_bestpracticesguide.pdf" style="font-family: arial;" target="_blank">NVIDIA OpenCL Best Practices Guide Version 1.0</a></p><p><span style="font-family: arial;"><a href="https://developer.nvidia.com/opencl" target="_blank">NVIDIA OpenCL SDK Code Samples</a> <br /></span></p><h4 style="text-align: left;"><span style="font-family: arial;">Qualcomm</span></h4><p><span style="font-family: arial;"><a href="file:///home/champ/%E4%B8%8B%E8%BC%89/80-nb295-11_c-1.pdf" target="_blank">Qualcomm Snapdragon Mobile Platform OpenCL General Programming and<br />Optimization</a> (原名 Adreno OpenCL Programming Guide)<br /></span></p><h4 style="text-align: left;"><span style="font-family: arial;">ARM</span></h4><p><a href="https://developer.arm.com/documentation/101574/latest/" style="font-family: arial;" target="_blank">Mali GPU Bifrost Valhall OpenCL Developer Guide</a></p><p><span style="font-family: arial;"><a href="https://developer.arm.com/documentation/100614/latest/" target="_blank">Mali Midgard OpenCL Developer Guide</a><span><br /></span></span></p><h4 style="text-align: left;"><span style="font-family: arial;">Imagination <br /></span></h4><p><span style="font-family: arial;"><a href="https://blog.imaginationtech.com/a-quick-guide-to-writing-opencl-kernels-for-rogue/" target="_blank">A quick guide to writing OpenCL kernels for PowerVR Rogue GPUs</a> (Imagination 事實上是有 OpenCL performance optimization 相關文件, 只是比較可惜的是僅會對客戶提供)<br /></span></p><h3 style="text-align: left;"><span style="font-family: arial;">工具<br /></span></h3><p><span style="font-family: arial;">與 Optimization Guide 類似, 由於 GPU hardware counter 並沒有一致性的標準與介面, 所以這部份比較可惜的是必須仰賴各家的 Offline Compiler 與 Profiler<br /></span></p><h4 style="text-align: left;"><span style="font-family: arial;">Intel </span></h4><p><span style="font-family: arial;"><a href="https://www.intel.com/content/www/us/en/developer/tools/graphics-performance-analyzers/overview.html" target="_blank">Intel® Graphics Performance Analyzers</a><span><br /></span></span></p><p><span style="font-family: arial;"><a href="https://www.intel.com/content/www/us/en/docs/oneapi/programming-guide/2023-2/ahead-of-time-compilation-for-gpu.html" target="_blank">Ahead-of-Time Compilation for GPU <br /></a></span></p><h4 style="text-align: left;"><span style="font-family: arial;">AMD<br /></span></h4><p><a href="https://gpuopen.com/rgp/" style="font-family: arial;" target="_blank"><span>Radeon GPU Profiler</span></a></p><p><span style="font-family: arial;"><a href="https://gpuopen.com/rga/" target="_blank"><span>Radeon GPU Analyzer<br /></span></a></span></p><h4 style="text-align: left;"><span style="font-family: arial;">Nvidia</span></h4><p><span style="font-family: arial;"><a href="https://developer.nvidia.com/nvidia-visual-profiler" target="_blank"><span>NVIDIA Visual Profiler<br /></span></a></span></p><p><span style="font-family: arial;"><a href="https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html" target="_blank"><span>NVIDIA CUDA Compiler (NVCC)<br /></span></a></span></p><h4 style="text-align: left;"><span style="font-family: arial;">Qualcomm</span></h4><p><span style="font-family: arial;"><a href="https://developer.qualcomm.com/software/snapdragon-profiler" target="_blank"><span>Snapdragon Profiler<br /></span></a></span></p><p><span style="font-family: arial;"><a href="https://developer.qualcomm.com/software/adreno-gpu-sdk/tools" target="_blank"><span>Adreno GPU SDK<br /></span></a></span></p><h4 style="text-align: left;"><span style="font-family: arial;">ARM</span></h4><p><span style="font-family: arial;"><a href="https://developer.arm.com/Tools%20and%20Software/Graphics%20Analyzer" target="_blank"><span>Mali Graphics Analyzer<br /></span></a></span></p><p><span style="font-family: arial;"><a href="https://developer.arm.com/Tools%20and%20Software/Mali%20Offline%20Compiler" target="_blank"><span>Mali Offline Compiler<br /></span></a></span></p><h4 style="text-align: left;"><span style="font-family: arial;">Imagination</span></h4><p><span style="font-family: arial;"><a href="https://developer.imaginationtech.com/pvrtune/" target="_blank">PVRTune<br /></a></span></p><p><span style="font-family: arial;"><a href="https://developer.imaginationtech.com/pvrshadereditor/" target="_blank">PVRSHADEREDITOR</a> <br /></span></p><p><span style="font-family: arial;"><br /></span></p><p><span style="font-family: arial;"><br /></span></p><p><span style="font-family: arial;"><br /></span></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-80562151396167519562023-05-25T09:32:00.003-07:002023-05-25T09:32:38.986-07:00SIMD Programming Guide 簡報上線<p><span class="x193iq5w xeuugli x13faqbe x1vvkbs x10flsy6 x1lliihq x1s928wv xhkezso x1gmr53x x1cpjm7i x1fgarty x1943h6x x4zkp8e x41vudc x1f6kntn xvq8zen xo1l8bm xzsf02u x1yc453h" dir="auto">今年定下的年度目標之一是建立比較完整的 SIMD 教學資源, 原本預計撰寫三個部份, 最後完成是有四個部份:</span></p><ul><li>Part 1 - <span class="x193iq5w xeuugli x13faqbe x1vvkbs x10flsy6 x1lliihq x1s928wv xhkezso x1gmr53x x1cpjm7i x1fgarty x1943h6x x4zkp8e x41vudc x1f6kntn xvq8zen xo1l8bm xzsf02u x1yc453h" dir="auto">透過 compiler 跨硬體架構平台的 compiler vector extension 來撰寫 (architecture-independent) SIMD 程式</span></li><li><span class="x193iq5w xeuugli x13faqbe x1vvkbs x10flsy6 x1lliihq x1s928wv xhkezso x1gmr53x x1cpjm7i x1fgarty x1943h6x x4zkp8e x41vudc x1f6kntn xvq8zen xo1l8bm xzsf02u x1yc453h" dir="auto">Part 2 -</span>講述一般而言 SIMD 指令有哪些類型, 開始導入 Arch-dependent Intrinsics, 並提供 NEON 與 SSE 的具體使用例子, 與 Lab2 的說明</li><li>Part
3 - SIMD optimization 的一些基本步驟, 套用 SIMD ILP 的兩種基本面向, 並各自以 Gaussian 5x5 與
4x4 SAD 為舉例, 最後是 fuse / tiling / sliding-window / prefetch 等初步的進階技巧</li><li><span class="x193iq5w xeuugli x13faqbe x1vvkbs x10flsy6 x1lliihq x1s928wv xhkezso x1gmr53x x1cpjm7i x1fgarty x1943h6x x4zkp8e x41vudc x1f6kntn xvq8zen xo1l8bm xzsf02u x1yc453h" dir="auto">Part 4 - </span><span class="x193iq5w xeuugli x13faqbe x1vvkbs x10flsy6 x1lliihq x1s928wv xhkezso x1gmr53x x1cpjm7i x1fgarty x1943h6x x4zkp8e x41vudc x1f6kntn xvq8zen xo1l8bm xzsf02u x1yc453h" dir="auto">講述 SIMD 的弱點也就是 SIMD 不擅長或是缺點的部份 </span></li></ul><p><span class="x193iq5w xeuugli x13faqbe x1vvkbs x10flsy6 x1lliihq x1s928wv xhkezso x1gmr53x x1cpjm7i x1fgarty x1943h6x x4zkp8e x41vudc x1f6kntn xvq8zen xo1l8bm xzsf02u x1yc453h" dir="auto"></span></p><p><span class="x193iq5w xeuugli x13faqbe x1vvkbs x10flsy6 x1lliihq x1s928wv xhkezso x1gmr53x x1cpjm7i x1fgarty x1943h6x x4zkp8e x41vudc x1f6kntn xvq8zen xo1l8bm xzsf02u x1yc453h" dir="auto"><br /></span>
<iframe allowfullscreen="true" frameborder="0" height="299" mozallowfullscreen="true" src="https://docs.google.com/presentation/d/e/2PACX-1vTf1q8ZH_KiLF2CBc8WNePX8FemeE2R3vnG4LhmS6i6LhQQiujR46zmNBYaz9KcjptNQ_lbiQLQGIUi/embed?start=false&loop=false&delayms=60000" webkitallowfullscreen="true" width="480"></iframe>
</p><p><iframe allowfullscreen="true" frameborder="0" height="299" mozallowfullscreen="true" src="https://docs.google.com/presentation/d/e/2PACX-1vQPLcrH2v-JlH-wK_CKM8rIjOZgPAK6fu4VOHomwYM1fZ_K8q9YyU-b0jiYEXqrG9N1lYxT5Fepr4sV/embed?start=false&loop=false&delayms=60000" webkitallowfullscreen="true" width="480"></iframe> </p><p> <iframe allowfullscreen="true" frameborder="0" height="299" mozallowfullscreen="true" src="https://docs.google.com/presentation/d/e/2PACX-1vR8R5I-mp31JbzLDd_xPogpHDBe71y-GIxd2I-1xSV1B8Yqh_vEICNyhb7f8yf0yYscWcWrRR7CmPu0/embed?start=false&loop=false&delayms=60000" webkitallowfullscreen="true" width="480"></iframe> </p><p> <iframe allowfullscreen="true" frameborder="0" height="299" mozallowfullscreen="true" src="https://docs.google.com/presentation/d/e/2PACX-1vSRCEyP5ISYlwJqznt2UVv9gzPOenniC-UESucvG3dOGd9Dxfmo7pz-0wajCe_PwVqodB8tSOXopUsO/embed?start=false&loop=false&delayms=60000" webkitallowfullscreen="true" width="480"></iframe>
</p>
網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com1tag:blogger.com,1999:blog-8142113334815576231.post-75522507505209457152022-08-07T07:16:00.001-07:002022-08-07T07:16:08.879-07:00Halide Introduction 2022 簡報上線<p>近日應公司同仁邀請分享對 Halide 做介紹<br />於是基於 2019 的投影片做一點整理並增加一點常被詢問的資訊<br /></p>
<iframe allowfullscreen="true" frameborder="0" height="480" mozallowfullscreen="true" src="https://docs.google.com/presentation/d/e/2PACX-1vTO9K13qKVAYG83Ogy7KPwS2VRp3udUB5Wf8gjpRCizBA6Irn-6vsx3kl_fojJ7ak3ev-imVDQNBB3y/embed?start=false&loop=false&delayms=30000" webkitallowfullscreen="true" width="800"></iframe>
網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-56580134735901667132022-06-01T21:55:00.007-07:002022-06-01T21:55:57.154-07:00The Story After HEVC 簡報上線<p> </p><p>近日應於政大任教的朋友於其課程中分享心得的 "The Story After HEVC" 簡報上線</p><p></p><p>
<iframe allowfullscreen="true" frameborder="0" height="299" mozallowfullscreen="true" src="https://docs.google.com/presentation/d/e/2PACX-1vTXkc6GZKXZBjufBHDOnL2CBsLgZ6X5379dKlDuuWv3icTEk2EuY2fx1QsZ0alMCUxEBnhQVrDYrkQ0/embed?start=false&loop=false&delayms=30000" webkitallowfullscreen="true" width="480"></iframe> </p><p> 歡迎不吝指教</p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-56910738739329329112022-04-28T07:29:00.003-07:002022-04-28T07:31:43.003-07:00初探 clang 14.0 中的 Matrix Type <p>在<a href="https://champyen.blogspot.com/2022/03/clang-140-vector-extension-built-in.html" target="_blank">先前 Clang 14.0 的文章</a>中提到 Clang 14.0 新增了 Matrix Type, 出於個人的好奇, 於是就開始動手玩了一下, 這裡先說結論, Clang 14.0 中的 Matrix Type 還是開發階段, 尚不太好操作. 這裡嘗試使用 Matrix Type 的應用是 Tiled Matrix Multiplication. <br /></p><h3 style="text-align: left;">比較基準的 vector type 實作<br /></h3><p>首先是先前已經以 Clang Vector Extension 所實作的 float 型別 8x8 矩陣乘法</p><p></p><blockquote><p><span style="font-family: courier;">typedef float <span style="color: #990000;">float8</span> __attribute__((ext_vector_type(8)));<br />void gemm_vec(<br /><span> </span>// buffer, stride<br /><span> </span>float *a, int sa,<span> </span><br /><span> </span>float *b, int sb, <br /><span> </span>float *c, int sc<br />){
<br /><span> </span><span style="color: #990000;">float8</span> vb[8];
<br /><span> </span>for(int y = 0; y < 8; y++)<br /><span> </span><span> </span>vb[y] = *((<span style="color: #990000;">float8</span>*)(b + sb*y));<br /><span> </span>}<br /><span> </span>for(int y = 0; y < 8; y++){<br /><span> </span><span> </span><span style="color: #990000;">float8</span> vc = *((<span style="color: #990000;">float8</span>*)(c + sc*y));<br /><span> </span><span> </span><span style="color: #990000;">float8</span> va = *((<span style="color: #990000;">float8</span>*)(a + sa*y));<br /><span> </span><span> </span>for(int x = 0; x < 8; x++)<br /><span> </span><span> </span><span> </span>vc += va[x]*vb[x];<br /><span> </span><span> </span>*((<span style="color: #990000;">float8</span>*)(c + sc*y)) = vc;<br /><span> </span>}<br />}<br /></span></p><p></p></blockquote><h3 style="text-align: left;">以 matrix type 實作的過程<br /></h3><p>接著是參考 Clang Matrix Type 的說明嘗試定義 8x8 的 Matrix Type:</p><blockquote><p><span style="font-family: courier;">typedef float <span style="color: #38761d;">float8x8</span> __attribute__((matrix_type(8, 8)));</span></p></blockquote><p>首先值得一提的是定義的 matrix type 所宣告的變數, 類似於 vector type 內作為 1D array, matrix type 變數能作為 2D array 的語法來存取個別數值. 這裡定義好 float8x8 型別之後, 必須宣告變數並將 2D 資料載入, 於是會發現文件中並沒有說明如何載入資料! 這部份必須參考目前的 <a href="https://clang.llvm.org/docs/MatrixTypes.html" target="_blank">Draft Specification</a>, 目前 Clang 僅支援 3 個 function:</p><ul style="text-align: left;"><li><code class="docutils literal notranslate"><span class="pre">M2</span> <span class="pre">__builtin_matrix_transpose(M1</span> <span class="pre">matrix)</span></code></li><li><code class="docutils literal notranslate"><span class="pre">M</span> <span class="pre">__builtin_matrix_column_major_load(T</span> <span class="pre">*ptr,</span> <span class="pre">size_t</span> <span class="pre">row,</span> <span class="pre">size_t</span> <span class="pre">col,</span> <span class="pre">size_t</span> <span class="pre">columnStride)</span></code></li><li><code class="docutils literal notranslate"><span class="pre">void</span> <span class="pre">__builtin_matrix_column_major_store(M</span> <span class="pre">matrix,</span> <span class="pre">T</span> <span class="pre">*ptr,</span> <span class="pre">size_t</span> <span class="pre">columnStride)</span></code></li></ul><p>總之, 資料載入的方式有了, 那麼 column major load /store 的意義為何? 這主要是載入資料的順序, 參考 <a href="https://en.wikipedia.org/wiki/Row-_and_column-major_order" target="_blank">Wikipedia 關於 Row- and column-major order 頁面</a>的圖解, 可以得知對於 column major load 的意義在於:<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrCUeLUrwu1_WVEPsSK6di9HRp101n5HTt9ZaQVPeFoC833BuRRqQ9xTy2mpOCNmSfKygFn--h97Emygt9Wz4QNUW_BoQ7ZjMHcvvimKBHGyjp0BHSg4CWeYQfDA6KPQVHTH7kvi1CQUmG4sb-9f0m-ljL4-Ef9nRr9Yzt2RTM890sMwHZsDlW9-MMGg/s731/column_major.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="382" data-original-width="731" height="334" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrCUeLUrwu1_WVEPsSK6di9HRp101n5HTt9ZaQVPeFoC833BuRRqQ9xTy2mpOCNmSfKygFn--h97Emygt9Wz4QNUW_BoQ7ZjMHcvvimKBHGyjp0BHSg4CWeYQfDA6KPQVHTH7kvi1CQUmG4sb-9f0m-ljL4-Ef9nRr9Yzt2RTM890sMwHZsDlW9-MMGg/w640-h334/column_major.png" width="640" /></a></div><p>有點不太理解 clang 第一時間所支援的竟然不是 row major load / store. 然而使用 column major load store 的結果是所得到的 matrix 為原本所需的 transposed matrix. Anyway, 由於我們只有這個讀取方式, 所以我們先讀取原先 function 中的 a, b, c 矩陣:</p><p></p><blockquote> <span style="font-family: courier;">float8x8 ma, mb, mc;
<br />ma = __builtin_matrix_column_major_load(a, 8, 8, sa);<br />mb = __builtin_matrix_column_major_load(b, 8, 8, sb);<br />mc = __builtin_matrix_column_major_load(c, 8, 8, sc);
</span></blockquote><p></p><p>然而我們還是可以動些手腳來達成原本的 Tiled Matrix Multiplication, 首先我們原本要計算的資料為</p><p></p><blockquote><span style="font-family: courier;">C += A x B </span></blockquote><p>然而這當中的 A, B, C, 透過 <code class="docutils literal notranslate"><span class="pre">__builtin_matrix_column_major_load 所得到的已經是 transposed matrix (這裡我們用 A', B', C' 代表), 因此我們可以透過預先透過 </span></code><code class="docutils literal notranslate"><span class="pre">__builtin_matrix_transpose 來轉回 A, B, C. 正確計算後能再轉回原先的 column major matrix 來存回正確的數值.</span></code> </p><p><span style="font-family: courier;"></span></p><blockquote><p><span style="font-family: courier;">ma = __builtin_matrix_transpose(ma);<br />mb = __builtin_matrix_transpose(mb);<br />mc = __builtin_matrix_transpose(mc);<br />mc += ma*mb;<br />mc = __builtin_matrix_transpose(mc); //transposed again<br />__builtin_matrix_column_major_store(mc, c, sc); <br /></span></p><p></p></blockquote><p>這裡使用了四次<code class="docutils literal notranslate"><span class="pre"> __builtin_matrix_transpose, 僅僅是為了達到計算上正確的 C=AxB, 由於在回存時必須保持 transposed 狀態, 所以如果計算時能產生對應 C' 的資料即可, 於是我們可以這麼計算:</span></code></p><p><code class="docutils literal notranslate"><span class="pre"></span></code></p><blockquote><code class="docutils literal notranslate"><span style="font-family: courier;">C'+=B'xA'</span></code></blockquote><code class="docutils literal notranslate">於是我們整個實作就可以簡化如下:<br /></code><p></p><p><span style="font-family: courier;"></span></p><blockquote><p><span style="font-family: courier;">void gemm_vec(<br /><span> </span>float *a, int sa, <br /><span> </span>float *b, int sb, <br /><span> </span>float *c, int sc<br />){<br /></span><span style="font-family: courier;"> <span style="color: #38761d;">float8x8</span> ma, mb, mc;
<br /></span><span style="font-family: courier;"> ma = __builtin_matrix_column_major_load(a, 8, 8, sa);
<br /></span><span style="font-family: courier;"> mb = __builtin_matrix_column_major_load(b, 8, 8, sb);
<br /></span><span style="font-family: courier;"> mc = __builtin_matrix_column_major_load(c, 8, 8, sc);
<br /></span><span style="font-family: courier;"> mc += mb * ma;
<br /><span> </span>__builtin_matrix_column_major_store(mc, c, sc);<br />}</span></p><p></p></blockquote><p>最後是編譯時要注意, clang 必須加入 <span style="color: #990000;"><b>-fenable-matrix</b></span> 這個參數<br /></p><p>以這種實作搭配 AVX2 在 Intel Core i5-8350U 測試, 對於 naive, vector type 與 matrix type 來測試用於兩個 512x512 matrix multiplication 的效能<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPW6LgDBYK-2QSzuc9TrwDnDiPzOrzhSSuBWxoXYbY3hPDlr_9XdV1L35nrzdiOowxDwbxYxGEoWFzugQRpriMbyji1QauBS4bm1XpaBC1bRML8XAxyT_iiSRNkcW8MnDFdUE_fINravfJqMsYcfwLqITl4OmYy9dcIjwuSgmzW7KHTSXh1GzcKgYkRw/s475/mm_mat.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="360" data-original-width="475" height="304" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPW6LgDBYK-2QSzuc9TrwDnDiPzOrzhSSuBWxoXYbY3hPDlr_9XdV1L35nrzdiOowxDwbxYxGEoWFzugQRpriMbyji1QauBS4bm1XpaBC1bRML8XAxyT_iiSRNkcW8MnDFdUE_fINravfJqMsYcfwLqITl4OmYy9dcIjwuSgmzW7KHTSXh1GzcKgYkRw/w400-h304/mm_mat.png" width="400" /></a></div><p></p><p>因此 single thread 來執行 naive, vector type, matrix type 三種實作的時間分別為 129.9ms, 26.0ms, 27.0ms. 因此以 matrix type 可以得到十分接近的 vector extension 的性能, 但是實作上相當簡潔易懂.<br /></p><p>到此許多人應該會關心 clang 未來是否支援 row-major 的形式? 個人認為是會的, 首先<a href="https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#techs=AMX" target="_blank"> Intel AMX 指令集</a>所使用的即是 row major 的 load/store. 再者是在 2020 年 LLVM 開發者大會中 <a href="https://youtu.be/I8TIBvvM2K0" target="_blank">"Matrix Support in LLVM and Clang" 的演講</a>的倒數第二張投影片 "Remaining Work" 明確標示了 Row-major support.</p><p><br /></p><h3 style="text-align: left;">使用後個人想提的幾個 idea:</h3><p>定義 matrix 的同時, 應同時定義出 row 與 column 的 vector 型別, 目前不知如何取得定義語法, 這裡暫時以 ".row_vec" 與 ".col_vec" 來表示. 也就是說當我們定義了:<br /></p><p></p><blockquote><span style="font-family: courier;">typedef float <span style="color: #38761d;">float8x8</span> __attribute__((matrix_type(8, 8)));</span></blockquote><p></p><p>那麼可以用此定義一併定義出對應用在 row 與 col 的 vector type (或是分開定義但是 compiler 偵測 vector length):<br /></p><p><span style="font-family: courier;"></span></p><blockquote><p><span style="font-family: courier;">float8x8.row_vec<span> </span>vrow, vrow2;<br />float8x8.col_vec vcol, vcol2;<br /></span></p><p></p></blockquote><p>如此可以合併使用 vector extension 與 matrix type 來處理 row major 或是 column major 的數值設定:<br /></p><p><span style="font-family: courier;"></span></p><blockquote><p><span style="font-family: courier;">float8x8 mat;<br />...<br />vrow = *((</span><span style="font-family: courier;">float8x8.row_vec<span>*)ptr_row);<br /></span></span><span style="font-family: courier;"><span><span style="font-family: courier;">vcol = *((</span><span style="font-family: courier;">float8x8.col_vec<span>*)ptr_col);<br /></span></span>mat.row[1] = vrow;<span> </span><span> </span>// set a row<br />mat.col[2] = vcol;<span> </span><span> </span>// set a column</span></span></p></blockquote><p><span style="font-family: courier;"><span></span></span></p><p><span style="font-family: courier;"><span></span></span>另外可以做 row vector 與 column vector 與矩陣相乘運算:<br /></p><blockquote><p><span style="font-family: courier;">float8x8 mat;<br />...<br />vrow = *((</span><span style="font-family: courier;">float8x8.row_vec<span>*)ptr_row);<br /></span></span><span style="font-family: courier;"><span><span style="font-family: courier;">vcol = *((</span><span style="font-family: courier;">float8x8.col_vec<span>*)ptr_col);<br /></span></span>vrow2 = vcol*mat;<br />vcol2 = mat*vrow;<br /></span></span></p></blockquote><p><br /></p><p> </p><p><span style="font-family: courier;"><span></span></span></p><span style="font-family: courier;"><span></span></span><p></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-7897061839241237142022-03-17T05:38:00.003-07:002022-03-17T08:36:57.190-07:00Clang 14.0 將增加 vector extension built-in functions<p>由於 LLVM / Clang 14.0 發佈的接近, 近日就花了點時間看一下對於 vector extension 是否有相關的更新. 目前在 <a href="https://clang.llvm.org/docs/LanguageExtensions.html" target="_blank">Clang 官方網站文件</a>上已經可以看到.<br /><br />Clang 14.0 中最令人振奮的莫過於新增的兩個部份</p><ul style="text-align: left;"><li>Vector Builtins</li><li>Matrix Types</li></ul><h4 style="text-align: left;">Vector Builtins</h4><p style="text-align: left;">vector builtins 主要能提供 compiler 明確使用對應指令來加速, 而這些不容易透過 C operator 來表示, builtins 分為兩類 elementwise builtins 與 reduction builtins </p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEh8lzAI841tCcwWFe_oR6vQFDibCOm7q03yX1GbWJwlEJ_LymEtwgBTudLCVaMCvlmtyz9Q_i-5VrzIoX67Mun0hN_RlOTPFwp5ty_9rAFRdCAHN0XOpABGiPWRP6w8oWqZwbaJZO3i2MtRqe4PC5AIrU9UhnPiMBwjsXDDpXwRUalCZN0cSSxQ-O3xVw=s1048" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="532" data-original-width="1048" height="324" src="https://blogger.googleusercontent.com/img/a/AVvXsEh8lzAI841tCcwWFe_oR6vQFDibCOm7q03yX1GbWJwlEJ_LymEtwgBTudLCVaMCvlmtyz9Q_i-5VrzIoX67Mun0hN_RlOTPFwp5ty_9rAFRdCAHN0XOpABGiPWRP6w8oWqZwbaJZO3i2MtRqe4PC5AIrU9UhnPiMBwjsXDDpXwRUalCZN0cSSxQ-O3xVw=w640-h324" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 1 - Elementwise Builtins 列表</td></tr></tbody></table><p>Elementwise Builtins 本質上就是 Per-lane operation, 像是 __builtin_elementwise_max, 事實上透過先前分享過的 Clang ternary operator 就可以實作, 也就是說下列的 ternary operation:<br /></p><blockquote><p>vc = va > vb ? va : vb;<br /></p></blockquote><p>基本上等同於:<br /></p><blockquote><p style="text-align: left;">vc = __builtin_elementwise_max(va, vb);<br /></p></blockquote><p style="text-align: left;">事實上 elementwise builtins 並不限定於套用在 vector type, 基本的 scalar types 也可以.<br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjZ8Bb1GEqiK_v9qiYQqza6pPOFSehVrxJlcBM1I2a0eARyV3eKMrrv8pSzNsgKkYxcP1pCIIddx_65Z1nBNxfEOQpMwZTpTodMY3oa2zU-wbmAjdPAU_5j4j9MwqWy7SNuysXNsEsA1xASvahk5UoYT3lKOYx8mUMpvGrUhMWKoN2Mbc8YT9zSUuQb-w=s1054" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="547" data-original-width="1054" height="332" src="https://blogger.googleusercontent.com/img/a/AVvXsEjZ8Bb1GEqiK_v9qiYQqza6pPOFSehVrxJlcBM1I2a0eARyV3eKMrrv8pSzNsgKkYxcP1pCIIddx_65Z1nBNxfEOQpMwZTpTodMY3oa2zU-wbmAjdPAU_5j4j9MwqWy7SNuysXNsEsA1xASvahk5UoYT3lKOYx8mUMpvGrUhMWKoN2Mbc8YT9zSUuQb-w=w640-h332" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 2 - Reduction Builtins 列表<br /></td></tr></tbody></table><p>Reduction Builtins 主要用在可以快速從 vector 中算出單一數值的結果, 舉凡像是總和(sum), 最大值(max)與最小值(min) 等等. 在 Clang 中還有特別的 - 所謂水平方向 AND / OR / XOR. 這對一些特別的應用很有幫助.</p><h4 style="text-align: left;">Matrix Types<br /></h4><p style="text-align: left;">由於現今新一代的 CPU 都加入了對於 matrix 操作加速的指令(Intel 的 AMX, ARM 的 SVE Matrix Extension 與 SME), 因此 Clang 加入 matrix type 也有助於更有效去使用這些.<br /></p><p style="text-align: left;">在 Clang 中可以透過下列方式定義與使用 Matrix:</p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEi_WCz5oN3VH3hz08Dd5_0RSaeCLmJu9N4G4riVUGy299zAL-1XGNxwHeZs8ljUWlxeCCj97sxndp3BFcms6lpRFIy_WvlNMBILFTx8mivFqboAp_pGxXimNBDy6Ar4EKhy_rq8UDTdafb5NdseZDK7Eb6o4rpoPugAKUyJ3X5f9dX8ZL0yUtkQ4Q2W9A=s1040" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="115" data-original-width="1040" height="70" src="https://blogger.googleusercontent.com/img/a/AVvXsEi_WCz5oN3VH3hz08Dd5_0RSaeCLmJu9N4G4riVUGy299zAL-1XGNxwHeZs8ljUWlxeCCj97sxndp3BFcms6lpRFIy_WvlNMBILFTx8mivFqboAp_pGxXimNBDy6Ar4EKhy_rq8UDTdafb5NdseZDK7Eb6o4rpoPugAKUyJ3X5f9dX8ZL0yUtkQ4Q2W9A=w640-h70" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 3 - Clang Matrix Type 定義與使用範例<br /></td></tr></tbody></table><p style="text-align: left;">透過定義 Matrix Type, 能夠直接支援基本運算操作. 目前<a href="https://clang.llvm.org/docs/LanguageExtensions.html#matrix-types" target="_blank">官方說明與範例</a>中並沒有顯示與 vector 的互動. 但以目前的型態撰寫相關應用已便利不少.<br /></p><p style="text-align: left;"><br /></p><p style="text-align: left;"><br /></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-67690540776863556672022-03-12T02:18:00.006-08:002022-03-12T02:35:09.561-08:00ARM SVE 研讀筆記 Part-6 - Extended floating-point horizontal reductions 與 SVE/SVE2 optionals<p>這篇是系列文的最後一篇, 主要談兩個列於標題的項目.</p><h4 style="text-align: left;">Extended floating-point horizontal reductions <br /></h4><p>在討論 Extended floating-point horizontal reduction 時必須要知道 ARM 在 ARMv8 時增加了一些 instruction / intrinsic functions. 從官方的 <a href="https://developer.arm.com/documentation/ihi0073/latest" target="_blank">ARM NEON intrinsics reference</a> 可以查找出這次要討論的主角 vaddvq_f32 / vaddvq_f64. 這兩個 intrinsics 的目的在於把向量中所有 float / double elements 加總回傳. 雖然相當簡單, 但是 floating-point 的操作對於驗證有個很重要的議題 - order. 透過 reference 查找出此兩者, 可以得知在 ARMv8 是透過 FADDP 指令來達成的. 這個指令是將相鄰的成對數值相加. 從 vaddvq_f32 可以看出是利用兩個 FADDP 指令達成加總的目的. 這樣做的好處是可以減少 instruction latency. 然而這樣的次序在驗證上需要特別處理. <br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhyr2sAqErZdnPqnIv0fs1D4mzcRz3XtuCy1U1mDA7BOgcABsePLj0LJ0SLjEowZMOkXpN5EQWNXQXCWaCZiS-b7V0ognn3VxxSJ7ZhyTJZAHAk9-DB4kHYcg6donwE8iouGDPBqRrue1_hJ0dFclJjfGy_nh_d6qj61cIj_HfBnTEJOoThARHAZKjThg=s1030" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="579" data-original-width="1030" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEhyr2sAqErZdnPqnIv0fs1D4mzcRz3XtuCy1U1mDA7BOgcABsePLj0LJ0SLjEowZMOkXpN5EQWNXQXCWaCZiS-b7V0ognn3VxxSJ7ZhyTJZAHAk9-DB4kHYcg6donwE8iouGDPBqRrue1_hJ0dFclJjfGy_nh_d6qj61cIj_HfBnTEJOoThARHAZKjThg=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 1 - SVE 同時提供了 floating-point in-order 與 tree-based reduction 處理<br /></td></tr></tbody></table><p></p><p>在 SVE 中同時提供了 in-order (由左至右) 與 tree-base (成對相加) 的作法. 如此可以取決於效能還是結果驗證從這兩者選擇. 在 ARM C Language Extension 可以看到下列的段落:<br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiWGQUpHWkJkX4Y1FXFNn9ErxSLZEhK24y4JnNwQFp8z-z1cQMVvjsngtnExc1IPc7sSxrT2vj2hkNAUKnG32y62fIrUA_dCQRZ7i7dIJqN5HP-Xi0JKD_KBFnNv-j0OuFLvt7ZSEWoWyjSVoHpvV7_BHuOWGKt7qBB2HFbutB5l1H2GG5CabZC_4pstA=s1016" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="658" data-original-width="1016" height="414" src="https://blogger.googleusercontent.com/img/a/AVvXsEiWGQUpHWkJkX4Y1FXFNn9ErxSLZEhK24y4JnNwQFp8z-z1cQMVvjsngtnExc1IPc7sSxrT2vj2hkNAUKnG32y62fIrUA_dCQRZ7i7dIJqN5HP-Xi0JKD_KBFnNv-j0OuFLvt7ZSEWoWyjSVoHpvV7_BHuOWGKt7qBB2HFbutB5l1H2GG5CabZC_4pstA=w640-h414" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 2 - 兩種不同順序浮點數加總的 SVE intrinsic function <br /></td></tr></tbody></table><p></p><p>依照先前說明轉換 NEON intrinsics 至 SVE intrinsics 的規則, 可以輕易的了解 NEON 中的 <span style="color: #800180;"><b>vaddv</b></span>q_f32 對應的 SVE intrinsic function 為 s<span style="color: #800180;"><b>vaddv</b></span>_f32. 也映證了原有的方式是基於效能的 tree-based. 而新增加的是由左至右的作法是 svadd<span style="color: #660000;"><b>a</b></span>_* 系列. 而由於 in-order 的特性, 在執行上也較為沒有效率, 從 <a href="Arm Cortex-X2 Core Software Optimization Guide" target="_blank">Arm Cortex-X2 Core Software Optimization Guide</a> 可以看出 FADDP 與 FADDA 兩者有著相當大落差的 exec latency (也就是需要幾個 CPU cycle 才能產生所需結果)<br /></p><h4 style="text-align: left;">SVE/SVE2 options<br /></h4><p>在 ARM 制定 SVE 的過程中, SVE 與 SVE2 各自有其核心目標, 這篇主要是說明 SVE 到 SVE2 制定上功能上的演進. 搭配 <a href="https://developer.arm.com/architectures/cpu-architecture/a-profile" target="_blank">ARM A-Profile 網站</a>與 ARM C Language Extension 文件閱讀會比較有感.<br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgvkCwaicHqbT9w2kGp7-Fb0eb1pXQAn3RQWSHEyO-o9TwK--8DDxwN_DoI4i-LUwHpEIz85djfkv4OQ6GLm0qadnVzgzBaEdZP0ImhmN3Nyjg0YMNjt9ZlPA7ksFQSIOY49A9s_qB_Z8KFqQ-62FBAs5_wXYWYg81JMz96zNltCFjkQJcITtwmYXUWGw=s1036" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="582" data-original-width="1036" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEgvkCwaicHqbT9w2kGp7-Fb0eb1pXQAn3RQWSHEyO-o9TwK--8DDxwN_DoI4i-LUwHpEIz85djfkv4OQ6GLm0qadnVzgzBaEdZP0ImhmN3Nyjg0YMNjt9ZlPA7ksFQSIOY49A9s_qB_Z8KFqQ-62FBAs5_wXYWYg81JMz96zNltCFjkQJcITtwmYXUWGw=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 3 - SVE Base functions<br /></td></tr></tbody></table><p></p><p>SVE base 中建立了 SVE Programming Model 的基本需求. 基本上大部分的 intrinsic functions 都會落在這個類別.<br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjrmOhPro6fIehVgb2ujG2VTW-gQ1rq-MruC6tc_oPkQE4BW7y-O9P8pTjMkmn2SsQJGJSm_cNHXSvit4Qv_CkQgAORnIDluIG1hwnbPfobn6YWWFwuuTngZqelcxniUQ4hIQvNoKiGfPC-xSAOvPQXG56-v5rEnl9SnRHZSfHcM5_bAUgP_EyRsqqEVA=s1038" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="583" data-original-width="1038" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEjrmOhPro6fIehVgb2ujG2VTW-gQ1rq-MruC6tc_oPkQE4BW7y-O9P8pTjMkmn2SsQJGJSm_cNHXSvit4Qv_CkQgAORnIDluIG1hwnbPfobn6YWWFwuuTngZqelcxniUQ4hIQvNoKiGfPC-xSAOvPQXG56-v5rEnl9SnRHZSfHcM5_bAUgP_EyRsqqEVA=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 4 - SVE optional<br /></td></tr></tbody></table><p></p><p>在 ARMv8.6 提出時對於 SVE 增加對 BFloat 16 與 GEMM 的支援. GEMM 上主要是能以 vector 來完成 8x2 & 2x8 (8bit) 或是 2x2 (floating-point) 的 Matrix Multiply. 詳情建議閱讀<a href="https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/arm-architecture-developments-armv8-6-a" target="_blank">官方 Blog 介紹文章</a>. 值得一提的是 SVE option 的提出時間點 (ARMv9.1) 比 SVE2 推出的時間(ARMv9.0)還晚.<br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhx3RJxL3B81ItZg5OlYl6NSVE_goF0ev7NnsSNKnazi9eHcn4tkTStnuk5DCFRpdZdKhtWom96mWU6KwRRcqhOFoyddogGsFmlU3iP_xXwQaid7l0Jrmc4PgEEv0J9M9Fwmh0IuNWrEdzzhgUlzN57kYqmvDUAplFj6HK2_V3yvAi8w6mkKzZkY-Tzzg=s1039" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="584" data-original-width="1039" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEhx3RJxL3B81ItZg5OlYl6NSVE_goF0ev7NnsSNKnazi9eHcn4tkTStnuk5DCFRpdZdKhtWom96mWU6KwRRcqhOFoyddogGsFmlU3iP_xXwQaid7l0Jrmc4PgEEv0J9M9Fwmh0IuNWrEdzzhgUlzN57kYqmvDUAplFj6HK2_V3yvAi8w6mkKzZkY-Tzzg=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 5 - SVE2 Base 新增功能列表<br /></td></tr></tbody></table><p></p><p>將 SVE 列為核心功能是在 ARMv9.0 的事情, 當時是以 SVE2 的版本加入. 相較於一開始與 Fuji 合作於 A64FX 超級電腦核心的 ARMv8.2 中採用的 SVE 已有相當的時日. SVE2 很大的部份在補足 SVE 設計上的缺漏. 特別是在 SVE 中缺少的 data width widening & narrowing 的運算. 另外像是 while loop control 對於 decremental counter 的支援, 複數與大數的支援等等 ...<br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhXWyx1-Wq2QES3vMaSuGNQRcoMlGb8rfqtOR6rWw9iZCfuxawvA3pTWM4IJw6_JbZkgjOGDatQMhUjg4w3JFbLueDeK8YYA6Z8we5-p8lsvkzJWBf79Er7tO3IUgsm7PfybpO6qaYMw1xvGyfdVpF3c1xHc90fz_6lCR0IPgNIA6cTzsH0totF0_BF9w=s1041" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="585" data-original-width="1041" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEhXWyx1-Wq2QES3vMaSuGNQRcoMlGb8rfqtOR6rWw9iZCfuxawvA3pTWM4IJw6_JbZkgjOGDatQMhUjg4w3JFbLueDeK8YYA6Z8we5-p8lsvkzJWBf79Er7tO3IUgsm7PfybpO6qaYMw1xvGyfdVpF3c1xHc90fz_6lCR0IPgNIA6cTzsH0totF0_BF9w=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 7 - SVE2 optional 增加的功能<br /></td></tr></tbody></table><p>最後是 SVE optional functions, 這一部份主要集中於 Crypto 應用的部份. 透過這些指令可以加速 AES-128, SHA-3 與 SM4 的加解密應用. 另外是一個較大的 extension - SME. SME 基於 SVE / SVE2 提供了更有效處理 Matrix 操作的功能. 除了提供 outer-product engine 外也額外增加新的執行模式 - "Streaming Mode". 軟體實作可以藉由使用這個模式來支援更大的 vector length 與達到更高的 throughput. 詳情可以參考 <a href="https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/scalable-matrix-extension-armv9-a-architecture" target="_blank">ARM 官方 SME 介紹文</a>. <br /></p><p>至此大略介紹了 ARM SVE/SVE2 的功能與相關 extensions. 後續希望有機會在撰寫相關實務文章.<br /></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-13883580040967908582022-03-04T22:25:00.005-08:002022-03-04T22:29:50.424-08:00ARM SVE 研讀筆記 Part-5 - Gather & Scatter<div><p>在 Processor 中開始出現 Gather and Scatter 相關指令之前之前, 許多 Hardware IP 就俱備有 Gather & Scatter DMA. 像是由於影像處理的需求, 一些 DSP 就俱備 2D DMA, 許多商業市場上的應用 DSP 都俱備這種能力(e.g: CEVA, Cadence Tensilica, Synopsys Arch ...), 而因為近年的 ML 對於 Tensor 操作的部份, 所以像是 >= 3D 或是俱備更多操作彈性的 DMA 在這幾年之中都出現了. 而 ARM SVE 迎合這個發展方向與相關的應用, 在推出時也針對 vector loading 做了補強, 增加了 Gather & Scatter 的相關指令.<br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgK5RdfidCdo3Kqq7uZRTymgacVK6n0vNaaddePGYzZMuMG4PE52dLpOlc9IrQh86yPdyoU39NfN6vUlo24fuSKHmBehbYW-1Tqarw0BWTqkylp2FaJNqT3YVAtedBtJSICt8-JvHz-EOxJjSZgfm_BynZMGwmxItIH5sFAJrCe7tqMri7MKGY-kQH0KQ=s1025" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="576" data-original-width="1025" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEgK5RdfidCdo3Kqq7uZRTymgacVK6n0vNaaddePGYzZMuMG4PE52dLpOlc9IrQh86yPdyoU39NfN6vUlo24fuSKHmBehbYW-1Tqarw0BWTqkylp2FaJNqT3YVAtedBtJSICt8-JvHz-EOxJjSZgfm_BynZMGwmxItIH5sFAJrCe7tqMri7MKGY-kQH0KQ=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 1 - Gather & Scatter 示意圖<br /></td></tr></tbody></table><p>首先必須說明 Gather 與 Scatter 的意義為何, 這是兩個對應的動作. 所謂的 Gather 是透過硬體所提供的規則將指定不連續的資料於 load in 後整併為單一個 vector 的動作; 而 Scatter 則是一個反向的行為, 也就是將所指定的 vector 中的資料, 透過規則所指定的方式, 將資料存放到各個不連續的記憶體位置中. 以這樣的方式看來 ARM NEON 原本就俱備的 vld2/vst2, vld3/vst3 與 vld4/vst4 事實上也是一個規則固定的 Gather / Scatter 退化型操作. 然而 vldN / vstN 因為規則太過固定, 因此實用性還是有所侷限. (話說 vld3 其實相較 x86 的 SSE/AVX 對於 3-channel 的資料, 像是 RGB/YUV 等等的操作真的方便不少) <br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEi-X9q-tQkxTMEygCkCavHucDpIpe4P2mO1BBj386F473a2zfQOl6veW6EUOKCMvhfDHhYpQWya3TiwWGL9NnWHCg-imUTGb8kHYdOXgFMlWqf5Sk3a_o89u4qW-1jA6Iz0P-wjFMM6qVOrdAS1DAZ_qI8IBafDdnwRkevseH9TyeAgkPtQ2WeJxplLEg=s1030" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="579" data-original-width="1030" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEi-X9q-tQkxTMEygCkCavHucDpIpe4P2mO1BBj386F473a2zfQOl6veW6EUOKCMvhfDHhYpQWya3TiwWGL9NnWHCg-imUTGb8kHYdOXgFMlWqf5Sk3a_o89u4qW-1jA6Iz0P-wjFMM6qVOrdAS1DAZ_qI8IBafDdnwRkevseH9TyeAgkPtQ2WeJxplLEg=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 2 - LD1 scalar base, vector offset 的例子<br /></td></tr></tbody></table><p>在 ARM 官方的文件 ARM C Ext. for SVE 中, 對於 Gather / Scatter 並非是匯整在同一個章節, 相對地這些是分散在 6.2 Loads 與 6.3 Stores 兩節中. 對於 Gather / Scatter 中提供不連續記憶體位置的方式有三:</p><ol style="text-align: left;"><li>base only: 也就是每個 lane 使用對應 lane 指定的 address</li><li>base + offset: 每個 lane 的 address 計算方式為: base + offset (in bytes) 的方式</li><li>base + index: 每個 lane 的 address 計算方式為: base + index (of element) 的方式, 也就是位置計算是 index * sizeof(element)<br /></li></ol><p></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEh6YuDdyeAnPDW2SlZdtIzUeZ0yWqmqJrPxQgUIwfeYLGsEUfm3yZensqP6op17SM28q7vRgZ2-gqXGY9JQ5bCVnmh5IWB4uC9dp58NudLyqI1Fc9bvYbBY4mDybJZeLPOZDCr0A0om8HRdxlqPBlOvYVT-FjldcR3XkrVuomMlA_ZAck3nS6mVe0ZTNw=s1152" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="648" data-original-width="1152" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEh6YuDdyeAnPDW2SlZdtIzUeZ0yWqmqJrPxQgUIwfeYLGsEUfm3yZensqP6op17SM28q7vRgZ2-gqXGY9JQ5bCVnmh5IWB4uC9dp58NudLyqI1Fc9bvYbBY4mDybJZeLPOZDCr0A0om8HRdxlqPBlOvYVT-FjldcR3XkrVuomMlA_ZAck3nS6mVe0ZTNw=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 3 - AoS 與 SoA 的不同考量示意<br /></td></tr></tbody></table><p></p><p>而 2. 與 3. 當中的 base + offset/index 的組合有著 (scalar, vector) 或是 (vector, scalar) 的組合, 一共有 4 種類型. 從使用面向上來看這兩種有著不同的應用:</p><ul style="text-align: left;"><li>scalar base + vector offset/index - 使用單一的 base, 搭配個別的 offset/index, 也就是說這適合單一起點的 structure of arrays 的方式</li><li>vector base + scalar offset/index - 使用單一的 offset/index, 搭配個別的 base, 也就是說這實際上非常適合應用在讀取 array of structures.</li></ul></div><p></p><p></p><p><br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgwELUzyLdB_Ej7EQux09O0dlYARzt5gU-uyeLKXsTerMJr0BvhIm_ZDrkqr0-9sTSdnq4bcu52ya0JaKCau7O0ItGZIw5ok5LHzQLNL5nauulDe0aGtuv2SAQGzVk9olfc_fjnFeYdkoo-P8gDLrChp1_mLnpEMWITJ6DI9hhaikdxFI_LCprJ15m1jw=s1034" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="582" data-original-width="1034" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEgwELUzyLdB_Ej7EQux09O0dlYARzt5gU-uyeLKXsTerMJr0BvhIm_ZDrkqr0-9sTSdnq4bcu52ya0JaKCau7O0ItGZIw5ok5LHzQLNL5nauulDe0aGtuv2SAQGzVk9olfc_fjnFeYdkoo-P8gDLrChp1_mLnpEMWITJ6DI9hhaikdxFI_LCprJ15m1jw=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 4 - Vector Address 計算的相關 intrinsic 說明<br /></td></tr></tbody></table><br /><p>最後是輔助 Gather & Scatter 的 vector address 計算的功能, 在 ARM SVE 中提供了 ADR 以及 INDEX 這兩個指令, 前者提供了 per-lane 的個別計算, 後者是以 base 與 steps 兩個參數來規律地計算產生整個 vector (事實上這個 index function 也可以用在需要等差級數的 vector 時候, 像是 shuffle 這類的情況)<br /></p><p>下一篇會進入 ARM SVE 研讀筆記的最後一篇 Part-6, 除了介紹最後一個功能特性 - extended floating-point horizontal reductions 之外也討論 SVE / SVE2 各自涵蓋的功能特性範圍.<br /></p><div><p></p><p><br /></p><p><br /></p></div>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-55307800142346646442022-02-28T01:11:00.006-08:002022-03-20T18:31:21.090-07:00ARM SVE 研讀筆記 Part-4 - Predicate-driven loop control and management 與 Vector partitioning & software-managed speculation<h4 style="text-align: left;">Predication-driven loop control and management</h4><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjOoDui0mdgvVg2DYTkas5HB1GupXA0m5Im4rCavhpGrdDfqWQApcxcSZZHB9BcI1AUiRD8nWWM1kD1oCrk0i3jV9cNptgIYctmODq5I0fttsd8_Alu7r_W5LJ1SIgURp90Ek1s3sF-hkmGMIxF0yuPH-TsyMUvyuiarKWLE0kICQLTJahSTlafyl2Uqg=s1025" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="576" data-original-width="1025" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEjOoDui0mdgvVg2DYTkas5HB1GupXA0m5Im4rCavhpGrdDfqWQApcxcSZZHB9BcI1AUiRD8nWWM1kD1oCrk0i3jV9cNptgIYctmODq5I0fttsd8_Alu7r_W5LJ1SIgURp90Ek1s3sF-hkmGMIxF0yuPH-TsyMUvyuiarKWLE0kICQLTJahSTlafyl2Uqg=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 1 - Part-3 中使用的 ARM 官方 SVE 範例</td></tr></tbody></table><p style="text-align: left;"></p><p></p><p style="text-align: left;">事實上在 <a href="https://champyen.blogspot.com/2022/02/arm-sve-part-3-per-lane-predication.html" target="_blank">Part-3</a> 的 ARM 官方範例中, 就已經展示了這個特性, 在 ARM SVE 中提供了 WHILELT 與 WHILELE 指令來作為遞增 for loop 中的 > 與 >= 的比較, 並產生對應所需的 predication 結果來處理在一般 SIMD 當中較不方便處理的 loop 結尾, 這是由於 loop 的結尾所需處理的數量很可能並非是 vector-size 的倍數, 而記憶體存取的位置也很可能並非 vector-size aligned. 在範例中可以看到使用了 svwhilelt_b64, 這即為 SVE 提供的 loop control / management 功能. 在 ARM SVE 中針對處理資料寬度提供了各自對應的 intrinsic functions:<br /></p><ul style="text-align: left;"><li>8bit - svwhile<b>lt</b>_b8 / svwhile<b>le</b>_b8 / svcntb </li><li>16b - svwhile<b>lt</b>_b16 / svwhile<b>le</b>_b16 / svcnth <br /></li><li>32b - svwhile<b>lt</b>_b32 / svwhile<b>le</b>_b32 / svcntw <br /></li><li>64b - svwhile<b>lt</b>_b64 / svwhile<b>le</b>_b64 / svcntd<br /></li></ul><p style="text-align: left;">而在 ARM SVE2 中針對遞減 for loop 增加了 WHILEGT 與 WHILEGE, 很直覺地這是用來處理 loop control 中的 < 與 <= 的比較.<br /></p><ul style="text-align: left;"><li>8bit - svwhile<b>gt</b>_b8 / svwhile<b>ge</b>_b8 <br /></li><li>16b - svwhile<b>gt</b>_b16 / svwhile<b>ge</b>_b16 <br /></li><li>32b - svwhile<b>gt</b>_b32 / svwhile<b>ge</b>_b32<br /></li><li>64b - svwhile<b>gt</b>_b64 / svwhile<b>ge</b>_b64</li></ul><h4 style="text-align: left;">Vector partitioning & software-managed speculation<br /></h4><p style="text-align: left;">在許多時候 buffer 的長度並非 vector size 的倍數, 這造成 vector load 的行為可能會超過 buffer 邊界, 或是要處理的資料的長度未知, 可能在處理過程中造成 memory fault 的行為. 而在讀取過程中, 可能有因計算需求的其他對應的 vector load. 對於這樣的需求, ARM SVE 導入了 First-Faulting 與 Non-Faulting vector load 的設計.</p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiF16ECxSGSetWuGPKHwubYyBN356vUZswUgJo9F85dk0QqpoTXfl7yAodhD9_t02GnRsTLu08l_9KWD13Y18Rx9VaQzuU3TqRWUzsGt4BhC627XBwO4UgXoGmxh6i_0FJpg6jNfzio8l_TwQKViHFbuvIm3EMd4-SSUoRnOO9Wves09o9AvY64-Hb_ug=s1324" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="745" data-original-width="1324" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEiF16ECxSGSetWuGPKHwubYyBN356vUZswUgJo9F85dk0QqpoTXfl7yAodhD9_t02GnRsTLu08l_9KWD13Y18Rx9VaQzuU3TqRWUzsGt4BhC627XBwO4UgXoGmxh6i_0FJpg6jNfzio8l_TwQKViHFbuvIm3EMd4-SSUoRnOO9Wves09o9AvY64-Hb_ug=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 2 - ARM SVE 使用的 register set<br /></td></tr></tbody></table><br /><p style="text-align: left;">在 ARM SVE 的 register set 中有著一個不起眼的 register - first-fault register (FFR), 其寬度與 predicate register 相同. FFR 的目的在於存放執行 first-faulting or non-faulting vector load 中每個 lane 成功與否的結果. </p><p style="text-align: left;"></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhjXiBrFQ9sR17ZIoUfr2ZqvwwGEx4-s_eJsauEjf7D8e3096owsFM_08zRmDbKY4fq9GGyXCw0kJbQEdi9Nx9t4YoKtHZ9GBVeCOrj9D8D-eruHBaHS8v_5OZQscryh8pTmRZu8NjnXGRn2AUmX5PW71chr9ZyxnFH4C-Jd466MH-NUH8V21GCBy2maw=s1063" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="598" data-original-width="1063" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEhjXiBrFQ9sR17ZIoUfr2ZqvwwGEx4-s_eJsauEjf7D8e3096owsFM_08zRmDbKY4fq9GGyXCw0kJbQEdi9Nx9t4YoKtHZ9GBVeCOrj9D8D-eruHBaHS8v_5OZQscryh8pTmRZu8NjnXGRn2AUmX5PW71chr9ZyxnFH4C-Jd466MH-NUH8V21GCBy2maw=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 3 - first-fault register 運作機制<br /></td></tr></tbody></table><p></p><p style="text-align: left;">而 first-faulting 與 non-faulting 的差異在於 first-faulting vector load 的 VLDFF1 系列指令中, 只有第一個 active lane 會產生造成例外情形的 memory fault. 而 non-faulting vector load 的 VLDNF1 系列指令按照字義上而言就是不會產生 memory fault 的指令. 此外, 無論使用 first-faulting 或是 non-faulting vector load 的時候, 在 FFR 中每個 lane 對應的值若事先已被設為 0 / false, 那麼該 lane 的結果將會是 unknown 狀態. </p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiZkgL_tSA6jz7jpgbxksCpQlnM0ex1mwNWZThFqsFS0t--XH1QCmgO5aiGjwLIU_VsLGJGxDXCl6DEf8iHrj9FLoUjy_9Q09fXUErKP50Fw750_7EywmYH6bdcj8P11lz6lJn4ckhQon7FRBFdsc3xVyF9FLCfL4tMXnOcn_nINILZgJzvw2TRSq7x7Q=s459" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="194" data-original-width="459" height="169" src="https://blogger.googleusercontent.com/img/a/AVvXsEiZkgL_tSA6jz7jpgbxksCpQlnM0ex1mwNWZThFqsFS0t--XH1QCmgO5aiGjwLIU_VsLGJGxDXCl6DEf8iHrj9FLoUjy_9Q09fXUErKP50Fw750_7EywmYH6bdcj8P11lz6lJn4ckhQon7FRBFdsc3xVyF9FLCfL4tMXnOcn_nINILZgJzvw2TRSq7x7Q=w400-h169" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 4 - First-Faulting Vector Load 範例<br /></td></tr></tbody></table><p>在 Fig. 4 中為 ARM C Language Extension 對於 first-faulting 的範例. 一開始執行 SETFFR 之後, FFR 的所有值都會為 1/true, 接著連續執行三次 LDFF1 指令, 將指定資料讀取至 Z0 ~ Z3. 最後透過 RDFFR 指令將 FFR 的內容存放至 P3 當中. 而 P3 所存放的數值表示哪些 lanes 在 Z0 ~ Z3 當中皆為 valid 狀態. 此資訊對於一些不定數目但需要合併處理的情況相當有幫助.</p><p><br />3/21 - 感謝來自 FB 臉友 <a href="https://www.facebook.com/chimerawang" target="_blank">王北北</a> 的指正, SVE 與 SVE2 支援的 WHILELT&WHILELE 與 WHILEGT&WHILEGE 一時不察寫反了, 已修正<br /></p><p style="text-align: left;"></p><p style="text-align: left;"></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com2tag:blogger.com,1999:blog-8142113334815576231.post-22944748125912773952022-02-26T22:12:00.006-08:002022-03-12T00:43:45.020-08:00ARM SVE 研讀筆記 Part-3 - Per-lane predication<p>在切入 ARM SVE 第二個特性 - Per-lane predication 之前, 讓我們先以 ARM 官方 SVE 範例程式為例, 說明 SVE 的 C/C++ 程式看起來會是怎樣的型態.<br /></p><p>使用 SIMD Intrinsics 依然是性價比相當高的方式, 除了可以直接與 C/C++ 程式碼混合撰寫外, 也避免了直接使用 assembly 撰寫的曠日費時與難以除錯. 當然使用 SIMD intrinsics 的缺點是可攜性與跨平台問題. 這點就在先前文章多少提及, 在此就不綴述. 這裡讓我們以第一個範例來切入 SVE intrinsics 的使用</p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgeA7C6dns_7otVq1aosX5GTZmW7ddBxdwij8uWA0Y7E-xlyoaPpkyOsvxq-FdgEsDskU82VB4SIKxikrDyjnasDE5FxsV3KikrDpvpH966zxEVLrfH4nHnLd9AMVxJAahki36FwlkCutkoycYL8U_k1SpVW6BXmMLryzAaiFuuqpGukhxw8aPa3IEt_Q=s1025" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="576" data-original-width="1025" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEgeA7C6dns_7otVq1aosX5GTZmW7ddBxdwij8uWA0Y7E-xlyoaPpkyOsvxq-FdgEsDskU82VB4SIKxikrDyjnasDE5FxsV3KikrDpvpH966zxEVLrfH4nHnLd9AMVxJAahki36FwlkCutkoycYL8U_k1SpVW6BXmMLryzAaiFuuqpGukhxw8aPa3IEt_Q=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 1 - SVE intrinsics 使用範例程式 double dot</td></tr></tbody></table><p style="text-align: left;">Fig. 1 中的程式來自 ARM 官方 SVE intrinsics 的範例, 這當中有幾個重點:<br /></p><ul style="text-align: left;"><li>首先是第一行藍色標示的部份, "arm_sve.h" 的使用對 SVE intrinsics 是必要的</li><li>在上一篇有談到 SVE vector type 型別的使用來宣告變數, 上圖中以草綠色標示</li><li>如同一般的 ARM NEON 程式, 呼叫所需要的 intrinsic function, 這裡以暗紅色標示. 值得一提的是, 雖然相較於 NEON intrinsics, SVE 增加了 postfix 來做特定用途. 但多數基本功能, 都可以將 ARM NEON intrinsics 原有的名稱在開頭加上一個 "s" 並且移除用來判別處理 64/128-bit 寬度的 "q", 就可以索引到所需的功能. 例如, 原本做 float 型別的vector element 總和會使用 vaddv<span style="color: #990000;"><b>q</b></span>_f32, 這裡就可以去搜尋到 Fig. 2 中所列出的 <span style="color: #990000;"><b>s</b></span>vaddv_f32</li><li>最後是紫色標示的部份, 這也是本文要說明的 Per-lane predication, 在 ARM SVE 中新增了在 DSP SIMD 中常見的 Per-lane predication, 這能用來做更為細緻的控制, 來減少在程式撰寫中因 SIMD 限制而不得不以 scalar code 來處理的部份. 這個範例精簡地展示了 predicate 用在 SVE 中的 3 個 predication 特性 - 本文的 per-lane predication 與後續的Predicate-driven loop control and management 與 Vector partitioning & software-managed speculation.</li></ul><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEglt-5pnu6nF_EWD9yVGdUZMeUri4_SNQU7Qd0XdPnYNI4f4kzTe6gFdOl7BqRuxc7ieWhGFRnHhXwLo7A-biZt3B9sZqYmjSNQeSJ-biWFRm-CW0mmy_ChQxxudW3XSYIt3Xo8Mnt1rseOIDePYU9XgRkWmh6-rnrzWet-AUSh_AGy12rYS3wUFjY0ag=s790" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="152" data-original-width="790" height="124" src="https://blogger.googleusercontent.com/img/a/AVvXsEglt-5pnu6nF_EWD9yVGdUZMeUri4_SNQU7Qd0XdPnYNI4f4kzTe6gFdOl7BqRuxc7ieWhGFRnHhXwLo7A-biZt3B9sZqYmjSNQeSJ-biWFRm-CW0mmy_ChQxxudW3XSYIt3Xo8Mnt1rseOIDePYU9XgRkWmh6-rnrzWet-AUSh_AGy12rYS3wUFjY0ag=w640-h124" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 2 - 透過 NEON vaddvq_f32 能找到的 SVE intrinsics 系列<br /></td></tr></tbody></table><br /><p>所謂的 Per-lane predication 功能是指在 SIMD 中在套用的操作上, 對於每個 lane 提供一個 bit 來做 active or inactive 的控制. 而 active lane 即表示需要直接套用所使用的操作. 對於 inactive lane 部份則有幾個模式, 這點會在稍候解釋.<br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhfiv6Vj5qvzZW4BUNGLgi07QNvnxrGZgwXHWDgZUOMf-TiJhetByHHAiijJmT48uxu95Mzk_r3rMfxUMOBzDg6VXq3yPAyS_klwhT3w498RCY9VqFWa4DLhIX4ukkH5wLMEvRbkOhR0MXDar8HheXA4-4b2awaJyM-ATiNILPy8d1HhMie536dfpfrgg=s1069" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="601" data-original-width="1069" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEhfiv6Vj5qvzZW4BUNGLgi07QNvnxrGZgwXHWDgZUOMf-TiJhetByHHAiijJmT48uxu95Mzk_r3rMfxUMOBzDg6VXq3yPAyS_klwhT3w498RCY9VqFWa4DLhIX4ukkH5wLMEvRbkOhR0MXDar8HheXA4-4b2awaJyM-ATiNILPy8d1HhMie536dfpfrgg=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 3 - predication 圖示<br /></td></tr></tbody></table><br /><p>在 ARM SVE 的設計上對於多數 intrinsic function 都加入了 svbool_t 型別的參數 pg, 這即是用來作為 lane control 的 predication. 其基本概念如 Fig. 3 所示, 一旦該 lane predication 值為 1/true 的即表示該 lane 狀態是 active; 而若該 lane 的 predication 值為 0/false 的, 該 lane 狀態即為 inactive. 若 intrinsic function 沒有特別的 postfix, 那麼 inactive lane 則是維持原本的數值.<br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjRWCee9RUWoPZHQ1zfgNreVCva5Si0_7_j0DbB6FgF2z9Umpk2dvEuANMcK55fKVSj3n4NUYUVQoh1mEjjeS2VACEnpSSLMHQVBij0ujYNy8833HcSiduabsjMTC4-IyhDyUHrfzOPzp-JS4i5IILnmY-rJz-wcQAqkY07KpaO-xpH3XtHKIUNkxUEMQ=s1069" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="601" data-original-width="1069" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEjRWCee9RUWoPZHQ1zfgNreVCva5Si0_7_j0DbB6FgF2z9Umpk2dvEuANMcK55fKVSj3n4NUYUVQoh1mEjjeS2VACEnpSSLMHQVBij0ujYNy8833HcSiduabsjMTC4-IyhDyUHrfzOPzp-JS4i5IILnmY-rJz-wcQAqkY07KpaO-xpH3XtHKIUNkxUEMQ=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 4 - Predication register 對應 Vector register 的方式.<br /></td></tr></tbody></table><br />若對<a href="https://champyen.blogspot.com/2022/02/arm-sve-1.html" target="_blank"> Part-1 </a>的內容還有印象的話, ARM SVE 的 vector register 為 128b 的倍數, 可為 128~2048b 的任何選擇. 而 predication register 為 16b 的倍數, 可為 16~256b 的任何選擇. 但 predication register 與 vector register 長度的關係是連動的. 一旦決定了 ARM SVE 中關於 vector length 的 LEN 參數, 就固定了兩者的長度. 這也表示 vector / predication 是以 8:1 的比例對應的. 對於 8b 以上的型別會有額外的 bit. 這時的選擇方式如 Fig. 4 所圖解的, 對於 16/32/64b 的 data types, 是以對應的多個 bits 之中最低位的 bit 來控制該 lane. <br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiitxeeczWlbkRFroP8ZVmNO13CkgKp91X7cP7cvotuHH0vkFZAgjChf3quY-Z2ZwIe66H1Njkvx_dq2t4vqNFj629OZZWmdOhvqYOZi7WJPJtck1t9RMAD63yCPNTV6Mb8JTX7ouF0MVaiDLy0crZu-VVdABmFms3uqPsYhWfXtg1UD8qetFGgdS_U_g=s1069" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="601" data-original-width="1069" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEiitxeeczWlbkRFroP8ZVmNO13CkgKp91X7cP7cvotuHH0vkFZAgjChf3quY-Z2ZwIe66H1Njkvx_dq2t4vqNFj629OZZWmdOhvqYOZi7WJPJtck1t9RMAD63yCPNTV6Mb8JTX7ouF0MVaiDLy0crZu-VVdABmFms3uqPsYhWfXtg1UD8qetFGgdS_U_g=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 5 - Predication 產生與操作的方式<br /></td></tr></tbody></table><p></p><p>到此可以輕易地理解, Predication 通常是用來處理 SIMD 中通常難以處理的 if/else 或是 switch case 等等的流程. 因此 predication 的產生最直觀的就是對應 C++ boolean type 中的許多 logical operations - 舉凡數值間各種比較運算 !=, ==, <, <= , >, >= 與 boolean type 間各類 !, &&, || 等等. <br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhn11MiYuNgSI3pEByx1eyxLKQOy3q9KWmtWXzi96jRfcnv8l79zSf0zD8WsE7dzinmZLdOH9EcyVQDhorcwoTlEuPgn0dRU438ZM8J2aMZkF31QLJ8phK0PvWThfQhVoe5CagmnJkImtOzyprynXWWgHcIi2CqIlCfmQKC0qO6dcdrXi9elWPeK-BIGw=s954" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="326" data-original-width="954" height="218" src="https://blogger.googleusercontent.com/img/a/AVvXsEhn11MiYuNgSI3pEByx1eyxLKQOy3q9KWmtWXzi96jRfcnv8l79zSf0zD8WsE7dzinmZLdOH9EcyVQDhorcwoTlEuPgn0dRU438ZM8J2aMZkF31QLJ8phK0PvWThfQhVoe5CagmnJkImtOzyprynXWWgHcIi2CqIlCfmQKC0qO6dcdrXi9elWPeK-BIGw=w640-h218" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 6 - Inactive Lane 的模式<br /></td></tr></tbody></table><br /><p>最後是 inactive lane 的動作, 依照所使用的 intrinsic function 共有 3 種, 也就是在一些 intrinsic functions 的名稱中在 data type 之後還可以看到 "_z", "_m" 或是 "_z" 的 postfix. 這是對於 inactive lane 模式的選擇. 使用了 "_z" 結尾的 intrinsic function 表示了希望 inactive lane 的數值填為 0; 而使用了 "_m" 結尾的 function 則是代表維持原本輸入的第一個 vector 參數並不做任何的更動; 最後是 "_x", 這表示不在意 inactive lane 的數值, 讓 compiler 去針對 compilation 參數(performance or code size)去作較佳的選擇( _z, _m 或是甚至不使用 predication ), 一旦使用 "_x" 的 intrinsic function 必須清楚地知道, inactive lane 當中並沒有預設或保證當中為何值.<br /></p><p>下一篇將延續 Per-lane predication 來同時講述 Predicate-driven loop control and management 與 Vector partitioning & software-managed speculation.<br /></p><p><br /></p><br />網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-25225062273481292882022-02-26T05:39:00.003-08:002022-02-26T22:35:47.270-08:00ARM SVE 研讀筆記 Part-2 - Vector Length Agnostic (VLA)<p>個人認為 Vector Length Agnostic (VLA) 是討論 SVE 時必須先討論的特性. 談到這特性必須先了解多數的 SIMD 指令集都有固定使用的 vector width/length, 像是 intel 對於 <a href="https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html" target="_blank">x86 平台的 SIMD</a>
發展過程, 從 MMX 的 64-bit 開始, 一路發展 SSE / 128-bit , AVX & AVX2 / 256-bit
到最近的 AVX-512 / 512-bit 每一組都有固定的 vector length, 但是即便使用相同的運算, 對於使用了不同的 SIMD instruction set, 這也必須對應到 CPU 內不同的 opcode. 因此使用舊的 SIMD instruction 的程式並無法受益於新一代 SIMD ISA 的好處. <br /></p><p>而 ARM SVE 在制定時一開始就允許 IC 商能夠依照應用的需求的考量, 去配置硬體內的 SIMD vector length. 另外制定 SVE 專屬的 VLA vector type 與 intrinsics, 讓開發者能夠達到撰寫所撰寫的 ARM SVE 程式無需特別針對 vector length 改寫或重新編譯, 即可執行運作. 而 IC vendor 也能更為安心地藉由增加 vector length 達到效能增加而無需擔心程式的重新客製問題.<br /></p><p></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEi-VAettEoUdNpTVjcj0M96RhvutKjOTEZ9JNJ4zQKu8QNjGbv_VX_46T9vvjuIbaNIx-JvFP33MbKLyYYYa0NuxjTGxzYlRVyFwi_LvFN1x4hDtV7W-jQgHmftEYyH6X97Kwnq0VYO1g51cQhHcW6MXmRt2-yyqUT3c2rhrHO77zMVWaWVPXRv-I4iGQ=s1038" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="583" data-original-width="1038" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEi-VAettEoUdNpTVjcj0M96RhvutKjOTEZ9JNJ4zQKu8QNjGbv_VX_46T9vvjuIbaNIx-JvFP33MbKLyYYYa0NuxjTGxzYlRVyFwi_LvFN1x4hDtV7W-jQgHmftEYyH6X97Kwnq0VYO1g51cQhHcW6MXmRt2-yyqUT3c2rhrHO77zMVWaWVPXRv-I4iGQ=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 1 - Vector Length Agnostic 的圖解與程式特性<br /></td></tr></tbody></table><br />類似於其他各 SIMD 指令集的 C intrinsics, 需要對應的 vector type 與 intrinsics function. ARM 對於 SVE, 也提供了對應的 vector type 以及 function 介面. 如 Fig. 2 中所列出的, 其 vector type 涵蓋了基本的 8/16/32/64-bit signed / unsigned int 與 float / double 等等, 然而還增加了 boolean, float16 與 bfloat16 等等新的型別. <br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhCcbStT09-6XOqIk4MOdJqlDESL597VxrTH5efyF8F5ytW_9SWbb72xdZVrjRkf2NnCGNQ1e9EINEHezMDSSjXoj4aeksHT1jpsZ9Fxj_PuZDppjZ9fDMDTWO6GQfWLPDc9HhJmT3s5Jm27ltifKc54kQV35eD26nTR8wf6_2bZC4AnTgz4uCTkrYdPA=s1042" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="586" data-original-width="1042" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEhCcbStT09-6XOqIk4MOdJqlDESL597VxrTH5efyF8F5ytW_9SWbb72xdZVrjRkf2NnCGNQ1e9EINEHezMDSSjXoj4aeksHT1jpsZ9Fxj_PuZDppjZ9fDMDTWO6GQfWLPDc9HhJmT3s5Jm27ltifKc54kQV35eD26nTR8wf6_2bZC4AnTgz4uCTkrYdPA=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 2 - ARM SVE intrinsics提供的 vector types<br /></td></tr></tbody></table><br /><p></p><p>然而這些 Vector types 與其他 SIMD intrinsics 所提供的最大不同是這些是 "sizeless", 也就是對於 compiler 而言, 這些 vector types 並沒有俱備任何 size 資訊. 於第一時間或許覺得沒大問題, 但是事實上 VLA 帶來了許多在語言特性以及工具使用上的限制: </p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjoQLObw1Kwdg-j1KrYJD4FN_xce-GE39opEgYPtp9w7ACL8xzXhhJ115QeoqdkOw-C51znh9cDfqH2TUOR6vJrjATGhvHeyXRC_pnX9eKHZ3Sw2Zw2HuHz9sndkXr_Qe8aMSAfnnOvHQ4Sg9eKePQcQ98uj2uCRg8zsgtv6__PJI0tgy6l0sGQmnK77g=s1049" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="590" data-original-width="1049" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEjoQLObw1Kwdg-j1KrYJD4FN_xce-GE39opEgYPtp9w7ACL8xzXhhJ115QeoqdkOw-C51znh9cDfqH2TUOR6vJrjATGhvHeyXRC_pnX9eKHZ3Sw2Zw2HuHz9sndkXr_Qe8aMSAfnnOvHQ4Sg9eKePQcQ98uj2uCRg8zsgtv6__PJI0tgy6l0sGQmnK77g=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 3 - Sizeless 帶來的實務限制<br /></td></tr></tbody></table><br />如 Fig. 3 所列出的, VLA 帶來的限制主要都是因為缺乏 size 資訊造成需要此資訊的語言特性使用的限制. 這些包含了無法去使用 sizeof / alignment, pointer arithmetic. 比較不直覺的像是無法作為 union / struct / class 的 member field 與宣告用來存放的 STL container , 另外無法成為 exception handling 中 throw / catch 機制的 object. 最後是 lambda 中 capature 機制無法 capture by value. (但是能夠使用 capture by reference )<br /><p></p><p> </p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-83838873556294009352022-02-25T05:37:00.002-08:002022-02-25T05:38:54.893-08:00Halide 實務心得 5<p>在實作影像處理演算的過程或是實務的功能整合中, 都圍繞著 <a href="https://halide-lang.org/docs/class_halide_1_1_runtime_1_1_buffer.html" target="_blank">Halide::Runtime:: Buffer</a> 的使用. 有時難以避免需要初始化預設值或是從其他 Buffer 物件複製資料. 這些可以透過 <a href="https://halide-lang.org/docs/class_halide_1_1_runtime_1_1_buffer.html#ab6dd431ae848e2b16ba659f3578f0776" target="_blank">Buffer::fill()</a> 或是 <a href="https://halide-lang.org/docs/class_halide_1_1_runtime_1_1_buffer.html#a36b8651b74d74d3495b4c664c708af05" target="_blank">Buffer::copy_from</a> 來做基本的填值處理.</p><p>對於一些相當 routine 但是卻又需要計算的部份, 通常會透過 loop 來走過每個座標, 甚至每個一個 value. 像是做簡單的 gamma correction 可能就會做這樣的動作:<br /></p><blockquote><p>Buffer<uint8_t, 2> src = Buffer<uint8_t>(w, h);<br />...<br />Buffer<uint8_t, 2> dst = Buffer<uint8_t>(w, h);<br />for(int y = 0; y < dst.height(); y++){<br /> for(int x = 0; x < dst.width(); x++){<br /> for(int c = 0; c < dst.channel(); c++){<br /> dst(x, y, c) = gamma(src(x, y, c));<br /> }<br /> }<br />}<br /></p></blockquote><p>或是可能是格式轉換 (像是 RGB <-> YUV 或是這裡的 Demosaic):</p><blockquote><p>Buffer<uint8_t, 2> src = Buffer<uint8_t>(w, h);<br />...<br />Buffer<uint8_t, 2> dst = Buffer<uint8_t>(w, h);<br />for(int y = 0; y < dst.height(); y++){<br /> for(int x = 0; x < dst.width(); x++){<br /><span> </span><span> </span>uint8_t r, g, b;<br /><span> </span><span> demosaic(src(x, y), r, g, b);</span><br /> dst(x, y, 0) = r;<br /><span> </span><span> </span>dst(x, y, 1) = g;<br /><span> </span><span> </span>dst(x, y, 2) = b;<br /> }<br />}<br /></p></blockquote><p>一旦數量很多, 或是 dimension 很大一一撰寫 loop 除了費時費力外, 也讓 code 不易維護與可讀性不佳. 對於這種問題, Halide 透過了 C++ lambda 的方式能夠簡短地處理, Halide Buffer 提供了兩個在其官方教學沒有提到的兩個實用的方式: <a href="https://halide-lang.org/docs/class_halide_1_1_runtime_1_1_buffer.html#af1a45d711ebe0a05a95e12df6752a1fa" target="_blank">for_each_value</a> 與 <a href="https://halide-lang.org/docs/class_halide_1_1_runtime_1_1_buffer.html#acb05dc40bab73f738b2dfa53f334b970" target="_blank">for_each_element</a> :<br /></p><p>對於 Gamma 的例子可以簡短地改寫為:</p><blockquote><p>Buffer<uint8_t, 2> dst = Buffer<uint8_t>(w, h);<br />dst.for_each_value([&](uint8_t &dval, uint8_t &sval){<br /> dval = gamma(sval);<br />}, src);<br /></p></blockquote><p>而對於像 demosaic 的例子則可以改寫為:</p><blockquote><p>Buffer<uint8_t, 2> dst = Buffer<uint8_t>(w, h);<br />dst.for_each_element([&](int x, int y){<br /><span> </span><span> </span>uint8_t r, g, b;<br /><span> </span><span> demosaic(src(x, y), r, g, b);</span><br /> dst(x, y, 0) = r;<br /><span> </span><span> </span>dst(x, y, 1) = g;<br /><span> </span><span> </span>dst(x, y, 2) = b;<br />});<br /></p></blockquote><p> 這樣的作法也適合套用在一些特殊的初始化. <br /></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-18344390440219157462022-02-21T01:48:00.004-08:002022-02-21T01:58:30.579-08:00How to make "Dark mode" listed in Facebook APP on Android TabletI've been annoyed by Facebook's dark mode on my Android tablet for a long time. <div>After one of the update several months ago, my Facebook app doesn't list "dark mode" on menu any more. All articles on the internet said you can find it under "Setting & Privacy" of burger menu. But I just can't find it for a long time</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjv6Pp4NM7MAqLqvSFg6EVmzSSLRoNRm_-v6uLu8fNZ9o7TNZP6kyEctH0k_RwmAV2MH0XE8lVSuubH8WvFjkdX-UzcpfCDJIU5Wj6iqiRlM5kIhS_0MUbJ0DxYMQGVb9d0OMzhz4ePP3tC/s1600/1645436537018929-0.png" style="margin-left: 1em; margin-right: 1em;">
<img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjv6Pp4NM7MAqLqvSFg6EVmzSSLRoNRm_-v6uLu8fNZ9o7TNZP6kyEctH0k_RwmAV2MH0XE8lVSuubH8WvFjkdX-UzcpfCDJIU5Wj6iqiRlM5kIhS_0MUbJ0DxYMQGVb9d0OMzhz4ePP3tC/w384-h640/1645436537018929-0.png" width="384" />
</a>
</div><br /></div><div><br /><div>Today I accidentally found the root cause. Therefore I write this to share my finding in English to let more people know about the tricky thing. It's all about display setting. If you have the same issue, please check your "Display size" in the "Display" menu of Settings. </div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOWVtxyMir46Ein2CidF9KonELO1X-vqb5zd3sOkyqLjoaOXA5NZLjhnfCtvlN2kcrCF_riepct5o5p3-35FAOseLworoHuZT41EU2IvI3T91gFGk2roI57RZvQJ0wc_qwPlh1YrxZE-Vp/s1600/1645436533335710-1.png" style="margin-left: 1em; margin-right: 1em;">
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOWVtxyMir46Ein2CidF9KonELO1X-vqb5zd3sOkyqLjoaOXA5NZLjhnfCtvlN2kcrCF_riepct5o5p3-35FAOseLworoHuZT41EU2IvI3T91gFGk2roI57RZvQJ0wc_qwPlh1YrxZE-Vp/s1600/1645436533335710-1.png" width="400" />
</a>
</div><br /></div><div>For some Android tablet users, they want to show more on the screen, so it would be changed to "default", "small", "smaller", or "smallest". There is no identical setting that will make dark mode be shown on menu. Just try "default", "large", "larger" or "largest" one bye one. After eche changing of the setting, please remember to kill Facebook app and the relaunch it. Then you will find a setting that works. (According to my experience, the "middle" one will be the first working setting.) <br /></div></div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkQq21bdAzYdDjqSArNFRja86guLcY8d1Tz7m5wIVjVNGfAweAYZMy8WeFrDzrqkwh916z_rA4nzM_Rbuvez8Xb8ArRilz3omqArWQPXpC5Drnj-xbuA7BIOXWhDSCv195tqp4__6osvCw/s1600/1645436529532942-2.png" style="margin-left: 1em; margin-right: 1em;">
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkQq21bdAzYdDjqSArNFRja86guLcY8d1Tz7m5wIVjVNGfAweAYZMy8WeFrDzrqkwh916z_rA4nzM_Rbuvez8Xb8ArRilz3omqArWQPXpC5Drnj-xbuA7BIOXWhDSCv195tqp4__6osvCw/s1600/1645436529532942-2.png" width="400" />
</a>
</div></div><div><br />Hope this article would be helpful for some people.<br /></div>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-65075436499684235242022-02-16T23:55:00.005-08:002022-02-26T22:32:26.164-08:00ARM SVE 研讀筆記 Part-1 - ARM SVE 基本概述ARM 於 ARMv8.2 時期開發了新一代 SIMD 指令集, 以選擇性擴充的方式來接替已使用多年的 NEON, 並在 ARMv9 中預設涵蓋, 該 SIMD ISA 全名為 Scalable Vector Extension 簡寫為 SVE. 這中中最著名的使用案例是與富士合作的 ARM 超級電腦平台中使用了配置為 512b vector length 的 SVE 實作. 相較 NEON 而言, SVE 除了新增功能特性的設計上, 除了增進便利性, 也同時一併考慮了 SIMD 效能擴充 / 延伸性的問題. <div><br /></div><div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><img border="0" height="365" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoQc-9zMBeIHU_iUxSH2RPDEWQkQh3XBlcWaodXyvAQSamZM9JuwOIZLkRU5iANTrcADGX8JIU7j8Vt0dUM9FWEglDYyMgRGWltG77fO4_lLSggOE3wm8itkw5iFULjfOSFSTGnpR55Akj/w640-h365/1645066121332141-0.png" style="margin-left: auto; margin-right: auto;" width="640" /></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig.1 - ARM SVE vector register 與 NEON register 關係圖.<br /></td></tr></tbody></table><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoQc-9zMBeIHU_iUxSH2RPDEWQkQh3XBlcWaodXyvAQSamZM9JuwOIZLkRU5iANTrcADGX8JIU7j8Vt0dUM9FWEglDYyMgRGWltG77fO4_lLSggOE3wm8itkw5iFULjfOSFSTGnpR55Akj/s1600/1645066121332141-0.png" style="margin-left: 1em; margin-right: 1em;">
</a>
</div><div><br /></div><div>ARM SVE 的設計最特別的地方在於它採用了不同於現今主流 CPU / DSP 皆以 fixed vector length 來制定出一組 SIMD 指令集的方式. 在設計上採用了硬體實作中可對於內部 vector length 作可調整配置的方式, 這也是名稱上使用了 "Scalable" 一詞的原因. ARM SVE 允許硬體商因應用效能上需求的考量而去決定 vector length, 越長的 vector 因為需要更多的 ALU 來提供 ILP, 也會直接地反映在 die area 上. 而 ARM SVE 的 vector length 選擇上必須是介於 128~2048b 而且為 128b 的倍數.</div><div><br /></div><div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><img border="0" height="362" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwIFY-BUAgNVF11GAlnSubk13lJ613lDVX7PJhLpTUuaMBeZhqvUVZVOTJF07WNSGWYSP8RPUKSiYxquxI-CRjZTEvqj12DYLG4HfBF8bY9dcZJwJhAQhjGku2sE8GM4DVT0zJlB8T4e4-/w640-h362/1645066117077357-1.png" style="margin-left: auto; margin-right: auto;" width="640" /></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 2 - ARM SVE 新增的 predicate register.<br /></td></tr></tbody></table><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwIFY-BUAgNVF11GAlnSubk13lJ613lDVX7PJhLpTUuaMBeZhqvUVZVOTJF07WNSGWYSP8RPUKSiYxquxI-CRjZTEvqj12DYLG4HfBF8bY9dcZJwJhAQhjGku2sE8GM4DVT0zJlB8T4e4-/s1600/1645066117077357-1.png" style="margin-left: 1em; margin-right: 1em;">
</a>
</div></div></div><div>設計上與 ARM NEON 的另一個重大差異, 在於 ARM SVE 導入 16 個 predicate register. 這是用在於 DSP SIMD 中相當常見的 Per-lane predication 用途上. 在此必須特別說明的是於 Fig. 2 中的 LEN 參數與 Fig. 1 的是連動的相同數值, 這表示 IC vendor 選擇了 LEN 就同時固定了 vector 與 predicate register 的長度, 因此兩者長度的比例固定為 8:1. 在 ARM SVE 中透過使用 predicate, 能夠精確的控制個別資料的計算與否, 如此處理一些 if/else 等等的狀況會更為便利. 由於 SVE 的進階功能都圍繞著 predication 的使用, 因而 SVE 也有著對 predication 運算操作的指令.</div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiRCZqOpzLK6dlX1L_tQyVWH-NkWQX-JP4AsC9wFr7AYVmhAmISyHfIZi8drO-utyV8NDT2rJ1Vd-LAsAkuujs_g0hzV3uxyp7o4Z3lyDUIKaVw8bhXQ4w-3G9HuRO6bx2XLX7rUyuxL24PFD0ajrrEsXa61rp8cG34-otKHBOfmWEbdMrrSntyaye1aw=s974" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="548" data-original-width="974" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEiRCZqOpzLK6dlX1L_tQyVWH-NkWQX-JP4AsC9wFr7AYVmhAmISyHfIZi8drO-utyV8NDT2rJ1Vd-LAsAkuujs_g0hzV3uxyp7o4Z3lyDUIKaVw8bhXQ4w-3G9HuRO6bx2XLX7rUyuxL24PFD0ajrrEsXa61rp8cG34-otKHBOfmWEbdMrrSntyaye1aw=w640-h360" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Fig. 3 - 總結 ARM SVE 的重大特性<br /></td></tr></tbody></table><p></p><p style="text-align: left;">個人統整認為 SVE 的主要特性有 1(個人認為應該加入) + 5, 分別是:<br /></p><ul style="text-align: left;"><li>Vector Length Agnostic (VLA)</li><li>Gather-load and Scatter-Store</li><li>Per-lane predication</li><li>Predicate-driven loop control and managment</li><li>Vector partitioning for software-managed speculation</li><li>Extended floating-point and bitwise horizontal reductions.<br /></li></ul><p style="text-align: left;">後續陸續將這些心得一一分享. 下一篇將介紹說明 Vector Length Agnostic (VLA)<br /></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com1tag:blogger.com,1999:blog-8142113334815576231.post-4682212750950926232022-02-09T04:21:00.002-08:002022-02-09T04:21:31.555-08:00Halide 實務心得 4<p>這陣子因為同事在弄 Hexagon DSP 且需要實作 Laplacian Pyramid 來完成所需功能, 由於這是 Halide 當初推出時 paper 內說明的範例, 所以搜尋了一下檔名, 建議他參考 Halide git repo 內的 Laplacian 範例.</p><p>今日與演算開發者開會討論時, 這位同事建議演算開發者考慮去使用 Halide language, 並且以當中的 downsample 為範例解說給演算開發的同事聽. 這時我注意到了 downsample 中有著先前沒看過, 另外也沒在任何 Halide 文件 / 教學文件提到的東西:</p><blockquote><p><b>using Halide::_;</b><br /></p></blockquote><p>最有趣的是透過使用 "_" 所實作的 downsample Funciton:</p><blockquote><p> Func downsample(Func f) <br />{
<br /><b><span style="color: #990000;"> using Halide::_;
</span><br /></b> Func downx, downy;
<br /> downx(x, y, <span style="color: #990000;"><b>_</b></span>) = (<br /> f(2 * x - 1, y, <span style="color: #990000;"><b>_</b></span>) + <br /> 3.0f * (f(2 * x, y, <span style="color: #990000;"><b>_</b></span>) + f(2 * x + 1, y, <span style="color: #990000;"><b>_</b></span>)) + <br /> f(2 * x + 2, y, <span style="color: #990000;">_</span>)<br /> ) / 8.0f;
<br /> downy(x, y, <b><span style="color: #990000;">_</span></b>) = (<br /> downx(x, 2 * y - 1, <span style="color: #990000;"><b>_</b></span>) + <br /> 3.0f * (downx(x, 2 * y, <span style="color: #990000;"><b>_</b></span>) + downx(x, 2 * y + 1, <b><span style="color: #990000;">_</span></b>)) + <br /> downx(x, 2 * y + 2, <span style="color: #990000;"><b>_</b></span>)<br /> ) / 8.0f;
<br /> return downy;
<br />}</p></blockquote><p>很明顯地, <b>"Halide::_"</b> 是用來處理 function 內 argument / dimension 不定數量的問題. 然而詳細的使用應該有正式的解說, 經過搜尋後發現在<a href="https://halide-lang.org/docs/class_halide_1_1_var.html#a2e033e439e668e4f1ced0ce854682117"> Halide 文件中的 Var </a>當中, 這個功能稱為 Implicit Variable Constructor. 文件中的解釋為: <b><span style="color: #38761d;">"Implicit variables are injected automatically into a function call if
the number of arguments to the function are fewer than its
dimensionality and a placeholder ("_") appears in its argument list.
Defining a function to equal an expression containing implicit variables
similarly appends those implicit variables, in the same order, to the
left-hand-side of the definition where the placeholder ('_') appears."</span></b> 這也就是說在 function 呼叫若使用了 "_" 在參數 list 中 , 一旦參數的數目少於函數所需參數, 將會自動對應遞補足. 透過使用 implicit variable "_" 來定義一個函數, 這表示函數有著以 "_" 出現所代表的參數 list. <br /></p><p>透過 implicit variable constructor 如此可以無論傳進的 Func f 有幾個參數, 像是對於影像處理的函數而言, 最前面兩個參數能固定被使用作為座標的 (x, y), 而對應的維度在處理時將直接補足對應. 因此無論純 2D 黑白影像可以套用外, 3D (有3個參數, 分別為 Width, Height, Channels) 的 RGB / RGBA / YUV444 等等影像都可以直接套用這個 downsample 來處理, 更重要的是參數更多的更高維度資料都可以因此而透過單一實作來處理. 另外應可以搭配 RDom / RVar 來使用, 像是上述程式碼中的 downx / downy 的範圍, 搭配 RDom 來使用, 應該能實作出更為簡潔的 Func 表示方式. <br /><br /><br /></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-68777954164067409742021-09-15T06:57:00.002-07:002021-09-15T07:26:51.010-07:00Halide 實務心得 3<p>在 Halide 的使用上會有錯覺地認為 <a href="https://halide-lang.org/docs/class_halide_1_1_runtime_1_1_buffer.html" target="_blank">Halide::Runtime::Buffer</a> 的使用必須與 libHalide.so or libHalide.a linking 才可以. 但其實 Halide::Runtime::Buffer 是可以單獨使用的, 只需要 header files, 基本上一般的程式只要確認有:<br /></p><blockquote><p>#include <HalideBuffer.h><br /></p></blockquote><p>並且確認編譯時有在 include path 即可, 單獨的使用上並不需要任何 libHalide, 相較使用 raw pointer 或是 STL 元件, 這樣的方式有很多的好處:</p><ol style="text-align: left;"><li>俱備 reference count 特性<br /></li><li>提供類似函數的存取介面<br /></li><li>能夠彈性的配置 planar 或 interleaved 資料</li><li>能夠帶入或取得 raw pointer</li><li>可搭配 halide_image_io.h 來載入/另存 jpeg or png 圖檔 <br /></li></ol><h4 style="text-align: left;">俱備 reference count 特性</h4><p>現今的 C++ 元件多半具有這樣的特性, 除了使用上的簡潔彈性外, 好處是可以減少程式中充斥自行管理 buffer/object 管理上的配置與釋放相關的程式碼, 除了有效避免 memory leak / double free 之外, 也能降低因 pointer 操作的錯誤發生. 而這樣的特性也讓程式中對於 Buffer 的指定與傳遞更為便利.<br /></p><h4 style="text-align: left;"> 提供類似函數的存取介面</h4><p> Halide::Runtime::Buffer 並不是單純地提供 Halide 所使用的 Buffer object 存在, 每個所配置產生的 Buffer object 是能夠做存取操作的, 與 STL 不同的地方是, Halide::Runtime::Buffer 使用的是類似函式呼叫的形式來存取資料, 假設我們如下宣告了一個 Buffer.<br /></p><blockquote><p>Buffer<uint8_t> rgb_planes(1920, 1080, 3);<br /></p></blockquote><p> </p><p>概念上, 這配置了 Width - 1920, Height - 1080, Channel - 3 的 Buffer, 也就是 sizeof(uint8_t) x 1920x1080x3 大小的記憶體空間, 每個 channel 有 1920x1080 , 而要存取該特定的位置, 可以使用下列方式:<br /></p><p></p><blockquote><p>// write to a pixel of a channel<br /></p><p> rgb_plane(100, 100, 0) = 128</p><p>// read a pixel of a channel<br /></p><p> uint_8 pix_val = rgb_plane(200, 200, 1)<br /></p><p> </p></blockquote><p>可以減少計算 buffer offset 的錯誤, 程式碼也更為簡潔, 而因為採用了類似函數的形式, 程式的邏輯過程會更接近 functional 的感覺. 另外是也方便將查表轉為計算實作的驗證流程.<br /></p><h4 style="text-align: left;">能夠彈性的配置 planar 或 interleaved 資料</h4><p> 許多的圖形資料並非單存以基本的 planar 形式存在, 難免會需要操作 interleaved RGB or RGBA 的資料, 以 Halide 參數順序代表資料格式與 loop 巢狀結構的概念, 那麼要宣告先前 rgb_plane 的 interleaved 版本, 必須這麼宣告:<br /></p><blockquote>Buffer<uint8_t> rgb_planes(3, 1920, 1080);</blockquote><p>這的確是可行的, 但操作 planar 與 interleave 的程式流程就會使用不同的 index 次序, 而為了解決這樣的方式 Halide::Runtime::Buffer 提供了 make_interleaved 的介面來建構這樣的 Buffer object. 而最大的優點為, 這能夠保有與上一點相同次序的存取方式, 而對應底層不同的 memory layout.</p><h4 style="text-align: left;">能夠帶入或取得 raw pointer <br /></h4><p>為了銜接一些其他的實作或操作, 像是使用 OpenCV library 或是以 SIMD 加速的程式, 在 Buffer 的宣告上是能夠傳入外部的 pointer. 相對地需要 Buffer 對應的 buffer pointer 是能夠透過 Halide::Runtime::Buffer::data() 這個呼叫取得. 如此就能銜接/整合/驗證以不同方式實作的資料處理功能.<br /></p><p> <b>可搭配 halide_image_io.h 來載入/另存 jpeg or png 圖檔</b></p><p> 在 Halide tutorial 中使用的 load_image 與 save_image 對於實務上是很便利的工具, 這也能在 include halide_image_io.h 之後個別來使用, 而需要注意的是這會需要 link libpng 與 libjpeg.<br /></p><p> <br /></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-63139554121539282492021-02-28T21:34:00.003-08:002021-02-28T21:34:32.538-08:00 Raspberry Pi Zero W Project Part 3 - Software Interrupt & Handler example <p>For ARM architecture, the instruction for software interrupt is <a href="https://www.keil.com/support/man/docs/armasm/armasm_dom1361289909139.htm" target="_blank">SWI or SVC (ARMv7)</a>.</p><p>Using the instruction is very simple:</p><p></p><blockquote><i>SVC #imm</i></blockquote><p></p><p>When an ARM processor executes the instruction, it will <i><b>switch to SVC/SWI mode and jump to the SWI or SVC hander</b></i> in exception vector table specified by Vector Base Address Register (VBAR).</p><p>Similar to previous parts, please get from repo <a href="https://github.com/champyen/rpiz_bare_metal.git " target="_blank"><i>https://github.com/champyen/rpiz_bare_metal.git </i></a> </p><p>please checkout the commit: <b>637086c2</b></p><blockquote>$ git checkout 637086c2</blockquote><p>It is not difficult to use SWI/SVC instruction. But it requires experience to apply it well. Using SWI/SVC instrution for system calls, we need to:</p><ul style="text-align: left;"><li>design system call table ( SWI number , system call pair) <br /></li><li>implement SWI/SVC handler, get the system call number<br /></li><li>implement system calls with corresponding number<br /></li></ul><p>For get the SWI (or system call) number, it is describe in ARM's <a href="https://developer.arm.com/documentation/dui0040/d/handling-processor-exceptions/swi-handlers" target="_blank">"SWI Handler's"</a> document. The SWI number is encoded as lowest 24 bit in SWI/SVC instruction. Therefore we could get SWI/SVC number by fetching the instruction itself and clearing the MSb 8bits with BIC instruction right after entering SWI hander:</p><p></p><blockquote><p><i>ldr r0, [lr,#-4]<br />bic r0, r0, #0xff000000</i></p></blockquote><p>After getting SWI number, we could pass it to system_handler as an argument saved in R0. The system_hander is very similar to ISR, but we don't need to check the hardware status to know which hardware interrupts CPU. We just need to handler the SWI or system call by the SWI number. Of course, as IRQ has Interrupt latency SWI/SVC instruction consumes more cycles than normal instructions for most CPUs.</p><p>For Application processor it is easy to understand the meaning of mode switching and SWI handling. In fact, Cortex-R, and Cortex-M processor also has SWI/SVC instruction. For most Embedded / RTOS developers, they think it is useless or trivial in such processor.</p><p>For some embedded or RTOS, mutual-exclusive applications can be loaded on demand. This can be done by well-designed linker-script (e.g.: different LAs with same). For such Embedded / RTOS, SWI / SVC instruction is useful to maintain an API layer between Applications and Kernel. An intuitive / naive way is to setup and maintain table of function pointers on the kernel side (Of course we still need to setup API index for using the function as SWI number). For development it has an drawback: it is hard to maintain or adjust or expand the table. With SWI/SVC instruction, no table forwarding is needed. Besides it provides flexibility to group system calls and reserve numbers for future needs. (Since for table-based method, it required to reserve a huge table).<br /></p><p><br /></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-47853850789595456572021-02-06T07:00:00.006-08:002021-02-06T07:00:57.706-08:00 Raspberry Pi Zero W Project Part 2 - Interrupt Service Routine (ISR) example<p>After implementing naive function and printf, let's add ISR to it.</p><p>please checkout the commit: <b>91abc0d3</b></p><p></p><blockquote>$ git checkout 91abc0d3</blockquote><p></p><p>There are many changes from previous commit:</p><h3 style="text-align: left;">head.S</h3><p>it becomes more complicated. As we know, its context starts at 0x8000 address.<br /></p><p></p><blockquote>start:<br /> <b>ldr pc, reset_target /* 0x00 mode: svc */</b><br /> ldr pc, undefined_target /* 0x04 mode: ? */<br /> ldr pc, swi_target /* 0x08 mode: svc */<br /> ldr pc, prefetch_target /* 0x0c mode: abort */<br /> ldr pc, abort_target /* 0x10 mode: abort */<br /> ldr pc, unused_target /* 0x14 unused */<br /> ldr pc, irq_target /* 0x18 mode: irq */<br /> ldr pc, fiq_target /* 0x1c mode: fiq */<br /><b><br />reset_target: .word reset_entry</b><br />undefined_target: .word undefined_entry<br />swi_target: .word syscall_entry<br />prefetch_target: .word prefetch_entry<br />abort_target: .word abort_entry<br />unused_target: .word unused_entry<br />irq_target: .word irq_entry<br />fiq_target: .word fiq_entry</blockquote><br />After loading the binary, it will jump to a routine named <b>"reset_entry"</b>. Before we trace the <i>reset_entry</i>. The 8 "ldr pc, XXXXXX" instructions are so called <b>Exception Vector Table</b>. (FIQ is a special irq mode, it has a advantage - the implementation can be start at the location, the jump is not necessary. Therefore one jump delay is saved. ) It is used to handle system exceptions. Each has corresponding privileged mode to it. Besides, each mode has dedicated LR and SP registers - this means OS / firmware implementation should take care of stack space arrangement for the mode:<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIQoqo968ZY2csIbyYTi93gsDWYuwYanoEBiAuS6nigYfq3KkJjSO0KdJE_AF4WAdD4q0VwvOObnOHAHEnnwRI4MpR97n_6_SnngbgWlIxj-qQzSm5E2lzdNce4-AyY1d7Yh0YZ65BDkYJ/s739/arm_modes.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="631" data-original-width="739" height="546" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIQoqo968ZY2csIbyYTi93gsDWYuwYanoEBiAuS6nigYfq3KkJjSO0KdJE_AF4WAdD4q0VwvOObnOHAHEnnwRI4MpR97n_6_SnngbgWlIxj-qQzSm5E2lzdNce4-AyY1d7Yh0YZ65BDkYJ/w640-h546/arm_modes.png" width="640" /></a></div><br /><p>In fact, for <i>reset_entry</i> here, its major work is setting stack for each mode:<br /></p><p></p><blockquote><p></p><p><b>reset_entry</b>:<br /><b> /* set VBAR to 0x8000 */<br /> mov r0, #0x8000<br /> mcr p15, 0, r0, c12, c0, 0</b><br /><br /> /* (PSR_FIQ_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS) */<br /> mov r0,#0xD1<br /> msr cpsr_c,r0<br /> ldr sp, stack_fiq_top<br /><br />... other 4 modes ...<br /><br /> /* (PSR_SVC_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS) */<br /> mov r0,#0xD3<br /> msr cpsr_c,r0<br /> ldr sp, stack_svc_top<br /><br /><b> cpsie i<br /> bl bare_metal_start</b></p></blockquote><p>In addition to stack assignment and jump to<b><i> </i></b><i>bare_metal_start</i><b><i> </i></b>, there are two key points here:</p><ol style="text-align: left;"><li>setup Vector Base Address Register (VBAR) - From ARMv6, the exception vector can be placed other than 0x00000000 and 0xFF000000. This is achieved by setting VBAR, please refer to "3.2.43 c12, Secure or Non-secure Vector Base Address Register" in ARM1176JZF-S TRM.<br /></li><li>enable interrupt -<a href="https://developer.arm.com/documentation/dui0473/k/arm-and-thumb-instructions/cps" target="_blank"> cpsie instruction</a><br /></li></ol><p>And we have to trace isr_entry:</p><p></p><blockquote>irq_entry:<br /> stmfd sp!, {r0-r12, lr}<br /><b> add lr, pc, #4</b><br /> bl isr_entry<br /> ldmfd sp!, {r0-r12, lr}<br /><b><br /> subs pc, lr, #4</b></blockquote><b></b>For ISR, it is not surprised to backup and restore all (non-dedicated) registers. The most interesting things are - LR register setting and the instruction to leave IRQ mode. For LR setting it is easy to figure out, the target return address is the 'bl isr_entry' not 'add lr, pc, #4". That's the main reason to save "pc+4" to LR. And for leaving each mode, please refer to "2.12.2 Exception entry and exit summary" of ARM1176JZF-S TRM.<br /><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0TWXc7rDhGNEkdbpx0XJw2V3RsVx2hc93jYvBzssjcBZ2MS6pIHbLO35GazDcPPoOrJ5TDYBf4V44JR6XCeQWmBY1snJ3PaHQ2abMnyrkxbOWcS6JSCX6VIahF1gIIyuMyjrVrogv2vOe/s841/irq_summary.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="659" data-original-width="841" height="502" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0TWXc7rDhGNEkdbpx0XJw2V3RsVx2hc93jYvBzssjcBZ2MS6pIHbLO35GazDcPPoOrJ5TDYBf4V44JR6XCeQWmBY1snJ3PaHQ2abMnyrkxbOWcS6JSCX6VIahF1gIIyuMyjrVrogv2vOe/w640-h502/irq_summary.png" width="640" /></a></div><p></p><h3 style="text-align: left;">isr.c </h3><p>There 3 functions in the source file: timer_enable, timer_check and isr_enty. Here we use "System Timer" in BCM2835, please refer to Chap 7 and Chap 12 of <a href="https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/README.md" target="_blank">"BCM2835 Peripheral specification"</a>. Besides the IRQ number of System Timer is not listed in the document, please refer to the link of "errata and some additional information" on the page.<br /></p><p>The <i>timer_enable</i> will enable System Timer 1 or 3 by index and <i>timer_check</i> is used to clear IRQ state and update next timeout interrupt. Therefore the <i>isr_enty</i> just check status and call timer_check for clear the IRQ.</p><h3 style="text-align: left;">bare_metal.c <br /></h3><p>For demonstrate IRQ and main thread's progress, a busy loop with counter is added. The loop will print out a number when specified condition is met. And Timer is enabled before the loop, You can see the timer tick with ISR and the main thread keeps counting.<br /></p><blockquote><p>void bare_metal_start(void)<br />{<br /> int base = 0;<br /> asm volatile (<br /> "mov %0, sp\n\t" : "=r" (base)<br /> );<br /><br /> printf("\n\n%s:%x: Hello World! %s %s %d\n\n", __func__, base, __DATE__, __TIME__, __LINE__);<br /> printf("enter busy loop\n");<br /><br /><b> timer_enable(1);<br /></b><br /><b> volatile int i = 0;<br /> while(1){<br /> if((i++ & 0x00FFFFFF) == 0)<br /> printf("%d\n", i);<br /> }</b><br />}<br /></p></blockquote><p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYMJulI7eGlcmF6SJ4wB8M-v3-kRtCuZoV1YJYNGCubB_w-jMvsVXQiKWPX3SzFLYhaXrMkuETIjidYfBhn5NpLzGjonN6__QJQHiK4i_8IhcKqPnZmfKKzifCV1iHGi3X13JcnoAAaWjG/s550/tick.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="550" data-original-width="310" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYMJulI7eGlcmF6SJ4wB8M-v3-kRtCuZoV1YJYNGCubB_w-jMvsVXQiKWPX3SzFLYhaXrMkuETIjidYfBhn5NpLzGjonN6__QJQHiK4i_8IhcKqPnZmfKKzifCV1iHGi3X13JcnoAAaWjG/w360-h640/tick.png" width="360" /></a></div><br /><p><br /></p><p> </p><p> </p><p> </p><p> </p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-22318875244580149432021-01-23T04:04:00.004-08:002021-02-02T05:16:43.145-08:00Raspberry Pi Zero W Project Part 1 - bare metal printf implementation<p>Step 0 - update config.txt and overlay</p><p>config.txt: <br /></p><p></p><blockquote><p>enable_uart=1<br />dtoverlay=miniuart-bt # or disable-bt as you want<br />kernel=bare_metal.bin<br /></p></blockquote><p></p><p>Please remember to download <a href="https://github.com/raspberrypi/firmware/blob/master/boot/overlays/miniuart-bt.dtbo">miniuart-bt.dtbo</a> or <a href="https://github.com/raspberrypi/firmware/blob/master/boot/overlays/disable-bt.dtbo">disable-bt.dtbo</a> and place the files to a folder named "overlays" in your SD card. </p><p>Now, Let's start the lab.<br /></p><p>The first step to control a CPU is to dump any message you want!</p><p></p><blockquote><p>$ git clone https://github.com/champyen/rpiz_bare_metal.git</p><p>$ git checkout 4e769069</p></blockquote><p></p><p>There are 4 major files in the example:</p><ul style="text-align: left;"><li>bare_metal.c - the main example flow</li><li>head.S - the glue code for entering the flow</li><li>bare_metal.c - the main example flow</li><li>printf.c - printf for RPi Zero's PL011 uart</li><li>bare_metal.ld - linker script of the example</li></ul><p>From the "<a href="https://www.raspberrypi.org/documentation/configuration/config-txt/boot.md" target="_blank">Boot options in config.txt</a>" of Raspberry Pi Document, we know that the default start address is <b>0x8000</b>.<br /></p><p>In bare_metal.ld, you can see the linker script:</p><p></p><blockquote>OUTPUT_ARCH(arm)<br />SECTIONS {<br /><b> . = 0x8000;</b><br /> .text . : {<br /> *(.text)<br /> }<br /> . = ALIGN(4);<br /> .data . : {<br /> *(.data)<br /> }<br /> . = ALIGN(4);<br /> .bss . : {<br /> *(.bss)<br /> *(COMMON)<br /> }<br /> . = ALIGN(4);<br /> .rodata . : {<br /> *(.rodata)<br /> *(.rodata.*)<br /> }<br />}</blockquote>In Makefile, you can see the linking order is - <b>head.o </b>bare_metal.o printf.o.<p></p><p><br />Therefore, after bootcode.bin, it will jump to <b>the first function in head.S</b>.</p><p>In head.S:</p><p></p><blockquote>.text<br />_start:<br /> ldr sp, =<b>stack_top</b><br /> <b>bl</b> <b>bare_metal_start</b><br /><br /><b>stack_top</b>: .word 0x100000</blockquote>Before calling the demo function - bare_metal_start, head.S does only one thing - setup the address of 0x100000 as stack pointer. <br /><p></p><p>In bare_metal.c:<br /></p><blockquote><p>void printf(const char *fmt, ...);<br />void bare_metal_start(void)<br />{<br /> printf("\n\n%s: Hello World! %s %s %d\n\n", __func__, __DATE__, __TIME__, __LINE__);<br /> printf("enter busy loop\n");<br /> while(1);<br />}<br /></p><p></p></blockquote><p>the bare_metal_start function calls the 'printf' implemented in printf.c to dump two messages.</p><p>In printf, the fundamental function is <b>_putc</b>.<br /></p><p></p><blockquote>#define PL011_BASE 0x20201000<br />#define PL011_DR (PL011_BASE + 0x00)<br />#define PL011_FR (PL011_BASE + 0x18)<br />#define _REG(x) *((unsigned int <b>t</b> *)(x))<br /><br />void _putc(<b>unsigned char c</b>)<br />{<br /> while( (_REG(PL011_FR) & 0x80) == 0);<br /><b> _REG(PL011_DR) = c;</b><br />}</blockquote>Please refer to Chapter 13 of the <a href="https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/README.md" target="_blank">"Peripheral Specificaion"</a> of BCM2835, the _putc just check the Transmit buffer status, and send out a char when Transmit buffer/register is empty.<p></p><p><span></span><br /></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXMEXWYnridODxPCJCLTbGcbyHtypb-vYHmVzbEX1mStRng4vzNB8LL7hxeNZFOr84evIas_XFkoXoVr1K6QznBj_HIqlkUcVP-lqwNiqGNnSlpSWAUE2jk7zQ6ZzKs2E-4VTk6oCeVzaH/s357/rpiz_bm_printf.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="110" data-original-width="357" height="198" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXMEXWYnridODxPCJCLTbGcbyHtypb-vYHmVzbEX1mStRng4vzNB8LL7hxeNZFOr84evIas_XFkoXoVr1K6QznBj_HIqlkUcVP-lqwNiqGNnSlpSWAUE2jk7zQ6ZzKs2E-4VTk6oCeVzaH/w640-h198/rpiz_bm_printf.png" width="640" /></a></div><br /><span></span><br /><p></p><p><br /></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-91343005215719982982020-12-29T08:02:00.002-08:002020-12-29T08:12:08.434-08:00Raspberry Pi Zero W Project Part 0.5 - Documents<div><h4 style="text-align: left;"><b>Documents </b><br /></h4><p>Before starting the project, to collect documents is an important things. </p><p>It is important to study document before and during implementation.</p><p>The documents needed in the project are:</p><p>1. Raspberry Pi's Official Documents</p><ul style="text-align: left;"><li><span></span><a href="https://www.raspberrypi.org/documentation/configuration/" target="_blank">Configuration</a> :</li><ul><li><a href="https://www.raspberrypi.org/documentation/configuration/config-txt/README.md" target="_blank">config.txt</a></li><ul><li><a href="https://www.raspberrypi.org/documentation/configuration/config-txt/boot.md" target="_blank">boot.md</a> - default booting address is 0x8000 for RPiZ, it can be specified<br /></li></ul><li><a href="https://www.raspberrypi.org/documentation/configuration/uart.md">UART configuration</a> </li></ul><li><a href="https://www.raspberrypi.org/documentation/hardware/raspberrypi/README.md" target="_blank">Raspberry Pi Hardware</a> :</li><ul><li><a href="https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/README.md">BCM2835</a></li><li><a href="https://www.raspberrypi.org/documentation/hardware/raspberrypi/bootmodes/README.md">Boot modes</a> </li></ul></ul></div><div><p>2. Broadcom BCM2835 <a href="https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf">Peripheral specification</a></p><p>3. <a href="https://developer.arm.com/documentation/ddi0301/h/" target="_blank">ARM1176</a></p><h4 style="text-align: left;"><b>BCM2825 Address Space</b><br /></h4><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFyq6hyphenhyphen-00bNft8SGlXPfJwb2TArLugPP-Rw7lJ9JSXbiYGP1Isz7Yuf7-RYT4IoFwsZUt0C1sB-6n24SHjTrGhyphenhyphenRkCABlTNowEo20IDS7CQGNjp9-tI9y8sbo27iSvSMfbq9jTKl8_giI/s1145/BCM2835-Memory-Map-Large.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="679" data-original-width="1145" height="380" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFyq6hyphenhyphen-00bNft8SGlXPfJwb2TArLugPP-Rw7lJ9JSXbiYGP1Isz7Yuf7-RYT4IoFwsZUt0C1sB-6n24SHjTrGhyphenhyphenRkCABlTNowEo20IDS7CQGNjp9-tI9y8sbo27iSvSMfbq9jTKl8_giI/w640-h380/BCM2835-Memory-Map-Large.png" width="640" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg9UZ57-SkLqDJITxD7FnQ7MPLdW4MKQhYDLxOfcFici6KJYlpWBGx4G0QQCSyo_P5ookNtjpmUfrAMoxppiSdR0G-zlQmskVziEu_DRgC1LiwJ9WIJ1XtQcpt3a0BQQfM0tTreM2eDECc/s915/Screenshot_2020-12-30+Microsoft+Word+-+BCM2835+ARM+Peripherals+docx+-+BCM2835-ARM-Peripherals+pdf.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="409" data-original-width="915" height="286" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg9UZ57-SkLqDJITxD7FnQ7MPLdW4MKQhYDLxOfcFici6KJYlpWBGx4G0QQCSyo_P5ookNtjpmUfrAMoxppiSdR0G-zlQmskVziEu_DRgC1LiwJ9WIJ1XtQcpt3a0BQQfM0tTreM2eDECc/w640-h286/Screenshot_2020-12-30+Microsoft+Word+-+BCM2835+ARM+Peripherals+docx+-+BCM2835-ARM-Peripherals+pdf.png" width="640" /></a></div></div><div><br /></div><div>It is important to know the address view of CPU and VPU/GPU. You have to know the difference between physical address and bus address of BCM2835. Otherwise you can't get hardware work correctly.<br /></div><div><br /></div><div><h4 style="text-align: left;"><b>Why Raspberry Pi Zero WH?<br /></b></h4><p>The reasons of selecting RPi ZW as my project platform are:</p><ol style="text-align: left;"><li>cheap, easy to get one, ARMv6 provides enough features of modern ARM Application processors.<br /></li><li>single core, simple & good for teaching / practice.</li><li>low power, RPi ZW can be powered by almost any USB slot. <br /></li><li>RPi has a lot of documents / projects / implementations for reference. <br /></li></ol></div><div style="text-align: left;"><h4>What's in Next?</h4></div><div>In next part, simple assembly programming on RPi Zero W will be introduced. We need to use assembly to implement some important parts of OS and bootloader.<br /></div>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-38359096879598795262020-12-27T06:23:00.002-08:002020-12-29T07:25:37.776-08:00Raspberry Pi Zero W Project Part 0 - u-boot building / testing<p> 1. <a href="https://raspberrypi.stackexchange.com/questions/10442/what-is-the-boot-sequence" target="_blank">Raspberry Pi (Pre-4B) Boot Sequence</a></p><p>Currently, loader.bin is not needed. <br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhU1pWT3MQ_4F5p47rJlX2juu4zI4zpXDKRunsYNFyyJPY7_OfnuNiZZJ8xo1tePjQAgLTbWORZ0CJ-LlbTpMxgA9trqzog3MqrqbfqU-J8aJu4_Yue_y9dOSEwFE9F1xe6KVkZQ5lzJwMN/s1123/xEB4q.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1123" data-original-width="794" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhU1pWT3MQ_4F5p47rJlX2juu4zI4zpXDKRunsYNFyyJPY7_OfnuNiZZJ8xo1tePjQAgLTbWORZ0CJ-LlbTpMxgA9trqzog3MqrqbfqU-J8aJu4_Yue_y9dOSEwFE9F1xe6KVkZQ5lzJwMN/w453-h640/xEB4q.png" width="453" /></a></div><p>2. Boot from SD - from power-on to bootcode.bin</p><ul style="text-align: left;"><li>FAT32 formatting - For Linux user's <a href="http://qdosmsq.dunbar-it.co.uk/blog/2013/06/noobs-for-raspberry-pi/" target="_blank">instructions </a>(in fact similar to add a SSD or HDD partition)</li><li>Files needed - <a href="https://github.com/raspberrypi/firmware/blob/master/boot/bcm2708-rpi-zero-w.dtb" target="_blank">bcm2708-rpi-zero-w.dtb</a>, <a href="https://github.com/raspberrypi/firmware/blob/master/boot/bootcode.bin" target="_blank">bootcode.bin</a>, <a href="https://github.com/raspberrypi/firmware/blob/master/boot/start.elf" target="_blank">start.elf</a>, config.txt </li><li><a href="https://www.raspberrypi.org/documentation/hardware/raspberrypi/bootmodes/README.md" target="_blank">modification</a> of bootcode.bin to enable UART is required <br /></li><li><a href="https://www.raspberrypi.org/documentation/configuration/config-txt/" target="_blank">settings in config.txt</a></li><li>connect UART as following picture</li></ul><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgya3Tjtdgqj_Us89M0zwSKcFggG59OUZxegIHRic5BWO7DY8jZbKcp67K7x5D5G-ji7d7nMCPDjxwxOjazpygEGOMuThHdNwIfS3-RDwT0Qrdhs1KRBHwGKvJisex9PlcoMIuJFWhx3Bhi/s1080/132870901_10221009690725223_6318717441511756718_o.jpeg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="607" data-original-width="1080" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgya3Tjtdgqj_Us89M0zwSKcFggG59OUZxegIHRic5BWO7DY8jZbKcp67K7x5D5G-ji7d7nMCPDjxwxOjazpygEGOMuThHdNwIfS3-RDwT0Qrdhs1KRBHwGKvJisex9PlcoMIuJFWhx3Bhi/w640-h360/132870901_10221009690725223_6318717441511756718_o.jpeg" width="640" /></a></div><br /> <p></p><p>3. Toolchain </p><p>GNU Arm Embedded Toolchain - <a href="https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads" target="_blank">Download</a><br /></p><p>4. u-boot</p><p></p><blockquote><p>$ sudo apt-get install flex bison <br /></p><p>$ git clone https://github.com/u-boot/u-boot; cd u-boot</p><p>$ export PATH=PATH_TO_TOOLCHAIN/gcc-arm-none-eabi-10-2020-q4-major/bin/:$PATH</p><p>$ export CROSS_COMPILE=arm-none-eabi-</p><p>$ make rpi_0_w_defconfig<br /></p><p>$ make -s -j4; cp ./u-boot.bin PATH_TO_SDCARD<br /></p></blockquote><p>5. booting</p><p>config.txt:<br /></p><blockquote><p>enable_uart=1<br />uart_2ndstage=1<br />kernel=u-boot.bin<br /></p><p></p></blockquote><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9MZ1QXmuio3O3bVIvFn8RtWKdi15ATrUZYm3lTJm3a5Vmzp5Hu7V7cWtr03gMc83LbKz-_epoogqzBLATE-JfblhGFgY_8IgVedjNBhzo9GKqu5IjvJvr9cZ_E5mDDwIYWplo-oHHmpIe/s496/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7_2020-12-27_22-21-01.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="345" data-original-width="496" height="446" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9MZ1QXmuio3O3bVIvFn8RtWKdi15ATrUZYm3lTJm3a5Vmzp5Hu7V7cWtr03gMc83LbKz-_epoogqzBLATE-JfblhGFgY_8IgVedjNBhzo9GKqu5IjvJvr9cZ_E5mDDwIYWplo-oHHmpIe/w640-h446/%25E8%259E%25A2%25E5%25B9%2595%25E5%25BF%25AB%25E7%2585%25A7_2020-12-27_22-21-01.png" width="640" /></a></div><p><br /></p><p><br /></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0tag:blogger.com,1999:blog-8142113334815576231.post-29686110430692065462020-11-14T19:30:00.001-08:002020-11-24T07:33:03.952-08:00正體中文資訊考古 - 21世紀多媒體英漢雙向辭典<p> <span class="d2edcug0 hpfvmrgz qv66sw1b c1et5uql gk29lw5a a8c37x1j keod5gw0 nxhoafnm aigsh9s9 d9wwppkn fe6kdd0r mau55g9w c8b282yb hrzyx87i jq4qci2q a3bd9o3v knj5qynh oo9gr5id hzawbc8m" dir="auto"></span></p><div class="kvgmc6g5 cxmmr5t8 oygrvhab hcukyx3x c1et5uql ii04i59q"><div dir="auto" style="text-align: start;">今天分享的是一個相當有歷史意義的字典</div><div dir="auto" style="text-align: start;">"21世紀多媒體英漢雙向辭典"</div><div dir="auto" style="text-align: start;"><span><a class="oajrlxb2 g5ia77u1 qu0x051f esr5mh6w e9989ue4 r7d6kgcz rq0escxv nhd2j8a9 nc684nl6 p7hjln8o kvgmc6g5 cxmmr5t8 oygrvhab hcukyx3x jb3vyjys rz4wbd8a qt6c0cv9 a8nywdso i1ao9s8h esuyzwwr f1sip0of lzcic4wl py34i1dx gpro0wi8" href="https://archive.org/details/21Century_Bictionary" rel="nofollow noopener" role="link" tabindex="0" target="_blank">https://archive.org/details/21Century_Bictionary</a></span></div><div dir="auto" style="text-align: start;">比較可惜的地方是該辭典入手時反射層的 CD cover 已有無法挽救的刮痕</div><div dir="auto" style="text-align: start;">部份發音資料檔損毀, 因此 (c, d, i, m, p, y) 開頭的單字無發法發音</div></div><div class="o9v6fnle cxmmr5t8 oygrvhab hcukyx3x c1et5uql ii04i59q"><div dir="auto" style="text-align: start;">其歷史意義在許多現今"免費"字典軟體中</div><div dir="auto" style="text-align: start;">有個廣為流傳的一個字典檔是 "21世紀英漢漢英雙向辭典"</div><div dir="auto" style="text-align: start;">廣受歡迎的主要原因是字彙數目豐富, 許多人推薦是因為涵蓋 2X 萬單字, 勝過僅數萬字的其他字典</div><div dir="auto" style="text-align: start;">多年來經過了許多各方使用者的編修與補充</div><div dir="auto" style="text-align: start;">然而有近年許多人在考究該辭典出處究竟為何, 像是</div><div dir="auto" style="text-align: start;"><span><a class="oajrlxb2 g5ia77u1 qu0x051f esr5mh6w e9989ue4 r7d6kgcz rq0escxv nhd2j8a9 nc684nl6 p7hjln8o kvgmc6g5 cxmmr5t8 oygrvhab hcukyx3x jb3vyjys rz4wbd8a qt6c0cv9 a8nywdso i1ao9s8h esuyzwwr f1sip0of lzcic4wl py34i1dx gpro0wi8" href="https://www.pdawiki.com/forum/thread-13376-1-1.html?fbclid=IwAR0PTGgaUlmUB3-OFATR9tsxv3OUwcqbZJfdKAQ10NnZYxfJdyspooeH8cA" rel="nofollow noopener" role="link" tabindex="0" target="_blank">https://www.pdawiki.com/forum/thread-13376-1-1.html</a></span></div></div><div class="o9v6fnle cxmmr5t8 oygrvhab hcukyx3x c1et5uql ii04i59q"><div dir="auto" style="text-align: start;">該討論串有些資訊, 但是從 2015 年當時的翻譯比對是沒有意義的</div><div dir="auto" style="text-align: start;">原因是該字典檔已經流傳非常久</div><div dir="auto" style="text-align: start;">經過許多人依照偏好地自其他辭典合併/替換解釋內容與補充的字非常多</div><div dir="auto" style="text-align: start;">而結論的"新世紀英漢辭典"即便最新版本收錄詞彙也約莫十餘萬, 這是十分明顯的不相符, 也將成就歸錯他人</div><div dir="auto" style="text-align: start;">然而可以確認的是這辭典的流傳有直接關聯的, </div><div dir="auto" style="text-align: start;">是近 2000 年所推出的星際譯王 StarDict</div><div dir="auto" style="text-align: start;">然而該作者也沒有解釋這字典檔出處 </div><div dir="auto" style="text-align: start;">這本辭典的條目的與其他辭典不同的特徵在於會把音節點納入</div><div dir="auto" style="text-align: start;">因此這篇有個特別的說明就是其他字典找不到的</div><div dir="auto" style="text-align: start;">這裡提供討論中出現的 abrupt 與 saguaro 的字義, 1994 年的辭典與此分毫不差</div><div dir="auto" style="text-align: start;">除了字彙的字義上的完全符合外("新世紀"很多不同)</div><div dir="auto" style="text-align: start;">當時也未有以"新世紀"為辭典內容的電腦辭典軟體</div><div dir="auto" style="text-align: start;">封面上的 "B"ictionary 並非錯字, 該辭典當時上市時發明的詞, 透過搜尋可以找到一個說明 "為微系電腦股份有限公司發展之超強功能電腦雙向辭典之註冊商標其囊括十六項業界第一: 1. 辭彙最多, 約二十萬字 2. 片語最多,約五千辭 3. 例句最多, 約六千句 4. 英漢漢 ...", 可以得知字彙數目是相符的</div></div><div class="o9v6fnle cxmmr5t8 oygrvhab hcukyx3x c1et5uql ii04i59q"><div dir="auto" style="text-align: start;">而星際譯王作者應沒有心力去建立這麼龐大的字典檔, 而應該完全是由現成軟體檔案的轉換</div><div dir="auto" style="text-align: start;">因而無論推出年份, 字彙的數目, 解釋的內容與名稱都只有這個辭典最為可能, 只是因為軟體是 1994 年推出, 完整版本流通數少而讓他人無法直接證明</div></div><div class="o9v6fnle cxmmr5t8 oygrvhab hcukyx3x c1et5uql ii04i59q"><div dir="auto" style="text-align: start;">微系電腦股份有限公司 1990/6 年成立, 2001/1 年解散</div><div dir="auto" style="text-align: start;"> </div><div dir="auto" style="text-align: start;"> </div><div dir="auto" style="text-align: start;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2njLPbGE5zzjU5x0ZWonucusVwLMeKUIJB8V527piqQFWW6V5mNsDPHURQSdyG9NrtLC1otX5myoFCxHXtXzisfhiz0Qs8ann-BxhWNxK3vP-pV-fiKKhVlkMmVwiy4cPpED3GBUaPYBS/s726/00_CD+Cover.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="726" data-original-width="726" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2njLPbGE5zzjU5x0ZWonucusVwLMeKUIJB8V527piqQFWW6V5mNsDPHURQSdyG9NrtLC1otX5myoFCxHXtXzisfhiz0Qs8ann-BxhWNxK3vP-pV-fiKKhVlkMmVwiy4cPpED3GBUaPYBS/w400-h400/00_CD+Cover.png" width="400" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfBNAFpkIUrHD9tfceCgqtO-P0uwfGtM1rH1UgDJbVUAPaw2JAjUCLcsBXSMSGOGESUtRA_GMeTJm16xc9EmeyASNGmp8nUjtK5e5aLAtOeHy5HcGfu9Z1od2QZ_eEv7bFkER_d-ykoahO/s1026/01_Install.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="826" data-original-width="1026" height="323" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfBNAFpkIUrHD9tfceCgqtO-P0uwfGtM1rH1UgDJbVUAPaw2JAjUCLcsBXSMSGOGESUtRA_GMeTJm16xc9EmeyASNGmp8nUjtK5e5aLAtOeHy5HcGfu9Z1od2QZ_eEv7bFkER_d-ykoahO/w400-h323/01_Install.png" width="400" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFpS6qj0gJqucuGMFOX-iQm4SpepXTtKwD9bXYreYOMmdlQ5xQXZ0pkDS4ImvjU8tzL7OE2bTiu1_9SafaISHDr58ZNJUHMcMvcWPQJwcXLiX1uiiPKrD17_g88AFbVjReJ1iSkJgMSnvS/s1026/02_Launch.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="826" data-original-width="1026" height="323" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFpS6qj0gJqucuGMFOX-iQm4SpepXTtKwD9bXYreYOMmdlQ5xQXZ0pkDS4ImvjU8tzL7OE2bTiu1_9SafaISHDr58ZNJUHMcMvcWPQJwcXLiX1uiiPKrD17_g88AFbVjReJ1iSkJgMSnvS/w400-h323/02_Launch.png" width="400" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ6m-9lUyAlu36cAxMU2jmiNv7ZjMWU0sBDhZa2Y37OVz7GVn2ops4Z8PTQYQNvi_h28TC5Qb_fJKteHEENMpGThBV2RtUowJRVs39-WUo71xQPnAa4VX_oFfIRbkC0LZZCh8ZmQxPJ_Ak/s976/03_lookup_abrupt.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="826" data-original-width="976" height="339" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ6m-9lUyAlu36cAxMU2jmiNv7ZjMWU0sBDhZa2Y37OVz7GVn2ops4Z8PTQYQNvi_h28TC5Qb_fJKteHEENMpGThBV2RtUowJRVs39-WUo71xQPnAa4VX_oFfIRbkC0LZZCh8ZmQxPJ_Ak/w400-h339/03_lookup_abrupt.png" width="400" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPbIqIhePHG9ZrIAiFqHCJKAUZ-mvVmLfku0Ah52T0Vk2JDxE8hUO5l0XsFKJ33k_cyTZM-StrXm10ME_ygyyw8bPngnVoyvOtrvoPp_ESW3S5p82ooPgwXyQ3F6Uq8g_gI6nqaqMt8G1v/s976/04_lookup_saguaro.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="826" data-original-width="976" height="339" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPbIqIhePHG9ZrIAiFqHCJKAUZ-mvVmLfku0Ah52T0Vk2JDxE8hUO5l0XsFKJ33k_cyTZM-StrXm10ME_ygyyw8bPngnVoyvOtrvoPp_ESW3S5p82ooPgwXyQ3F6Uq8g_gI6nqaqMt8G1v/w400-h339/04_lookup_saguaro.png" width="400" /></a></div><br /> </div></div><p></p>網路黑貓http://www.blogger.com/profile/05570293149894954776noreply@blogger.com0