Home Up
Home Teaching Glossary ARM Processors Supplements Prof issues About

####  Simulator for a simple CPU (sARM.py) - Version V1.1 of 3 April 2014

####  (c) Alan Clements 2014

####  License: For non-commercial use

####  http://alanclements.org/sarmassembler.html

####  This program was written and tested using Notepad and Python 3.3 (with IDLE) on a PC system running Windows 8.1

####  This program takes a text file in .txt format and executes the code line-by-line

####  The simulator displays the register contents (eight registers)

####  In this version there is only a small memory and stack

####  The simulated CPU  provide RISC, CISC and stack processor instructions because it is designed to help students experiment with instruction sets

####  Author: Alan Clements

####  Email address: clementscomputerorganization AT gmail DOT com

####  This program expects a notepad-compatible source file with the extension .txt

####  If you answer n to the request to turn of single-step, an instruction is executed at each carriage return

####  Register contents are displayed in both decimal and hexadecimal values. THe PC, stack, and 8 memory locations are displayed as well as the instruction


import sys

import random

random.seed()   #initialise random number generator


####This version uses a predefined source file located C:\Users\Alan\Desktop\assembtest.txt. Other users will have to change this.


##Splash header

print('sARM simulator: A simple ARM style simulator ')

print('This simlates RISC CISC and stack-based code ')

print('sARM is not intended as tool for writing serious assembly programs but as a means of introducing assembly language ')

print('(c) Alan Clements 2014')

print('')


Ask = input("Do you wish to use the default file? Type 'Yes' or 'No' ")        #Use either pre-defined file or provide a default source file

nada = ('No','NO','no','n','N')


if Ask in nada:    

   file = open(input('Input file to assemble '), 'r')

else:

   file = open("C:\\Users\Alan\\Desktop\\assembtest.txt", 'r')                #Open the existing sample file

prog = file.readlines()                                                        #prog is the original source file as text. We have to process this to remove spaces and resolve labels

file.close


# This list defines all opcodes

all_ops  = {'ADD', 'ADD_M','M_ADD','AND','BEQ','B','BGT','BL','BLT','BNE','BRA','BSR','DCB','CLR','CMP','DEC','DCBZ','END','EOR','EQU','IN','INC',

           'JMP','LDR','LSL','LSR','MIN','MAX','MLA','MMP', 'MOV','MUL','NAM','NOT','NOP','OR','OUT',

           'PRINT','PUSH','PULL','RBEQ','RND','RTL', 'RTS','SEQ','S_MUL','SNE','STOP','STR','SUB','S_ADD','S_SUB','S_DUP','S_SWAP','TRAP'}

class_d  = {'END'  : 2,   'EQU':3}

class_0  = {'NOP'  : 10,  'RTS': 11,  'STOP': 12, 'RTL':  13}

class_1  = {'CLR'  : 21,  'DEC': 23,  'INC':  22, 'NOT':  24, 'RND':    27, 'IN':    28, 'OUT':   29}

class_1a = {'B'    : 31,  'BL' : 30,  'BRA':  31, 'BEQ':  32, 'BNE':    33, 'BSR':   39, 'BGT':  303, 'BLT': 304}

class_1b = {'DCBZ' : 38}

class_2a = {'MOV'  : 41,  'CMP': 42,  'LSL':  43, 'LSR':  44, 'ADD_M':  48, 'M_ADD': 49}

class_2b = {'LDR'  : 51,  'STR': 52,  'MMP':  59}

class_2c = {'JMP'  : 501}

class_3  = {'ADD'  : 61,  'AND': 63,  'EOR':  65, 'MUL':  66, 'OR':     64, 'SUB':   62, 'MLA': 67}

class_3a = {'MIN'  : 601, 'MAX': 602, 'SEQ': 610, 'SNE': 611}

class_3b = {'RBEQ' : 610}

class_4  = {'PUSH' : 71,  'PULL': 72}

class_4a = {'S_ADD': 81,  'S_SUB':82, 'S_MUL':83, 'S_DUP':84, 'S_SWAP': 85}

class_5  = {'TRAP' : 91}

source   = []                                      #Here's where the source code will go

labeltable = {}                                    #This is for the symbol/label table

data_memory = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]    #data memory (it's small)

reg = [0,0,0,0,0,0,0,0]                            #register fle

stack = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]          #16 location stack


# these functions are used to extract one or more operands from an op-code

def operand1(instruction):             #get one operand in a register

    reg_name = instruction[1]

   reg_num = int(reg_name[1:])

   return reg_num


def operand2(instruction):             #get two operands: register and register/literal

   if len(instruction) < 3:           #check that we have two operands

       print('Too few operands in 3-operand instruction at', old_PC, 'instruction', instruction)

       sys.exit()                     #Exit on insufficent operands    

   L = False

   d = int(instruction[1][1:])        #Get the destation - first elelent - strip R convert to integer

    s_name = instruction[2]

   if s_name[0] == 'R':

       s = int(s_name[1:])

   if s_name[0] == '#':

       L = True

       if s_name[1:3] =='0B':         #check for binary integer

          s = int((s_name[1:]),2)

       elif s_name[1:3] =='0X':       #check for hexadeximal integer

          s = int((s_name[1:]),16)

       elif s_name[1:].isdigit():

          s = int(s_name[1:])

       elif s_name[1] == '-':

          s = -int(s_name[2:])           

       else:

            s = symboltable.get(s_name[1:],-1)

            if s == -1:                    #if label not valid, display error message and stop

               print('Label error: Label',target,'in line',opcode,'is not in the symbol table')

               sys.exit()

       if L and (s > 255):

           print('Literal range error. Program halted at PC =',PC-1)

           sys.exit()

   return d,s,L


def operand2a(instruction):             #get 2 operands: register and memory address for LDR and STR instructions

   error = False                       #

    reg_name = instruction[1]           # read first operand

    d = int(reg_name[1:])               #d is the register number of the first operand

   s_name = instruction[2]             #get the second operand

   if s_name[0] == '[' and s_name[1] == 'R' and s_name[3] == ']':  #ensure that operand 2 is of the form [R0]

       s = int(s_name[2])              #if correct form get the register number

   else:                               #if not correct form, print message and stop

       error = True

   if error or s > 7:

       print('Address mode error in LDR or STR')

       sys.exit()

   return d,s,L


def operand3(instruction):              #get 3 operands: d, s1, s2 L is true for a literaloperand

   if len(instruction) < 4:            #check that we have three operands

       print('Too few operands in 3-operand instruction at', old_PC, 'instruction', instruction)

       sys.exit()                      #Exit on insufficent operands

   L = False

   d  = int (instruction[1][1:])       #get destination register number

   s1 = int (instruction[2][1:])       #get source 1 register number

   s2_name = instruction[3]            #get source 2 name to test for r0 or #12 or #-12 or #Label  

   if s2_name[0] == 'R':

      s2 = int (s2_name[1:])           #if it's a register, get the number

   if s2_name[0] == '#':               #if it's a literal, sort out integer, negative, lable

       L = True

       if s2_name[1:3] =='0B':         #check for binary integer

          s2 = int((s2_name[1:]),2)

       elif s2_name[1:3] =='0X':       #check for hexadeximal integer

          s2 = int((s2_name[1:]),16)

       elif s2_name[1:].isdigit():     #if digit, strip off # and convert to integer

          s2 = int(s2_name[1:])

       elif s2_name[1] == '-':         #if negative, strip off # and -, convert to integer and negate

          s2 = -int(s2_name[2:])           

       else:

          s2 = symboltable.get(s2_name[1:],-1) #if none of the above, look up in the symbol table

          if s2 == -1:                    #if label not valid, display error message and stop

             print('Label error: Label',target,'in line',opcode,'is not in the symbol table')

             sys.exit()           

          if s2 > 255:

             sys.exit()

   return d,s1,s2,L


#here we provide some stack opeations

#note that the program is inconsistant - some instructions are executed in-line and some as functions

def PUSH (SP,destination,L):            #define some stack operations

   SP = SP - 1

   if not L:

       stack[SP] = reg[destination]    #push register

   else:

       stack[SP] = destination         #push literal

   return SP


def PULL (SP,a):

   reg[a] = stack[SP]

   SP = SP + 1

   return SP


def StackAdd(SP):

   p = stack[SP]

   SP = SP + 1

   q = stack[SP]

   stack[SP] = p + q

   return SP


def StackSub(SP):

   p = stack[SP]

   SP = SP + 1

   q = stack[SP]

   stack[SP] = p - q

   return SP


def StackMul(SP):

   p = stack[SP]

   SP = SP + 1

   q = stack[SP]

   stack[SP] = p * q

   return SP


def StackDup(SP):

   p = stack[SP]

   SP = SP - 1

   stack[SP] = p

   return SP


def StackSwap(SP):

   p = stack[SP]

   q = stack[SP+1]

   stack[SP] = q

   stack[SP+1] = p

   return SP


def convert(num_val):

   print('num_value',num_val)

   if num_val.startswith('0X'):

        return int(num_val,16)

   elif num_val.startswith("0B"):

        return int(num_val,2)

   else:

        num_val = symboltable.get(num_val,-1)    #find PC value of the label and jump

        if num_val == -1:                             #

           print('Label error: Label',target,'in line',opcode,'is not in the symbol table')

           sys.exit()        

        return int(num_val)


def display():                                    #display machine status

   inst1 = ' '.join(inst)                        #make a temporary string of the instruction for printing

   if not single_step: print()                   #print blank line but not in sinrungle step mode

   print('PC ={0:3}'.format(old_PC),'Registers', '%3s'%reg[0],'%3s'%reg[1],'%3s'%reg[2],'%3s'%reg[3],'%3s'%reg[4],'%3s'%reg[5],'%3s'%reg[6],'%3s'%reg[7],

         '{0:25}'.format(inst1),'Z =',Z,'N =',N,'C =',C,'V =',V,'SP =',SP, 'LR =',LR,

         'Memory',data_memory[0:8],'Stack =',stack[SP:-1])

   print('Registers in hexadecimal', ' '.join([hex(i) for i in reg]))

   return


############################################################################################################

# This is entry point to the code                                                                          #

# First process the text file containing the assembly language                                             #

# We generate a new source file with comments etc removed and interpret that                               #

############################################################################################################


#first clean up the source file                   #here we remove blank lines and comment lines

#begin by prefixing labels with $ to make them identifiable in later processing

for line in range(0, len(prog)):                  #read all lines

   codeline = prog[line]

   codeline = codeline.upper()                   #convert to upper case

   while ',' in codeline:                        #remove commas        

       codeline = codeline.replace(',',' ')      #convert commas to spaces

   while '  ' in codeline:                       #remove multiple spaces

       codeline = codeline.replace('  ',' ')

   if codeline[0].isalpha():                     #if first element is a character then prepend a $ marker

      codeline  = '$' + codeline

    prog[line] = codeline

prog = [l.strip() for l in prog if l.strip()]     #remove blank lines prog is now the partically tidied file


prog1 = []                                        #create new text file without blank lines and cooment lines

for line in range(0, len(prog)):                  #scan each line and remove those beginning with ;

   codeline = prog[line]

   if codeline[0]  != ';':

       prog1.append(codeline)

   if codeline[0]  == ';':

       continue

for line in range(0, len(prog1)):

   codeline = prog1[line]

   for word in range(0, len(codeline)):          #now remove any comment fields

       token = codeline[word]                    #read a token from the command

       if token.startswith(';'):                 #if it's a comment

          codeline = codeline[0:word]            #then just keep previous fields

          prog1[line] = codeline  

          break                                  #stop scanning when ";" found


                                                 #look for repeated labels       

duplicates = {}

for line in range(0, len(prog1)):                 #scan the code line by line

   codeline = prog1[line].split()                #convert string to list of tokens

   if codeline[0].startswith('$'):               #look for a label that starts with '$'          

       if codeline[0] in duplicates:             #If it's already in the list we have a duplicate

         print('ERROR - second instance of label',codeline[0],'found at line',line)

         sys.exit()                              #And stop

       else:

         duplicates.update({codeline[0]:line})   #If label not repeated add to the table

  


symboltable = {}                                  #create a symbol table with labels

prog2 = []                                        #remove equate directives and put data in symbol table

for line in range (0, len(prog1)):

   equate = False

   codeline = prog1[line].split()                #codeline is the current line as a sequence of lists                    

   for word in range(0,len(codeline)):

       if codeline[word] == 'EQU':               #check for an equate directive

           equate = True

           label = codeline[0]                   #get the label (first item)

           if codeline[2].isdigit():             #check whether the operand is a numeric value or another label

              value = int(codeline[2])           #if it's a numeric value, capture it

           else:

              value = symboltable.get(codeline[2]) #else look look up the label in the symbol table

           symboltable.update({label[1:]:value}) #update the symbol table (remove $ from label)

   if not equate:

       prog2.append(codeline)                    #if this is not an equate line, then add it to the final source code

for line in range(0, len(prog2)):                 #look for labels and add them to the symboltable

   inst = prog2[line]

   if inst[0].startswith('$'):       

      newlabel = inst[0].replace('$','')         #rememver to delete the $ that we've used as a label marker     

      if newlabel in symboltable:                #test for multiply defined label

          print('Label clash at line',line,'Progream terminated') #provide error message and go home

          sys.exit()                             #terminate on error

      symboltable.update({newlabel:line})        #update the symbol table

      

print ('Symbol table ')                           #display the symbol table

for labels,values in symboltable.items():

   print(labels, values)

print()


listing = []                                      #Create and print a listing file with no blanks, and no labels

for i in range (0,len(prog2)):

   this_line = prog2[i]                          #get a ine

   if this_line[-1] == '@BREAK':                 #is the rightmost element a break command?

       this_line.pop()                           #if is is then drop if off the list

   if this_line[0][0] == '$':                    #does the line start with a label?

       listing.append(this_line[1:])             #if so, copy the line without the label

   else:

       listing.append(this_line)

   print('Line', '%3s'%i,'   ',' '.join(listing[i]))

# At this stage we're good to go. We have a program in prog2

###########################################################################################################################

#                                            Main loop - read and execute the code                                        #

###########################################################################################################################


single_step = True                               #Determines whether we execute line by line or run continuously - initally assume single step mode

if input('Enter "y" to turn off single step mode ') == 'y': #ask if we want to turn single step off

   single_step = False

b_lab = False                                    #turn off (initially) trace on label mode

run = True                                       #Execution continues while run is true

cycle = 0                                        #number of instructions executed - we are recording the instruction count

trace = False                                    #trace flag: true to start trace mode and false to turn it off

first_time = True                                #This flag supresses printing the first line

Z,N,C,V = 0,0,0,0                                #Status flags: Zero, negative, carry, overflow ***NOT YET FULLY IMPLEMENTED***

PC = 0                                           #program counter

SP = 15                                          #initialize stack pointer - stack frows to lower addresses

LR = 0                                           #link register for use by branch and link instructions

reg_history = []

r0,r1,r2,r3,r4,r5,r6,r7 =[],[],[],[],[],[],[],[] #Define the NAMED register set. THIS FUNCTION IS NOT YET USED. IT WAS INTENDED TO HOLD PRESERVED VALUES


while run:                                       #MAIN LOOP until Run is false, or STOP command, or error

   old_PC = PC                                  #save current PC for later display

   inst = prog2[PC]                             #read the next instruction to be executed NOTE: inst is the instruction, opcode is the instruction without any label

   PC = PC + 1


   if inst[0].startswith('$'):                  #get opcode which is first or second element

      opcode = inst[1:]                         #second element if we have a lable

   else:

      opcode = inst[0:]                         #first element if we don't have a label

   if opcode[0] not in all_ops:                 #check for valid op=code

      print ('Ilegal opcode error')             #if not in the list then stop and exit

      print ('Operation',opcode[0],'in line',opcode,'is not in the instruction set')

      sys.exit()                                #exit here


   inst_class = class_0.get(opcode[0],0)        #look for entry in class 0

   if inst_class == 10:                         #NOP

      pass                                      #Do nothing for NOP

   

   if inst_class == 11:                         #RTS

      PC = stack[SP]                            #Pull PC off the stack

       SP = SP + 1                               #increment stack pointer


   if inst_class == 13:                         #RTL (return using link register)

      PC = LR                                   #Copy link register to PC to return

    

   if inst_class == 12:                         #look for STOP

      run = False                               #clear the run flag to halt execution

      print('STOP execution at PC =',old_PC,'Total number of instructions',cycle)     

      break                                     #terminate execution messageand leave the execution loop

    

   if (class_1.get(opcode[0])) == 21:           #test for CLR (CLR r0 clears register, CLR 4 clears memory, CLR Lable clears memory)

       clr_operand = opcode[1]                  #get the operand which is r0, 123, or label

       if clr_operand[0] == 'R':                #if the operand begins with R, extract the register number and clear it

           reg[int(clr_operand[1])] = 0

       elif  clr_operand[0].isdigit():          #now look for numeric operand

           data_memory[int(clr_operand)] = 0    #clear the memory

       elif clr_operand[0].isalpha():

           d = symboltable.get(clr_operand)     #if not numeric then try the symbol table

           data_memory[d] = 0                   #clear the memory            


   if (class_1.get(opcode[0])) == 27:           #test for RND (generate random number in range 0 to 255)

       reg[int(opcode[1][1:])] = random.randint(0,255)  #increment the register


   if (class_1.get(opcode[0])) == 22:           #test for INC (increment register)

       d = int(opcode[1][1:])                   #get the register address       

       reg[d] = reg[d] + 1                      #increment the register


   if (class_1.get(opcode[0])) == 23:           #test for DEC (increment register)

       d = int(opcode[1][1:])                   #get the register address       

       reg[d] = reg[d] - 1                      #increment the register


   if (class_1.get(opcode[0])) == 24:           #test for NOT (Boolean invert)  ~~~@@@TEST@@@~~~

       d = inst[1]                              #get the register address

       d = int(d[1:])

       print ('    X =',d, bin(d))

       reg[d] = ~reg[d]&0xFF                    #complement the register  

       print ('NOT X =',d, bin(d))


   if (class_1.get(opcode[0])) == 28:           #test for IN (read char into specified register)

       d = int(opcode[1][1:])                   #get the register address

       reg[d] = int(input('Enter number into register '))


   if (class_1.get(opcode[0])) == 29:           #test for OUT (print char in register)

       d = int(opcode[1][1:])                   #get the register address

       print ("Contents of register ",d, " are: ",reg[d])


   if (class_1a.get(opcode[0])) == 30:          #test for BL (BL 123, or BL NEXT)

         target = opcode[1]                     #get the operand (integer or label)

         LR = PC                                #save return address in link register

         if (target.isdigit()):                 #see if the opeand is numeric

             PC = int(target)                   #if target is an integer than load PC

         else:                                  #otherwise, we need to look up label value

             PC = symboltable.get(target)       #find PC value of the label and jump to it


   if (class_1a.get(opcode[0])) == 31:          #test for BRA (BRA 123, or BRA NEXT)

         target = opcode[1]                     #get the operand (integer or label)

         if (target.isdigit()):                 #see if the opeand is numeric

             PC = int(target)                   #if target is an integer than load PC

         else:                                  #otherwise, we need to look up label value

             PC = symboltable.get(target,-1)    #find PC value of the label and jump to it

             if PC == -1:                       #if label not valid, display error message and stop

                print('Label error: Label',target,'in line',opcode,'is not in the symbol table')

                sys.exit()


   if (class_1a.get(opcode[0])) == 39:          #test for BSR

         SP = SP - 1                            #push return address

         stack[SP] = PC

         target = opcode[1]                     #get the operand (int or label)

         if (target.isdigit()):

             PC = int(target)                   #if target is an integer than load PC

         else:

             PC = symboltable.get(target,-1)    #find PC value of the label and jump

             if PC == -1:                       #if label not valid, display error message and stop

                print('Label error: Label',target,'in line',opcode,'is not in the symbol table')

                sys.exit()                            


   if (class_1a.get(opcode[0])) == 32:          #test for BEQ (e.g., BEQ 123, or BEQ NEXT)

         target = opcode[1]                     #get the operand (integer or label)

         if (target.isdigit()):                 #see if the opeand is numeric

             if Z == 1:                         #test Z-bit

                PC = int(target)                #if target is an integer than load PC

         else:                                  #otherwise, we need to look up label value

             if Z == 1:                         #test Z-bit

                PC = symboltable.get(target,-1) #find PC value of the label and jump to it

                if PC == -1:                    #if label not valid, display error message and stop

                    print('Label error: Label',target,'in line',opcode,'is not in the symbol table')

                    sys.exit()     


   if (class_1a.get(opcode[0])) == 33:          #test for BNE (e.g., BNE 123, or BNE NEXT)

         target = opcode[1]                     #get the operand (integer or label)

         if (target.isdigit()):                 #see if the opeand is numeric

             if Z != 1:                         #test Z-bit

                PC = int(target)                #if target is an integer than load PC

         else:                                  #otherwise, we need to look up label value

             if Z != 1:                         #test Z-bit

                PC = symboltable.get(target,-1) #find PC value of the label and jump to it

                if PC == -1:                    #if label not valid, display error message and stop

                   print('Label error: Label',target,'in line',opcode,'is not in the symbol table')

                   sys.exit()


   if (class_1a.get(opcode[0])) == 303:         #test for BGT (e.g., BGT 123, or BGT NEXT)

         target = opcode[1]                     #get the operand (integer or label)

         if (target.isdigit()):                 #see if the opeand is numeric

             if N != 1:                         #test N-bit (not negative to take branch)

                PC = int(target)                #if target is an integer than load PC

         else:                                  #otherwise, we need to look up label value

             if N != 1:                         #test N-bit

                PC = symboltable.get(target,-1) #find PC value of the label and jump to it

                if PC == -1:                    #if label not valid, display error message and stop

                   print('Label error: Label',target,'in line',opcode,'is not in the symbol table')

                   sys.exit()


   if (class_1a.get(opcode[0])) == 304:         #test for BLT (e.g., BLT 123, or BLT NEXT)

         target = opcode[1]                     #get the operand (integer or label)

         if (target.isdigit()):                 #see if the opeand is numeric

             if N == 1:                         #test N-bit (not negative to take branch)

                PC = int(target)                #if target is an integer than load PC

         else:                                  #otherwise, we need to look up label value

             if N == 1:                         #test N-bit

                PC = symboltable.get(target,-1) #find PC value of the label and jump to it

                if PC == -1:                    #if label not valid, display error message and stop

                   print('Label error: Label',target,'in line',opcode,'is not in the symbol table')

                   sys.exit()


   if (class_3b.get(opcode[0])) == 610:         #test for RBEQ (e.g., RBEQ r0,r1,Label i.e., branch to Label on r0=r1)

         print('opcode', opcode)

         if reg[int(opcode[1][1:])] == reg[int(opcode[2][1:])]: #compare the two registers

              if (opcode[3].isdigit()):         #see if the operand is numeric i.e., the register number

                  PC = int(opcode[3])

              else:

                  target = symboltable.get(opcode[3],-1)

                  if target == -1:              #if label not valid, display error message and stop

                     print('Label error: Label',target,'in line',opcode,'is not in the symbol table')

                     sys.exit()

                  PC = int(target)       


   if (class_2c.get(opcode[0])) == 501:         #test for JMP (e.g., JMP [r0])

         target = opcode[1]                     #get the operand [[r0]

         if (target[2].isdigit()):              #see if the opeand is numeric i.e., the register number

             PC = reg[int(target[2])]           #If all is well, then do the jump - load the PC with the new address


   if (class_1b.get(opcode[0])) == 38:          #test for DCBZ (e.g., DCBZ r0,#10 or DCBZ r0,NEXT)

         target = opcode[2]                     #this is a loop branch that ends on 0 count or Z = 1

         counter = opcode[1]

         d = int(counter[1:])

         if not(target.isdigit()):

             target = symboltable.get(target,-1)

             if target == -1:                    #if label not valid, display error message and stop

                print('Label error: Label',target,'in line',opcode,'is not in the symbol table')

                sys.exit()

         reg[d] = reg[d] - 1

         if reg[d] !=0 and Z ==0:

            PC = int(target)


   if (class_2a.get(opcode[0])) == 41:          #test for MOV PC,LR (special case of MOV)

         if opcode[1] == 'PC' and opcode[2] == 'LR':

              PC = LR                            #if we find MOV PC,LR load PC with return address and leave                                                 

    if (class_2a.get(opcode[0])) == 41:          #test for MOV (e.g., MOV r1,r2 or MOV r3,#12)      

         p,q,L = operand2(opcode)

         if L:

             reg[p] = q

         else:

             reg[p] = reg[q]

    if (class_2a.get(opcode[0])) == 42:          #test for CMP       

         p,q,L = operand2(opcode)

         if L:

             a, b = reg[p], q

         else:

             a, b = reg[p], reg[q]

         Z,N = 0,1                              #set Z-bit to 0 and N-bit to 1 initially

         if (a - b) == 0:                       # do comparison

            Z,N = 1,0                           #set Z = 1 and N = 0 if same

         if (a - b) > 0:

            N = 0                               #if  a > b then clean N as not negative


   if (class_2a.get(opcode[0])) == 43:          #test for LSL (e.g., LSL r1,r2 or LSL r3,#12)

         p,q,L = operand2(opcode)

         if L:

               reg[p] = reg[p]  << q

         else:

               reg[p] = reg[p] << reg[q]


   if (class_2a.get(opcode[0])) == 44:          #test for LSR  

         p,q,L = operand2(opcode)

         if L:

              reg[p] = reg[p]  >> q

         else:

              reg[p] = regp[p] >> reg[q]


   if (class_2b.get(opcode[0])) == 51:          #test for LDR (load register register indirect)

          p,q,L = operand2a(opcode)

          reg[p] = data_memory[reg[q]]

       

   if (class_2b.get(opcode[0])) == 52:          #test for STR (store register register indirect)

          p,q,L = operand2a(opcode)

          data_memory[reg[q]] = reg[p]


   if (class_2b.get(opcode[0])) == 59:          #test for MMP move [rs]+ to [rd]+

         d1 = inst[1]                           #this is a memory-to-memory move with auto postincrementing

         d2 = int(d1[1:])  

         s1 = inst[2]

         s2 = int(s1[1:])

         data_memory[reg[d2]] = data_memory[reg[s2]]

         reg[d2] = reg[d2] + 1

         reg[s2] = reg[s2] + 1

 

   if (class_3.get(opcode[0])) == 61:           #test for ADD r0,r1,r2 (d = s1 + s2)

         p,q,r,L = operand3(opcode)

         a = reg[q]

         if L:                                  #get register value or literal for source 2

             b = r

         else:

             b = reg[r]

         if (a > 127) or (b > 128) or a < -128 or b < -128:

            print ('Source range error in addition at PC =',PC)

            sys.exit()

         Z,N,C,V = 0,0,0,0

         s = a + b

         if s == 0:

             Z = 1

         if s < 0:

            N = 1

         if s > 127:

            C = 1

         if s < -128:

            C = 1

         if (((a > 0) and (b > 0) and (s > 7)) or ((a < 0) and (b < 0) and (s > 8))):

            V = 1

         reg[p] = s


   if (class_3.get(opcode[0])) == 67:           #test for MLA r0,r1,r2 (d = d + s1 * s2)

         p,q,r,L = operand3(opcode)

         s = reg[p]

         a = reg[q]

         if L:                                  #get register value or literal for source 2

             b = r

         else:

             b = reg[r]

         if (a > 127) or (b > 128) or a < -128 or b < -128:

            print ('Source range error in addition at PC =',PC)

            sys.exit()

         Z,N,C,V = 0,0,0,0

         s = s + a * b

         if s == 0:

             Z = 1

         if s < 0:

            N = 1

         if s > 127:

            C = 1

         if s < -128:

            C = 1

         reg[p] = s


   if (class_3.get(opcode[0])) == 62:           #test for SUB

         Z,N = 0,0

         p,q,r,L = operand3(opcode)

         if L:

              reg[p] = reg[q] - r

         else:

              reg[p] = reg[q] - reg[r]

         if reg[p] == 0:

             Z = 1

         if reg[p] < 0:

             N = 1


   if (class_3.get(opcode[0])) == 66:           #test for MUL

         Z,N = 0,0

         print('multiply')

         p,q,r,L = operand3(opcode)

         if L:

              reg[p] = reg[q] * r

         else:

              reg[p] = reg[q] * reg[r]

         if reg[p] == 0:

             Z = 1

         if reg[p] < 0:

             N = 1


   if (class_3.get(opcode[0])) == 63:           #test for AND

        p,q,r,L = operand3(opcode)

        if L:

              reg[p] = reg[q] & r

        else:

              reg[p] = reg[q] & reg[r]

              if reg[p] == 0: Z = 1


   if (class_3.get(opcode[0])) == 64:           #test for OR

        p,q,r,L = operand3(opcode)

        if L:

              reg[p] = reg[q] | r

        else:

              reg[p] = reg[q] | reg[r]

              if reg[p] == 0: Z = 1             


   if (class_3.get(opcode[0])) == 65:           #test for EOR

        p,q,r,L = operand3(opcode)

        if L:

              reg[p] = reg[q] ^ r               

        else:

              reg[p] = reg[q] ^ reg[r]

              if reg[p] == 0: Z = 1             


   if (class_3a.get(opcode[0])) == 601:         #test for MIN MIN r0,r1,r2 returns the minimum of r1 and r2 in r0

        p,q,r,L = operand3(opcode)

        if L:

              if reg[q] <= r:

                 reg[p] = reg[q]

              else:

                 reg[p] = r                   

        else:

              if reg[q] <= reg[r]:

                 reg[p] = reg[q]

              else:

                 reg[p] = reg[r]  


   if (class_3a.get(opcode[0])) == 602:         #test for MAX

        p,q,r,L = operand3(opcode)

        if L:

              if reg[q] >= r:

                 reg[p] = reg[q]

              else:

                 reg[p] = r                   

        else:

              if reg[q] >= reg[r]:

                 reg[p] = reg[q]

              else:

                 reg[p] = reg[r]  


   if (class_3a.get(opcode[0])) == 610:         #test for SEQ  (SEQ r0,r1,r2 sets r0 to 11111111 if r1 = r2 or 00000000 otherwise

        p,q,r,L = operand3(opcode)

        if L:

              if reg[q] == r:

                 reg[p] = 0b11111111

              else:

                 reg[p] = 0                   

        else:

              if reg[q] >= reg[r]:

                 reg[p] = 0b11111111

              else:

                 reg[p] = 0  


   if (class_3a.get(opcode[0])) == 611:         #test for SNE  (SNE r0,r1,r2 sets r0 to 11111111 if r1 != r2 or 00000000 otherwise

        p,q,r,L = operand3(opcode)

        if L:

              if reg[q] == r:

                 reg[p] = 0b11111111

              else:

                 reg[p] = 0                   

        else:

              if reg[q] >= reg[r]:

                 reg[p] = 0b11111111

              else:

                 reg[p] = 0  


   if (class_2a.get(opcode[0])) == 48:          #ADD_M add to memory      

         p,q,L = operand2a(opcode)              #Format ADD_M r0,[r1]

         data_memory[reg[q]] = data_memory[reg[q]] + reg[p]



   if (class_2a.get(opcode[0])) == 49:          #M_ADD add  memory to register     

         p,q,L = operand2a(opcode)              #Format M_ADD r0,[r1]

         reg[p] = data_memory[reg[q]] + reg[p]

################################################################################################

#### stack operations checked here                                                           ###

################################################################################################             

   if (class_4.get(opcode[0]))  == 71:          #Test for PUSH

       L = False                                #Set not literal mode

       if opcode[1][0] =='R':                   #If PUSH r0 then do it

          SP = PUSH(SP,operand1(opcode),L)

       else:                                    #If not register operand, assume literal

          L = True                              #Tell function to expect a literal

          literal = opcode[1][1:]

          print('literal',literal)

          SP = PUSH(SP,convert(literal),L)      #Push literal value

   

   if (class_4.get(opcode[0])) == 72:  SP = PULL(SP,operand1(opcode))

   if (class_4a.get(opcode[0])) == 81: SP = StackAdd(SP)

   if (class_4a.get(opcode[0])) == 82: SP = StackSub(SP)

   if (class_4a.get(opcode[0])) == 83: SP = StackMul(SP)

   if (class_4a.get(opcode[0])) == 84: SP = StackDup(SP)

   if (class_4a.get(opcode[0])) == 85: SP = StackSwap(SP)

#################################################################################################    

### THis section deals with the trap instruction which is an operating system call            ###

### Normally in a real system the OS would implement trap functions. Here they are build in   ###

#################################################################################################

   if (class_5.get(opcode[0]))  == 91:          #TRAP instruction

       if len(opcode) == 4: pass                #Look for three operand TRAP (not yet implemented)

       if len(opcode) == 3:                     #look for two operand TRAP

            if opcode[1] == 'TRACE':

                single_step = False             #Turn off single step if we are using trace

                if opcode[2] == 'ON':  trace = True

                if opcode[2] == 'OFF': trace = False


            if opcode[1] == 'LABELS':

                single_step = False             #Turn off single step if we are using trace mode

                if opcode[2] == 'ON':  b_lab = True

                if opcode[2] == 'OFF': b_lab = False


            if opcode[1] == 'REG' and opcode[2] == 'CHANGE':

               pass                             #This function not yet implemented


       if len(opcode) == 2:                     #Look for one operand TRAP (can be used to selectively print data)

           if opcode[1] == 'SP':

               print('Current SP =', SP, 'Current stack =', stack[SP:-1])

           if opcode[1] == 'MEM':

               print('Current memory =', data_memory)

           if opcode[1] == 'STATUS':

               print('Processor status: PC=',PC,'SP=',SP,'LR=',LR,'Z bit=',Z,'N bit=', N,'Cycles =',cycle)

           if opcode[1] == 'REG':

               print('Registers =', reg)

               

   if (class_5.get(opcode[0]))  != 91:            #are we executing a TRAP (don't increment cycle counter for a TRAP instruction)?

       cycle = cycle + 1                          #if not then increment cycle count



X##################################################################################################################################

# This section deals with single-steping, the trace mode, and user breakpoints


   if trace:                                    #if trace is on then print the registers etc

        if inst[-1] == 'BREAK':                 #check for break point (last word of an instruction)

           inst.pop()                           #drop the BREAK tag for printing             

        inst1 = ' '.join(inst)                  #make a temporary string for printing

        print('PC = {0:3}'.format(PC), '  Registers',reg, '  Memory',data_memory[0:7], '   Opcode  {0:20}'.format(inst1),'Z',Z,'C',C,'N',N,'V',V,'SP',SP, 'Link',LR,'  Current stack',stack[SP:-1])


   if single_step:                              #Single step mode - display after instruction and wait for enter

        input()

        display()         

                                                  

   if inst[-1] == 'BREAK' and not (trace or single_step):  #check for break point (last word of an instruction) and not in trace mode

        inst.pop()                              #drop the BREAK tag for printing

        inst1 = ' '.join(inst)                  #make a temporary string for printing

        print('Breakpoint at next PC =',PC, '   Opcode  {0:20}'.format(inst1),'Instructions executed',cycle)

        print('PC =',old_PC, 'Registers ',reg, 'Memory',data_memory[0:8],'Z=',Z,'N=',N,'SP=',SP, 'Link=',LR,'  Current stack',stack[SP:-1])

        print()


   if b_lab:    #print labelled line

        if inst[0].startswith('$'):

           # print(inst[1:], 'pc =', PC)

            display()

    if inst[-1].endswith('WATCH'):              #look for register watch of the form r4=12,watch     

        token1 = inst[-1]                      #get the register assignment r4=12.watch    

        token2 = token1[0:-6]                  #extract r4=12

        r_num = int(token2[1])                 #get number of register to watch

        r_val = int(token2[3:])                #get value being monitored

        r_act = reg[r_num]                     #get actual contents of monitored register

        if r_act == r_val:                     #check for the value monitored

                print('Register',r_num,'monitored for',r_val)

                display()

       

       

#################################################################################################################################

#################################################################################################################################

   

#Notes on instruction types

       # sARM operations:                             'ADD','AND','BEQ','B','BL','BNE','CMP','EOR','LDR','LSL','LSR','MOV',

       #                                              'MUL','NOT,'NOP','OR','RTL','STOP','STR','SUB'


       # Experimental and stack operations:           'BSR','PUSH','PULL','RBEQ','RTS','S_ADD','S_SUB','S_MUL','S_DUP','S_SWAP'


       # General operations not implemented by sARM:  'CLR','DEC','DCBZ','IN','INC','MIN','MAX','MLA','OUT','PRINT','SEQ','SNE','TRAP'


       # CISC memory operations:                      'ADD_M','M_ADD','MMP'







#############################################################################################################################

#SAMPLE CODE - I USED IN TESTING   - this is meaningless code     

#         mov r0,#0

# nop

#xx  add r1,r1,r0 r1=3.watch

#   add r0,r0,#1

#   bra xx

#

#

#; PUT RANDOM NUMBERS IN 4 CONSECUTIVE MEMORY LOCATIOND

#     mov  r0,#4        ;4 locations to fill

#     clr  r1           ;pointer

#Loop rnd  r2           ;put a random value in r2

#     str  r2,[r1]      ;store in memory

#     add  r1,r1,#1     ;increment pointer

#     sub  r0,r0,#1     ;decrement count

#     bne  Loop         ;repeat until all done

#     bra copy

#xxxx equ 2

#; SEARCH MEMORY FOR LARGEST VALUE

#     mov  r0,#4        ;4 locations to read

#     mov  r3,#0        ;dummy biggest

#     clr  r1           ;pointer

#xxx1 ldr  r2,[r1]      ;read from  memory break

#     cmp  r3,r2        ;compare new value  break

#     bgt  xxx2         ;skip on old greater than new

#     mov  r3,r2        ;record new large value

#xxx2 add  r1,r1,#1     ;increment pointer break

#     sub  r0,r0,#1     ;decrement count

#     bne  xxx1         ;repeat until all done

#xxyxx equ 4

#;  COPY LIST AND REVERSE IT WITH TWO POINTERS

#     mov  r0,#4        ;locations to copy

#Src  equ  0            ;source location

#Dest equ  4            ;destination location

#     mov  r1,#Src      ;load source pointer

#     mov  r2,#Dest     ;load destination pointer

#     add  r2,r2,#3     ;make destination point to end

#Looop  ldr  r3,[r1]      ;REPEAT read element

#    str  r3,[r2]      ;       store in memory

#     add  r1,r1,#1     ;       increment source pointer

#     sub  r2,r2,#1     ;       decrement source pointer

#     sub  r0,r0,#1     ;       decrement count

#     bne  Looop          ;repeat until all done     

#     stop


This is a python program that takes a .txt file (assembly language program) and executes it - line by line - showing registers.

It is still a work in progress and not all functions are complete. THE user manual is not complete.

It has an extended instruction set for experimental purposes - but executes the sARM’s instructions.

You can download the Python source file here as a  .txt file. You will have to change the extension to .py to run this file.

SARM.txt

Alternately, to download it as a.py file, right click here and save the file. SARM.py

The user manual for this CPU simulator is here.

This is a test SARM.py second test