diff options
| -rw-r--r-- | atk16_emu/emu.py | 6 | ||||
| -rw-r--r-- | resources/py/aoc23_1a.py | 48 | ||||
| -rw-r--r-- | test/e2e/emulation_speed/bootstrap.atk16 | 78 | ||||
| -rw-r--r-- | test/e2e/emulation_speed/long_loop.atk16 | 16 | ||||
| -rw-r--r-- | test/e2e/emulation_speed/test_emulation_speed.py | 43 | ||||
| -rw-r--r-- | test/e2e/fibo/test_fibo.py | 7 |
6 files changed, 191 insertions, 7 deletions
diff --git a/atk16_emu/emu.py b/atk16_emu/emu.py index 4257636..f0671bd 100644 --- a/atk16_emu/emu.py +++ b/atk16_emu/emu.py @@ -2,6 +2,7 @@ import random from typing import Literal from dataclasses import dataclass import sys +import time from .opcodes import * from .colors import C @@ -278,6 +279,11 @@ class Machine: while self.running: self.step() + # TODO limit emulation speed to 393359.375 Hz ~= 2542 ns per instruction + # this is in order to have similar speed as the actual machine + # see: test_emulation_speed.py + # naive time.sleep is slow and not accurate enough + def check_nth_flag(self, n: int) -> bool: match n: case 0: return self.fr.carry diff --git a/resources/py/aoc23_1a.py b/resources/py/aoc23_1a.py new file mode 100644 index 0000000..12e3953 --- /dev/null +++ b/resources/py/aoc23_1a.py @@ -0,0 +1,48 @@ +import atk16 + +GRAPHICS_NO_MODE: atk16.ConstWord16 = 0 +GRAPHICS_TEXT_MODE: atk16.ConstWord16 = 1 +GRAPHICS_SPRITE_MODE: atk16.ConstWord16 = 2 + +GRAPHICS_MODE_PP: atk16.ConstWord16 = 0x17 +TEXT_MEM_PP: atk16.ConstWord16 = 0x19 + +graphics_mode_p = atk16.load(GRAPHICS_MODE_PP) +text_mem_p = atk16.load(TEXT_MEM_PP) + +atk16.store(graphics_mode_p, GRAPHICS_TEXT_MODE) + +INPUT_P: atk16.ConstWord16 = 0x9000 +INPUT_ELEM_SIZE: atk16.ConstWord16 = 256 + +# 1a example + +atk16.store(INPUT_P + 0, "1") +atk16.store(INPUT_P + 1, "a") +atk16.store(INPUT_P + 2, "b") +atk16.store(INPUT_P + 3, "c") +atk16.store(INPUT_P + 4, "2") +atk16.store(INPUT_P + 5, "\0") +# atk16.store_const_vec(INPUT_P + 0, "1abc2\0") +# atk16.store_const_vec(INPUT_P + 256, "pqr3stu8vwx\0") +# atk16.store_const_vec(INPUT_P + 512, "a1b2c3d4e5f\0") +# atk16.store_const_vec(INPUT_P + 768, "treb7uchet\0") + +row_p = INPUT_P +i = 0 + +first = atk16.load(row_p) +last = 0 + +while True: + char_p = INPUT_P + i + char = atk16.load(char_p) + + if char == "\0": + break + + last = char + i += 1 + +atk16.store(text_mem_p + 0, first) +atk16.store(text_mem_p + 1, last)
\ No newline at end of file diff --git a/test/e2e/emulation_speed/bootstrap.atk16 b/test/e2e/emulation_speed/bootstrap.atk16 new file mode 100644 index 0000000..9b024e6 --- /dev/null +++ b/test/e2e/emulation_speed/bootstrap.atk16 @@ -0,0 +1,78 @@ +@let SP RH + +; vector table fields +@let vector_table 0x10 +@let vt_ISR0 0x10 ; ISR0 +@let vt_ISR1 0x11 ; ISR1 +@let vt_ISR2 0x12 ; ISR2 +@let vt_ISR3 0x13 ; ISR3 +@let vt_stack_addr 0x14 ; Stack address +@let vt_term_pp_addr 0x15 ; Terminal peripheral address +@let vt_kb_pp_addr 0x16 ; Keyboard peripheral address +@let vt_gr_mode_addr 0x17 ; Graphics mode setting address +@let vt_sprite_mem 0x18 ; Sprite memory address +@let vt_text_mem 0x19 ; Text memory buffer address + +; memory segments +@let stack_segment 0x8000 +@let mmio_segment 0xE000 +@let terminal_addr 0xE000 +@let keyboard_addr 0xE001 +@let gr_mode_addr 0xE002 +@let sprite_mem 0xE800 +@let text_mem 0xF800 ; to 0xFFFF +; note: sprite memory & text memory can use the same space +; since they are never used at the same time + +; graphics mode settings +@let gr_disabled_mode 0b00 +@let gr_text_mode 0b01 +@let gr_sprite_mode 0b10 + +@address vector_table + keyboard_isr ; 0x10 + hlt_isr ; 0x11 + hlt_isr ; 0x12 + hlt_isr ; 0x13 + stack_segment ; 0x14 + terminal_addr ; 0x15 + keyboard_addr ; 0x16 + gr_mode_addr ; 0x17 + sprite_mem ; 0x18 + text_mem ; 0x19 + +@label hlt_isr + ldi 0x55 RE + hlt + +@label keyboard_isr + spu RA + spu RB + ldi vt_kb_pp_addr RA ; RA := Keyboard peripheral address pointer + ldr RA RA ; RA := Keyboard peripheral address deref + ldr RA RA ; RA := <16-bit keyboard character code> from MMIO register + ldi vt_term_pp_addr RB ; RB := Terminal peripheral address pointer + ldr RB RB ; RB := Terminal peripheral address deref + str RA RB ; terminal <- character code (ASCII?) + mov RA RE ; RE := character code (for debugging) + spo RB + spo RA + rti +@label program_segment + +; define short prelude that sets up vital instructions +; NOTE: must be shorter than the vector table offset +@address 0x0 +; set up stack pointer to point to beginning of stack segment + ldi vt_stack_addr RA + ldr RA SP + jpi program_segment + +@address program_segment +; set graphics mode to disabled + ldi vt_gr_mode_addr RA + ldr RA RA + ldi gr_disabled_mode RB + str RB RA +; jump to main (user defined) + jpi main diff --git a/test/e2e/emulation_speed/long_loop.atk16 b/test/e2e/emulation_speed/long_loop.atk16 new file mode 100644 index 0000000..2303311 --- /dev/null +++ b/test/e2e/emulation_speed/long_loop.atk16 @@ -0,0 +1,16 @@ +; bootstrap code (must be called to setup mandatory data) +; will jump to label main +@include bootstrap + +@label iters + 10000 + +@label main + ldi iters RA + ldr RA RA +@label loop + dec RA + bri zero done + jpi loop +@label done + hlt diff --git a/test/e2e/emulation_speed/test_emulation_speed.py b/test/e2e/emulation_speed/test_emulation_speed.py new file mode 100644 index 0000000..f2bcd22 --- /dev/null +++ b/test/e2e/emulation_speed/test_emulation_speed.py @@ -0,0 +1,43 @@ +import time +from atk16_asm import assemble +from atk16_emu import Machine +from test.utils import pad_bytearray + +def test_emulation_speed(): + filename = "test/e2e/emulation_speed/long_loop.atk16" + with open(filename, "r") as f: + source = f.read() + + obj = assemble(source, filename) + rom_image = pad_bytearray(obj.program) + + machine = Machine() + machine.load_rom_image(rom_image) + machine.reset() + start = time.perf_counter_ns() + machine.run_until_halted() + end = time.perf_counter_ns() + machine.print_state_summary() + + elapsed = end - start + print("Elapsed time:", elapsed, "ns") + steps_taken = machine.steps_taken + mean_ns_per_instr = elapsed / steps_taken + print("Instructions / second:", steps_taken / (elapsed / 1e9)) + print("ns / instruction:", mean_ns_per_instr) + + # VGA frequency is 25.175 MHz + # Divided by 16, that's 1.5734375 MHz (main clock) + # Instructions take 3–5 cycles, the average instruction takes around 4 cycles + # 1.5734375 MHz / 4 = 393359.375 Hz + # That is the goal instruction frequency + # TODO limit emulation speed to 393359.375 Hz ~= 2542 ns per instruction + # this is in order to have similar speed as the actual machine + + expected = 2542 + allowed_range = 0.05 # 0..1 + expected_min = expected * (1 - allowed_range) + expected_max = expected * (1 + allowed_range) + percentage = 100 * mean_ns_per_instr / expected + assert expected_min < mean_ns_per_instr, f"Emulator seems to run too fast ({percentage:.2f}% of expected ns per instruction)" + assert mean_ns_per_instr < expected_max, f"Emulator seems to run too slow ({percentage:.2f}% of expected ns per instruction)" diff --git a/test/e2e/fibo/test_fibo.py b/test/e2e/fibo/test_fibo.py index 65af07a..664e701 100644 --- a/test/e2e/fibo/test_fibo.py +++ b/test/e2e/fibo/test_fibo.py @@ -22,13 +22,6 @@ def test_fibo(): elapsed = end - start print("Elapsed time:", elapsed, "ns") steps_taken = machine.steps_taken - - # VGA frequency is 25.175 MHz - # Divided by 16, that's 1.5734375 MHz (main clock) - # Instructions take 3–5 cycles, the average instruction takes around 4 cycles - # 1.5734375 MHz / 4 = 393359.375 Hz - # That is the goal instruction frequency - # TODO limit emulation speed to 393359.375 Hz print("Instructions / second:", steps_taken / (elapsed / 1e9)) assert machine.ra.value == 41443 |
