mirror of
https://github.com/irmen/prog8.git
synced 2024-11-29 17:50:35 +00:00
timer things
This commit is contained in:
parent
27e6749533
commit
ab84b6012b
22
testvm-timer.txt
Normal file
22
testvm-timer.txt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
; source code for a tinyvm program for the timer
|
||||||
|
%block b1_timer
|
||||||
|
%vardefs
|
||||||
|
var byte teller_timer 0
|
||||||
|
const word screenloc_timer 1028
|
||||||
|
const byte one_timer 1
|
||||||
|
%end_vardefs
|
||||||
|
|
||||||
|
%instructions
|
||||||
|
push teller_timer
|
||||||
|
push one_timer
|
||||||
|
add
|
||||||
|
dup
|
||||||
|
pop teller_timer
|
||||||
|
push screenloc_timer
|
||||||
|
swap
|
||||||
|
syscall memwrite_word
|
||||||
|
return 0
|
||||||
|
%end_instructions
|
||||||
|
%subblocks
|
||||||
|
%end_subblocks
|
||||||
|
%end_block ;b1_timer
|
@ -5,7 +5,8 @@ var byte teller 0
|
|||||||
var byte numbertoprint 0
|
var byte numbertoprint 0
|
||||||
const byte one 1
|
const byte one 1
|
||||||
const byte thousand 1000
|
const byte thousand 1000
|
||||||
const byte space_chr 32
|
const byte space_chr 10
|
||||||
|
const word screenstart 1024
|
||||||
%end_vardefs
|
%end_vardefs
|
||||||
|
|
||||||
%instructions
|
%instructions
|
||||||
@ -15,8 +16,13 @@ back:
|
|||||||
add
|
add
|
||||||
dup
|
dup
|
||||||
dup
|
dup
|
||||||
|
dup
|
||||||
|
push screenstart
|
||||||
|
swap
|
||||||
|
syscall memwrite_word
|
||||||
pop teller
|
pop teller
|
||||||
call 1 printnumber
|
call 1 printnumber
|
||||||
|
syscall delay
|
||||||
push thousand
|
push thousand
|
||||||
cmp_lt
|
cmp_lt
|
||||||
jump_if_true back
|
jump_if_true back
|
||||||
|
@ -1,17 +1,26 @@
|
|||||||
import sys
|
import sys
|
||||||
from .parse import Parser
|
from .parse import Parser
|
||||||
from .program import Program, Opcode, Block, Instruction
|
# from .program import Program, Opcode, Block, Instruction
|
||||||
from .vm import VM
|
from .vm import VM
|
||||||
|
|
||||||
|
mainprogram = None
|
||||||
|
timerprogram = None
|
||||||
|
|
||||||
source = open(sys.argv[1]).read()
|
if len(sys.argv) >= 2:
|
||||||
|
source = open(sys.argv[1]).read()
|
||||||
|
parser = Parser(source)
|
||||||
|
mainprogram = parser.parse()
|
||||||
|
|
||||||
|
if len(sys.argv) == 3:
|
||||||
|
source = open(sys.argv[2]).read()
|
||||||
|
parser = Parser(source)
|
||||||
|
timerprogram = parser.parse()
|
||||||
|
|
||||||
|
if len(sys.argv) not in (2, 3):
|
||||||
|
raise SystemExit("provide 1 or 2 program file names as arguments")
|
||||||
|
|
||||||
parser = Parser(source)
|
|
||||||
program = parser.parse()
|
|
||||||
timerprogram = Program([Block("timer", None, [], [
|
|
||||||
Instruction(Opcode.RETURN, [], None, None)
|
|
||||||
], {}, [])])
|
|
||||||
# zero page and hardware stack of a 6502 cpu are off limits for now
|
# zero page and hardware stack of a 6502 cpu are off limits for now
|
||||||
VM.readonly_mem_ranges = [(0x00, 0xff), (0x100, 0x1ff), (0xa000, 0xbfff), (0xe000, 0xffff)]
|
VM.readonly_mem_ranges = [(0x00, 0xff), (0x100, 0x1ff), (0xa000, 0xbfff), (0xe000, 0xffff)]
|
||||||
vm = VM(program)
|
vm = VM(mainprogram, timerprogram)
|
||||||
|
vm.enable_charscreen(0x0400, 40, 25)
|
||||||
vm.run()
|
vm.run()
|
||||||
|
@ -13,6 +13,7 @@ class Opcode(enum.IntEnum):
|
|||||||
POP3 = 15
|
POP3 = 15
|
||||||
DUP = 16
|
DUP = 16
|
||||||
DUP2 = 17
|
DUP2 = 17
|
||||||
|
SWAP = 18
|
||||||
ADD = 50
|
ADD = 50
|
||||||
SUB = 51
|
SUB = 51
|
||||||
MUL = 52
|
MUL = 52
|
||||||
|
80
tinyvm/vm.py
80
tinyvm/vm.py
@ -20,7 +20,7 @@
|
|||||||
# read [byte/bytearray from keyboard],
|
# read [byte/bytearray from keyboard],
|
||||||
# wait [till any input comes available], @todo
|
# wait [till any input comes available], @todo
|
||||||
# check [if input is 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 @todo)
|
||||||
#
|
#
|
||||||
# CPU: stack based execution, no registers.
|
# CPU: stack based execution, no registers.
|
||||||
# unlimited dynamic variables (v0, v1, ...) that have a value and a type.
|
# unlimited dynamic variables (v0, v1, ...) that have a value and a type.
|
||||||
@ -63,6 +63,8 @@ import collections
|
|||||||
import array
|
import array
|
||||||
import threading
|
import threading
|
||||||
import pprint
|
import pprint
|
||||||
|
import tkinter
|
||||||
|
import tkinter.font
|
||||||
from typing import Dict, List, Tuple, Union
|
from typing import Dict, List, Tuple, Union
|
||||||
from il65.emit import mflpt5_to_float, to_mflpt5
|
from il65.emit import mflpt5_to_float, to_mflpt5
|
||||||
from .program import Instruction, Variable, Block, Program, Opcode
|
from .program import Instruction, Variable, Block, Program, Opcode
|
||||||
@ -91,6 +93,9 @@ class Memory:
|
|||||||
def get_byte(self, index: int) -> int:
|
def get_byte(self, index: int) -> int:
|
||||||
return self.mem[index]
|
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:
|
def get_sbyte(self, index: int) -> int:
|
||||||
return 256 - self.mem[index]
|
return 256 - self.mem[index]
|
||||||
|
|
||||||
@ -222,8 +227,9 @@ class VM:
|
|||||||
timer_irq_interlock = threading.Lock()
|
timer_irq_interlock = threading.Lock()
|
||||||
timer_irq_event = threading.Event()
|
timer_irq_event = threading.Event()
|
||||||
|
|
||||||
def __init__(self, program: Program, timerprogram: Program=Program([])) -> None:
|
def __init__(self, program: Program, timerprogram: Program=None) -> None:
|
||||||
opcode_names = [oc.name for oc in Opcode]
|
opcode_names = [oc.name for oc in Opcode]
|
||||||
|
timerprogram = timerprogram or Program([])
|
||||||
for ocname in opcode_names:
|
for ocname in opcode_names:
|
||||||
if not hasattr(self, "opcode_" + ocname):
|
if not hasattr(self, "opcode_" + ocname):
|
||||||
raise NotImplementedError("missing opcode method for " + ocname)
|
raise NotImplementedError("missing opcode method for " + ocname)
|
||||||
@ -245,6 +251,7 @@ class VM:
|
|||||||
self.program = self.main_program
|
self.program = self.main_program
|
||||||
self.stack = self.main_stack
|
self.stack = self.main_stack
|
||||||
self.pc = None # type: Instruction
|
self.pc = None # type: Instruction
|
||||||
|
self.charscreen = None
|
||||||
self.system = System(self)
|
self.system = System(self)
|
||||||
assert all(i.next for i in self.main_program
|
assert all(i.next for i in self.main_program
|
||||||
if i.opcode != Opcode.TERMINATE), "main: all instrs next must be set"
|
if i.opcode != Opcode.TERMINATE), "main: all instrs next must be set"
|
||||||
@ -257,6 +264,9 @@ class VM:
|
|||||||
threading.Thread(target=self.timer_irq, name="timer_irq", daemon=True).start()
|
threading.Thread(target=self.timer_irq, name="timer_irq", daemon=True).start()
|
||||||
print("[TinyVM starting up.]")
|
print("[TinyVM starting up.]")
|
||||||
|
|
||||||
|
def enable_charscreen(self, screen_address: int, width: int, height: int) -> None:
|
||||||
|
self.charscreen = (screen_address, width, height)
|
||||||
|
|
||||||
def flatten_programs(self, main: Program, timer: Program) \
|
def flatten_programs(self, main: Program, timer: Program) \
|
||||||
-> Tuple[List[Instruction], List[Instruction], Dict[str, Variable], Dict[str, Instruction]]:
|
-> Tuple[List[Instruction], List[Instruction], Dict[str, Variable], Dict[str, Instruction]]:
|
||||||
variables = {} # type: Dict[str, Variable]
|
variables = {} # type: Dict[str, Variable]
|
||||||
@ -322,6 +332,9 @@ class VM:
|
|||||||
i.next = nexti
|
i.next = nexti
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
|
if self.charscreen:
|
||||||
|
threading.Thread(target=ScreenViewer.create, args=(self.memory, self.system, self.charscreen), name="screenviewer", daemon=True).start()
|
||||||
|
|
||||||
self.pc = self.program[0] # first instruction of the main program
|
self.pc = self.program[0] # first instruction of the main program
|
||||||
self.stack.push(CallFrameMarker(None)) # enter the call frame so the timer program can end with a RETURN
|
self.stack.push(CallFrameMarker(None)) # enter the call frame so the timer program can end with a RETURN
|
||||||
try:
|
try:
|
||||||
@ -342,11 +355,15 @@ class VM:
|
|||||||
print("[TinyVM execution ended.]")
|
print("[TinyVM execution ended.]")
|
||||||
|
|
||||||
def timer_irq(self) -> None:
|
def timer_irq(self) -> None:
|
||||||
|
# This is the timer 'irq' handler. It runs the timer program at a certain interval.
|
||||||
|
# NOTE: executing the timer program will LOCK the main program and vice versa!
|
||||||
|
# (because the VM is a strictly single threaded machine)
|
||||||
resolution = 1/30
|
resolution = 1/30
|
||||||
wait_time = resolution
|
wait_time = resolution
|
||||||
while True:
|
while True:
|
||||||
self.timer_irq_event.wait(wait_time)
|
self.timer_irq_event.wait(wait_time)
|
||||||
self.timer_irq_event.clear()
|
self.timer_irq_event.clear()
|
||||||
|
print("....timer irq", wait_time, time.time()) # XXX
|
||||||
start = time.perf_counter()
|
start = time.perf_counter()
|
||||||
if self.timer_program:
|
if self.timer_program:
|
||||||
with self.timer_irq_interlock:
|
with self.timer_irq_interlock:
|
||||||
@ -408,6 +425,11 @@ class VM:
|
|||||||
self.stack.push(x)
|
self.stack.push(x)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def opcode_SWAP(self, instruction: Instruction) -> bool:
|
||||||
|
value2, value1 = self.stack.pop2()
|
||||||
|
self.stack.push2(value2, value1)
|
||||||
|
return True
|
||||||
|
|
||||||
def opcode_PUSH2(self, instruction: Instruction) -> bool:
|
def opcode_PUSH2(self, instruction: Instruction) -> bool:
|
||||||
value1 = self.variables[instruction.args[0]].value
|
value1 = self.variables[instruction.args[0]].value
|
||||||
value2 = self.variables[instruction.args[1]].value
|
value2 = self.variables[instruction.args[1]].value
|
||||||
@ -561,6 +583,7 @@ class VM:
|
|||||||
Opcode.POP3: opcode_POP3,
|
Opcode.POP3: opcode_POP3,
|
||||||
Opcode.DUP: opcode_DUP,
|
Opcode.DUP: opcode_DUP,
|
||||||
Opcode.DUP2: opcode_DUP2,
|
Opcode.DUP2: opcode_DUP2,
|
||||||
|
Opcode.SWAP: opcode_SWAP,
|
||||||
Opcode.ADD: opcode_ADD,
|
Opcode.ADD: opcode_ADD,
|
||||||
Opcode.SUB: opcode_SUB,
|
Opcode.SUB: opcode_SUB,
|
||||||
Opcode.MUL: opcode_MUL,
|
Opcode.MUL: opcode_MUL,
|
||||||
@ -588,16 +611,16 @@ class System:
|
|||||||
def __init__(self, vm: VM) -> None:
|
def __init__(self, vm: VM) -> None:
|
||||||
self.vm = vm
|
self.vm = vm
|
||||||
|
|
||||||
def _encodestr(self, string: str, alt: bool=False) -> bytearray:
|
def encodestr(self, string: str, alt: bool=False) -> bytearray:
|
||||||
return bytearray(string, self.vm.str_alt_encoding if alt else self.vm.str_encoding)
|
return bytearray(string, self.vm.str_alt_encoding if alt else self.vm.str_encoding)
|
||||||
|
|
||||||
def _decodestr(self, bb: Union[bytearray, array.array], alt: bool=False) -> str:
|
def decodestr(self, bb: Union[bytearray, array.array], alt: bool=False) -> str:
|
||||||
return str(bb, self.vm.str_alt_encoding if alt else self.vm.str_encoding) # type: ignore
|
return str(bb, self.vm.str_alt_encoding if alt else self.vm.str_encoding) # type: ignore
|
||||||
|
|
||||||
def syscall_printstr(self) -> bool:
|
def syscall_printstr(self) -> bool:
|
||||||
value = self.vm.stack.pop()
|
value = self.vm.stack.pop()
|
||||||
if isinstance(value, (bytearray, array.array)):
|
if isinstance(value, (bytearray, array.array)):
|
||||||
print(self._decodestr(value), end="")
|
print(self.decodestr(value), end="")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise TypeError("printstr expects bytearray", value)
|
raise TypeError("printstr expects bytearray", value)
|
||||||
@ -605,23 +628,23 @@ class System:
|
|||||||
def syscall_printchr(self) -> bool:
|
def syscall_printchr(self) -> bool:
|
||||||
character = self.vm.stack.pop()
|
character = self.vm.stack.pop()
|
||||||
if isinstance(character, int):
|
if isinstance(character, int):
|
||||||
print(self._decodestr(bytearray([character])), end="")
|
print(self.decodestr(bytearray([character])), end="")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise TypeError("printchr expects integer (1 char)", character)
|
raise TypeError("printchr expects integer (1 char)", character)
|
||||||
|
|
||||||
def syscall_input(self) -> bool:
|
def syscall_input(self) -> bool:
|
||||||
self.vm.stack.push(self._encodestr(input()))
|
self.vm.stack.push(self.encodestr(input()))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def syscall_getchr(self) -> bool:
|
def syscall_getchr(self) -> bool:
|
||||||
self.vm.stack.push(self._encodestr(input() + '\n')[0])
|
self.vm.stack.push(self.encodestr(input() + '\n')[0])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def syscall_decimalstr_signed(self) -> bool:
|
def syscall_decimalstr_signed(self) -> bool:
|
||||||
value = self.vm.stack.pop()
|
value = self.vm.stack.pop()
|
||||||
if type(value) is int:
|
if type(value) is int:
|
||||||
self.vm.stack.push(self._encodestr(str(value)))
|
self.vm.stack.push(self.encodestr(str(value)))
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise TypeError("decimalstr expects int", value)
|
raise TypeError("decimalstr expects int", value)
|
||||||
@ -633,7 +656,7 @@ class System:
|
|||||||
strvalue = "${:x}".format(value)
|
strvalue = "${:x}".format(value)
|
||||||
else:
|
else:
|
||||||
strvalue = "-${:x}".format(-value) # type: ignore
|
strvalue = "-${:x}".format(-value) # type: ignore
|
||||||
self.vm.stack.push(self._encodestr(strvalue))
|
self.vm.stack.push(self.encodestr(strvalue))
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise TypeError("hexstr expects int", value)
|
raise TypeError("hexstr expects int", value)
|
||||||
@ -668,3 +691,40 @@ class System:
|
|||||||
for i, b in enumerate(strbytes): # type: ignore
|
for i, b in enumerate(strbytes): # type: ignore
|
||||||
self.vm.memory.set_byte(address+i, b) # type: ignore
|
self.vm.memory.set_byte(address+i, b) # type: ignore
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def syscall_smalldelay(self) -> bool:
|
||||||
|
time.sleep(1/100)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def syscall_delay(self) -> bool:
|
||||||
|
time.sleep(0.5)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenViewer(tkinter.Tk):
|
||||||
|
def __init__(self, memory: Memory, system: System, screenconfig: Tuple[int, int, int]) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.fontsize = 14
|
||||||
|
self.memory = memory
|
||||||
|
self.system = system
|
||||||
|
self.address = screenconfig[0]
|
||||||
|
self.width = screenconfig[1]
|
||||||
|
self.height = screenconfig[2]
|
||||||
|
self.monospace = tkinter.font.Font(self, family="Courier", weight="bold", size=self.fontsize)
|
||||||
|
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.after(10, self.update_screen)
|
||||||
|
|
||||||
|
def update_screen(self):
|
||||||
|
self.canvas.delete(tkinter.ALL)
|
||||||
|
for y in range(self.height):
|
||||||
|
line = self.memory.get_bytes(self.address+y*self.width, self.width)
|
||||||
|
text = self.system.decodestr(line)
|
||||||
|
self.canvas.create_text(4, self.fontsize*y, text=text, fill="white", font=self.monospace, anchor=tkinter.NW)
|
||||||
|
self.after(10, self.update_screen)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, memory: Memory, system: System, screenconfig: Tuple[int, int, int]) -> None:
|
||||||
|
viewer = cls(memory, system, screenconfig)
|
||||||
|
viewer.mainloop()
|
||||||
|
Loading…
Reference in New Issue
Block a user