From 3d5418c0a1364e866d4a9c2e49528db868de4869 Mon Sep 17 00:00:00 2001 From: sql-hkr Date: Sat, 25 Oct 2025 18:51:57 +0900 Subject: [PATCH] Add BRGE and BRLT branch instructions (#10) Implemented op_brge and op_brlt methods for signed conditional branching in the CPU class. Updated the README to document these instructions and added unit tests to verify correct control flow behavior. --- README.md | 2 +- src/tiny8/cpu.py | 36 +++++++++++++++++++++++++++++++++++- tests/test_control_flow.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a5e08eb..256af0c 100644 --- a/README.md +++ b/README.md @@ -221,7 +221,7 @@ Below is a concise, categorized summary of the Tiny8 instruction set (mnemonics - JMP label / RJMP offset — unconditional jump - CALL label / RCALL offset — call subroutine (push return address) - RET / RETI — return from subroutine / return from interrupt (sets I) - - BRNE / BREQ / BRCS / BRCC — conditional branches based on flags + - BRNE / BREQ / BRCS / BRCC / BRGE / BRLT — conditional branches based on flags - CP Rd, Rr / CPI Rd, K — compare (sets flags) - CPSE Rd, Rr — compare and skip if equal diff --git a/src/tiny8/cpu.py b/src/tiny8/cpu.py index f3e4cf3..5d8b6cb 100644 --- a/src/tiny8/cpu.py +++ b/src/tiny8/cpu.py @@ -549,7 +549,19 @@ def op_out(self, port: int, rr: int): val = self.read_reg(rr) self.write_ram(port, val) - def op_jmp(self, label: str): + def op_jmp(self, label: str | int): + """Jump to a given label or numeric address by updating the program counter. + + This operation sets the CPU's program counter (self.pc) to the target address minus one. + The subtraction of one accounts for the fact that the instruction dispatcher will typically + increment the program counter after the current instruction completes. + + Args: + label (str | int): The jump target. If a string, it is treated as a symbolic label + and looked up in self.labels to obtain its numeric address. If an int (or any + value convertible to int), it is used directly as the numeric address. + """ + if isinstance(label, str): if label not in self.labels: raise KeyError(f"Label {label} not found") @@ -893,6 +905,28 @@ def op_brcc(self, label: str): if not c: self.op_jmp(label) + def op_brge(self, label: str | int): + """BRGE - Branch if Greater or Equal (Signed) + + Args: + label: Destination label or address to jump to if the condition is met. + """ + + s = self.get_flag(SREG_S) + if not s: + self.op_jmp(label) + + def op_brlt(self, label: str | int): + """BRLT - Branch if Less Than (Signed). + + Args: + label: Destination label or address to jump to if the condition is met. + """ + + s = self.get_flag(SREG_S) + if s: + self.op_jmp(label) + def op_push(self, rr: int): """Push a register value onto the stack. diff --git a/tests/test_control_flow.py b/tests/test_control_flow.py index eb657d9..c351250 100644 --- a/tests/test_control_flow.py +++ b/tests/test_control_flow.py @@ -98,3 +98,39 @@ def test_cpse_skips_next(self): # If cpse skipped the next instruction, r6 should remain zero and r7 should be loaded self.assertEqual(cpu.read_reg(6), 0x00) self.assertEqual(cpu.read_reg(7), 0xBB) + + def test_brlt(self): + # BRLT: branch when signed (Rd < Rr). Use -1 (0xFF) < 1 (0x01) + src = """ + ldi r0, $FF ; -1 + ldi r1, $01 ; 1 + cp r0, r1 + brlt less + ldi r3, $01 + jmp done + less: + ldi r3, $02 + done: + nop + """ + cpu = self.run_asm(src) + # -1 < 1 so branch taken -> r3 == 2 + self.assertEqual(cpu.read_reg(3), 0x02) + + def test_brge(self): + # BRGE: branch when signed (Rd >= Rr). Use 1 >= -1 + src2 = """ + ldi r0, $01 ; 1 + ldi r1, $FF ; -1 + cp r0, r1 + brge ge + ldi r3, $01 + jmp done + ge: + ldi r3, $02 + done: + nop + """ + cpu2 = self.run_asm(src2) + # 1 >= -1 so branch taken -> r3 == 2 + self.assertEqual(cpu2.read_reg(3), 0x02)