【计算机体系结构】记分牌调度算法 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的含义,数据相关是在哪里解决的、三张表是如何协同工作的——这些在上面我都有很详细的分析了,这里不再一一说明。

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