mirror of
https://github.com/irmen/prog8.git
synced 2024-11-25 19:31:36 +00:00
memory mapped i/o (charin, charout)
This commit is contained in:
parent
7a5813d3be
commit
bcc1a6b91f
60
tests/test_vmcore.py
Normal file
60
tests/test_vmcore.py
Normal 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
110
tinyvm/core.py
Normal 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)
|
26
tinyvm/examples/printiovm.txt
Normal file
26
tinyvm/examples/printiovm.txt
Normal 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
|
@ -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):
|
||||
|
@ -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"]
|
||||
|
||||
|
131
tinyvm/vm.py
131
tinyvm/vm.py
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user