使用Python编写一个模仿CPU工作的程序
今天早上早些时候,在我的PlanetPython源中,我读到了一篇有趣的文章"开发CARDIAC:纸板计算机(Developingupwards:CARDIAC:TheCardboardComputer)",它是关于名为Cardiac的纸板计算机的.我的一些追随者和读者应该知道,我有一个名为简单CPU(simple-cpu)的项目,过去的数月我一直工作于此,并且已经发布了源代码.我真的应该给这个项目提供一个合适的许可证,这样,其他人可能更感兴趣,并在他们自己的项目中使用.不管怎样,但愿在这发布之后,我可以完成这件事.
在读完了这篇文章以及它链接的页面后,我受到了一些启发,决定为它编写我自己的模拟器,因为我有编写字节码引擎的经验.我计划着跟随这篇文章继续往前,先写一篇关于汇编器的文章,接下来是关于编译器的文章.这样,通过这些文章,你基本上可以学到,如何用Python为Cardiac创建编译工具集.在简单CPU(simple-cpu)项目中,我已经编写了一个完整的可工作的汇编器.在内置的游戏中,已经有了可工作的编译器的最初步骤.我也选择Cardiac作为一个验证机器是因为它绝对的简单.不需要复杂的记忆,每个操作码只接受单一的参数,所以它是绝好的学习工具.此外,所有的数据参数都是相同的,不需要检测程序是需要一个寄存器,字符串或者还是内存地址.实际上,只有一个寄存器,累加器.因此,让我们开始吧!我们将基于类来创建,这样包含范围.如果你想尝试的话,你可以简单通过子类来增加新的操作码.首先,我们将集中于初始化例程.这个CPU非常简单,所以我们只需要初始化下面的内容:CPU寄存器,操作码,内存空间,读卡器/输入,和打印/tty/输出.
classCardiac(object):
"""Thisclassisthecardiac"CPU"."""
def__init__(self):
self.init_cpu()
self.reset()
self.init_mem()
self.init_reader()
self.init_output()
defreset(self):
"""ThismethodresetstheCPU'sregisterstotheirdefaults."""
self.pc=0#:ProgramCounter
self.ir=0#:InstructionRegister
self.acc=0#:Accumulator
self.running=False#:Arewerunning?
definit_cpu(self):
"""Thisfancymethodwillautomaticallybuildalistofouropcodesintoahash.Thisenablesustobuildatypicalcase/selectsysteminPythonandalsokeepsthingsmoreDRY.Wecouldhavealsousedthegetattrduringtheprocess()methodbefore,andwrappeditaroundatry/exceptblock,butthatlooksabitmessy.Thiskeepsthingscleanandsimplewithaniceone-to-onecall-map."""
self.__opcodes={}
classes=[self.__class__]#:Thisholdsalltheclassesandbaseclasses.
whileclasses:
cls=classes.pop()#Poptheclassesstackandbeing
ifcls.__bases__:#Doesthisclasshaveanybaseclasses?
classes=classes+list(cls.__bases__)
fornameindir(cls):#Letsiteratethroughthenames.
ifname[:7]=='opcode_':#Weonlywantopcodeshere.
try:
opcode=int(name[7:])
exceptValueError:
raiseNameError('Opcodesmustbenumeric,invalidopcode:%s'%name[7:])
self.__opcodes.update({opcode:getattr(self,'opcode_%s'%opcode)})
definit_mem(self):
"""ThismethodresetstheCardiac'smemoryspacetoallblankstrings,asperCardiacspecs."""
self.mem=[''foriinrange(0,100)]
self.mem[0]='001'#:TheCardiacbootstrapoperation.
definit_reader(self):
"""Thismethodinitializestheinputreader."""
self.reader=[]#:Thisvariablecanbeaccessedafterinitializingtheclasstoprovideinputdata.
definit_output(self):
"""Thismethodinitializestheoutputdeck/paper/printer/teletype/etc..."""
self.output=[]
但愿我写的注释能让你们看明白代码的各部分功能. 也许你已经发现这段代码处理指令集的方法(method)跟simple-cpu项目有所不同.由于它能让开发者根据自己的需求轻松的扩展类库,我打算在后续的项目中继续使用这种处理方式.随着我对各部分功能原理的深入理解,项目也在不断的发展变化.其实吧, 做这样一个项目真的能让人学到不少东西. 对于精通计算机的人来说, CPU的工作原理啦,指令集是怎么处理的啦,都不是问题啦. 关键是,能够按照自己的想法去实现这样一个CPU仿真器,真的很好玩.根据自己想象中的样子,亲手打造出这样一台仿真器,然后看着它屁颠屁颠的运行着,那叫一个有成就感.
接下来,我们讲下工具函数(utilityfunctions),这些函数在很多地方都会用到,而且允许在子类(subclasses)中重写:
defread_deck(self,fname):
"""将指令读到reader中."""
self.reader=[s.rstrip('\n')forsinopen(fname,'r').readlines()]
self.reader.reverse()
deffetch(self):
"""根据指令指针(programpointer)从内存中读出指令,然后将指令指针加1."""
self.ir=int(self.mem[self.pc])
self.pc+=1
defget_memint(self,data):
"""由于我们是以字符串形式(*string*based)保存内存数据的,要仿真Cardiac,就要将字符串转化成整数.如果是其他存储形式的内存,如mmap,可以根据需要重写本函数."""
returnint(self.mem[data])
defpad(self,data,length=3):
"""本函数的功能是像Cardiac那样,在数字的前面补0."""
orig=int(data)
padding='0'*length
data='%s%s'%(padding,abs(data))
iforig<0:
return'-'+data[-length:]
returndata[-length:]
本文后面我会另外给大家一段能结合Mixinclasses使用的代码,灵活性(pluggable)更强些. 最后就剩下这个处理指令集的方法了:
defprocess(self):
"""本函数只处理一条指令.默认情况下,从循环代码(runningloop)中调用,你也可以自己写代码,以单步调试的方式调用它,或者使用time.sleep()降低执行的速度.如果想用TK/GTK/Qt/curses做的前端界面(frontend),在另外一个线程中操作,也可以调用本函数."""
self.fetch()
opcode,data=int(math.floor(self.ir/100)),self.ir%100
self.__opcodes[opcode](data)
defopcode_0(self,data):
"""输入指令"""
self.mem[data]=self.reader.pop()
defopcode_1(self,data):
"""清除指令"""
self.acc=self.get_memint(data)
defopcode_2(self,data):
"""加法指令"""
self.acc+=self.get_memint(data)
defopcode_3(self,data):
"""测试累加器内容指令"""
ifself.acc<0:
self.pc=data
defopcode_4(self,data):
"""位移指令"""
x,y=int(math.floor(data/10)),int(data%10)
foriinrange(0,x):
self.acc=(self.acc*10)%10000
foriinrange(0,y):
self.acc=int(math.floor(self.acc/10))
defopcode_5(self,data):
"""输出指令"""
self.output.append(self.mem[data])
defopcode_6(self,data):
"""存储指令"""
self.mem[data]=self.pad(self.acc)
defopcode_7(self,data):
"""减法指令"""
self.acc-=self.get_memint(data)
defopcode_8(self,data):
"""无条件跳转指令"""
self.pc=data
defopcode_9(self,data):
"""终止,复位指令"""
self.reset()
defrun(self,pc=None):
"""这段代码一直执行到遇到终止/复位指令为止."""
ifpc:
self.pc=pc
self.running=True
whileself.running:
self.process()
print"Output:\n%s"%'\n'.join(self.output)
self.init_output()if__name__=='__main__':
c=Cardiac()
c.read_deck('deck1.txt')
try:
c.run()
except:
print"IR:%s\nPC:%s\nOutput:%s\n"%(c.ir,c.pc,'\n'.join(c.output))
raise
这段是上面提到的,能在Mixin中使用的代码,我重构过后,代码如下:
classMemory(object):
"""本类实现仿真器的虚拟内存空间的各种功能"""
definit_mem(self):
"""用空白字符串清除Cardiac系统内存中的所有数据"""
self.mem=[''foriinrange(0,100)]
self.mem[0]='001'#:启动Cardiac系统.
defget_memint(self,data):
"""由于我们是以字符串形式(*string*based)保存内存数据的,要仿真Cardiac,就要将字符串转化成整数.如果是其他存储形式的内存,如mmap,可以根据需要重写本函数."""
returnint(self.mem[data])
defpad(self,data,length=3):
"""在数字前面补0"""
orig=int(data)
padding='0'*length
data='%s%s'%(padding,abs(data))
iforig<0:
return'-'+data[-length:]
returndata[-length:]
classIO(object):
"""本类实现仿真器的I/O功能.Toenablealternatemethodsofinputandoutput,swapthis."""
definit_reader(self):
"""初始化reader."""
self.reader=[]#:此变量在类初始化后,可以用来读取输入的数据.
definit_output(self):
"""初始化诸如:deck/paper/printer/teletype/之类的输出功能..."""
self.output=[]
defread_deck(self,fname):
"""将指令读到reader中."""
self.reader=[s.rstrip('\n')forsinopen(fname,'r').readlines()]
self.reader.reverse()
defformat_output(self):
"""格式化虚拟I/O设备的输出(output)"""
return'\n'.join(self.output)
defget_input(self):
"""获取IO的输入(input),也就是说用reader读取数据,代替原来的raw_input()."""
try:
returnself.reader.pop()
exceptIndexError:
#如果reader遇到文件结束标志(EOF)就用raw_input()代替reader.
returnraw_input('INP:')[:3]
defstdout(self,data):
self.output.append(data)
classCPU(object):
"""本类模拟cardiacCPU."""
def__init__(self):
self.init_cpu()
self.reset()
try:
self.init_mem()
exceptAttributeError:
raiseNotImplementedError('YouneedtoMixinamemory-enabledclass.')
try:
self.init_reader()
self.init_output()
exceptAttributeError:
raiseNotImplementedError('YouneedtoMixinaIO-enabledclass.')
defreset(self):
"""用默认值重置CPU的寄存器"""
self.pc=0#:指令指针
self.ir=0#:指令寄存器
self.acc=0#:累加器
self.running=False#:仿真器的运行状态?
definit_cpu(self):
"""本函数自动在哈希表中创建指令集.这样我们就可以使用case/select方式调用指令,同时保持代码简洁.当然,在process()中使用getattr然后用try/except捕捉异常也是可以的,但是代码看起来就没那么简洁了."""
self.__opcodes={}
classes=[self.__class__]#:获取全部类,包含基类.
whileclasses:
cls=classes.pop()#把堆栈中的类弹出来
ifcls.__bases__:#判断有没有基类
classes=classes+list(cls.__bases__)
fornameindir(cls):#遍历名称.
ifname[:7]=='opcode_':#只需要把指令读出来即可try:
opcode=int(name[7:])
exceptValueError:
raiseNameError('Opcodesmustbenumeric,invalidopcode:%s'%name[7:])
self.__opcodes.update({opcode:getattr(self,'opcode_%s'%opcode)})
deffetch(self):
"""根据指令指针(programpointer)从内存中读取指令,然后指令指针加1."""
self.ir=self.get_memint(self.pc)
self.pc+=1
defprocess(self):
"""处理当前指令,只处理一条.默认情况下是在循环代码中调用(runningloop),也可以自己写代码,以单步调试方式调用,或者利用time.sleep()降低执行速度.在TK/GTK/Qt/curses做的界面的线程中调用本函数也是可以的."""
self.fetch()
opcode,data=int(math.floor(self.ir/100)),self.ir%100
self.__opcodes[opcode](data)
defopcode_0(self,data):
"""输入指令"""
self.mem[data]=self.get_input()
defopcode_1(self,data):
"""清除累加器指令"""
self.acc=self.get_memint(data)
defopcode_2(self,data):
"""加法指令"""
self.acc+=self.get_memint(data)
defopcode_3(self,data):
"""测试累加器内容指令"""
ifself.acc<0:
self.pc=data
defopcode_4(self,data):
"""位移指令"""
x,y=int(math.floor(data/10)),int(data%10)
foriinrange(0,x):
self.acc=(self.acc*10)%10000
foriinrange(0,y):
self.acc=int(math.floor(self.acc/10))
defopcode_5(self,data):
"""输出指令"""
self.stdout(self.mem[data])
defopcode_6(self,data):
"""存储指令"""
self.mem[data]=self.pad(self.acc)
defopcode_7(self,data):
"""减法指令"""
self.acc-=self.get_memint(data)
defopcode_8(self,data):
"""无条件跳转指令"""
self.pc=data
defopcode_9(self,data):
"""停止/复位指令"""
self.reset()
defrun(self,pc=None):
"""这段代码会一直运行,直到遇到halt/reset指令才停止."""
ifpc:
self.pc=pc
self.running=True
whileself.running:
self.process()
print"Output:\n%s"%self.format_output()
self.init_output()
classCardiac(CPU,Memory,IO):
passif__name__=='__main__':
c=Cardiac()
c.read_deck('deck1.txt')
try:
c.run()
except:
print"IR:%s\nPC:%s\nOutput:%s\n"%(c.ir,c.pc,c.format_output())
raise
大家可以从DevelopingUpwards:CARDIAC:TheCardboardComputer中找到本文使用的deck1.txt.
希望本文能启发大家,怎么去设计基于类的模块,插拔性强(pluggable)的Paython代码,以及如何开发CPU仿真器. 至于本文CPU用到的汇编编译器(assembler),会在下一篇文章中教大家.
这段是上面提到的,能在Mixin中使用的代码,我重构过后,代码如下:
classMemory(object):
"""本类实现仿真器的虚拟内存空间的各种功能"""
definit_mem(self):
"""用空白字符串清除Cardiac系统内存中的所有数据"""
self.mem=[''foriinrange(0,100)]
self.mem[0]='001'#:启动Cardiac系统.
defget_memint(self,data):
"""由于我们是以字符串形式(*string*based)保存内存数据的,要仿真Cardiac,就要将字符串转化成整数.如果是其他存储形式的内存,如mmap,可以根据需要重写本函数."""
returnint(self.mem[data])
defpad(self,data,length=3):
"""在数字前面补0"""
orig=int(data)
padding='0'*length
data='%s%s'%(padding,abs(data))
iforig<0:
return'-'+data[-length:]
returndata[-length:]
classIO(object):
"""本类实现仿真器的I/O功能.Toenablealternatemethodsofinputandoutput,swapthis."""
definit_reader(self):
"""初始化reader."""
self.reader=[]#:此变量在类初始化后,可以用来读取输入的数据.
definit_output(self):
"""初始化诸如:deck/paper/printer/teletype/之类的输出功能..."""
self.output=[]
defread_deck(self,fname):
"""将指令读到reader中."""
self.reader=[s.rstrip('\n')forsinopen(fname,'r').readlines()]
self.reader.reverse()
defformat_output(self):
"""格式化虚拟I/O设备的输出(output)"""
return'\n'.join(self.output)
defget_input(self):
"""获取IO的输入(input),也就是说用reader读取数据,代替原来的raw_input()."""
try:
returnself.reader.pop()
exceptIndexError:
#如果reader遇到文件结束标志(EOF)就用raw_input()代替reader.
returnraw_input('INP:')[:3]
defstdout(self,data):
self.output.append(data)
classCPU(object):
"""本类模拟cardiacCPU."""
def__init__(self):
self.init_cpu()
self.reset()
try:
self.init_mem()
exceptAttributeError:
raiseNotImplementedError('YouneedtoMixinamemory-enabledclass.')
try:
self.init_reader()
self.init_output()
exceptAttributeError:
raiseNotImplementedError('YouneedtoMixinaIO-enabledclass.')
defreset(self):
"""用默认值重置CPU的寄存器"""
self.pc=0#:指令指针
self.ir=0#:指令寄存器
self.acc=0#:累加器
self.running=False#:仿真器的运行状态?
definit_cpu(self):
"""本函数自动在哈希表中创建指令集.这样我们就可以使用case/select方式调用指令,同时保持代码简洁.当然,在process()中使用getattr然后用try/except捕捉异常也是可以的,但是代码看起来就没那么简洁了."""
self.__opcodes={}
classes=[self.__class__]#:获取全部类,包含基类.
whileclasses:
cls=classes.pop()#把堆栈中的类弹出来
ifcls.__bases__:#判断有没有基类
classes=classes+list(cls.__bases__)
fornameindir(cls):#遍历名称.
ifname[:7]=='opcode_':#只需要把指令读出来即可try:
opcode=int(name[7:])
exceptValueError:
raiseNameError('Opcodesmustbenumeric,invalidopcode:%s'%name[7:])
self.__opcodes.update({opcode:getattr(self,'opcode_%s'%opcode)})
deffetch(self):
"""根据指令指针(programpointer)从内存中读取指令,然后指令指针加1."""
self.ir=self.get_memint(self.pc)
self.pc+=1
defprocess(self):
"""处理当前指令,只处理一条.默认情况下是在循环代码中调用(runningloop),也可以自己写代码,以单步调试方式调用,或者利用time.sleep()降低执行速度.在TK/GTK/Qt/curses做的界面的线程中调用本函数也是可以的."""
self.fetch()
opcode,data=int(math.floor(self.ir/100)),self.ir%100
self.__opcodes[opcode](data)
defopcode_0(self,data):
"""输入指令"""
self.mem[data]=self.get_input()
defopcode_1(self,data):
"""清除累加器指令"""
self.acc=self.get_memint(data)
defopcode_2(self,data):
"""加法指令"""
self.acc+=self.get_memint(data)
defopcode_3(self,data):
"""测试累加器内容指令"""
ifself.acc<0:
self.pc=data
defopcode_4(self,data):
"""位移指令"""
x,y=int(math.floor(data/10)),int(data%10)
foriinrange(0,x):
self.acc=(self.acc*10)%10000
foriinrange(0,y):
self.acc=int(math.floor(self.acc/10))
defopcode_5(self,data):
"""输出指令"""
self.stdout(self.mem[data])
defopcode_6(self,data):
"""存储指令"""
self.mem[data]=self.pad(self.acc)
defopcode_7(self,data):
"""减法指令"""
self.acc-=self.get_memint(data)
defopcode_8(self,data):
"""无条件跳转指令"""
self.pc=data
defopcode_9(self,data):
"""停止/复位指令"""
self.reset()
defrun(self,pc=None):
"""这段代码会一直运行,直到遇到halt/reset指令才停止."""
ifpc:
self.pc=pc
self.running=True
whileself.running:
self.process()
print"Output:\n%s"%self.format_output()
self.init_output()
classCardiac(CPU,Memory,IO):
passif__name__=='__main__':
c=Cardiac()
c.read_deck('deck1.txt')
try:
c.run()
except:
print"IR:%s\nPC:%s\nOutput:%s\n"%(c.ir,c.pc,c.format_output())
raise
大家可以从DevelopingUpwards:CARDIAC:TheCardboardComputer中找到本文使用的deck1.txt的代码,我用的是从1计数到10的那个例子.