【計算機體系結構】記分牌調度算法 Python

一、前言

代碼大部分來自該網址。本文章主要是對算法進行分析,同時根據該網址的代碼進行具體實現的講解。

該網址中的代碼我找到了三處bug:

1、輸入指令中最後一條指令無法讀入。

是個小bug,很容易修。

2、輸入指令的數據相關未處理好。

舉例如下:

ADD F0 F1 F2

MULT F3 +45 F0

SUBD F6 F3 F0

MULT由於F0被ADD阻塞,SUBD由於F3和F0被MULT和ADD阻塞,上述網址中的算法無法針對這種情況給出正確的流水順序,實際上表現爲直接卡死。需要修改判斷邏輯。

3、在同一週期有多條指令寫回完畢時,無法處理多條指令。

是個不大不小的bug,修改返回值就能解決。

我將該應用部署在了我的服務器上,可以嘗試訪問該網址去使用。

testcase如下:

LD F6 34+ R2
LD F2 45+ R3
MULT F0 F2 F4
SUBD F8 F6 F2
DIVD F10 F0 F6
ADDD F6 F8 F2
LD F6 34+ R2
LD F2 45+ R3
MULT F0 F2 F4
SUBD F8 F6 F2
SUBD F6 F8 F0
DIVD F10 F0 F6
ADDD F6 F8 F2
LD F6 34+ R2
LD F2 45+ R3
MULT F0 F2 F4
SUBD F8 F6 F2
MULT F10 F0 F8
ADDD F0 F10 F6
SUBD F6 F8 F0
DIVD F10 F0 F6
ADDD F6 F8 F2
LD F0 12+ R1
SUBD F9 F10 F0

若出現500錯誤,說明我的小服務器不堪重負,請清理瀏覽器緩存+重啓瀏覽器再次訪問即可。

二、記分牌調度算法分析

1、概述

記分牌調度算法用於實現指令流水線的亂序輸出,解決部分數據相關問題。它維護三個數據結構:

Instruction Status:保存輸入指令流的各個屬性,記錄其流水狀態。

Function Unit Status:保存CPU中各功能部件的狀態。

Register Result Status:保存寄存器的使用狀態。

通過這三張表的配合,實現指令的亂序執行。

2、數據結構

如下爲Instruction Status:

Instruction J K Issue Read Operand Execution Complete Write Result Target
               
               
               
               
               
               

其各列含義如下:

Instruction:輸入的指令名稱,如ADD、SUBD、LD什麼的;

J、K:源寄存器,如F3、F4等;

Issue、Read Operand、Execution Complete、Write Result:指令執行的四個步驟,內容爲該步驟執行完畢的週期。具體如下:

Issue:該階段對Function Unit Status進行初始化,具體步驟見後文;

Read Operand:該階段從源寄存器取數;

Execution Complete:該階段表示指令執行完畢,釋放功能部件的資源;

Write Result:該階段將數據寫入目標寄存器;

Target:目標寄存器,如F3,F4等。

如下爲Function Unit Status,Function Unit Status僅與CPU的硬件結構有關係,因此可以直接將其初始化:

Name Busy Op Fi Fj Fk Qj Qk Rj Rk
Integer                  
Mult1                  
Mult2                  
Add                  
Divide                  

其各列含義如下:

Name:功能部件名稱,如Integer、Add、Divide等;

Busy:該部件是否忙綠,取值Yes或No;

Op:該部件執行什麼操作,這和Name在某些指令上重複,在某些指令上不重複,比如ADD和SUBD都是用Add部件實現的;

Fi、Fj、Fk:源寄存器和目標寄存器名稱;

Qj、Qk:佔據源寄存器的指令名,若Rj、Rk爲No,則該處有值,其值爲向該寄存器寫入值的指令名;

Rj、Rk:指示源寄存器是否被佔據,取值Yes或No。

如下爲Register Result Status:

F0 F1 F2 F3 F4 F5 F6 F7 F8
                 

其各列爲目標寄存器名稱,保存目前佔據該寄存器的功能部件名。

3、分析

在算法開始之前,首先要讀入所有指令,在所有指令讀入的過程中,對Instruction Status進行初始化:

我們以前言中所述的第一個testcase爲例進行初始化:

Instruction J K Issue Read Operand Execution Complete Write Result Target
LD             F6
LD             F2
MULT F2 F4         F0
SUBD F6 F2         F8
DIVD F0 F6         F10
ADDD F8 F2         F6

注意常數和R寄存器不會保存,只保存F寄存器。

然後開始第一個週期

在第一個週期中,進入第一條指令LD的Issue階段,譯碼,從而知道這是一條取數指令,其源寄存器中不包含F寄存器,目標寄存器爲F6。因此可以更新Function Unit Status如下:

Name Busy Op Fi Fj Fk Qj Qk Rj Rk
Integer Yes LD F6         Yes Yes
Mult1                  
Mult2                  
Add                  
Divide                  

Busy、Op、Fi、Fj、Fk都好說,照着上面的Instruction Status直接寫就可以,但是Rj、Rk如何得知呢?通過查閱Register Result Status得到。LD指令看不出,我們以MULT F2 F4 F0爲例,源寄存器爲F4和F0,因此我們需要去看Register Result Status中這兩個寄存器的值,若其值爲空,則Rj、Rk值爲Yes,若不空,則其值爲No,Qj、Qk爲Register Result Status中的對應值。

爲什麼這麼看?因爲在Issue階段,我們要解決寫後讀相關,即不能在源寄存器值更新之前我就讀入。爲了防止這種情況,就需要知道源寄存器的值是否更新。如何看這個源寄存器的值是否更新呢?Register Result Status表中儲存的“佔據”該寄存器的指令實際上就會將該寄存器的值更新,那麼,通過查閱Register Result Status,就可以知道有沒有指令要更新源寄存器,如果有,那麼就等待,沒有,就不用等待。

同樣的,要更新Register Result Status如下:

F0 F1 F2 F3 F4 F5 F6 F7 F8
            Integer    

表示LD將要更新F6這個寄存器的值,一切想訪問F6的指令都要等LD執行完再執行。

第2、3、4週期分別執行第一條LD的取數、執行、寫回過程。第四周期執行完畢,將Integer釋放——這時第二條LD才能開始執行。第五週期開始執行LD,表格如下:

Instruction J K Issue Read Operand Execution Complete Write Result Target
LD     1 2 3 4 F6
LD     5       F2
MULT F2 F4         F0
SUBD F6 F2         F8
DIVD F0 F6         F10
ADDD F8 F2         F6
Name Busy Op Fi Fj Fk Qj Qk Rj Rk
Integer Yes LD F2         Yes Yes
Mult1                  
Mult2                  
Add                  
Divide                  
F0 F1 F2 F3 F4 F5 F6 F7 F8
    Integer            

第六週期時,我們會嘗試讀入下一條指令,實際上每一週期我們都會嘗試讀入下一條指令,但是第二、三、四周期Integer部件都被佔用,而LD又需要使用該部件,因此不能讀入。但是第二條LD的下一條指令是MULT,它使用Mult1或Mult2部件,這倆部件都閒着呢,因此可以讀入。同時,第六週期LD又會往下執行到取數階段。能夠執行到取數階段的前提是Rj、Rk在前一週期已經均爲Yes,很明顯,LD滿足。更新表格如下:與第一週期類似,沒什麼好說的,第六週期可就不一樣了:

Instruction J K Issue Read Operand Execution Complete Write Result Target
LD     1 2 3 4 F6
LD     5 6     F2
MULT F2 F4 6       F0
SUBD F6 F2         F8
DIVD F0 F6         F10
ADDD F8 F2         F6

同樣的,對於MULT,Busy、Op、F按部就班的讀入,在看Qj之前,要先看Rj,看Rj要看Register Result Status中的Fj,對於本例來說是F2,很不幸,在Register Result Status中F2被佔用,因此Rj爲No,Qj中填入Register Result Status中F2的值Integer。而Fk沒被佔用,因此Rk爲Yes。

Name Busy Op Fi Fj Fk Qj Qk Rj Rk
Integer Yes LD F2         Yes Yes
Mult1                  
Mult2 Yes MULT F0 F2 F4 Integer   No Yes
Add                  
Divide                  

Mult會更新Fi也就是F0的值,因此更新。

F0 F1 F2 F3 F4 F5 F6 F7 F8
Mult1   Integer            

第七週期時,我們會嘗試讀入下一條指令,SUBD。SUBD使用的Add部件目前空閒,因此可以讀入。同時,由於LD執行僅需一個週期,因此第二條LD執行完畢。但是MULT由於Rj爲No,源寄存器沒準備好,因此不能進行取數操作。更新表格如下:

Instruction J K Issue Read Operand Execution Complete Write Result Target
LD     1 2 3 4 F6
LD     5 6 7   F2
MULT F2 F4 6       F0
SUBD F6 F2 7       F8
DIVD F0 F6         F10
ADDD F8 F2         F6

同樣的,對於SUBD,Busy、Op、F按部就班的讀入,Fk爲F2,因此Qk、Rk與之前類似。

Name Busy Op Fi Fj Fk Qj Qk Rj Rk
Integer Yes LD F2         Yes Yes
Mult1                  
Mult2 Yes MULT F0 F2 F4 Integer   No Yes
Add Yes SUBD F8 F6 F2   Integer Yes No
Divide                  

SUBD也是一樣,佔據F8:

F0 F1 F2 F3 F4 F5 F6 F7 F8
Mult1   Integer           Add

接下來看第八週期。第八週期時,我們嘗試讀入DIVD,發現Mult2空閒,因此可以讀入。同時,第二條LD運行到寫回操作。注意這裏的讀後寫相關。我們需要查看LD之前的所有未執行到取數階段的指令,若其源寄存器與本條指令的目標寄存器相同,則無法寫回,直到不存在這樣的指令才能夠寫回。很顯然,LD可以寫回。那麼MULT和SUBD能不能執行讀數操作呢?這要看Rj、Rk準沒準備好。容易知道的是,它們都需要等到F2寫回後才能讀,而LD在本週期才寫回的F2,因此還不能讀。要等到下週期纔可以。

Instruction J K Issue Read Operand Execution Complete Write Result Target
LD     1 2 3 4 F6
LD     5 6 7 8 F2
MULT F2 F4 6       F0
SUBD F6 F2 7       F8
DIVD F0 F6 8       F10
ADDD F8 F2         F6

同樣的,對於DIVD,Busy、Op、F按部就班的讀入,Fk爲F4,因此Qk、Rk與之前類似。Fj爲F0,被Mult1佔用。

Name Busy Op Fi Fj Fk Qj Qk Rj Rk
Integer Yes LD F2         Yes Yes
Mult1 Yes DIVD F10 F0 F6 Mult1   No Yes
Mult2 Yes MULT F0 F2 F4 Integer   No Yes
Add Yes SUBD F8 F6 F2   Integer Yes No
Divide                  

DIVD也是一樣,佔據F10,不寫了:

F0 F1 F2 F3 F4 F5 F6 F7 F8
Mult1   Integer           Add

因此我們可以看到,這三張表格是如何聯動工作的。

三、代碼實現

具體代碼實現均參照前言中網址所述,在我修改的部分我有特別說明。

1、數據結構設計

我們需要設計三張表,因此新建三個類。第一個類如下:用於實現Instruction Status。

class InstructionStatusTable(object):
	def __init__(self):
		self.instructionList = list()
		self.usingTime = {
							"LD" : 1,
							"SD" : 1,
							"ADDD" : 2,
							"SUBD" : 2,
							"MULT" : 10,
							"DIVD" : 40
						}

	# 向指令列表instructionList中添加一條指令,其中target爲WR的目標寄存器
	def addInstruction(self, instruction):
		ins, target, j, k = re.split(' ', instruction)
		instructionItem = {
			"instruction" : ins,
			"target" : target,
			"j" : j,
			"k" : k,
			"issue" : "",
			"readOperand" : "",
			"exeComplet" : "",
			"writeResult" : ""
		}
		# issue,readOperand,exeComplet,writeResult均初始化爲0
		self.instructionList.append(instructionItem)

	def getList(self):
		return self.instructionList

	def getNext(self):
		for item in self.instructionList:
			if item["issue"] == "":
				return item["instruction"],item["target"],item["j"],item["k"]
		return None,None,None,None

	def fresh(self, name, target, instruction, funcUTable, cycle):
		release_flag = False
		# release_unit = ""
		release_target = []
		# 更新已發射項
		for item in self.instructionList:
			if item["issue"] != "":
				# 發射但未讀數
				if item["readOperand"] == "":
					for unit in funcUTable:
						#根據FunctionUnitStatus更新ReadOperand
						if self.change_name(item["instruction"]) in unit["name"] and unit["Fi"]==item["target"]:
							if unit["Rj"]!="No" and unit["Rk"]!="No":
								item["readOperand"] = cycle
				# 發射且讀數則考慮是否更新執行結束和寫回執行狀態
				if item["readOperand"] != "":
					#判斷是否執行結束
					if int(cycle) == int(item["readOperand"]) + self.usingTime[item["instruction"]] :
						item["exeComplet"] = cycle
					#若已經執行結束,而且未寫回
					if item["exeComplet"] != "" and item["writeResult"]=="":
						#若已經在之前寫回
						if int(cycle) >= int(item["exeComplet"]) + 1:
							#release_flag表示現在已經執行完,隨時可以寫回
							release_flag = True
							# 是其他已發射指令的輸入數據且該指令未完成數據讀取,則不能寫
							for item2 in self.instructionList:
								if item2["issue"] != "" and int(item2["issue"])<int(item["issue"]):
									if item2["j"] == item["target"] or item2["k"] == item["target"]:
										if item2["readOperand"]=="":
											release_flag = False
										elif int(item2["readOperand"]) >= int(cycle):
											release_flag = False
							# 完成寫回操作,指令執行結束,相應地更新表2和表3
							if release_flag:
								item["writeResult"] = cycle
								release_target.append(item["target"])
		# instruction非空則發射instruction
		if name != None:
			for item in self.instructionList:
				if item["instruction"]==instruction and item["target"]==target :
					item["issue"] = cycle
					break
		# 如果更新了writeResult則更新表2表3,釋放對應資源
		if len(release_target):
			# print (release_target)
			return release_target
		else :
			return False
    def change_name(self, name):
		Uname = ""
		if name == "LD" or name == "SD":
			Uname = "Integer"
		elif name == "MULT":
			Uname = "Mult"
		elif name == "SUBD":
			Uname = "Add"
		elif name == "DIVD":
			Uname = "Divide"
		return Uname

其中有如下幾個函數:

init:初始化函數,用於確定各個操作在執行階段需要耗費幾個週期。我們在這裏默認取數和寫數一個週期,加減兩個週期,乘十個週期,除四十個週期。

addInstruction:將指令添加進Instruction Status中,同時初始化其name、i、j、k。

getList:返回Instruction Status。

getNext:讀入下一條指令。如何讀下一條指令呢?只要它是第一條issue爲空的指令就可以讀入。但這個讀入只是第一步選擇,在調用該函數後還需要判斷所需的功能部件和目標寄存器是否空閒,只有空閒才真正讀入該指令。表觀上來看就是置該指令的issue爲當前的週期值。

fresh:核心函數。整個算法的兩大核心函數之一。用於在每個週期更新Instruction Status中各行的取數、執行、寫回、讀入的值。需要仔細研究。其參數有如下幾個:

name:希望讀入的下一條指令所用的功能部件的名稱, target:希望讀入的下一條指令的目標寄存器, instruction:希望讀入的下一條指令的名稱, funcUTable:Function Unit Status, cycle:當前週期。

首先判斷讀入是否完成,也就是Issue是否爲空。若Issue不爲空,那說明該條指令已讀入,可以開始判斷取數。若Issue爲空,直接退出——你指令都沒讀入還有什麼好判斷的?

然後判斷取值是否完成,也就是Read Operand是否爲空。若Read Operand爲空,就考慮能否取數。取數需要考慮數據相關,即兩個源寄存器需要都準備好了,才能夠讀入數據。表觀上來看就是需要Rj和Rk都爲Yes纔行。也就是說,需要本條指令之前的指令進行更改之後才能進行讀取。寫後讀相關是在這裏解決的。如果沒準備好,那讀入就不進行。若Read Operand不爲空,說明已經讀完數了。可以判斷執行。可能有同學在這裏有點迷糊,但又說不出哪裏迷糊:憑什麼我這類的Yes就能避免寫後讀啊?比如說本條指令後邊的指令把本指令的源寄存器佔了,那Rj就是No,就不能取值,但是本條指令之後的指令對源寄存器的讀取不應該影響到本條指令啊。是的,影響不到。爲什麼?因爲記分牌算法是順序讀入的,而且對R的初始化在讀取指令,也就是Issue段執行。也就是說,在線性流水線中是順序執行的指令,其Issue一定是由小到大的。不存在本條指令讀入的時候,因爲後面的指令要對寄存器進行寫入而阻塞,讀入的阻塞只能是因爲它前面的指令。來具體分析一下:讀數的阻塞是由於R爲No,R的更改需要在讀入指令時完成。設當前指令爲Im,它之後某一條指令爲In。對於我們的猜想,Im和In有如下關係:Im的源寄存器與In的目標寄存器相同,設其爲Fe,“判斷R爲Yes或No可能會誤判,因爲In可能置R爲No,而此時Im還沒讀”。實際上這種情況不存在。因爲Ln不可能置lm的R爲No。Lm的R只能它自己在讀入指令時賦值一次,該次賦值就是參考lm之前的所有指令。

接下來判斷執行是否完成。直接看當前週期是不是等於Read Operand+指令執行時間即可。可能有人要問:爲什麼是Read Operand呢?如果是Read Operand,不就說明取完數的下一個週期就直接開始執行麼?如果當時該部件忙怎麼辦?對於有這種疑問的同學,可以去看看3中分析爲什麼第二條LD不能讀入——一旦指令讀入,則就把它要用的部件預定了,其他人不能用。這也就是說,讀入的前提條件是Issue爲空,同時它要用的功能部件和目標寄存器都是空閒的。這樣也就不用在取完數之後判斷部件是否空閒了。

如果Execution Complete不爲空,那麼就要判斷能夠寫入目標寄存器。這是就面對另一個相關,即讀後寫。也就是說,本條指令需要等“本條指令之前的所有指令對目標寄存器的讀取都完成”後才能寫入目標寄存器。在這裏,我的處理與原網址中的處理出現了差異:原網址用兩個條件判斷,較爲複雜而且遺漏了特殊情況。我則是直接判斷,怎麼直接判斷?從上到下遍歷Instruction Status,對所有Issue不爲空的行,判斷它們的Issue是否小於本條指令的Issue。如果小於,說明該指令是“本條指令之前的指令”。然後判斷源寄存器與本條指令的目標寄存器是否相同,若相同,就看它的讀數操作是否爲空,不爲空,說明讀完了,不會影響本條指令,否則沒讀,本條指令不能寫回。

若本條指令在本週期寫回,那麼就需要將其佔用的目標寄存器和功能部件清空。這裏是原網址的一個bug,其作者僅考慮了一個週期僅有一條指令執行完畢的情況,實際上,一個週期可能有多條指令執行完畢。這時就需要將它們都釋放。修改起來很簡單,原來是返回一個變量,現在返回一個list即可。

最後更新Issue。由於Integer部件只有一個,所以最多有一個指令可以讀入,這條指令就是函數參數中的name。通過name、instruction和target就可以定位到指令——因此還有一個bug,如果有兩條完全相同的指令,那就會出錯。但是一般的testcase中都沒有,也就不考慮了。

fresh的返回值我們已經更改過了,是一個list,在調用fresh之後就可以着手釋放資源了。

change_name:功能函數,返回指令要用的部件名稱。

第二個類如下:用於實現Function Unit Status。

class FunctionalUnitTable(object):
	def __init__(self):
		self.funcUnitList = list()
		self.initlist()

	# 初始化表單
	def initlist(self):
		name = ['Integer', 'Mult1', 'Mult2', 'Add', 'Divide']
		for item in name :
			funcUnit = {
				"name" : item,
				"busy" : "No",
				"Op" : "",
				"Fi" : "",
				"Fj" : "",
				"Fk" : "",
				"Qj" : "",
				"Qk" : "",
				"Rj" : "",
				"Rk" : ""
			}
			self.funcUnitList.append(funcUnit)

	def getList(self):
		return self.funcUnitList

	def checkUnit(self, name):
		Uname = ""
		if name == "LD" or name == "SD":
			Uname = "Integer"
		elif name == "MULT":
			Uname = "Mult"
		elif name == "SUBD" or name == "ADDD":
			Uname = "Add"
		elif name == "DIVD":
			Uname = "Divide"
		for item in self.funcUnitList:
			if Uname != "Mult":
				if item["name"] == Uname : 
					if item["busy"] == "No":
						return item["name"]
			else :
				if item["name"] in ["Mult1","Mult2"]:
					if item["busy"] == "No":
						return item["name"]
		return False

	# 添加功能部件的使用
	def launch_ins(self, name, instruction, target, j, k, regTable):
		for item in self.funcUnitList:
			if item["name"] == name:
				item["busy"] = "Yes"
				item["Op"] = instruction
				item["Fi"] = target
				item["Fj"] = j
				item["Fk"] = k
				#若源操作數是寄存器
				if "F" in j:
					if regTable[j] != "" :
						item["Qj"] = regTable[j]
						item["Rj"] = "No"
					else :
						item["Rj"] = "Yes"
				#若源操作數是常數
				else :
					item["Rj"] = "Yes"
				if "F" in k:
					if regTable[k] != "" :
						item["Qk"] = regTable[k]
						item["Rk"] = "No"
					else :
						item["Rk"] = "Yes"
				else :
					item["Rk"] = "Yes"

	def release_ins(self, name):
		for unit in self.funcUnitList:
			if unit["name"] == name:
				unit["busy"] = "No"
				unit["Op"] = ""
				unit["Fi"] = ""
				unit["Fj"] = ""
				unit["Fk"] = ""
				unit["Qj"] = ""
				unit["Qk"] = ""
				unit["Rj"] = ""
				unit["Rk"] = ""
			if unit["Qj"] == name:
				unit["Qj"] = ""
				unit["Rj"] = "Yes"
			if unit["Qk"] == name:
				unit["Qk"] = ""
				unit["Rk"] = "Yes"

幾乎全是實現基本功能的函數,很簡單。這也說明了在這三張表中,最核心的就是Instruction Status。來看看都有什麼吧:

init、initlist、getList都是很簡單的函數,不再贅述。

checkUnit:參數name爲指令名稱。該函數首先通過指令名稱找到對應部件的名稱,然後判斷部件是否被佔用,如果被佔用,返回False,否則返回部件名稱。

launch_ins:用於更新Function Unit Status。參數有name, instruction, target, j, k, regTable。很直觀。

調用一次launch_ins,相當於Function Unit Status的一行,通過name找到對應的行。關鍵就是R和Q的更新。先更新Rj,怎麼更新?去看Register Result Status中對應的Fj,如果是空,那這裏就是Yes,否則是No,然後對應更新Qj即可。

release_ins:用於更新Function Unit Status,將對應部件置爲空閒狀態即可。

第三個類如下:用於實現Register Result Status。

class RegisterTable(object):
	def __init__(self):
		self.registerDict = {}
		self.initregister()

	def initregister(self):
		# 課件例題中使用的寄存器並沒有達到31個之多,所以在此爲了打印信息時方便僅設置12個寄存器
		for i in range(0, 12):
			fname = "F" + str(i)
			self.registerDict[fname] = ""

	def getDict(self):
		return self.registerDict

	def checkF(self,num):
		try:
			if self.registerDict[num] == "":
				return True
			else :
				return False
		except Exception as e:
			print (e)

	def launch_ins(self, name, target):
		self.registerDict[target] = name

	def release_ins(self, target):
		Rname = self.registerDict[target]
		self.registerDict[target] = ""
		return Rname

Register Result Status是一個輔助的類,因此其方法都很簡單直觀,不再分析。

接下來是具體的流水線運行過程了。代碼如下:

def goto_cycle(end_cycle, instructions):
	# insTable對應表Instruction status
	insTable = InstructionStatusTable()
	# funcUTable對應表Functional Unit status
	funcUTable = FunctionalUnitTable()
	# registerTable對應表Register result status
	registerTable = RegisterTable()
	#初始化InstructionStatus表
	for i in instructions:
		insTable.addInstruction(i)

	allDone = False
	cycle = 0
	#未走到最後一個週期且全部指令未執行完
	while (not allDone and cycle < end_cycle):
		cycle += 1
		# 判斷下一待發射指令能不能發射
		ins, target, j, k = insTable.getNext()
		U_available = None
		if ins != None:
			# 檢查functional unit是否被佔用,若該部件不繁忙,則返回該部件的名字,若繁忙,返回false
			U_available = funcUTable.checkUnit(ins)
			# 檢查目標寄存器是否被佔用,若沒被佔用,返回true,否則返回false
			F_available = registerTable.checkF(target)
			#部件可用且目標寄存器可用
			if U_available and F_available:
				#佔用目標寄存器,更新寄存器表和部件表,發射該指令
				registerTable.launch_ins(U_available, target)
				funcUTable.launch_ins(U_available, ins, target, j, k, registerTable.getDict())
			#部件不可用或目標寄存器被佔用
			else :
				U_available = None
		#re_target是已經寫回的一個寄存器
		re_target = insTable.fresh(U_available, target, ins, funcUTable.getList(), str(cycle))
		if re_target:
			for item in re_target:
				Rname = registerTable.release_ins(item)
				funcUTable.release_ins(Rname)
			
		# 判斷是否全部指令執行完畢
		allDone = True
		for item in insTable.getList():
			if item["writeResult"] == "" :
				allDone = False

這是第二個核心函數。輸入參數有end_cycle、instructions。前者是希望運行到的週期數,後者是指令列表。該函數將實現從第零週期開始運行,一直到目標週期爲止,或是所有指令執行完畢。下面來具體分析。

首先,將初始化三張表。然後開始循環:

在循環體中,首先進行週期加一。然後得到希望讀入的下一條指令。就像前文所說的,若該指令對應的功能部件和目標寄存器都是空閒的,那麼就讀入它,同時將功能部件和目標寄存器都置爲忙碌,更新Function Unit Status和Register Result Status;如果不是空閒的,就不讀入,也就不更新。

接下來更新Instruction Status,調用第一核心函數fresh。然後將其返回值中的所有指令佔用的資源釋放。

整個流程就是:讀入指令→更新表→釋放資源。

四、總結

記分牌調度算法算是較爲複雜的一個算法了。

想要理解該算法,我極力推薦按3.3的方式自己手動執行十個左右週期,這樣會對算法的原理有一個深刻認識,在實際代碼實現中也會容易很多。如果僅憑PPT,那麼將很難理解算法的具體實現,比如說Rj、Rk的含義,數據相關是在哪裏解決的、三張表是如何協同工作的——這些在上面我都有很詳細的分析了,這裏不再一一說明。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章