memory mapped i/o (charin, charout)

This commit is contained in:
Irmen de Jong 2018-03-05 23:13:19 +01:00
parent 7a5813d3be
commit bcc1a6b91f
8 changed files with 251 additions and 95 deletions

60
tests/test_vmcore.py Normal file
View File

@ -0,0 +1,60 @@
import pytest
from tinyvm.core import Memory
def test_memory_unsigned():
m = Memory()
m.set_byte(1000, 1)
m.set_byte(1001, 2)
m.set_byte(1002, 3)
m.set_byte(1003, 4)
m.set_byte(2000, 252)
m.set_byte(2001, 253)
m.set_byte(2002, 254)
m.set_byte(2003, 255)
assert 1 == m.get_byte(1000)
assert 2 == m.get_byte(1001)
assert 3 == m.get_byte(1002)
assert 4 == m.get_byte(1003)
assert 252 == m.get_byte(2000)
assert 253 == m.get_byte(2001)
assert 254 == m.get_byte(2002)
assert 255 == m.get_byte(2003)
assert b"\x01\x02\x03\x04" == m.get_bytes(1000, 4)
assert 0x0201 == m.get_word(1000)
assert 0xfffe == m.get_word(2002)
m.set_word(2002, 40000)
assert 40000 == m.get_word(2002)
assert 0x40 == m.get_byte(2002)
assert 0x9c == m.get_byte(2003)
def test_memory_signed():
m = Memory()
m.set_byte(1000, 1)
m.set_byte(1001, 2)
m.set_byte(1002, 3)
m.set_byte(1003, 4)
m.set_byte(2000, 252)
m.set_byte(2001, 253)
m.set_byte(2002, 254)
m.set_byte(2003, 255)
assert 1 == m.get_sbyte(1000)
assert 2 == m.get_sbyte(1001)
assert 3 == m.get_sbyte(1002)
assert 4 == m.get_sbyte(1003)
assert -4 == m.get_sbyte(2000)
assert -3 == m.get_sbyte(2001)
assert -2 == m.get_sbyte(2002)
assert -1 == m.get_sbyte(2003)
assert 0x0201 == m.get_sword(1000)
assert -2 == m.get_sword(2002)
m.set_sword(2002, 30000)
assert 30000 == m.get_sword(2002)
assert 0x30 == m.get_sbyte(2002)
assert 0x75 == m.get_sbyte(2003)
m.set_sword(2002, -30000)
assert -30000 == m.get_sword(2002)
assert 0x8ad0 == m.get_word(2002)
assert 0xd0 == m.get_byte(2002)
assert 0x8a == m.get_byte(2003)

110
tinyvm/core.py Normal file
View File

@ -0,0 +1,110 @@
"""
Simplistic 8/16 bit Virtual Machine to execute a stack based instruction language.
Core data structures and definitions.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
import enum
import struct
from typing import Callable
from il65.emit import mflpt5_to_float, to_mflpt5
class DataType(enum.IntEnum):
BOOL = 1
BYTE = 2
SBYTE = 3
WORD = 4
SWORD = 5
FLOAT = 6
ARRAY_BYTE = 7
ARRAY_SBYTE = 8
ARRAY_WORD = 9
ARRAY_SWORD = 10
MATRIX_BYTE = 11
MATRIX_SBYTE = 12
class ExecutionError(Exception):
pass
class TerminateExecution(SystemExit):
pass
class MemoryAccessError(Exception):
pass
class Memory:
def __init__(self):
self.mem = bytearray(65536)
self.readonly = bytearray(65536)
self.mmap_io_charout_addr = -1
self.mmap_io_charout_callback = None
self.mmap_io_charin_addr = -1
self.mmap_io_charin_callback = None
def mark_readonly(self, start: int, end: int) -> None:
self.readonly[start:end+1] = [1] * (end-start+1)
def memmapped_io_charout(self, address: int, callback: Callable) -> None:
self.mmap_io_charout_addr = address
self.mmap_io_charout_callback = callback
def memmapped_io_charin(self, address: int, callback: Callable) -> None:
self.mmap_io_charin_addr = address
self.mmap_io_charin_callback = callback
def get_byte(self, index: int) -> int:
if self.mmap_io_charin_addr == index:
self.mem[index] = self.mmap_io_charin_callback()
return self.mem[index]
def get_bytes(self, startindex: int, amount: int) -> bytearray:
return self.mem[startindex: startindex+amount]
def get_sbyte(self, index: int) -> int:
if self.mmap_io_charin_addr == index:
self.mem[index] = self.mmap_io_charin_callback()
return struct.unpack("b", self.mem[index:index+1])[0]
def get_word(self, index: int) -> int:
return self.mem[index] + 256 * self.mem[index+1]
def get_sword(self, index: int) -> int:
return struct.unpack("<h", self.mem[index:index+2])[0]
def get_float(self, index: int) -> float:
return mflpt5_to_float(self.mem[index: index+5])
def set_byte(self, index: int, value: int) -> None:
if self.readonly[index]:
raise MemoryAccessError("read-only", index)
self.mem[index] = value
if self.mmap_io_charout_addr == index:
self.mmap_io_charout_callback(value)
def set_sbyte(self, index: int, value: int) -> None:
if self.readonly[index]:
raise MemoryAccessError("read-only", index)
self.mem[index] = struct.pack("b", bytes([value]))[0]
if self.mmap_io_charout_addr == index:
self.mmap_io_charout_callback(self.mem[index])
def set_word(self, index: int, value: int) -> None:
if self.readonly[index] or self.readonly[index+1]:
raise MemoryAccessError("read-only", index)
self.mem[index], self.mem[index + 1] = struct.pack("<H", value)
def set_sword(self, index: int, value: int) -> None:
if self.readonly[index] or self.readonly[index+1]:
raise MemoryAccessError("read-only", index)
self.mem[index], self.mem[index + 1] = struct.pack("<h", value)
def set_float(self, index: int, value: float) -> None:
if any(self.readonly[index:index+5]):
raise MemoryAccessError("read-only", index)
self.mem[index: index+5] = to_mflpt5(value)

View File

@ -0,0 +1,26 @@
; source code for a tinyvm program; memory mapped I/O
%block b1
%vardefs
const byte chr_i 105
const byte chr_r 114
const byte chr_m 109
const byte chr_e 101
const byte chr_n 110
const byte chr_EOL 10
const word chrout 53248
const word chrin 53249
%end_vardefs
%instructions
loop:
push chrin
syscall memread_byte
push chrout
swap
syscall memwrite_byte
push chrout
push chr_EOL
syscall memwrite_byte
syscall delay
jump loop
%end_instructions
%end_block ;b1

View File

@ -7,8 +7,9 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
import array
from typing import Optional, List, Tuple, Dict, Any
from .program import DataType, Opcode, Program, Block, Variable, Instruction, Value
from .program import Opcode, Program, Block, Variable, Instruction, Value
from .vm import StackValueType
from .core import DataType
class ParseError(Exception):

View File

@ -9,6 +9,7 @@ import enum
import array
import operator
from typing import List, Dict, Optional, Union, Callable, Any
from .core import DataType
class Opcode(enum.IntEnum):
@ -45,21 +46,6 @@ class Opcode(enum.IntEnum):
SYSCALL = 205
class DataType(enum.IntEnum):
BOOL = 1
BYTE = 2
SBYTE = 3
WORD = 4
SWORD = 5
FLOAT = 6
ARRAY_BYTE = 7
ARRAY_SBYTE = 8
ARRAY_WORD = 9
ARRAY_SWORD = 10
MATRIX_BYTE = 11
MATRIX_SBYTE = 12
class Value:
__slots__ = ["dtype", "value", "length", "height"]

View File

@ -27,7 +27,7 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
# read [byte/bytearray from keyboard],
# wait [till any input comes available], @todo
# check [if input is available) @todo
# or via memory-mapped I/O (text screen matrix, keyboard scan register @todo)
# or via memory-mapped I/O (text screen matrix, keyboard scan register)
#
# CPU: stack based execution, no registers.
# unlimited dynamic variables (v0, v1, ...) that have a value and a type.
@ -73,76 +73,8 @@ import pprint
import tkinter
import tkinter.font
from typing import Dict, List, Tuple, Union, no_type_check
from il65.emit import mflpt5_to_float, to_mflpt5
from .program import Instruction, Variable, Block, Program, Opcode, Value, DataType
class ExecutionError(Exception):
pass
class TerminateExecution(SystemExit):
pass
class MemoryAccessError(Exception):
pass
class Memory:
def __init__(self):
self.mem = bytearray(65536)
self.readonly = bytearray(65536)
def mark_readonly(self, start: int, end: int) -> None:
self.readonly[start:end+1] = [1] * (end-start+1)
def get_byte(self, index: int) -> int:
return self.mem[index]
def get_bytes(self, startindex: int, amount: int) -> int:
return self.mem[startindex: startindex+amount]
def get_sbyte(self, index: int) -> int:
return 256 - self.mem[index]
def get_word(self, index: int) -> int:
return self.mem[index] + 256 * self.mem[index+1]
def get_sword(self, index: int) -> int:
return 65536 - (self.mem[index] + 256 * self.mem[index+1])
def get_float(self, index: int) -> float:
return mflpt5_to_float(self.mem[index: index+5])
def set_byte(self, index: int, value: int) -> None:
if self.readonly[index]:
raise MemoryAccessError("read-only", index)
self.mem[index] = value
def set_sbyte(self, index: int, value: int) -> None:
if self.readonly[index]:
raise MemoryAccessError("read-only", index)
self.mem[index] = value + 256
def set_word(self, index: int, value: int) -> None:
if self.readonly[index] or self.readonly[index+1]:
raise MemoryAccessError("read-only", index)
hi, lo = divmod(value, 256)
self.mem[index] = lo
self.mem[index+1] = hi
def set_sword(self, index: int, value: int) -> None:
if self.readonly[index] or self.readonly[index+1]:
raise MemoryAccessError("read-only", index)
hi, lo = divmod(value + 65536, 256)
self.mem[index] = lo
self.mem[index+1] = hi
def set_float(self, index: int, value: float) -> None:
if any(self.readonly[index:index+5]):
raise MemoryAccessError("read-only", index)
self.mem[index: index+5] = to_mflpt5(value)
from .program import Instruction, Variable, Block, Program, Opcode, Value
from .core import Memory, DataType, TerminateExecution
class CallFrameMarker:
@ -231,6 +163,8 @@ class VM:
str_alt_encoding = "iso-8859-15"
readonly_mem_ranges = [] # type: List[Tuple[int, int]]
timer_irq_resolution = 1/30
charout_address = 0xd000
charin_address = 0xd001
def __init__(self, program: Program, timerprogram: Program=None) -> None:
opcode_names = [oc.name for oc in Opcode]
@ -246,6 +180,8 @@ class VM:
if oc not in self.dispatch_table:
raise NotImplementedError("no dispatch entry in table for " + oc.name)
self.memory = Memory()
self.memory.memmapped_io_charout(self.charout_address, self.memmapped_charout)
self.memory.memmapped_io_charin(self.charin_address, self.memmapped_charin)
for start, end in self.readonly_mem_ranges:
self.memory.mark_readonly(start, end)
self.main_stack = Stack()
@ -259,6 +195,7 @@ class VM:
self.charscreen_address = 0
self.charscreen_width = 0
self.charscreen_height = 0
self.keyboard_scancode = 0
self.system = System(self)
assert all(i.next for i in self.main_program
if i.opcode != Opcode.TERMINATE), "main: all instrs next must be set"
@ -341,7 +278,7 @@ class VM:
def run(self) -> None:
if self.charscreen_address:
threading.Thread(target=ScreenViewer.create,
args=(self.memory, self.system, self.charscreen_address, self.charscreen_width, self.charscreen_height),
args=(self, self.charscreen_address, self.charscreen_width, self.charscreen_height),
name="screenviewer", daemon=True).start()
time.sleep(0.05)
@ -405,6 +342,13 @@ class VM:
if self.pc is not None:
print("* instruction:", self.pc)
def memmapped_charout(self, value: int) -> None:
string = self.system.decodestr(bytearray([value]))
print(string, end="")
def memmapped_charin(self) -> int:
return self.keyboard_scancode
def assign_variable(self, variable: Variable, value: Value) -> None:
assert not variable.const, "cannot modify a const"
assert isinstance(value, Value)
@ -742,6 +686,13 @@ class System:
self.vm.memory.set_byte(address+i, b) # type: ignore
return True
def syscall_memread_byte(self) -> bool:
address = self.vm.stack.pop()
assert isinstance(address, Value)
assert address.dtype == DataType.WORD
self.vm.stack.push(Value(DataType.BYTE, self.vm.memory.get_byte(address.value))) # type: ignore
return True
def syscall_smalldelay(self) -> bool:
time.sleep(1/100)
return True
@ -752,12 +703,11 @@ class System:
class ScreenViewer(tkinter.Tk):
def __init__(self, memory: Memory, system: System, screen_addr: int, screen_width: int, screen_height: int) -> None:
def __init__(self, vm: VM, screen_addr: int, screen_width: int, screen_height: int) -> None:
super().__init__()
self.title("IL65 tinyvm")
self.fontsize = 16
self.memory = memory
self.system = system
self.vm = vm
self.address = screen_addr
self.width = screen_width
self.height = screen_height
@ -765,19 +715,42 @@ class ScreenViewer(tkinter.Tk):
cw = self.monospace.measure("x")*self.width+8
self.canvas = tkinter.Canvas(self, width=cw, height=self.fontsize*self.height+8, bg="blue")
self.canvas.pack()
self.bind("<KeyPress>", self.keypress)
self.bind("<KeyRelease>", self.keyrelease)
self.after(10, self.update_screen)
def update_screen(self):
def keypress(self, e) -> None:
key = e.char or e.keysym
if len(key) == 1:
self.vm.keyboard_scancode = self.vm.system.encodestr(key)[0]
elif len(key) > 1:
code = 0
if key == "Up":
code = ord("w")
elif key == "Down":
code = ord("s")
elif key == "Left":
code = ord("a")
elif key == "Right":
code = ord("d")
self.vm.keyboard_scancode = code
else:
self.vm.keyboard_scancode = 0
def keyrelease(self, e) -> None:
self.vm.keyboard_scancode = 0
def update_screen(self) -> None:
self.canvas.delete(tkinter.ALL)
lines = []
for y in range(self.height):
line = self.system.decodestr(self.memory.get_bytes(self.address+y*self.width, self.width))
line = self.vm.system.decodestr(self.vm.memory.get_bytes(self.address+y*self.width, self.width))
lines.append("".join(c if c.isprintable() else " " for c in line))
for y, line in enumerate(lines):
self.canvas.create_text(4, self.fontsize*y, text=line, fill="white", font=self.monospace, anchor=tkinter.NW)
self.after(30, self.update_screen)
@classmethod
def create(cls, memory: Memory, system: System, screen_addr: int, screen_width: int, screen_height: int) -> None:
viewer = cls(memory, system, screen_addr, screen_width, screen_height)
def create(cls, vm: VM, screen_addr: int, screen_width: int, screen_height: int) -> None:
viewer = cls(vm, screen_addr, screen_width, screen_height)
viewer.mainloop()