aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--atk16_asm/builtin_asm/bootstrap.atk164
-rw-r--r--atk16_emu/emu.py43
-rw-r--r--atk16_emu/memory.py27
-rw-r--r--resources/asm/monitor.atk1656
-rw-r--r--resources/asm/monitor_macros.py16
5 files changed, 124 insertions, 22 deletions
diff --git a/atk16_asm/builtin_asm/bootstrap.atk16 b/atk16_asm/builtin_asm/bootstrap.atk16
index b3b4254..2e06fb0 100644
--- a/atk16_asm/builtin_asm/bootstrap.atk16
+++ b/atk16_asm/builtin_asm/bootstrap.atk16
@@ -7,11 +7,13 @@
@let terminal_addr 0xE7F0
@let keyboard_addr 0xE7F1
@let gr_mode_addr 0xE7F2
+@let iset_addr 0xE7F3
@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
@@ -29,6 +31,7 @@
@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
+@let vt_iset_addr 0x1A ; Interrupt set/clear address
@address vector_table
no_op_isr ; 0x10
@@ -41,6 +44,7 @@
gr_mode_addr ; 0x17
sprite_mem ; 0x18
text_mem ; 0x19
+ iset_addr ; 0x1A
@label no_op_isr
rti
diff --git a/atk16_emu/emu.py b/atk16_emu/emu.py
index 0b4e675..d519e5f 100644
--- a/atk16_emu/emu.py
+++ b/atk16_emu/emu.py
@@ -129,7 +129,10 @@ class Machine:
self.steps_taken = 0
self.running = False
- self.is_in_irq = False
+
+ self.interrupts = Interrupts(4)
+ self.is_critical_section: bool = False
+ self.active_interrupt_line = None
self.peripherals_enabled = peripherals_enabled
if peripherals_enabled:
@@ -190,6 +193,13 @@ class Machine:
new_machine.running = self.running
+ new_machine.interrupts = Interrupts(self.interrupts.n)
+ for i in range(self.interrupts.n):
+ new_machine.interrupts.interrupt_lines[i] = self.interrupts.interrupt_lines[i]
+
+ new_machine.is_critical_section = self.is_critical_section
+ new_machine.active_interrupt_line = self.active_interrupt_line
+
return new_machine
def mem_read(self, addr: int):
@@ -211,6 +221,9 @@ class Machine:
self.peripherals.graphics.activate_tpu()
elif addr == 0xE7F2 and value == 0b10:
self.peripherals.graphics.activate_ppu()
+ elif addr == 0xE7F3:
+ state = value & 1
+ self.is_critical_section = state == 1
elif 0xF800 <= addr <= 0xFFFF:
self.peripherals.graphics.write(addr, value)
elif addr == 0xE7F0:
@@ -268,18 +281,26 @@ class Machine:
raise ValueError(f"Invalid flag number: {n}")
def set_irq_line(self, line: int):
- if not self.is_in_irq:
- self.ipc.value = self.pc.value
- isr_addr_pointer = 0x10 + line # see bootstrap.atk16 vector table
- isr_addr = self.mem_read(isr_addr_pointer)
- self.pc.value = isr_addr
- self.is_in_irq = True
+ self.interrupts.set_interrupt(line)
def step(self):
if not self.running:
raise RuntimeError("Machine is not running")
self.steps_taken += 1
+
+ priority_interrupt_line = self.interrupts.get_priority_interrupt()
+ if priority_interrupt_line is not None and not self.is_critical_section:
+ # TODO: instead of handling interrupts in Python like this, maybe emulate
+ # ISRP0 and ISRP1 instructions properly? Should be the same effect,
+ # ISRP0-1 are kind of magical instructions.
+ self.active_interrupt_line = priority_interrupt_line
+ self.ipc.value = self.pc.value
+ isr_addr_pointer = 0x10 + priority_interrupt_line # see bootstrap.atk16 vector table
+ isr_addr = self.mem_read(isr_addr_pointer)
+ self.pc.value = isr_addr
+ self.is_critical_section = True
+
pc_addr = self.pc.value
self.pc.step()
@@ -358,7 +379,6 @@ class Machine:
case ISRP0():
# interrupt service routine, read store PC in IPC register, set PC to ISRA value
-
raise NotImplementedError()
case ISRP1():
@@ -368,7 +388,12 @@ class Machine:
case RTI():
# return from interrupt routine, read PC from IPC register
self.pc.value = self.ipc.value
- self.is_in_irq = False
+ self.is_critical_section = False
+ if self.active_interrupt_line is None:
+ raise RuntimeError("RTI without active interrupt line")
+
+ self.interrupts.clear_interrupt(self.active_interrupt_line)
+ self.active_interrupt_line = None
case HLT():
self.running = False
diff --git a/atk16_emu/memory.py b/atk16_emu/memory.py
index 2ce9c85..a27344d 100644
--- a/atk16_emu/memory.py
+++ b/atk16_emu/memory.py
@@ -53,4 +53,29 @@ class RAM:
if value < 0 or value >= 2 ** self.data_bits:
raise ValueError(f"Value {value} is out of range for {self.data_bits}-bit RAM")
- self.memory[addr] = value \ No newline at end of file
+ self.memory[addr] = value
+
+
+class Interrupts:
+ def __init__(self, n: int):
+ self.n = n
+ self.interrupt_lines = [False] * n
+
+ def set_interrupt(self, i: int):
+ if i < 0 or i >= self.n:
+ raise ValueError(f"Interrupt {i} is out of range for {self.n}-interrupt system")
+
+ self.interrupt_lines[i] = True
+
+ def clear_interrupt(self, i: int):
+ if i < 0 or i >= self.n:
+ raise ValueError(f"Interrupt {i} is out of range for {self.n}-interrupt system")
+
+ self.interrupt_lines[i] = False
+
+ def get_priority_interrupt(self) -> int | None:
+ for i in range(self.n):
+ if self.interrupt_lines[i]:
+ return i
+
+ return None
diff --git a/resources/asm/monitor.atk16 b/resources/asm/monitor.atk16
index 9a9484f..d3b8756 100644
--- a/resources/asm/monitor.atk16
+++ b/resources/asm/monitor.atk16
@@ -15,8 +15,9 @@
@data input_buffer_p 0xE801
@data input_cursor_p 0xE802
-@data title_string " »» ATK16 monitor v0.1 »»"
+@data title_string " »» ATK16 monitor v0.1 »»"
@data subtitle_string " Run [help] for a list of commands"
+@data prompt_string " > "
@label main
; initialize heap allocator
@@ -65,22 +66,33 @@
calli put_string
; END
+ m_newline RA RB
+ m_newline RA RB
+
+ ldi prompt_string RA
+ calli put_string
+
; loop forever
@label loop
- calli wait_for_input_newline
-
; BEGIN critical section
- ; disable_interrupts
- ; <work>
- ; enable_interrupts
- ; END
+ m_set_critical RA RB
+
+ calli is_newline_pressed
+ subi RG 1 RG ; if is_newline_pressed() == 1, then loop_newline_pressed
+ bri zero loop_newline_pressed
+ jpi loop_finally
+
+@label loop_newline_pressed
+ ; TODO
+ jpi loop_finally
+
+@label loop_finally
+ m_clear_critical RA RB
+ ; END critical section
jpi loop
;;; Subroutines
-; TODO: move the implementation body (between stack_stash and stack_restore) to
-; a macro so that it can be inlined without function call overhead when needed
-
@label newline
; move cursor to the beginning of the next line
; parameters: none
@@ -131,7 +143,7 @@
ldi 10 RB ; RB := newline character
@label wait_for_input_newline_loop
ldi input_cursor_p RA ; RA := data segment pointer
- ldr RA RA ; RA := &&char
+ ldr RA RA ; RA := 0xE802
ldr RA RA ; RA := &char
ldr RA RA ; RA := char
@@ -141,4 +153,24 @@
jpi wait_for_input_newline_loop
@label wait_for_input_newline_done
stack_restore RA RB
- return \ No newline at end of file
+ return
+
+@label is_newline_pressed
+ ; check if the last character in the input buffer is a newline
+ ; parameters: none
+ ; returns: RG = 1 if newline is pressed, 0 otherwise
+ stack_stash RA RB
+ ldi 10 RB ; RB := newline character
+
+ ldi input_cursor_p RA ; RA := data segment pointer
+ ldr RA RA ; RA := 0xE802
+ ldr RA RA ; RA := &char
+ ldr RA RA ; RA := char
+
+ ldi 1 RG ; RG := 1
+ sub RA RB RA ; if RA == newline, then return RG = 1
+ bri zero is_newline_pressed_done
+ ldi 0 RG ; else return RG = 0
+@label is_newline_pressed_done
+ stack_restore RA RB
+ return
diff --git a/resources/asm/monitor_macros.py b/resources/asm/monitor_macros.py
index e753996..b40be9e 100644
--- a/resources/asm/monitor_macros.py
+++ b/resources/asm/monitor_macros.py
@@ -27,3 +27,19 @@ def m_put_char(char_r: str, r1: str, r2: str) -> ExpandResult:
*expand_addi(r2, "1", r2),
*expand_str(r2, r1),
]
+
+def m_set_interrupt_state(r1: str, r2: str, value: int) -> ExpandResult:
+ return [
+ *expand_ldi("vt_iset_addr", r1),
+ *expand_ldr(r1, r1),
+ *expand_ldi(value, r2),
+ *expand_str(r2, r1),
+ ]
+
+@register_macro
+def m_set_critical(r1: str, r2: str) -> ExpandResult:
+ return m_set_interrupt_state(r1, r2, 1)
+
+@register_macro
+def m_clear_critical(r1: str, r2: str) -> ExpandResult:
+ return m_set_interrupt_state(r1, r2, 0)