diff options
| -rw-r--r-- | atk16_asm/builtin_asm/bootstrap.atk16 | 4 | ||||
| -rw-r--r-- | atk16_emu/emu.py | 43 | ||||
| -rw-r--r-- | atk16_emu/memory.py | 27 | ||||
| -rw-r--r-- | resources/asm/monitor.atk16 | 56 | ||||
| -rw-r--r-- | resources/asm/monitor_macros.py | 16 |
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) |
