Small CPU Emulator (LC3) in Python












0














I wrote a small emulator for fun. Full code @ bottom of post, available on GitHub here.



Design choices:




  • modeling 16 bit little endian memory — opted for ctypes and array-like access via __getitem__


  • Enum library




    • Opcodes - convenient to access: the order of the opcodes in the enum matches the opcode's numeric value when interpreted as an integer

    • Condition flags - convenient to access: named, so I can self.registers.cond = condition_flags.z
      where the right hand side is the enum.




  • Some classes:




    • CPU (class lc3)


      • Registers

      • Memory








Questions:




  • How could I get started adding unit tests?

  • Is there a better choice than using an IntEnum for the opcodes?

  • How might I organize the code better? In particular, I dislike having dump_state (a diagnostic printing function), and all of my instruction implementations (eg op_and_impl) right next to each other in the lc3 class.

  • How else might I organize this mapping of opcodes to implementation functions?


# first attempt
if opcode == opcodes.op_add:
self.op_add_impl(instruction)
elif opcode == opcodes.op_and:
self.op_and_impl(instruction)
elif opcode == opcodes.op_not:
self.op_not_impl(instruction)
... truncated https://github.com/ianklatzco/lc3/blob/7bace0a30353d4b1d4c720eddca07c1828f7c3e0/lc3.py#L303

# second attempt
opcode_dict = {
opcodes.op_add: self.op_add_impl,
opcodes.op_and: self.op_and_impl,
opcodes.op_not: self.op_not_impl,
... truncated https://github.com/ianklatzco/lc3/blob/67353ebb50367430a7d2921d701ea92aa2f0968e/lc3.py#L304
try:
opcode_dict[opcode](instruction)
except KeyError:
raise UnimpError("invalid opcode")



  • How could I address this inconsistency between accessing GPRs (general purpose registers) and PC, condition register?


class registers():
def __init__(self):
self.gprs = (c_int16 * 8)()
self.pc = (c_uint16)()
self.cond = (c_uint16)()
# I instantiated the gprs as a ctypes "array" instead of a single c_uint16.
# To access:
# registers.gprs[0]
# This is convenient when I need to access a particular register, and I have the index handy from a decoded instruction.
# registers.pc.value
# The .value is annoying.






Full code



# usage: python3 lc3.py ./second.obj

# This project inspired by https://justinmeiners.github.io/lc3-vm/

# There was a lot of copy-pasting lines of code for things like
# pulling pcoffset9 out of an instruction.
# https://justinmeiners.github.io/lc3-vm/#1:14
# ^ talks about a nice compact way to encode instructions using bitfields and
# c++'s templates.
# i am curious if you could do it with python decorators.

# update: i tried this and it was mostly just an excuse to learn decorators, but it
# isn't the right tool. i am curious how else you might do it.

from ctypes import c_uint16, c_int16
from enum import IntEnum
from struct import unpack
from sys import exit, stdin, stdout, argv
from signal import signal, SIGINT
import lc3disas # in same dir

DEBUG = False

class UnimpError(Exception):
pass

def signal_handler(signal, frame):
print("nbye!")
exit()

signal(SIGINT, signal_handler)

# https://stackoverflow.com/a/32031543/1234621
# you're modeling sign-extend behavior in python, since python has infinite
# bit width.
def sext(value, bits):
sign_bit = 1 << (bits - 1)
return (value & (sign_bit - 1)) - (value & sign_bit)

'''
iirc the arch is 16bit little endian.
options: ctypes or just emulate it in pure python.
chose: ctypes
'''
class memory():
def __init__(self):
# ctypes has an array type. this is one way to create instances of it.
self.memory = (c_uint16 * 65536)()

def __getitem__(self, arg):
if (arg > 65535) or (arg < 0):
raise MemoryError("Accessed out valid memory range.")

return self.memory[arg]

def __setitem__(self, location, thing_to_write):
if (location > 65536) or (location < 0):
raise MemoryError("Accessed out valid memory range.")

self.memory[int(location)] = thing_to_write

class registers():
def __init__(self):
self.gprs = (c_int16 * 8)()
self.pc = (c_uint16)()
self.cond = (c_uint16)()

# not actually a class but an enum.
class opcodes(IntEnum):
op_br = 0
op_add = 1
op_ld = 2
op_st = 3
op_jsr = 4
op_and = 5
op_ldr = 6
op_str = 7
op_rti = 8
op_not = 9
op_ldi = 10
op_sti = 11
op_jmp = 12
op_res = 13
op_lea = 14
op_trap = 15

class condition_flags(IntEnum):
p = 0
z = 1
n = 2

class lc3():
def __init__(self, filename):
self.memory = memory()
self.registers = registers()
self.registers.pc.value = 0x3000 # default program starting location
self.read_program_from_file(filename)

def read_program_from_file(self,filename):
with open(filename, 'rb') as f:
_ = f.read(2) # skip the first two byte which specify where code should be mapped
c = f.read() # todo support arbitrary load locations
for count in range(0,len(c), 2):
self.memory[0x3000+count/2] = unpack( '>H', c[count:count+2] )[0]

def update_flags(self, reg):
if self.registers.gprs[reg] == 0:
self.registers.cond = condition_flags.z
if self.registers.gprs[reg] < 0:
self.registers.cond = condition_flags.n
if self.registers.gprs[reg] > 0:
self.registers.cond = condition_flags.p

def dump_state(self):
print("npc: {:04x}".format(self.registers.pc.value))
print("r0: {:05} ".format(self.registers.gprs[0]), end='')
print("r1: {:05} ".format(self.registers.gprs[1]), end='')
print("r2: {:05} ".format(self.registers.gprs[2]), end='')
print("r3: {:05} ".format(self.registers.gprs[3]), end='')
print("r4: {:05} ".format(self.registers.gprs[4]), end='')
print("r5: {:05} ".format(self.registers.gprs[5]), end='')
print("r6: {:05} ".format(self.registers.gprs[6]), end='')
print("r7: {:05} ".format(self.registers.gprs[7]))

print("r0: {:04x} ".format(c_uint16(self.registers.gprs[0]).value), end='')
print("r1: {:04x} ".format(c_uint16(self.registers.gprs[1]).value), end='')
print("r2: {:04x} ".format(c_uint16(self.registers.gprs[2]).value), end='')
print("r3: {:04x} ".format(c_uint16(self.registers.gprs[3]).value), end='')
print("r4: {:04x} ".format(c_uint16(self.registers.gprs[4]).value), end='')
print("r5: {:04x} ".format(c_uint16(self.registers.gprs[5]).value), end='')
print("r6: {:04x} ".format(c_uint16(self.registers.gprs[6]).value), end='')
print("r7: {:04x} ".format(c_uint16(self.registers.gprs[7]).value))

print("cond: {}".format(condition_flags(self.registers.cond.value).name))

def op_add_impl(self, instruction):
sr1 = (instruction >> 6) & 0b111
dr = (instruction >> 9) & 0b111
if ((instruction >> 5) & 0b1) == 0: # reg-reg
sr2 = instruction & 0b111
self.registers.gprs[dr] = self.registers.gprs[sr1] + self.registers.gprs[sr2]
else: # immediate
imm5 = instruction & 0b11111
self.registers.gprs[dr] = self.registers.gprs[sr1] + sext(imm5, 5)
self.update_flags(dr)

def op_and_impl(self, instruction):
sr1 = (instruction >> 6) & 0b111
dr = (instruction >> 9) & 0b111

if ((instruction >> 5) & 0b1) == 0: # reg-reg
sr2 = instruction & 0b111
self.registers.gprs[dr] = self.registers.gprs[sr1] & self.registers.gprs[sr2]
else: # immediate
imm5 = instruction & 0b11111
self.registers.gprs[dr] = self.registers.gprs[sr1] & sext(imm5, 5)

self.update_flags(dr)

def op_not_impl(self, instruction):
sr = (instruction >> 6) & 0b111
dr = (instruction >> 9) & 0b111

self.registers.gprs[dr] = ~ (self.registers.gprs[sr])

self.update_flags(dr)

def op_br_impl(self, instruction):
n = (instruction >> 11) & 1
z = (instruction >> 10) & 1
p = (instruction >> 9) & 1
pc_offset_9 = instruction & 0x1ff

if (n == 1 and self.registers.cond == condition_flags.n) or
(z == 1 and self.registers.cond == condition_flags.z) or
(p == 1 and self.registers.cond == condition_flags.p):
self.registers.pc.value = self.registers.pc.value + sext(pc_offset_9, 9)

# also ret
def op_jmp_impl(self, instruction):
baser = (instruction >> 6) & 0b111

self.registers.pc.value = self.registers.gprs[baser]

def op_jsr_impl(self, instruction):
# no jsrr?
if 0x0400 & instruction == 1: raise UnimpError("JSRR is not implemented.")
pc_offset_11 = instruction & 0x7ff

self.registers.gprs[7] = self.registers.pc.value
self.registers.pc.value = self.registers.pc.value + sext(pc_offset_11, 11)

def op_ld_impl(self, instruction):
dr = (instruction >> 9) & 0b111
pc_offset_9 = instruction & 0x1ff
addr = self.registers.pc.value + sext(pc_offset_9, 9)
self.registers.gprs[dr] = self.memory[addr]
self.update_flags(dr)

def op_ldi_impl(self, instruction):
dr = (instruction >> 9) & 0b111
pc_offset_9 = instruction & 0x1ff
addr = self.registers.pc.value + sext(pc_offset_9, 9)
self.registers.gprs[dr] = self.memory[ self.memory[addr] ]
self.update_flags(dr)

def op_ldr_impl(self, instruction):
dr = (instruction >> 9) & 0b111
baser = (instruction >> 6) & 0b111
pc_offset_6 = instruction & 0x3f

addr = self.registers.gprs[baser] + sext(pc_offset_6, 6)
self.registers.gprs[dr] = self.memory[addr]

self.update_flags(dr)

def op_lea_impl(self, instruction):
dr = (instruction >> 9) & 0b111
pc_offset_9 = instruction & 0x1ff

self.registers.gprs[dr] = self.registers.pc.value + sext(pc_offset_9, 9)
self.update_flags(dr)

def op_st_impl(self, instruction):
dr = (instruction >> 9) & 0b111
pc_offset_9 = instruction & 0x1ff
addr = self.registers.pc.value + sext(pc_offset_9, 9)

self.memory[addr] = self.registers.gprs[dr]

def op_sti_impl(self, instruction):
dr = (instruction >> 9) & 0b111
pc_offset_9 = instruction & 0x1ff
addr = self.registers.pc.value + sext(pc_offset_9, 9)

self.memory[ self.memory[addr] ] = self.registers.gprs[dr]

def op_str_impl(self, instruction):
dr = (instruction >> 9) & 0b111
baser = (instruction >> 6) & 0b111
pc_offset_6 = instruction & 0x3f

addr = self.registers.gprs[baser] + sext(pc_offset_6, 6)
self.memory[addr] = self.registers.gprs[dr]

def op_trap_impl(self, instruction):
trap_vector = instruction & 0xff

if trap_vector == 0x20: # getc
c = stdin.buffer.read(1)[0]
self.registers.gprs[0] = c
return

if trap_vector == 0x21: # out
stdout.buffer.write( bytes( [(self.registers.gprs[0] & 0xff)] ) )
stdout.buffer.flush()
return

if trap_vector == 0x22: # puts
base_addr = self.registers.gprs[0]
index = 0

while (self.memory[base_addr + index]) != 0x00:
nextchar = self.memory[base_addr + index]
stdout.buffer.write( bytes( [nextchar] ) )
index = index + 1

return

if trap_vector == 0x25:
self.dump_state()
exit()

raise ValueError("undefined trap vector {}".format(hex(trap_vector)))

def op_res_impl(self, instruction):
raise UnimpError("unimplemented opcode")
def op_rti_impl(self, instruction):
raise UnimpError("unimplemented opcode")

def start(self):
while True:
# fetch instruction
instruction = self.memory[self.registers.pc.value]

# update PC
self.registers.pc.value = self.registers.pc.value + 1

# decode opcode
opcode = instruction >> 12

if DEBUG:
print("instruction: {}".format(hex(instruction)))
print("disassembly: {}".format(lc3disas.single_ins(self.registers.pc.value, instruction)))
self.dump_state()
input()

opcode_dict =
{
opcodes.op_add: self.op_add_impl,
opcodes.op_and: self.op_and_impl,
opcodes.op_not: self.op_not_impl,
opcodes.op_br: self.op_br_impl,
opcodes.op_jmp: self.op_jmp_impl,
opcodes.op_jsr: self.op_jsr_impl,
opcodes.op_ld: self.op_ld_impl,
opcodes.op_ldi: self.op_ldi_impl,
opcodes.op_ldr: self.op_ldr_impl,
opcodes.op_lea: self.op_lea_impl,
opcodes.op_st: self.op_st_impl,
opcodes.op_sti: self.op_sti_impl,
opcodes.op_str: self.op_str_impl,
opcodes.op_trap:self.op_trap_impl,
opcodes.op_res: self.op_res_impl,
opcodes.op_rti: self.op_rti_impl
}

try:
opcode_dict[opcode](instruction)
except KeyError:
raise UnimpError("invalid opcode")

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

if len(argv) < 2:
print ("usage: python3 lc3.py code.obj")
exit(255)
l = lc3(argv[1])
l.start()








share







New contributor




ian5v is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

























    0














    I wrote a small emulator for fun. Full code @ bottom of post, available on GitHub here.



    Design choices:




    • modeling 16 bit little endian memory — opted for ctypes and array-like access via __getitem__


    • Enum library




      • Opcodes - convenient to access: the order of the opcodes in the enum matches the opcode's numeric value when interpreted as an integer

      • Condition flags - convenient to access: named, so I can self.registers.cond = condition_flags.z
        where the right hand side is the enum.




    • Some classes:




      • CPU (class lc3)


        • Registers

        • Memory








    Questions:




    • How could I get started adding unit tests?

    • Is there a better choice than using an IntEnum for the opcodes?

    • How might I organize the code better? In particular, I dislike having dump_state (a diagnostic printing function), and all of my instruction implementations (eg op_and_impl) right next to each other in the lc3 class.

    • How else might I organize this mapping of opcodes to implementation functions?


    # first attempt
    if opcode == opcodes.op_add:
    self.op_add_impl(instruction)
    elif opcode == opcodes.op_and:
    self.op_and_impl(instruction)
    elif opcode == opcodes.op_not:
    self.op_not_impl(instruction)
    ... truncated https://github.com/ianklatzco/lc3/blob/7bace0a30353d4b1d4c720eddca07c1828f7c3e0/lc3.py#L303

    # second attempt
    opcode_dict = {
    opcodes.op_add: self.op_add_impl,
    opcodes.op_and: self.op_and_impl,
    opcodes.op_not: self.op_not_impl,
    ... truncated https://github.com/ianklatzco/lc3/blob/67353ebb50367430a7d2921d701ea92aa2f0968e/lc3.py#L304
    try:
    opcode_dict[opcode](instruction)
    except KeyError:
    raise UnimpError("invalid opcode")



    • How could I address this inconsistency between accessing GPRs (general purpose registers) and PC, condition register?


    class registers():
    def __init__(self):
    self.gprs = (c_int16 * 8)()
    self.pc = (c_uint16)()
    self.cond = (c_uint16)()
    # I instantiated the gprs as a ctypes "array" instead of a single c_uint16.
    # To access:
    # registers.gprs[0]
    # This is convenient when I need to access a particular register, and I have the index handy from a decoded instruction.
    # registers.pc.value
    # The .value is annoying.






    Full code



    # usage: python3 lc3.py ./second.obj

    # This project inspired by https://justinmeiners.github.io/lc3-vm/

    # There was a lot of copy-pasting lines of code for things like
    # pulling pcoffset9 out of an instruction.
    # https://justinmeiners.github.io/lc3-vm/#1:14
    # ^ talks about a nice compact way to encode instructions using bitfields and
    # c++'s templates.
    # i am curious if you could do it with python decorators.

    # update: i tried this and it was mostly just an excuse to learn decorators, but it
    # isn't the right tool. i am curious how else you might do it.

    from ctypes import c_uint16, c_int16
    from enum import IntEnum
    from struct import unpack
    from sys import exit, stdin, stdout, argv
    from signal import signal, SIGINT
    import lc3disas # in same dir

    DEBUG = False

    class UnimpError(Exception):
    pass

    def signal_handler(signal, frame):
    print("nbye!")
    exit()

    signal(SIGINT, signal_handler)

    # https://stackoverflow.com/a/32031543/1234621
    # you're modeling sign-extend behavior in python, since python has infinite
    # bit width.
    def sext(value, bits):
    sign_bit = 1 << (bits - 1)
    return (value & (sign_bit - 1)) - (value & sign_bit)

    '''
    iirc the arch is 16bit little endian.
    options: ctypes or just emulate it in pure python.
    chose: ctypes
    '''
    class memory():
    def __init__(self):
    # ctypes has an array type. this is one way to create instances of it.
    self.memory = (c_uint16 * 65536)()

    def __getitem__(self, arg):
    if (arg > 65535) or (arg < 0):
    raise MemoryError("Accessed out valid memory range.")

    return self.memory[arg]

    def __setitem__(self, location, thing_to_write):
    if (location > 65536) or (location < 0):
    raise MemoryError("Accessed out valid memory range.")

    self.memory[int(location)] = thing_to_write

    class registers():
    def __init__(self):
    self.gprs = (c_int16 * 8)()
    self.pc = (c_uint16)()
    self.cond = (c_uint16)()

    # not actually a class but an enum.
    class opcodes(IntEnum):
    op_br = 0
    op_add = 1
    op_ld = 2
    op_st = 3
    op_jsr = 4
    op_and = 5
    op_ldr = 6
    op_str = 7
    op_rti = 8
    op_not = 9
    op_ldi = 10
    op_sti = 11
    op_jmp = 12
    op_res = 13
    op_lea = 14
    op_trap = 15

    class condition_flags(IntEnum):
    p = 0
    z = 1
    n = 2

    class lc3():
    def __init__(self, filename):
    self.memory = memory()
    self.registers = registers()
    self.registers.pc.value = 0x3000 # default program starting location
    self.read_program_from_file(filename)

    def read_program_from_file(self,filename):
    with open(filename, 'rb') as f:
    _ = f.read(2) # skip the first two byte which specify where code should be mapped
    c = f.read() # todo support arbitrary load locations
    for count in range(0,len(c), 2):
    self.memory[0x3000+count/2] = unpack( '>H', c[count:count+2] )[0]

    def update_flags(self, reg):
    if self.registers.gprs[reg] == 0:
    self.registers.cond = condition_flags.z
    if self.registers.gprs[reg] < 0:
    self.registers.cond = condition_flags.n
    if self.registers.gprs[reg] > 0:
    self.registers.cond = condition_flags.p

    def dump_state(self):
    print("npc: {:04x}".format(self.registers.pc.value))
    print("r0: {:05} ".format(self.registers.gprs[0]), end='')
    print("r1: {:05} ".format(self.registers.gprs[1]), end='')
    print("r2: {:05} ".format(self.registers.gprs[2]), end='')
    print("r3: {:05} ".format(self.registers.gprs[3]), end='')
    print("r4: {:05} ".format(self.registers.gprs[4]), end='')
    print("r5: {:05} ".format(self.registers.gprs[5]), end='')
    print("r6: {:05} ".format(self.registers.gprs[6]), end='')
    print("r7: {:05} ".format(self.registers.gprs[7]))

    print("r0: {:04x} ".format(c_uint16(self.registers.gprs[0]).value), end='')
    print("r1: {:04x} ".format(c_uint16(self.registers.gprs[1]).value), end='')
    print("r2: {:04x} ".format(c_uint16(self.registers.gprs[2]).value), end='')
    print("r3: {:04x} ".format(c_uint16(self.registers.gprs[3]).value), end='')
    print("r4: {:04x} ".format(c_uint16(self.registers.gprs[4]).value), end='')
    print("r5: {:04x} ".format(c_uint16(self.registers.gprs[5]).value), end='')
    print("r6: {:04x} ".format(c_uint16(self.registers.gprs[6]).value), end='')
    print("r7: {:04x} ".format(c_uint16(self.registers.gprs[7]).value))

    print("cond: {}".format(condition_flags(self.registers.cond.value).name))

    def op_add_impl(self, instruction):
    sr1 = (instruction >> 6) & 0b111
    dr = (instruction >> 9) & 0b111
    if ((instruction >> 5) & 0b1) == 0: # reg-reg
    sr2 = instruction & 0b111
    self.registers.gprs[dr] = self.registers.gprs[sr1] + self.registers.gprs[sr2]
    else: # immediate
    imm5 = instruction & 0b11111
    self.registers.gprs[dr] = self.registers.gprs[sr1] + sext(imm5, 5)
    self.update_flags(dr)

    def op_and_impl(self, instruction):
    sr1 = (instruction >> 6) & 0b111
    dr = (instruction >> 9) & 0b111

    if ((instruction >> 5) & 0b1) == 0: # reg-reg
    sr2 = instruction & 0b111
    self.registers.gprs[dr] = self.registers.gprs[sr1] & self.registers.gprs[sr2]
    else: # immediate
    imm5 = instruction & 0b11111
    self.registers.gprs[dr] = self.registers.gprs[sr1] & sext(imm5, 5)

    self.update_flags(dr)

    def op_not_impl(self, instruction):
    sr = (instruction >> 6) & 0b111
    dr = (instruction >> 9) & 0b111

    self.registers.gprs[dr] = ~ (self.registers.gprs[sr])

    self.update_flags(dr)

    def op_br_impl(self, instruction):
    n = (instruction >> 11) & 1
    z = (instruction >> 10) & 1
    p = (instruction >> 9) & 1
    pc_offset_9 = instruction & 0x1ff

    if (n == 1 and self.registers.cond == condition_flags.n) or
    (z == 1 and self.registers.cond == condition_flags.z) or
    (p == 1 and self.registers.cond == condition_flags.p):
    self.registers.pc.value = self.registers.pc.value + sext(pc_offset_9, 9)

    # also ret
    def op_jmp_impl(self, instruction):
    baser = (instruction >> 6) & 0b111

    self.registers.pc.value = self.registers.gprs[baser]

    def op_jsr_impl(self, instruction):
    # no jsrr?
    if 0x0400 & instruction == 1: raise UnimpError("JSRR is not implemented.")
    pc_offset_11 = instruction & 0x7ff

    self.registers.gprs[7] = self.registers.pc.value
    self.registers.pc.value = self.registers.pc.value + sext(pc_offset_11, 11)

    def op_ld_impl(self, instruction):
    dr = (instruction >> 9) & 0b111
    pc_offset_9 = instruction & 0x1ff
    addr = self.registers.pc.value + sext(pc_offset_9, 9)
    self.registers.gprs[dr] = self.memory[addr]
    self.update_flags(dr)

    def op_ldi_impl(self, instruction):
    dr = (instruction >> 9) & 0b111
    pc_offset_9 = instruction & 0x1ff
    addr = self.registers.pc.value + sext(pc_offset_9, 9)
    self.registers.gprs[dr] = self.memory[ self.memory[addr] ]
    self.update_flags(dr)

    def op_ldr_impl(self, instruction):
    dr = (instruction >> 9) & 0b111
    baser = (instruction >> 6) & 0b111
    pc_offset_6 = instruction & 0x3f

    addr = self.registers.gprs[baser] + sext(pc_offset_6, 6)
    self.registers.gprs[dr] = self.memory[addr]

    self.update_flags(dr)

    def op_lea_impl(self, instruction):
    dr = (instruction >> 9) & 0b111
    pc_offset_9 = instruction & 0x1ff

    self.registers.gprs[dr] = self.registers.pc.value + sext(pc_offset_9, 9)
    self.update_flags(dr)

    def op_st_impl(self, instruction):
    dr = (instruction >> 9) & 0b111
    pc_offset_9 = instruction & 0x1ff
    addr = self.registers.pc.value + sext(pc_offset_9, 9)

    self.memory[addr] = self.registers.gprs[dr]

    def op_sti_impl(self, instruction):
    dr = (instruction >> 9) & 0b111
    pc_offset_9 = instruction & 0x1ff
    addr = self.registers.pc.value + sext(pc_offset_9, 9)

    self.memory[ self.memory[addr] ] = self.registers.gprs[dr]

    def op_str_impl(self, instruction):
    dr = (instruction >> 9) & 0b111
    baser = (instruction >> 6) & 0b111
    pc_offset_6 = instruction & 0x3f

    addr = self.registers.gprs[baser] + sext(pc_offset_6, 6)
    self.memory[addr] = self.registers.gprs[dr]

    def op_trap_impl(self, instruction):
    trap_vector = instruction & 0xff

    if trap_vector == 0x20: # getc
    c = stdin.buffer.read(1)[0]
    self.registers.gprs[0] = c
    return

    if trap_vector == 0x21: # out
    stdout.buffer.write( bytes( [(self.registers.gprs[0] & 0xff)] ) )
    stdout.buffer.flush()
    return

    if trap_vector == 0x22: # puts
    base_addr = self.registers.gprs[0]
    index = 0

    while (self.memory[base_addr + index]) != 0x00:
    nextchar = self.memory[base_addr + index]
    stdout.buffer.write( bytes( [nextchar] ) )
    index = index + 1

    return

    if trap_vector == 0x25:
    self.dump_state()
    exit()

    raise ValueError("undefined trap vector {}".format(hex(trap_vector)))

    def op_res_impl(self, instruction):
    raise UnimpError("unimplemented opcode")
    def op_rti_impl(self, instruction):
    raise UnimpError("unimplemented opcode")

    def start(self):
    while True:
    # fetch instruction
    instruction = self.memory[self.registers.pc.value]

    # update PC
    self.registers.pc.value = self.registers.pc.value + 1

    # decode opcode
    opcode = instruction >> 12

    if DEBUG:
    print("instruction: {}".format(hex(instruction)))
    print("disassembly: {}".format(lc3disas.single_ins(self.registers.pc.value, instruction)))
    self.dump_state()
    input()

    opcode_dict =
    {
    opcodes.op_add: self.op_add_impl,
    opcodes.op_and: self.op_and_impl,
    opcodes.op_not: self.op_not_impl,
    opcodes.op_br: self.op_br_impl,
    opcodes.op_jmp: self.op_jmp_impl,
    opcodes.op_jsr: self.op_jsr_impl,
    opcodes.op_ld: self.op_ld_impl,
    opcodes.op_ldi: self.op_ldi_impl,
    opcodes.op_ldr: self.op_ldr_impl,
    opcodes.op_lea: self.op_lea_impl,
    opcodes.op_st: self.op_st_impl,
    opcodes.op_sti: self.op_sti_impl,
    opcodes.op_str: self.op_str_impl,
    opcodes.op_trap:self.op_trap_impl,
    opcodes.op_res: self.op_res_impl,
    opcodes.op_rti: self.op_rti_impl
    }

    try:
    opcode_dict[opcode](instruction)
    except KeyError:
    raise UnimpError("invalid opcode")

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

    if len(argv) < 2:
    print ("usage: python3 lc3.py code.obj")
    exit(255)
    l = lc3(argv[1])
    l.start()








    share







    New contributor




    ian5v is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.























      0












      0








      0







      I wrote a small emulator for fun. Full code @ bottom of post, available on GitHub here.



      Design choices:




      • modeling 16 bit little endian memory — opted for ctypes and array-like access via __getitem__


      • Enum library




        • Opcodes - convenient to access: the order of the opcodes in the enum matches the opcode's numeric value when interpreted as an integer

        • Condition flags - convenient to access: named, so I can self.registers.cond = condition_flags.z
          where the right hand side is the enum.




      • Some classes:




        • CPU (class lc3)


          • Registers

          • Memory








      Questions:




      • How could I get started adding unit tests?

      • Is there a better choice than using an IntEnum for the opcodes?

      • How might I organize the code better? In particular, I dislike having dump_state (a diagnostic printing function), and all of my instruction implementations (eg op_and_impl) right next to each other in the lc3 class.

      • How else might I organize this mapping of opcodes to implementation functions?


      # first attempt
      if opcode == opcodes.op_add:
      self.op_add_impl(instruction)
      elif opcode == opcodes.op_and:
      self.op_and_impl(instruction)
      elif opcode == opcodes.op_not:
      self.op_not_impl(instruction)
      ... truncated https://github.com/ianklatzco/lc3/blob/7bace0a30353d4b1d4c720eddca07c1828f7c3e0/lc3.py#L303

      # second attempt
      opcode_dict = {
      opcodes.op_add: self.op_add_impl,
      opcodes.op_and: self.op_and_impl,
      opcodes.op_not: self.op_not_impl,
      ... truncated https://github.com/ianklatzco/lc3/blob/67353ebb50367430a7d2921d701ea92aa2f0968e/lc3.py#L304
      try:
      opcode_dict[opcode](instruction)
      except KeyError:
      raise UnimpError("invalid opcode")



      • How could I address this inconsistency between accessing GPRs (general purpose registers) and PC, condition register?


      class registers():
      def __init__(self):
      self.gprs = (c_int16 * 8)()
      self.pc = (c_uint16)()
      self.cond = (c_uint16)()
      # I instantiated the gprs as a ctypes "array" instead of a single c_uint16.
      # To access:
      # registers.gprs[0]
      # This is convenient when I need to access a particular register, and I have the index handy from a decoded instruction.
      # registers.pc.value
      # The .value is annoying.






      Full code



      # usage: python3 lc3.py ./second.obj

      # This project inspired by https://justinmeiners.github.io/lc3-vm/

      # There was a lot of copy-pasting lines of code for things like
      # pulling pcoffset9 out of an instruction.
      # https://justinmeiners.github.io/lc3-vm/#1:14
      # ^ talks about a nice compact way to encode instructions using bitfields and
      # c++'s templates.
      # i am curious if you could do it with python decorators.

      # update: i tried this and it was mostly just an excuse to learn decorators, but it
      # isn't the right tool. i am curious how else you might do it.

      from ctypes import c_uint16, c_int16
      from enum import IntEnum
      from struct import unpack
      from sys import exit, stdin, stdout, argv
      from signal import signal, SIGINT
      import lc3disas # in same dir

      DEBUG = False

      class UnimpError(Exception):
      pass

      def signal_handler(signal, frame):
      print("nbye!")
      exit()

      signal(SIGINT, signal_handler)

      # https://stackoverflow.com/a/32031543/1234621
      # you're modeling sign-extend behavior in python, since python has infinite
      # bit width.
      def sext(value, bits):
      sign_bit = 1 << (bits - 1)
      return (value & (sign_bit - 1)) - (value & sign_bit)

      '''
      iirc the arch is 16bit little endian.
      options: ctypes or just emulate it in pure python.
      chose: ctypes
      '''
      class memory():
      def __init__(self):
      # ctypes has an array type. this is one way to create instances of it.
      self.memory = (c_uint16 * 65536)()

      def __getitem__(self, arg):
      if (arg > 65535) or (arg < 0):
      raise MemoryError("Accessed out valid memory range.")

      return self.memory[arg]

      def __setitem__(self, location, thing_to_write):
      if (location > 65536) or (location < 0):
      raise MemoryError("Accessed out valid memory range.")

      self.memory[int(location)] = thing_to_write

      class registers():
      def __init__(self):
      self.gprs = (c_int16 * 8)()
      self.pc = (c_uint16)()
      self.cond = (c_uint16)()

      # not actually a class but an enum.
      class opcodes(IntEnum):
      op_br = 0
      op_add = 1
      op_ld = 2
      op_st = 3
      op_jsr = 4
      op_and = 5
      op_ldr = 6
      op_str = 7
      op_rti = 8
      op_not = 9
      op_ldi = 10
      op_sti = 11
      op_jmp = 12
      op_res = 13
      op_lea = 14
      op_trap = 15

      class condition_flags(IntEnum):
      p = 0
      z = 1
      n = 2

      class lc3():
      def __init__(self, filename):
      self.memory = memory()
      self.registers = registers()
      self.registers.pc.value = 0x3000 # default program starting location
      self.read_program_from_file(filename)

      def read_program_from_file(self,filename):
      with open(filename, 'rb') as f:
      _ = f.read(2) # skip the first two byte which specify where code should be mapped
      c = f.read() # todo support arbitrary load locations
      for count in range(0,len(c), 2):
      self.memory[0x3000+count/2] = unpack( '>H', c[count:count+2] )[0]

      def update_flags(self, reg):
      if self.registers.gprs[reg] == 0:
      self.registers.cond = condition_flags.z
      if self.registers.gprs[reg] < 0:
      self.registers.cond = condition_flags.n
      if self.registers.gprs[reg] > 0:
      self.registers.cond = condition_flags.p

      def dump_state(self):
      print("npc: {:04x}".format(self.registers.pc.value))
      print("r0: {:05} ".format(self.registers.gprs[0]), end='')
      print("r1: {:05} ".format(self.registers.gprs[1]), end='')
      print("r2: {:05} ".format(self.registers.gprs[2]), end='')
      print("r3: {:05} ".format(self.registers.gprs[3]), end='')
      print("r4: {:05} ".format(self.registers.gprs[4]), end='')
      print("r5: {:05} ".format(self.registers.gprs[5]), end='')
      print("r6: {:05} ".format(self.registers.gprs[6]), end='')
      print("r7: {:05} ".format(self.registers.gprs[7]))

      print("r0: {:04x} ".format(c_uint16(self.registers.gprs[0]).value), end='')
      print("r1: {:04x} ".format(c_uint16(self.registers.gprs[1]).value), end='')
      print("r2: {:04x} ".format(c_uint16(self.registers.gprs[2]).value), end='')
      print("r3: {:04x} ".format(c_uint16(self.registers.gprs[3]).value), end='')
      print("r4: {:04x} ".format(c_uint16(self.registers.gprs[4]).value), end='')
      print("r5: {:04x} ".format(c_uint16(self.registers.gprs[5]).value), end='')
      print("r6: {:04x} ".format(c_uint16(self.registers.gprs[6]).value), end='')
      print("r7: {:04x} ".format(c_uint16(self.registers.gprs[7]).value))

      print("cond: {}".format(condition_flags(self.registers.cond.value).name))

      def op_add_impl(self, instruction):
      sr1 = (instruction >> 6) & 0b111
      dr = (instruction >> 9) & 0b111
      if ((instruction >> 5) & 0b1) == 0: # reg-reg
      sr2 = instruction & 0b111
      self.registers.gprs[dr] = self.registers.gprs[sr1] + self.registers.gprs[sr2]
      else: # immediate
      imm5 = instruction & 0b11111
      self.registers.gprs[dr] = self.registers.gprs[sr1] + sext(imm5, 5)
      self.update_flags(dr)

      def op_and_impl(self, instruction):
      sr1 = (instruction >> 6) & 0b111
      dr = (instruction >> 9) & 0b111

      if ((instruction >> 5) & 0b1) == 0: # reg-reg
      sr2 = instruction & 0b111
      self.registers.gprs[dr] = self.registers.gprs[sr1] & self.registers.gprs[sr2]
      else: # immediate
      imm5 = instruction & 0b11111
      self.registers.gprs[dr] = self.registers.gprs[sr1] & sext(imm5, 5)

      self.update_flags(dr)

      def op_not_impl(self, instruction):
      sr = (instruction >> 6) & 0b111
      dr = (instruction >> 9) & 0b111

      self.registers.gprs[dr] = ~ (self.registers.gprs[sr])

      self.update_flags(dr)

      def op_br_impl(self, instruction):
      n = (instruction >> 11) & 1
      z = (instruction >> 10) & 1
      p = (instruction >> 9) & 1
      pc_offset_9 = instruction & 0x1ff

      if (n == 1 and self.registers.cond == condition_flags.n) or
      (z == 1 and self.registers.cond == condition_flags.z) or
      (p == 1 and self.registers.cond == condition_flags.p):
      self.registers.pc.value = self.registers.pc.value + sext(pc_offset_9, 9)

      # also ret
      def op_jmp_impl(self, instruction):
      baser = (instruction >> 6) & 0b111

      self.registers.pc.value = self.registers.gprs[baser]

      def op_jsr_impl(self, instruction):
      # no jsrr?
      if 0x0400 & instruction == 1: raise UnimpError("JSRR is not implemented.")
      pc_offset_11 = instruction & 0x7ff

      self.registers.gprs[7] = self.registers.pc.value
      self.registers.pc.value = self.registers.pc.value + sext(pc_offset_11, 11)

      def op_ld_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      pc_offset_9 = instruction & 0x1ff
      addr = self.registers.pc.value + sext(pc_offset_9, 9)
      self.registers.gprs[dr] = self.memory[addr]
      self.update_flags(dr)

      def op_ldi_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      pc_offset_9 = instruction & 0x1ff
      addr = self.registers.pc.value + sext(pc_offset_9, 9)
      self.registers.gprs[dr] = self.memory[ self.memory[addr] ]
      self.update_flags(dr)

      def op_ldr_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      baser = (instruction >> 6) & 0b111
      pc_offset_6 = instruction & 0x3f

      addr = self.registers.gprs[baser] + sext(pc_offset_6, 6)
      self.registers.gprs[dr] = self.memory[addr]

      self.update_flags(dr)

      def op_lea_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      pc_offset_9 = instruction & 0x1ff

      self.registers.gprs[dr] = self.registers.pc.value + sext(pc_offset_9, 9)
      self.update_flags(dr)

      def op_st_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      pc_offset_9 = instruction & 0x1ff
      addr = self.registers.pc.value + sext(pc_offset_9, 9)

      self.memory[addr] = self.registers.gprs[dr]

      def op_sti_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      pc_offset_9 = instruction & 0x1ff
      addr = self.registers.pc.value + sext(pc_offset_9, 9)

      self.memory[ self.memory[addr] ] = self.registers.gprs[dr]

      def op_str_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      baser = (instruction >> 6) & 0b111
      pc_offset_6 = instruction & 0x3f

      addr = self.registers.gprs[baser] + sext(pc_offset_6, 6)
      self.memory[addr] = self.registers.gprs[dr]

      def op_trap_impl(self, instruction):
      trap_vector = instruction & 0xff

      if trap_vector == 0x20: # getc
      c = stdin.buffer.read(1)[0]
      self.registers.gprs[0] = c
      return

      if trap_vector == 0x21: # out
      stdout.buffer.write( bytes( [(self.registers.gprs[0] & 0xff)] ) )
      stdout.buffer.flush()
      return

      if trap_vector == 0x22: # puts
      base_addr = self.registers.gprs[0]
      index = 0

      while (self.memory[base_addr + index]) != 0x00:
      nextchar = self.memory[base_addr + index]
      stdout.buffer.write( bytes( [nextchar] ) )
      index = index + 1

      return

      if trap_vector == 0x25:
      self.dump_state()
      exit()

      raise ValueError("undefined trap vector {}".format(hex(trap_vector)))

      def op_res_impl(self, instruction):
      raise UnimpError("unimplemented opcode")
      def op_rti_impl(self, instruction):
      raise UnimpError("unimplemented opcode")

      def start(self):
      while True:
      # fetch instruction
      instruction = self.memory[self.registers.pc.value]

      # update PC
      self.registers.pc.value = self.registers.pc.value + 1

      # decode opcode
      opcode = instruction >> 12

      if DEBUG:
      print("instruction: {}".format(hex(instruction)))
      print("disassembly: {}".format(lc3disas.single_ins(self.registers.pc.value, instruction)))
      self.dump_state()
      input()

      opcode_dict =
      {
      opcodes.op_add: self.op_add_impl,
      opcodes.op_and: self.op_and_impl,
      opcodes.op_not: self.op_not_impl,
      opcodes.op_br: self.op_br_impl,
      opcodes.op_jmp: self.op_jmp_impl,
      opcodes.op_jsr: self.op_jsr_impl,
      opcodes.op_ld: self.op_ld_impl,
      opcodes.op_ldi: self.op_ldi_impl,
      opcodes.op_ldr: self.op_ldr_impl,
      opcodes.op_lea: self.op_lea_impl,
      opcodes.op_st: self.op_st_impl,
      opcodes.op_sti: self.op_sti_impl,
      opcodes.op_str: self.op_str_impl,
      opcodes.op_trap:self.op_trap_impl,
      opcodes.op_res: self.op_res_impl,
      opcodes.op_rti: self.op_rti_impl
      }

      try:
      opcode_dict[opcode](instruction)
      except KeyError:
      raise UnimpError("invalid opcode")

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

      if len(argv) < 2:
      print ("usage: python3 lc3.py code.obj")
      exit(255)
      l = lc3(argv[1])
      l.start()








      share







      New contributor




      ian5v is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.











      I wrote a small emulator for fun. Full code @ bottom of post, available on GitHub here.



      Design choices:




      • modeling 16 bit little endian memory — opted for ctypes and array-like access via __getitem__


      • Enum library




        • Opcodes - convenient to access: the order of the opcodes in the enum matches the opcode's numeric value when interpreted as an integer

        • Condition flags - convenient to access: named, so I can self.registers.cond = condition_flags.z
          where the right hand side is the enum.




      • Some classes:




        • CPU (class lc3)


          • Registers

          • Memory








      Questions:




      • How could I get started adding unit tests?

      • Is there a better choice than using an IntEnum for the opcodes?

      • How might I organize the code better? In particular, I dislike having dump_state (a diagnostic printing function), and all of my instruction implementations (eg op_and_impl) right next to each other in the lc3 class.

      • How else might I organize this mapping of opcodes to implementation functions?


      # first attempt
      if opcode == opcodes.op_add:
      self.op_add_impl(instruction)
      elif opcode == opcodes.op_and:
      self.op_and_impl(instruction)
      elif opcode == opcodes.op_not:
      self.op_not_impl(instruction)
      ... truncated https://github.com/ianklatzco/lc3/blob/7bace0a30353d4b1d4c720eddca07c1828f7c3e0/lc3.py#L303

      # second attempt
      opcode_dict = {
      opcodes.op_add: self.op_add_impl,
      opcodes.op_and: self.op_and_impl,
      opcodes.op_not: self.op_not_impl,
      ... truncated https://github.com/ianklatzco/lc3/blob/67353ebb50367430a7d2921d701ea92aa2f0968e/lc3.py#L304
      try:
      opcode_dict[opcode](instruction)
      except KeyError:
      raise UnimpError("invalid opcode")



      • How could I address this inconsistency between accessing GPRs (general purpose registers) and PC, condition register?


      class registers():
      def __init__(self):
      self.gprs = (c_int16 * 8)()
      self.pc = (c_uint16)()
      self.cond = (c_uint16)()
      # I instantiated the gprs as a ctypes "array" instead of a single c_uint16.
      # To access:
      # registers.gprs[0]
      # This is convenient when I need to access a particular register, and I have the index handy from a decoded instruction.
      # registers.pc.value
      # The .value is annoying.






      Full code



      # usage: python3 lc3.py ./second.obj

      # This project inspired by https://justinmeiners.github.io/lc3-vm/

      # There was a lot of copy-pasting lines of code for things like
      # pulling pcoffset9 out of an instruction.
      # https://justinmeiners.github.io/lc3-vm/#1:14
      # ^ talks about a nice compact way to encode instructions using bitfields and
      # c++'s templates.
      # i am curious if you could do it with python decorators.

      # update: i tried this and it was mostly just an excuse to learn decorators, but it
      # isn't the right tool. i am curious how else you might do it.

      from ctypes import c_uint16, c_int16
      from enum import IntEnum
      from struct import unpack
      from sys import exit, stdin, stdout, argv
      from signal import signal, SIGINT
      import lc3disas # in same dir

      DEBUG = False

      class UnimpError(Exception):
      pass

      def signal_handler(signal, frame):
      print("nbye!")
      exit()

      signal(SIGINT, signal_handler)

      # https://stackoverflow.com/a/32031543/1234621
      # you're modeling sign-extend behavior in python, since python has infinite
      # bit width.
      def sext(value, bits):
      sign_bit = 1 << (bits - 1)
      return (value & (sign_bit - 1)) - (value & sign_bit)

      '''
      iirc the arch is 16bit little endian.
      options: ctypes or just emulate it in pure python.
      chose: ctypes
      '''
      class memory():
      def __init__(self):
      # ctypes has an array type. this is one way to create instances of it.
      self.memory = (c_uint16 * 65536)()

      def __getitem__(self, arg):
      if (arg > 65535) or (arg < 0):
      raise MemoryError("Accessed out valid memory range.")

      return self.memory[arg]

      def __setitem__(self, location, thing_to_write):
      if (location > 65536) or (location < 0):
      raise MemoryError("Accessed out valid memory range.")

      self.memory[int(location)] = thing_to_write

      class registers():
      def __init__(self):
      self.gprs = (c_int16 * 8)()
      self.pc = (c_uint16)()
      self.cond = (c_uint16)()

      # not actually a class but an enum.
      class opcodes(IntEnum):
      op_br = 0
      op_add = 1
      op_ld = 2
      op_st = 3
      op_jsr = 4
      op_and = 5
      op_ldr = 6
      op_str = 7
      op_rti = 8
      op_not = 9
      op_ldi = 10
      op_sti = 11
      op_jmp = 12
      op_res = 13
      op_lea = 14
      op_trap = 15

      class condition_flags(IntEnum):
      p = 0
      z = 1
      n = 2

      class lc3():
      def __init__(self, filename):
      self.memory = memory()
      self.registers = registers()
      self.registers.pc.value = 0x3000 # default program starting location
      self.read_program_from_file(filename)

      def read_program_from_file(self,filename):
      with open(filename, 'rb') as f:
      _ = f.read(2) # skip the first two byte which specify where code should be mapped
      c = f.read() # todo support arbitrary load locations
      for count in range(0,len(c), 2):
      self.memory[0x3000+count/2] = unpack( '>H', c[count:count+2] )[0]

      def update_flags(self, reg):
      if self.registers.gprs[reg] == 0:
      self.registers.cond = condition_flags.z
      if self.registers.gprs[reg] < 0:
      self.registers.cond = condition_flags.n
      if self.registers.gprs[reg] > 0:
      self.registers.cond = condition_flags.p

      def dump_state(self):
      print("npc: {:04x}".format(self.registers.pc.value))
      print("r0: {:05} ".format(self.registers.gprs[0]), end='')
      print("r1: {:05} ".format(self.registers.gprs[1]), end='')
      print("r2: {:05} ".format(self.registers.gprs[2]), end='')
      print("r3: {:05} ".format(self.registers.gprs[3]), end='')
      print("r4: {:05} ".format(self.registers.gprs[4]), end='')
      print("r5: {:05} ".format(self.registers.gprs[5]), end='')
      print("r6: {:05} ".format(self.registers.gprs[6]), end='')
      print("r7: {:05} ".format(self.registers.gprs[7]))

      print("r0: {:04x} ".format(c_uint16(self.registers.gprs[0]).value), end='')
      print("r1: {:04x} ".format(c_uint16(self.registers.gprs[1]).value), end='')
      print("r2: {:04x} ".format(c_uint16(self.registers.gprs[2]).value), end='')
      print("r3: {:04x} ".format(c_uint16(self.registers.gprs[3]).value), end='')
      print("r4: {:04x} ".format(c_uint16(self.registers.gprs[4]).value), end='')
      print("r5: {:04x} ".format(c_uint16(self.registers.gprs[5]).value), end='')
      print("r6: {:04x} ".format(c_uint16(self.registers.gprs[6]).value), end='')
      print("r7: {:04x} ".format(c_uint16(self.registers.gprs[7]).value))

      print("cond: {}".format(condition_flags(self.registers.cond.value).name))

      def op_add_impl(self, instruction):
      sr1 = (instruction >> 6) & 0b111
      dr = (instruction >> 9) & 0b111
      if ((instruction >> 5) & 0b1) == 0: # reg-reg
      sr2 = instruction & 0b111
      self.registers.gprs[dr] = self.registers.gprs[sr1] + self.registers.gprs[sr2]
      else: # immediate
      imm5 = instruction & 0b11111
      self.registers.gprs[dr] = self.registers.gprs[sr1] + sext(imm5, 5)
      self.update_flags(dr)

      def op_and_impl(self, instruction):
      sr1 = (instruction >> 6) & 0b111
      dr = (instruction >> 9) & 0b111

      if ((instruction >> 5) & 0b1) == 0: # reg-reg
      sr2 = instruction & 0b111
      self.registers.gprs[dr] = self.registers.gprs[sr1] & self.registers.gprs[sr2]
      else: # immediate
      imm5 = instruction & 0b11111
      self.registers.gprs[dr] = self.registers.gprs[sr1] & sext(imm5, 5)

      self.update_flags(dr)

      def op_not_impl(self, instruction):
      sr = (instruction >> 6) & 0b111
      dr = (instruction >> 9) & 0b111

      self.registers.gprs[dr] = ~ (self.registers.gprs[sr])

      self.update_flags(dr)

      def op_br_impl(self, instruction):
      n = (instruction >> 11) & 1
      z = (instruction >> 10) & 1
      p = (instruction >> 9) & 1
      pc_offset_9 = instruction & 0x1ff

      if (n == 1 and self.registers.cond == condition_flags.n) or
      (z == 1 and self.registers.cond == condition_flags.z) or
      (p == 1 and self.registers.cond == condition_flags.p):
      self.registers.pc.value = self.registers.pc.value + sext(pc_offset_9, 9)

      # also ret
      def op_jmp_impl(self, instruction):
      baser = (instruction >> 6) & 0b111

      self.registers.pc.value = self.registers.gprs[baser]

      def op_jsr_impl(self, instruction):
      # no jsrr?
      if 0x0400 & instruction == 1: raise UnimpError("JSRR is not implemented.")
      pc_offset_11 = instruction & 0x7ff

      self.registers.gprs[7] = self.registers.pc.value
      self.registers.pc.value = self.registers.pc.value + sext(pc_offset_11, 11)

      def op_ld_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      pc_offset_9 = instruction & 0x1ff
      addr = self.registers.pc.value + sext(pc_offset_9, 9)
      self.registers.gprs[dr] = self.memory[addr]
      self.update_flags(dr)

      def op_ldi_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      pc_offset_9 = instruction & 0x1ff
      addr = self.registers.pc.value + sext(pc_offset_9, 9)
      self.registers.gprs[dr] = self.memory[ self.memory[addr] ]
      self.update_flags(dr)

      def op_ldr_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      baser = (instruction >> 6) & 0b111
      pc_offset_6 = instruction & 0x3f

      addr = self.registers.gprs[baser] + sext(pc_offset_6, 6)
      self.registers.gprs[dr] = self.memory[addr]

      self.update_flags(dr)

      def op_lea_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      pc_offset_9 = instruction & 0x1ff

      self.registers.gprs[dr] = self.registers.pc.value + sext(pc_offset_9, 9)
      self.update_flags(dr)

      def op_st_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      pc_offset_9 = instruction & 0x1ff
      addr = self.registers.pc.value + sext(pc_offset_9, 9)

      self.memory[addr] = self.registers.gprs[dr]

      def op_sti_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      pc_offset_9 = instruction & 0x1ff
      addr = self.registers.pc.value + sext(pc_offset_9, 9)

      self.memory[ self.memory[addr] ] = self.registers.gprs[dr]

      def op_str_impl(self, instruction):
      dr = (instruction >> 9) & 0b111
      baser = (instruction >> 6) & 0b111
      pc_offset_6 = instruction & 0x3f

      addr = self.registers.gprs[baser] + sext(pc_offset_6, 6)
      self.memory[addr] = self.registers.gprs[dr]

      def op_trap_impl(self, instruction):
      trap_vector = instruction & 0xff

      if trap_vector == 0x20: # getc
      c = stdin.buffer.read(1)[0]
      self.registers.gprs[0] = c
      return

      if trap_vector == 0x21: # out
      stdout.buffer.write( bytes( [(self.registers.gprs[0] & 0xff)] ) )
      stdout.buffer.flush()
      return

      if trap_vector == 0x22: # puts
      base_addr = self.registers.gprs[0]
      index = 0

      while (self.memory[base_addr + index]) != 0x00:
      nextchar = self.memory[base_addr + index]
      stdout.buffer.write( bytes( [nextchar] ) )
      index = index + 1

      return

      if trap_vector == 0x25:
      self.dump_state()
      exit()

      raise ValueError("undefined trap vector {}".format(hex(trap_vector)))

      def op_res_impl(self, instruction):
      raise UnimpError("unimplemented opcode")
      def op_rti_impl(self, instruction):
      raise UnimpError("unimplemented opcode")

      def start(self):
      while True:
      # fetch instruction
      instruction = self.memory[self.registers.pc.value]

      # update PC
      self.registers.pc.value = self.registers.pc.value + 1

      # decode opcode
      opcode = instruction >> 12

      if DEBUG:
      print("instruction: {}".format(hex(instruction)))
      print("disassembly: {}".format(lc3disas.single_ins(self.registers.pc.value, instruction)))
      self.dump_state()
      input()

      opcode_dict =
      {
      opcodes.op_add: self.op_add_impl,
      opcodes.op_and: self.op_and_impl,
      opcodes.op_not: self.op_not_impl,
      opcodes.op_br: self.op_br_impl,
      opcodes.op_jmp: self.op_jmp_impl,
      opcodes.op_jsr: self.op_jsr_impl,
      opcodes.op_ld: self.op_ld_impl,
      opcodes.op_ldi: self.op_ldi_impl,
      opcodes.op_ldr: self.op_ldr_impl,
      opcodes.op_lea: self.op_lea_impl,
      opcodes.op_st: self.op_st_impl,
      opcodes.op_sti: self.op_sti_impl,
      opcodes.op_str: self.op_str_impl,
      opcodes.op_trap:self.op_trap_impl,
      opcodes.op_res: self.op_res_impl,
      opcodes.op_rti: self.op_rti_impl
      }

      try:
      opcode_dict[opcode](instruction)
      except KeyError:
      raise UnimpError("invalid opcode")

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

      if len(argv) < 2:
      print ("usage: python3 lc3.py code.obj")
      exit(255)
      l = lc3(argv[1])
      l.start()






      python lc-3





      share







      New contributor




      ian5v is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.










      share







      New contributor




      ian5v is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.








      share



      share






      New contributor




      ian5v is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      asked 3 mins ago









      ian5v

      101




      101




      New contributor




      ian5v is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.





      New contributor





      ian5v is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      ian5v is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.



























          active

          oldest

          votes











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });






          ian5v is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210676%2fsmall-cpu-emulator-lc3-in-python%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown






























          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          ian5v is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          ian5v is a new contributor. Be nice, and check out our Code of Conduct.













          ian5v is a new contributor. Be nice, and check out our Code of Conduct.












          ian5v is a new contributor. Be nice, and check out our Code of Conduct.
















          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210676%2fsmall-cpu-emulator-lc3-in-python%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Create new schema in PostgreSQL using DBeaver

          Deepest pit of an array with Javascript: test on Codility

          Costa Masnaga