Skip to content
Nicholas Prado edited this page Sep 13, 2012 · 2 revisions

Up wiki heirarchy; Across to Ram; Across to Driver

The cpu is the core of the simpletron, as in your computer. It demands data from ram, whether an instruction or value, processes it, and stores or prints the result. It does so via the VNeuman Team's fetch execute loop, asking for the next memory value pointed at and reacting to its instruction code, before finally incrementing the instruction counter.

Most of the instruction set architecture is embodied in the static values and constructor for the Cpu. Most of the static constants are the op codes, albiet divided by 100. This stems from the expectation of cutting the uppermost digits of the fetched instruction apart from the address pointed to, the right two digits. So, READ is 10 rather than 1000. This permits the use of a hash (or switch, if Python featured one) for executing to the instruction. The others reflect the specifications of word size and so on. The value halfword is used to split the instructions in fillRegisters().

word = 1000
halfWord = 100 # word / 10, so mod & divide separate hi/lo order correctly
posWordLim = 9999
negWordLim = -9999
READ = 10
WRITE = 11
LOAD = 20
STORE = 21
ADD = 30
SUBTRACT = 31
DIVIDE = 32
MULTIPLY = 33
MODULUS = 34
BRANCH = 40
BRANCHNEG = 41
BRANCHZERO = 42
HALT = 43

I used Deitel's 'assembly' naming scheme to keep with the specification. Were I to, say, implement float and integer math operations, I would probably distinguish them with separate names like ADD_I and ADD_F. Further, I've used the word/halfword with Deitel's initial values. It is possible to make the Simpletron receive a word size and derive the rest of those constants. (PosWordLim = word * 10 - 1.) [Just noticed that word is an obsolete value. Used to use it as posWordLim, which was inept, hence the change.]

The constructor handles the mutable elements for Cpu: its registers. These are the program counter, accumulator, and instruction register. The Program (aka instruction) Counter tracks which instruction to request next from Ram. The accumulator receives and stages exported values to external devices. So, when performing a subtraction, make sure to load the correct value, or the result may be negative when it shouldn't.

The opCode and opAddr are just the separated values that the instruction register received from Ram and held apart as a convenience. OpVal represents a further convenience. Because the opAddr-ess is always a directed value, Cpu asks for the value at the address specified. (Even when less appropriate like the unary HALT, or BRANCHNEG when the acc isn't.) Mem is the Cpu's access to Ram, for getting and setting cells. Verbose enables the verbose mode, so that execution calls explain() as well.

def __init__( self, startPC, ramPtr, outputDesirablility ) :
	self.running = True
	self.pc = startPC # program counter
	self.acc = 0 # accumulator
	self.ir = 0 # instruction register
	self.opCode = 0 # instruction in the IR
	self.opAddr = 0 # address pointed to in IR
	self.opVal = 0 # value retrieved from opAddr
	self.mem = ramPtr
	self.verbose = outputDesirablility

Fetch(), of course, begins the cycle. It prompts Ram to examine the validity of the PC's value. Filling the registers when vouchsafed, beginning with the instruction register. The PC may hold an illegitimate value if the last BRANCH provided an inappropriate address or execution were about to overflow Ram's limits. In that case, the Cpu has to halt, but provides a core dump, for later study as to how this happened.

def fetch( self ): # vetted 11 12 7
	' get next, get indirected value '
	if self.mem.exceedAddrBound( self.pc ) :
		Cpu.fail_coreDump( self, "Address " + str( self.pc ) + " out of range" )
	else :
		Cpu.fillRegisters( self )

As implied, fillRegister_S() is a bit of a misnomer as opCode & opAddr aren't separate registers so much as a software equivalent of reading the values from the high and low order bits of the fetched instruction. (Without performing the math for each use.) OpCode receives the ir/halfword, ie the digits above 100. OpAddr receives ir%halfword, so it receives the digits less than 100. I check whether opAddr exceeds the valid bounds, but that isn't likely. Thereafter it retrieves the directed value and increments the program counter.

Incrementing it after the execution (or ahead of the initial fetch), would complicate values for compilers. In that case, Branches would point at memory values one less than the goal. This seemed a needless complication to offload for no improved convenience. As noted, retrieving the opVal_ue at the directed address could be offloaded to only instructions that involve the value, unlike HALT and WRITE. Whereas these reads would be expensive in a real cpu (pipeline or no, the value wouldn't be available for 10 instructions after this request), Simpletron ignores these physical simulations for our convenience.

def fillRegisters( self ) :
	self.ir = self.mem.getFrom( self.pc )
	self.opCode = self.ir / Cpu.halfWord # separate high order
	self.opAddr = self.ir % Cpu.halfWord # separate low order
	if self.mem.exceedAddrBound( self.opAddr ) :
		Cpu.fail_coreDump( "Address " + str( self.opAddr ) + " out of range" )
	else:
		self.opVal = self.mem.getFrom( self.opAddr )
		# this is a convenience, it may not be used if cpu writes to that location, or a goto
		self.pc += 1

python execute vs java execute.

flourish (explain() )

Clone this wiki locally