天天看點

吃透Chisel語言.39.Chisel實戰之單周期RISC-V處理器實作(上)——需求分析和初步設計Chisel實戰之單周期RISC-V處理器實作(上)——需求分析和初步設計

Chisel實戰之單周期RISC-V處理器實作(上)——需求分析和初步設計

需求分析

首先明确我們要做的是什麼,這個在标題裡面已經說明了,我們要做的是一個單周期RISC-V處理器。

但光是個短語不足以支撐我們開展項目,我們需要對項目目标做進一步的明确,也就是需求分析。

關于指令集架構(ISA)

設計一個處理器的依據是指令系統規範,也就是ISA的規範,不嚴謹地來說就是該指令集架構的機器語言的規範,即計算機軟體和硬體的接口。而設計處理器是在ISA規範的基礎上,對微體系結構進行設計,是以經典的教材《計算機體系結構:量化研究方法》中就将計算機體系結構描述為指令集架構(ISA)和微體系結構的結合。

是以,我們第一步就是要明确,我們這個項目支援的指令系統規範是什麼!

既然要做一個RISC-V處理器,那必然是要支援RISC-V指令集,我們可以在官網找到該指令集的規範檔案:

Specifications - RISC-V International (riscv.org)

指令集規範中包含了非特權指令集(目前版本規範為Unprivileged Spec v. 20191213)、特權指令集(目前版本規範為Privileged Spec v. 20211203),以及一些仍然處于其他階段的擴充規範。

我們這個項目的目标很簡單,不需要支援特權指令,更不需要支援拓展指令(比如向量拓展、位操作拓展等),僅需要支援非特權指令集就行了,而且是它的一個子集!

非特權指令集有以下基本子集:

基本集 說明 版本 狀态
RVWMO WMO即Weak Memory Ordering,是RISC-V的記憶體一緻性模型 2.0 正式準許
RV32I 最基本的RISC-V 32位整數指令集 2.1 正式準許
RV64I 最基本的RISC-V 64位整數指令集,可以看作是RV32I到64位的拓展 2.1 正式準許
RV32E 面向嵌入式微控制器的基本的RISC-V 32位整數指令集,可以看作是RV32I的精簡 1.9 草案
RV128I 最基本的RISC-V 128位整數指令集,可以看作是RV32I、RV64I到128位的拓展 1.7 草案

既然是基本子集,那就必須得有一個,我們的單周期處理器是單線程的、順序執行的,是以不需要考慮記憶體一緻性模型。我們作為例子也隻需要實作最簡單的32位版本,是以考慮将RV32I作為我們項目的基本指令集。而RV32E雖然是RV32I的精簡,但仍然處于草案階段,就也不考慮了。

注意:這裡的xx位指的是位址空間的位數。

還有一些拓展指令子集:

拓展集 說明 版本 狀态
M 整數乘法、除法拓展 2.0 正式準許
A 原子指令拓展 2.1 正式準許
F 單精度浮點數拓展 2.2 正式準許
D 雙精度浮點數拓展 2.2 正式準許
Q 四精度浮點數拓展 2.2 正式準許
C 壓縮指令拓展 2.0 正式準許
Counters 計數器、定時器、性能計數器拓展 2.0 草案
L 十進制浮點數拓展 0.0 草案
B 位操作拓展 0.0 草案
J 對動态轉譯語言的支援拓展,動态轉譯也叫JIT,此拓展用于支援動态檢查和垃圾回收 0.0 草案
T 事務性記憶體操作拓展 0.0 草案
P Packed-SIMD指令拓展 0.2 草案
V 向量拓展 0.7 草案
Zicsr 控制和狀态寄存器拓展 2.0 正式準許
Zifencei 指令抓取栅欄拓展 2.0 正式準許
Zam 非對齊原子記憶體操作拓展 0.1 草案
Ztso RVTSO(Total Store Ordering)記憶體一緻性模型拓展,是RVWMO的變體 0.1 草案

RV32I加上A拓展就可以支援作業系統了,但我們不需要,其他拓展更是不需要了。

我們可以根據需求選取需要的基本子集和拓展集,實作符合應用場景的處理器。由于我們的需求很簡單,那麼自然我們經過上面的分析,就可以得到結論:

我們隻需要支援RV32I基本指令集!不需要其他任何拓展!

當然了RV32I中的指令我們也并非都需要,在後續的實作中我們還會進行少許的取舍。

關于微體系結構(Microarchitecture)

CPU的設計無非隻有兩部分,一個是資料通路,另一個就是邏輯控制,不管如何我們先确定我們在微體系結構上的需求。

如果你學習過《計算機體系結構:量化研究方法》或其他類似的教材,那肯定知道很多現代處理器中常見的技術,比如Cache、流水線、分支預測、SIMD等等,想想就讓人害怕。不過好消息是我們這個項目暫時不涉及這些,不信我們先捋一捋:

  1. 記憶體層級方面:現代處理器都有Cache之類的東西,用來構成記憶體層級,然後用一些複雜的政策來保證Cache的命中率,而我們的項目中不需要記憶體層級,直接從記憶體通路指令、資料啥的就行;
  2. 指令集并行(ILP)方面:這裡引入了流水線技術,緊接着為了解決資料冒險,引入了轉發技術和動态排程技術(比如記分牌算法和Tomasulo算法),跟着一起出現的還有分支預測那些,另一方面引入了多發射技術,似乎越來越超綱了,但是我們不需要,我們是單周期CPU,沒有流水線,而且我們一次隻執行一條指令,也沒有多發射,根本就沒有指令集并行;
  3. 資料級并行(DLP)方面:顯然我們不需要,因為我們不支援向量拓展,跳過;
  4. 線程級并行(TLP)方面:我們是單核CPU,不支援多線程,更不存線上程之間共享記憶體,也沒有Cache啥的,是以我們同樣也不需要這個,複雜的記憶體一緻性模型完全不用考慮;

還有什麼位址轉換啥的,我們也不需要!

捋完了可以發現,我們什麼優化技術都不需要用上,簡簡單單就實作一個樸素的單周期RISC-V處理器就行了!

初步設計

需求分析結束之後,就可以開始我們的初步設計了!

RV32I指令集分析

通過上面的分析,我們隻需要把RV32I中我們需要的指令給支援了就行了,那麼我們從分析RV32I中的指令開始。

RV32I指令集指令有6種類型:

吃透Chisel語言.39.Chisel實戰之單周期RISC-V處理器實作(上)——需求分析和初步設計Chisel實戰之單周期RISC-V處理器實作(上)——需求分析和初步設計

其中:

  1. R類型即寄存器(Register)類型,有三個操作數,兩個源操作數均來自寄存器(rs1和rs2),目的操作數為寄存器(rd);
  2. I類型即立即數(Immediate)類型,有三個操作數,兩個源操作數分别來自立即數(imm)和寄存器(rs1),目的操作數為寄存器(rd);
  3. S類型即存儲(Store)類型,有三個操作數,均為源操作數,其中寄存器rs1和立即數imm運算得到存儲的位址,rs2寄存器的值為被存儲的數;
  4. B類型即分支(Branch)類型,有三個操作數,均為源操作數,其中兩個寄存器(rs1和rs2)的值用于比較,立即數imm的值為分支目的位址的偏移量;
  5. U類型即無符号立即數(Unsigned immediate)類型,有兩個操作數,立即數imm為源操作數,rd為目的操作數,此指令用于将立即數加載到指定寄存器rd;
  6. J類型即跳轉(Jump)類型,有兩個操作數,立即數imm為源操作數,用于計算跳轉目的位址,rd為目的操作數,用于記錄跳轉前指令的下一條指令的位址;

進一步地,我們分析RV32I中的所有指令,共計四十條,如下表所示:

吃透Chisel語言.39.Chisel實戰之單周期RISC-V處理器實作(上)——需求分析和初步設計Chisel實戰之單周期RISC-V處理器實作(上)——需求分析和初步設計

指令格式是比較規整的,除了

ECALL

EBREAK

以外,均顯然符合上面的六種指令格式類型。

可以按照指令的功能對指令進行分類:

  1. 直接跳轉類:包括

    JAL

    JALR

  2. 條件分支類:包括

    BEQ

    BNE

    BLT

    BGE

    BLTU

    BGEU

  3. 加載/存儲類:包括

    LB

    LH

    LW

    LBU

    LHU

    SB

    SH

    SW

  4. 算術邏輯運算和位運算類:包括所有加法、減法,按位與、或、異或,邏輯左移、邏輯右移、算術右移相關指令;
  5. 比較指令類:包括

    SLTI

    SLTIU

    SLT

    SLTU

  6. 其他指令類:

    FENCE

    ECALL

    EBREAK

再依次對這幾個類指令的行為進行分析:

  1. 直接跳轉類需要對PC寄存器的值進行直接修改,同時寫一個寄存器;
  2. 條件分支類首先需要進行比較,然後根據比較結果選擇是否修改PC寄存器;
  3. 加載/存儲類需要通路資料存儲,加載隻讀取資料,存儲隻寫入資料;
  4. 算術邏輯運算和位運算類會對操作數進行運算,然後将結果寫入目的寄存器;
  5. 比較指令類與算術邏輯運算和位運算類一緻,但操作變成了比較;
  6. 其他指令中,由于不需要維護記憶體一緻性和連續性,是以我們不需要實作

    FENCE

    ,同樣,由于不涉及環境調用中斷和調試調試中斷,是以我們暫時也不需要實作

    ECALL

    EBREAK

資料通路和控制邏輯的初步設計

根據上面的分析,我們設計的CPU中應該至少需要包含以下元件:

  1. 指令記憶體(

    MemInst

    ):接收一個32位的指令位址,讀取出指令;
  2. PC寄存器(

    PCReg

    ):為指令記憶體提供指令位址,每個時鐘周期位址都會+4,目前指令為跳轉時,下一條指令為跳轉目的位址,目前指令為分支指令且分支成功時,下一條指令為分支目标位址;
  3. 通用寄存器堆(

    Registers

    ):可讀可寫的寄存器,接收寄存器号,為運算單元提供操作數,接收運算結果或從資料記憶體讀取到的值;
  4. 資料記憶體(

    MemData

    ):根據加載/存儲位址,加載或存儲資料,加載或存儲依賴于譯碼器的譯碼;
  5. 指令譯碼器(

    Decoder

    ):對指令進行譯碼,解析得到立即數、操作碼、寄存器号等資訊;
  6. 運算單元(

    ALU

    ):根據操作數和操作碼進行運算,運算結果寫到寄存器,分支指令時将比較結果發送給PC,加載存儲指令時計算位址;

這些元件隻描述了資料通路,要使得CPU能正常運作,還需要良好的邏輯控制。

控制邏輯需要根據譯碼結果對資料通路進行控制,可能需要以下幾個方面:

  1. ctrlJump

    :指令是否為跳轉指令?如果是,需要給控制信号到PC,要求在下一時鐘周期修改為跳轉目的位址;
  2. ctrlBranch

    :指令是否為分支指令?如果是,根據運算單元的比較結果(分支與否),決定是否讓PC在下一個時鐘周期跳轉的分支目标位址;
  3. ctrlRegWrite

    :指令是否需要寫寄存器?如果是,将運算單元的結果或從資料記憶體中讀取的值寫入寄存器;
  4. ctrlLoad

    :指令是否為加載指令?如果是,寫入寄存器的值來源應該是資料記憶體;
  5. ctrlStore

    :指令是否為存儲指令?如果是,将寄存器中的值寫入資料記憶體;
  6. ctrlALUSrc

    :指令的操作數2是立即數還是寄存器值?根據此選擇操作數2的值;
  7. ctrlJAL

    :指令是否為JAL指令?如果是,操作數1的值應當為PC寄存器的值;
  8. ctrlOP

    :為ALU指定具體的操作,加?減?或者其他啥?

這些控制信号的生成和傳輸我們統一由控制器(Controller)完成。

上面的說明并不詳盡,隻作為初步設計,但我們也很難在開始的時候就考慮到所有細節,更多的細枝末節需要在設計、調試、修改的疊代中完善,但至少上面的内容足夠我們開始實作了。

最後放上初步設計的草圖:

吃透Chisel語言.39.Chisel實戰之單周期RISC-V處理器實作(上)——需求分析和初步設計Chisel實戰之單周期RISC-V處理器實作(上)——需求分析和初步設計

再次說明,上面的設計是不完備的,比如目前還未考慮到加載/存儲時是位元組、半字還是字,雖然隻是一個信号的問題,但足以展現還有很多不完善的地方,在實作中疊代設計是很有必要的。接下來,我們就将基于這個不完備的設計開始我們的項目開發!

繼續閱讀