RISC V流水線CPU模擬器(c語言實現)

2022-07-10 18:42:15 字數 4371 閱讀 8623

2023年11月27日

該risc-v流水線處理器分為兩部分:功能模擬部分,時序模擬部分。

功能時序分離的優勢有兩點:

不同功能模組化,減小耦合性,可以增強可擴充套件性。

有效降低流水線實現的複雜度和工作量。

具體實現上,功能模擬部分大體沿用之前編寫的單/多週期cpu,在其基礎上改進,加上了與時序模擬部分相互通訊的介面,將進行時序模擬所需要的資訊輸出到buffer檔案中;而時序部分讀取buffer檔案,通過功能模擬部分所提供的資訊,計算流水線的時序資訊,並統計輸出。

接下來是時序模擬的設計框架

雖然實際各級流水線是同時執行,但由於c語言的限制,所以需要選擇乙個順序。

若直接執行會違反時序,如果需要實現則要將每個階段分成兩部分:取資料和執行,兩部分分階段執行。

流水線暫存器後的段先取指令,全部取到指令後再順次執行。

不足是會造成各階段的割裂,帶來一些不必要的麻煩。

各級流水線大體執行框架如下:

void control()

else

return ;

}

pc的更新

獲取指令:根據此時的pc,從指令儲存器中讀入指令,統一讀入32位,冗餘欄位在後續過程中不會被使用,不會造成影響

指令長度確認:確定指令是否為壓縮指令,如果是則會將pc+4傳入多路選擇器,反之將pc+2傳入

框架如下:(僅表示if的邏輯,實際實現中不會出現)

void if()

else if(insn&3 == 3)

else

//寫入流水線暫存器

ifidreg.insn = insn;

ifidreg.c = (insn&3!=3);

return ;

}

從指令中提取相應資訊:提取出各個字段,生成出相應的立即數。採取的是固定字段解碼,在實際電路中各資料通路是並行的,固定字段可以降低整體複雜度。

生成控制訊號:根據指令型別生成相應的控制訊號(如果需要的話),並寫入流水線暫存器

判斷分支轉移:測試分支條件暫存器,以盡快完成分支轉移是否成功的檢測

在id段處理分支指令可以減少流水線的暫停帶來的效率損失

框架如下:(僅表示id的邏輯,實際實現中不會出現)

void id()

else

break;

default:

printf("error: no such instruction!\n");

break;}}

//判斷訪存行為型別,略——這裡直接賦值true

idexreg.w_r = true;

return ;

}

alu單元:從流水線暫存器讀取控制訊號,並根據控制訊號選擇相應的運算元、立即數並進行處理,得到結果aluoutput

傳遞控制訊號:為訪存段確定訪存行為(讀取 or 寫入),將控制訊號寫入流水線暫存器

大體框架如下:(僅表示id的邏輯,實際實現中不會出現)

void ex()

else

}//寫入流水線暫存器exmemreg

}

確定訪存行為:根據流水線暫存器中讀取的控制訊號來確定

邏輯框架較簡單,不在此贅述。

選擇資料和暫存器:根據控制訊號選擇正確的資料,確定目的暫存器

寫回暫存器:將所選資料寫回相應暫存器

邏輯框架較簡單,不在此贅述。

衝突控制單元:將各種冒險的檢測集中在衝突控制單元處理。衝突控制單元是乙個處理和傳遞全域性控制資訊的部件,從各流水線暫存器中讀取資料,進行分析,若發現存在冒險,則生成全域性控制訊號,控制各部件進行相應操作,以解決冒險。

控制訊號:控制訊號的更新不按照固定時鐘週期更新,而是依據各級流水線運**況動態執行。需要在各級流水線均完成自己的處理任務,並將資料寫入下一級流水線暫存器後,才能開始新一次的處理,處理過程包括從各級流水線暫存器讀取最新資料,然後更新控制訊號,並即時傳遞給各部件,然後等待流水線下一次的流動。

暫停:部分衝突可以通過資料重定向來解決,但有些衝突則必須進行暫停處理。暫停的控制是由衝突控制單元通過傳遞控制訊號來完成,當某一級流水線接收到暫停的訊號後,就不從上乙個流水線暫存器取值。當然,當某一級流水線被暫停時,它前面的各級流水線也會被暫停,這一點依然是由衝突控制單元來保證。

插入氣泡:被暫停的連續幾級流水線的最後一級,它需要向下乙個流水線暫存器寫入nop指令(亦可採用其他執行空操作的訊號機制),否則下一級流水線將重複操作,在執行某些指令的情況下會造成錯誤。這個過程就是插入氣泡。

衝突檢測的實現分為兩部分,分別處理資料衝突和控制衝突。

資料衝突:因為一條指令在id段可得到所有解碼資訊,包括源暫存器。而資料衝突的產生,正是後面指令是源暫存器與前面指令的目的暫存器相同,造成的若不處理會帶來錯誤的相關。所以所有資料衝突最早可以在id段確定下來,故資料衝突的檢測,是在id段出現新指令後,將其資訊與前面幾條指令相匹配,檢測是否存在衝突,且標記衝突型別。衝突型別標記用於後面的衝突處理。在出現多個衝突同時出現時,將判斷衝突是否可合併,可合併是指id段源暫存器和ex,mem,wb段中的多個同時出現了衝突,且衝突的暫存器相同,這樣實際只需要將最靠近id段的作為衝突即可,因為id段需要的是最新的資訊。而不可合併的衝突,都會計入統計中。

控制衝突:該方案尚未採用分支**機制,在每乙個分支跳轉處都暫停乙個週期,作為延遲槽。具體實現是在單/多週期流水線中,在向buffer中輸出資訊時,若當前指令是分支跳轉指令,則在後面再輸出乙個特定的nop指令,這樣在timingsimulator讀取buffer時,相當於已經對控制衝突進行了處理,只需正常處理nop指令即可。

優點:可以確定每乙個時鐘週期整個cpu的狀態資訊。

缺點:可能過於陷入細節,各級流水線執行速度的差異將增加整體的複雜性。

優點:可以將每一級流水線內部的操作視為原子操作,降低複雜度,隱藏很多不必要的細節。

缺點:難以任意地跟蹤檢視週期級cpu的狀態資訊。

此處選擇事件驅動的方式。

實現:維護乙個全域性的事件佇列,即此時流水線中正在執行的指令,每一條指令的執行視為乙個事件。在佇列中的每個事件有一些屬性:該指令的名稱、源暫存器、目的暫存器、指令執行開始時間、指令執行結束時間,在每一級流水線處理所需要的時間。其中,指令在各階段結束時間的更新,需要由佇列根據所有事件來統一確定,否則會因為各指令的執行週期數差異造成混亂。

事件佇列結構大致如下:

struct event;

typedef struct event event;

event queue[5];

還有一些配套的函式方法(具體見**)。

計數分為兩部分。一是在事件佇列每次正常出佇列時檢視被彈出事件的資訊,並進行持續地統計。統計各型別指令數量、執行週期數等。二是與衝突控制單元資料互動,每次出現資料冒險和控制冒險就進行記錄。還有一些功能模擬時的必要資訊,通過buffer進行傳遞。

統計資訊暫時分為4部分:

所執行的所有指令時序資訊,每一條包括:指令名稱,開始時間,結束時間,總耗時。

各種指令的執行數量,並從高到低進行了排序,便於檢視哪些指令出現頻率高。

衝突計數,統計了各型別資料衝突的數量,以及控制衝突的數量。

計算了cpi。

注意,這裡的資料衝突是流水線事件佇列每一次更新後進行檢測,且均以id段為中心,有些需要暫停的衝突,在暫停之後,會變成僅需要資料定向的衝突,此時會統計為兩次衝突。如果需要更換統計模式,僅記為一次衝突的話,對統計結果進行簡單的減法即可(因為這樣的多次統計必然是一一對應的)。

逐級向下進行部分關鍵函式的展示(為簡略,省去了細節,具體實現請閱讀**)

int main()

void init()

}

void timingsimulator()

cycles = out.time_end;

}

具體**會在該課程結束後上傳......

CPU設計學習 流水線

if instruction fetch,取指 id instruction decode,解碼 ex execute,執行 mem memory access,記憶體資料讀或者寫 wb write back,資料寫回到通用暫存器中 並不是所有指令都要經過這五個階段,例如運算指令在記憶體讀寫階段並沒...

流水線和復合估算器

變換器通常與分類器,回歸器或其他估計器組合以構建復合估計器。最常見的工具是 管道。管道通常與featureunion結合使用 後者將變換器的輸出連線成復合特徵空間。transformedtargetregressor處理轉換目標 即對數轉換y 相反,pipelines只轉換觀察到的資料 x 方便和封...

瀏覽器渲染流水線解析(二)

核心動畫,每一幀都是由 blink 生成,都需要產生乙個新的 main frame blink 觸發然後交由合成器執行,比如說傳統的 css translation 或者新的 animation api,如果它們觸發的動畫經由 blink 判斷可以交由合成器執行 使用 timer 或者 raf 由 ...