#!/usr/bin/python

# a small easy ATMEL AVR EMULATOR :-)))
# this version reads an assembler file (= output of lgc) and
# interprets it.

#; comment
#          ldi  R17, $E9   : 2
#WGLOOP0:  ldi  R18, $EF   : 2
#WGLOOP1:  dec  R18        : 1
#          brne WGLOOP1    : == 0: 1, sonst 2
#          dec  R17        : 1
#          brne WGLOOP0    : == 0: 1, sonst 2


import sys
import getopt
import re

def usage( ret=0 ):
  print "  Usage: simu2.py -f asmfile.asm"
  print "         -h : help"
  print "         -d : debug output"
  print "         -l : print read program"
  sys.exit( ret )

try:
  opts, args = getopt.getopt( sys.argv[1:], "ldhf:" )
except getopt.GetoptError, msg:
  print " ERROR:",msg
  usage(1)
  
opts = dict( opts )
if (opts.has_key( '-h' ))  or (not opts.has_key('-f')):
  usage(0)

debug       = 0
if opts.has_key('-d'): 
  debug = 1

listdebug   = 0
if opts.has_key('-l'): 
  listdebug = 1
  
asmfilename = opts['-f']

# ----------------------------------------------------------

def getCleanProgramLine( line ):
  "strip junk and comments from line"
  semicolon = line.find( ';' )
  if semicolon > -1:
    line = line[0:semicolon]
  line = line.strip()
  return line

def nibble2byte( nibble ):
  nibble = nibble.upper()
  if nibble.isalpha():
    o = (ord(nibble) - ord( 'A' )) + 10
  else:
    o = int( nibble )
  return o
  
def hex2byte( hex ):
  H = hex[0]
  L = hex[1]
  return 16*nibble2byte( H ) + nibble2byte( L )
  
def getByteValue( s ):
  if (len(s) == 3) and (s[0] == '$'): # hex code1
    return hex2byte( s[1:] )
  if (len(s) == 4) and (s[0:1] == '0x'): # hex code2
    return hex2byte( s[2:] )
  return int( s )      

# ----------------------------------------------------------
if debug:
  print "STEP1: reading the file"

re_withLabel = re.compile("^\s*([a-z0-9]+):\s+(.*)", re.IGNORECASE )
program = []
line_number = 0
jumpLabels = {}
jumpLabelsInv = {}

program.append( '; START (line 0, never be seen)' )
f = open( asmfilename, 'r' )
for line in f:
  line = line.strip()
  line_number += 1       # the interpreter later ignores comment lines / empty lines
  m = re_withLabel.search( line )
  if m != None:
    jumpLabels[ m.group(1) ]     = line_number
    jumpLabelsInv[ line_number ] = m.group(1)
    program.append( m.group(2) )
    if debug:
      print "%d : LABEL='%s', CMD='%s'" % ( line_number, m.group(1), m.group(2) )
  else:
    program.append( line )
    if debug:
      print "%d : CMD='%s'" % ( line_number, line, )
f.close()
if listdebug:
  print
  print "Program in my memory:"
  for i in range( len(program) ):
    label = jumpLabelsInv.get( i, '' )
    print " %10s  %d : %s" % (label, i, getCleanProgramLine( program[i]) )
  print "-"
  print "Jump-Labels:", jumpLabels
  print

# ----------------------------------------------------------
if debug:
  print "STEP2: resetting the CPU"
registers = {}
flags = {}
flags['z'] = 0
program_pointer = 1  # starts in line 1
clock_counter   = 0  # num clocks used by the cpu

# ----------------------------------------------------------
if debug:
  print "STEP3: interpreting the program"

    
def emulate():
  global program, registers, clock_counter, flags, program_pointer, jumpLabels

  re_ldi  = re.compile("\s*ldi\s*(r[0-9]+)\s*,\s*(.*)", re.IGNORECASE)
  re_dec  = re.compile("\s*dec\s*(r[0-9]+)", re.IGNORECASE)
  re_brne = re.compile("\s*brne\s*(.*)"    , re.IGNORECASE)

  line = program[ program_pointer ]
  line = getCleanProgramLine( line )
  if debug:
    print "'%s'" % (line, )

  identified = 0
    
  if (len(line) == 0):
    program_pointer += 1   # empty line, or only a comment
    identified = 1
  else:
    m = re_ldi.search( line )
    if m:
      reg = m.group(1).upper()
      val = m.group(2)
      val = getByteValue( val )
      registers[ reg ] = val
      clock_counter   += 1
      program_pointer += 1
      identified = 1
    else:      
       m = re_dec.search( line )
       if m:
         reg = m.group(1).upper()
         registers[ reg ] -= 1
         flags['z'] = 0
         if (registers[ reg ] == 0):
           flags['z'] = 1
         clock_counter   += 1
         program_pointer += 1
         identified = 1
       else:
          if (line.upper() == "NOP"):
            clock_counter   += 1
            program_pointer += 1
            identified = 1
          else:            
             m = re_brne.search( line )
             if m:
               target = m.group(1)
               target_line = jumpLabels.get( target, None )
               if target_line == None:
                 print "Error: unknown label in line %d : '%s'" % (program_pointer, line)
                 sys.exit(1)
               
               if (flags['z'] == 0):  # not equal
                 clock_counter   += 2
                 program_pointer = target_line  # branch
               else:
                 clock_counter += 1
                 program_pointer += 1
               identified = 1

  if identified == 0:
    print "Error: unknown cmd in line %d : '%s'" % (program_pointer, line)
    sys.exit(1)
              
  for reg in registers:
    if registers[reg] == -1:
      registers[reg] = 255

def dumpRegs():
  global registers, program_pointer, flags
  print "PC:", program_pointer, "  Z:",flags['z'], "  ", 
  regs = registers.keys()
  for r in regs:
    print r, registers[r], "  ",
  print
    

while ( 1 <= program_pointer <= (len(program)-1) ):
  if debug:
    dumpRegs()
  emulate()
  if debug:
    dumpRegs()
    print

dumpRegs()
print "clock_counter  :", clock_counter
