From c9b8abcdf6b625d1cf0560ed1d7a0871e1cfb536 Mon Sep 17 00:00:00 2001 From: Gagan Kumar Date: Sun, 8 Dec 2024 13:11:44 -0800 Subject: [PATCH 1/7] Ping Pong GUI and controllers --- Makefile | 7 +- planner/__main__.py | 15 ++- planner/asm/line_parser.py | 10 +- planner/asm/program_parser.py | 14 ++- planner/asm/program_parser_test.py | 9 +- planner/instruction.py | 43 +++++++-- planner/instruction_test.py | 2 + planner/sim/bin_parser.py | 30 ++---- planner/sim/devices.py | 136 ++++++++++++++++++++++++-- planner/sim/gui_devices.py | 99 +++++++++++++++++++ planner/sim/programs/ping_pong.py | 40 ++++++++ planner/unit.py | 5 + programs/boot_sequence.asm | 48 +++++----- programs/ping_pong.asm | 148 +++++++++++++++++++++++++++++ programs/ping_pong.not_ready_asm | 94 ------------------ requirements.txt | 3 +- 16 files changed, 530 insertions(+), 173 deletions(-) create mode 100644 planner/sim/gui_devices.py create mode 100644 planner/sim/programs/ping_pong.py create mode 100644 programs/ping_pong.asm delete mode 100644 programs/ping_pong.not_ready_asm diff --git a/Makefile b/Makefile index 1a87f0f..ab029aa 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,13 @@ SRC_DIR=. BUILD_DIR=build OUTPUT_DIR=output -.PHONY: clean test +.PHONY: clean test all + +all: $(patsubst programs/%.asm, $(OUTPUT_DIR)/programs/%.bin, $(shell find programs/ -name '*.asm')) clean: rm -r $(BUILD_DIR) + rm -r (OUTPUT_DIR) include emulator/Makefile.mk @@ -18,5 +21,3 @@ $(OUTPUT_DIR)/programs/%.bin: programs/%.asm mkdir -p $(dir $@) python3 -m planner asm -b $^ > $@ - -all: $(patsubst programs/%.asm, $(OUTPUT_DIR)/programs/%.bin, $(shell find programs/ -name '*.asm')) diff --git a/planner/__main__.py b/planner/__main__.py index 83d7b51..c835d3a 100644 --- a/planner/__main__.py +++ b/planner/__main__.py @@ -5,6 +5,7 @@ from planner.asm import program_parser from planner.sim import bin_parser from planner.sim import devices +from planner.sim.programs import ping_pong def args_parser(): @@ -22,6 +23,9 @@ def args_parser(): bin_parser = subparsers.add_parser("bin", help="Parse Binary") bin_parser.add_argument("bin_file") + + bin_parser = subparsers.add_parser("compile_and_execute") + bin_parser.add_argument("program") return parser def main(): @@ -31,17 +35,22 @@ def main(): if args.source == "asm": asm = program_parser.AsmParser() with open(args.asm_file, "r") as f: - for line in f.readlines(): - asm.parse_line(line) + asm.parse_lines(f.readlines()) output = asm.get_str(resolved=args.resolved, rom_binary=args.rom_binary) print(output) if args.source == "bin": with open(args.bin_file, "r") as f: _bin = bin_parser.BinRunner(f.read()) _bin.set_input_device(5, devices.Numpad("id(5), range(0-9)")) - _bin.set_output_device(6, devices.IntegerOutput("Screen6")) + _bin.set_output_device(6, devices.IntegerOutput("Screen6", bits=16)) while True: _bin.step() + if args.source == "compile_and_execute": + if args.program == "ping_pong": + ping_pong.start() + else: + print(f"{args.program} not found") + if __name__ == '__main__': diff --git a/planner/asm/line_parser.py b/planner/asm/line_parser.py index a23b1f2..c5a84cf 100644 --- a/planner/asm/line_parser.py +++ b/planner/asm/line_parser.py @@ -9,6 +9,7 @@ def address_of(op: unit.Operand): raise AssertionError("can't take address of %s" % op) def parse_line(line: str) -> Tuple[Optional[str], Optional[List[Tuple[unit.Operand, unit.LazyLabel]]]]: + orginal_line = line try: comment_index = line.index("#") line = line[:comment_index] @@ -30,7 +31,7 @@ def parse_line(line: str) -> Tuple[Optional[str], Optional[List[Tuple[unit.Opera for op in operands: op = op.strip() if len(op) == 0: - raise ValueError("no-length operand found in %s" % line) + raise ValueError("no-length operand found in '%s'" % orginal_line) optype = unit.Operand.CONSTANT if op.startswith("[") and op.endswith("]"): optype = address_of(optype) @@ -41,12 +42,15 @@ def parse_line(line: str) -> Tuple[Optional[str], Optional[List[Tuple[unit.Opera if op.startswith("R"): if len(op) != 2 or op[1] not in "0123456789": - raise ValueError("Invalid mem-register {op} provided, only R0..R9 supported.") + raise ValueError(f"Invalid mem-register {op} provided, only R0..R9 supported.") optype = address_of(optype) op = str(int(op[1:])*4) try: - value = unit.LazyLabel(util.LABEL_CONSTANT, int(op, 0)) # automatically understand base-10 and base-16 + int_val = int(op, 0) + assert int_val < (1<<8), "only 1-bit value is supported" + assert int_val >= 0, "negative value in ASM is not yet supported" + value = unit.LazyLabel(util.LABEL_CONSTANT, int_val) # automatically understand base-10 and base-16 except ValueError as e: if util.is_valid_label(op): value = unit.LazyLabel(op) diff --git a/planner/asm/program_parser.py b/planner/asm/program_parser.py index 5248272..aacff3e 100644 --- a/planner/asm/program_parser.py +++ b/planner/asm/program_parser.py @@ -59,15 +59,16 @@ def get_str(self, resolved=False, rom_binary=False): if not rom_binary: _content.append(f"{util.PROGRAM_ORG} equ {self.lm.labels[util.PROGRAM_ORG].get()}") for add, x in self.final_bytes: + resolved_instruction_with_address = f"{add:03x}: {x.get_str(resolved=resolved, binary=False)}" if not rom_binary: - _content.append(f"{add:03x}: {x.get_str(resolved=resolved, binary=False)}") + _content.append(resolved_instruction_with_address) else: if track_binary_address is None: track_binary_address = add # first address can be util.PROGRAM_ORG assert track_binary_address == add, "gaps found in binary representation" out = f"{x.get_str(resolved=resolved, binary=True)}" _content.append(out) - assert len(out) % 8 == 0 + assert len(out) % 8 == 0, f"failed at {resolved_instruction_with_address}" track_binary_address += len(out)//8 content = '\n'.join(_content) @@ -212,3 +213,12 @@ def parse_line(self, line: str): self.parse_bss(tokens) else: raise Exception(f"unknown section: {self.get_section()}") + + def parse_lines(self, lines: List[str]): + line_no = 1 + for line in lines: + try: + self.parse_line(line) + except ValueError as e: + raise ValueError(f"Parse failed at {line_no}: {e}\n {line}") + line_no += 1 diff --git a/planner/asm/program_parser_test.py b/planner/asm/program_parser_test.py index 847a58f..4e34066 100644 --- a/planner/asm/program_parser_test.py +++ b/planner/asm/program_parser_test.py @@ -35,19 +35,16 @@ class AsmParserTest(TestCase): def test_asm_parser_binary(self): asm = program_parser.AsmParser() - for line in PROGRAM: - asm.parse_line(line) + asm.parse_lines(PROGRAM) output = asm.get_str(resolved=True, rom_binary=True) def test_asm_parser_unresolved(self): asm = program_parser.AsmParser() - for line in PROGRAM: - asm.parse_line(line) + asm.parse_lines(PROGRAM) output = asm.get_str(resolved=False, rom_binary=False) def test_asm_parser_resolved(self): asm = program_parser.AsmParser() - for line in PROGRAM: - asm.parse_line(line) + asm.parse_lines(PROGRAM) output = asm.get_str(resolved=True, rom_binary=False) diff --git a/planner/instruction.py b/planner/instruction.py index 8f9bd81..d61a4aa 100644 --- a/planner/instruction.py +++ b/planner/instruction.py @@ -98,23 +98,48 @@ class ALU(Enum): PASS_RW = 5 # vrw_value AND = 6 OR = 7 + XOR = 8 @staticmethod def wire(sel) -> List: - assert sel.value >= 0 and sel.value < 8 - return [sel.value%2, (sel.value>>1)%2, sel.value>>2] + return [sel.value%2, (sel.value>>1)%2, (sel.value>>2)%2, (sel.value>>3)] @classmethod def from_binary(cls, bits: List[int]): - value = bits[0]+bits[1]*2+bits[2]*4 - assert value >= 0 and value <= 6 + value = bits[0]+bits[1]*2+bits[2]*4+bits[3]*8 return ALU(value) + @staticmethod + def execute(op, rw, r): + assert rw>=0 and rw<(1<<32) + assert r>=0 and r<(1<<32) + MASK = ((1<<32)-1) + if op == ALU.ADD: + return MASK&(rw+r) + if op == ALU.SUB: + # TODO: deal with negative number + return MASK&(rw-r) + if op == ALU.SHL: + return MASK&(rw<>r) + if op == ALU.PASS_R: + return MASK&(r) + if op == ALU.PASS_RW: + return MASK&(rw) + if op == ALU.AND: + return MASK&(rw&r) + if op == ALU.OR: + return MASK&(rw|r) + if op == ALU.XOR: + return MASK&(rw^r) + raise Exception(f"unsupported ALU op: {op}") + MAPPING = { - "alu_op" : [0, 1, 2], - "mblock_s1" : [3, 4], - "mblock_s2" : [5], - "mblock_s3" : [6, 7, 8], + "alu_op" : [0, 1, 2, 3], + "mblock_s1" : [4, 5], + "mblock_s2" : [6], + "mblock_s3" : [7, 8, 9], } class EncodedInstruction: @@ -346,6 +371,7 @@ def size(self): ("SHR", ALU.SHR), ("AND", ALU.AND), ("OR", ALU.OR), + ("XOR", ALU.XOR), ] ] + [ ParserInstruction(ins_name, unit.Operand.ADDRESS, unit.Operand.CONSTANT, @@ -360,6 +386,7 @@ def size(self): ("SHRC", ALU.SHR), ("ANDC", ALU.AND), ("ORC", ALU.OR), + ("XORC", ALU.XOR), ] ] diff --git a/planner/instruction_test.py b/planner/instruction_test.py index 0f9eb4f..908b97b 100644 --- a/planner/instruction_test.py +++ b/planner/instruction_test.py @@ -34,6 +34,8 @@ def test_all_instructions_validation(self): ("andc R9, 50", "ANDC [36], 50"), ("or R0, [65]", "OR [0], [65]"), ("orc R0, 65", "ORC [0], 65"), + ("xor R1, [44]", "XOR [4], [44]"), + ("xorc R1, 46", "XORC [4], 46"), ] instructions = set() diff --git a/planner/sim/bin_parser.py b/planner/sim/bin_parser.py index b8a60e5..9ec8b1e 100644 --- a/planner/sim/bin_parser.py +++ b/planner/sim/bin_parser.py @@ -53,7 +53,7 @@ def read_ram(self, addr: int, count: int) -> List[int]: else: ans.append(self.ram[addr+i]) - logging.info("RAM[%04x] => %s", addr, ans) + logging.debug("RAM[%04x] => %s", addr, ans) return ans def write_ram(self, addr: int, count: int, value: int) -> List[int]: @@ -66,7 +66,7 @@ def write_ram(self, addr: int, count: int, value: int) -> List[int]: for i in range(count): self.ram[(i+addr)%len(self.ram)] = arr_value[i] - logging.info("RAM[%04x] <= %s", addr, arr_value) + logging.debug("RAM[%04x] <= %s", addr, arr_value) def m_fetch_and_store_stage1( self, @@ -129,25 +129,7 @@ def m_fetch_and_store_stage3( def m_alu(self, rw: int, r: int, op: instruction.ALU): assert rw>=0 and rw<(1<<32) assert r>=0 and r<(1<<32) - MASK = ((1<<32)-1) - if op == instruction.ALU.ADD: - return MASK&(rw+r) - if op == instruction.ALU.SUB: - # TODO: deal with negative number - return MASK&(rw-r) - if op == instruction.ALU.SHL: - return MASK&(rw<>r) - if op == instruction.ALU.PASS_R: - return MASK&(r) - if op == instruction.ALU.PASS_RW: - return MASK&(rw) - if op == instruction.ALU.AND: - return MASK&(rw&r) - if op == instruction.ALU.OR: - return MASK&(rw|r) - raise Exception(f"unsupported ALU op: {op}") + return instruction.ALU.execute(op, rw, r) @staticmethod def m_pc_next(pc: int, value: int, flag_alu_zero: bool, update_program_counter: bool, is_powered_on: bool): @@ -161,11 +143,11 @@ def m_pc_next(pc: int, value: int, flag_alu_zero: bool, update_program_counter: def step(self): self.pc = self.pc_next self.pc_next = self.pc + 4 - logging.info("PC: 0x%x, flags: %s", self.pc, self.flags) + logging.debug("PC: 0x%x, flags: %s", self.pc, self.flags) ins_binary = self.read_ram(self.pc, 4) ins = instruction.FullyEncodedInstruction.from_binary(ins_binary) - logging.info("Instruction data: %s", ins) - logging.info("Instruction encoding: %s", + logging.debug("Instruction data: %s", ins) + logging.debug("Instruction encoding: %s", [str(x) for x in instruction.get_parsers_from_encoding(ins.encoded_instruction)]) mblock_s1 = ins.encoded_instruction.mblock_s1 mblock_s2 = ins.encoded_instruction.mblock_s2 diff --git a/planner/sim/devices.py b/planner/sim/devices.py index b8b570c..d4a375c 100644 --- a/planner/sim/devices.py +++ b/planner/sim/devices.py @@ -1,21 +1,28 @@ from typing import List import logging - +from threading import Thread +from copy import deepcopy +import time class Device: def __init__(self, bits = 8): - assert 1 <= bits and bits <= 32 + assert 0 <= bits and bits <= 32 + self.bits = bits self.value = [0 for _ in range(bits)] self.change_handlers = [] + def get_bit_count(self): + return self.bits + def add_change_handler(self, f): self.change_handlers.append(f) def _new_value(self, val): if val != self.value: - for handle in self.change_handlers: - handle(val, self.value) + old_value = self.value self.value = val + for handle in self.change_handlers: + handle(val, old_value) def get(self): return sum([self.value[i]<0 else 0 + + def refetch_voltage(self): + _i = 0 + for i in range(len(self.cathodes)): + for i2 in range(self.cathodes[i].get_bit_count()): + _j = 0 + for j in range(len(self.anodes)): + for j2 in range(self.anodes[j].get_bit_count()): + self.leds_voltage_diff[_i][_j] = ( + self.output_val_to_voltage(i2, self.cathodes[i].get()) - + self.output_val_to_voltage(j2, self.anodes[j].get()) + ) + _j += 1 + _i += 1 + + def recompute_brightness_step(self): + for i in range(self.height): + for j in range(self.width): + if self.leds_voltage_diff[i][j] > 3: + self.leds[i][j]=1 + else: + self.leds[i][j]=max(self.leds[i][j]-self.led_brightness_reduce_per_step, 0) + + def get_display_state(self): + state = [] + for i in range(self.height): + val = [x>self.led_brightness_threshold for x in self.leds[i]] + val = val[::-1] # lsb should support right most led + state.append(val) + return state + + def display_as_str(self): + state = self.get_display_state() + dis = [] + dis.append("+%s+"'' % ('-'*3*self.width)) + for i in range(len(state)): + val = ['#' if x else '.' for x in state[i]] + dis.append(''.join(["|%s|" % (x) for x in val])) + dis.append("+%s+"'' % ('-'*3*self.width)) + return '\n'.join(dis) + + + def display(self, only_if_changed=True): + new_display = self.get_display_state() + if only_if_changed and new_display == self.last_display: + return + self.last_display = new_display + print(new_display) diff --git a/planner/sim/gui_devices.py b/planner/sim/gui_devices.py new file mode 100644 index 0000000..8e61b4e --- /dev/null +++ b/planner/sim/gui_devices.py @@ -0,0 +1,99 @@ +from planner.sim import devices +import logging +import pygame + +class GUIDevice: + def __init__(self): + self.offset_x = 0 + self.offset_y = 0 + + def set_offset(self, offset: tuple[int, int]): + self.offset_x = offset[0] + self.offset_y = offset[1] + + def draw(self, surface): + pass + + def draw_width(self): + return 1 + + def draw_height(self): + return 1 + + +class GUIDeviceManager: + def __init__(self): + self.devices = [] + self.padding = 10 + + def add_device(self, offset: tuple[int, int], dev: GUIDevice): + dev.set_offset((offset[0]+self.padding, offset[1]+self.padding)) + self.devices.append(dev) + + def _draw(self): + pygame.draw.rect(self.surface, (255, 255, 255), pygame.Rect(0, 0, self.width, self.height)) + for dev in self.devices: + dev.draw(self.surface) + pygame.display.update() + + def draw_loop(self): + pygame.init() + self.width = self.padding*2 + max([d.offset_x+d.draw_width() for d in self.devices]) + self.height = self.padding*2 + max([d.offset_y+d.draw_height() for d in self.devices]) + self.surface = pygame.display.set_mode((self.width, self.height)) + while True: + self._draw() + + +class GUILed(GUIDevice): + def __init__(self, display: devices.LEDDisplay): + super(GUILed, self).__init__() + self.led_size = 10 + self.display = display + + _state = self.display.get_display_state() + self.lheight = len(_state) + self.lwidth = len(_state[0]) + + def draw(self, surface): + state = self.display.get_display_state() + for i in range(len(state)): + for j in range(len(state[0])): + color = (255, 0, 0) if state[i][j] else (150, 150, 150) + x = self.led_size*(2*j+1) + y = self.led_size*(2*i+1) + pygame.draw.rect(surface, color, pygame.Rect(self.offset_x + x, self.offset_y + y, self.led_size, self.led_size)) + + def draw_width(self): + return self.led_size*(2*self.lwidth) + + def draw_height(self): + return self.led_size*(2*self.lheight) + + + +class KeyPressedInput(GUIDevice): + def __init__(self, input: devices.LatchInput, key_bit_mapping): + super(KeyPressedInput, self).__init__() + self.input = input + self.key_bit_mapping = key_bit_mapping + + + def draw(self, surface): + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + if event.key in self.key_bit_mapping: + self.input.update_bit(self.key_bit_mapping[event.key], 1) + logging.info("Key pressed: %s", pygame.key.name(event.key)) + if event.type == pygame.KEYUP: + if event.key in self.key_bit_mapping: + self.input.update_bit(self.key_bit_mapping[event.key], 0) + logging.info("Key released: %s", pygame.key.name(event.key)) + + + def draw_width(self): + return 0 + + def draw_height(self): + return 0 + diff --git a/planner/sim/programs/ping_pong.py b/planner/sim/programs/ping_pong.py new file mode 100644 index 0000000..97b8a1a --- /dev/null +++ b/planner/sim/programs/ping_pong.py @@ -0,0 +1,40 @@ +from planner.asm import program_parser +from planner.sim import bin_parser, devices, gui_devices +from threading import Thread +import pygame + +PROGRAM_PATH = "programs/ping_pong.asm" + +def step_runner(_bin): + while True: + _bin.step() + +def start(): + asm = program_parser.AsmParser() + with open(PROGRAM_PATH, "r") as f: + asm.parse_lines(f.readlines()) + program_binary = asm.get_str(resolved=True, rom_binary=True) + + _bin = bin_parser.BinRunner(program_binary) + gui_manager = gui_devices.GUIDeviceManager() + + + input = devices.LatchInput("input", bits=4) + gui_manager.add_device((0, 0), gui_devices.KeyPressedInput(input, { + pygame.K_w: 0, + pygame.K_s: 1, + pygame.K_UP: 2, + pygame.K_DOWN: 3, + })) + _bin.set_input_device(1, input) + + + display = devices.LEDDisplay("LED", width_anode=16, height_cathode=8) + gui_manager.add_device((0, 0), gui_devices.GUILed(display)) + _bin.set_output_device(6, display.get_anodes()[0]) + _bin.set_output_device(7, display.get_cathodes()[0]) + + + processor = Thread(None, step_runner, None, (_bin, )) + processor.start() + gui_manager.draw_loop() diff --git a/planner/unit.py b/planner/unit.py index d8190cf..efcc839 100644 --- a/planner/unit.py +++ b/planner/unit.py @@ -37,6 +37,11 @@ def assign(self, o): if not isinstance(o.value, int): raise ValueError(f"{name} resolution failed during assign as {o.value}") + if 0 > o.value: + raise ValueError("only >=0 label values are supported, got: {o.value}") + if o.value >= (1<<8): + raise ValueError(f"only 1-bit label values are supported, got: {o.value}") + if self.value is None: self.value = o.value else: diff --git a/programs/boot_sequence.asm b/programs/boot_sequence.asm index 308d482..2e5ba79 100644 --- a/programs/boot_sequence.asm +++ b/programs/boot_sequence.asm @@ -2,37 +2,41 @@ # Program # ROM[BootSequence] # -# Input devices required -# * ROM[Program] output at 0 +# Input devices required at 0x0 +# * address # Output devices -# * ROM[Program] address-line at 0 +# * PROGRAM_ROM[address] at 0x0 # Really small program to copy ROM[Program] to RAM[Program] -PROGRAM_ORG equ 0x40 +PROGRAM_ORG equ 0x80 +PROGRAM_DEST equ 0x40 + +ROM_INPUT_VALUE equ 0x0 +ROM_OUTPUT_ADDRESS equ 0x0 section .text main: # read metadata: program size - movc R9, 0 # const -> ram - out 0, R9 # ram -> io - in R0, 0 # io -> ram - movc R8, 1 + # assume size mod 4 = 0 + movc R0, 0 + out 0x10, R0 + in R0, ROM_INPUT_VALUE + movc R5, 0 - movc R2, PROGRAM_ORG # const -> ram - movc R1, 1 # const -> ram + movc R1, 1 + movc R2, PROGRAM_DEST _copy_more: - out 0, R1 # ram -> io - in R3, 0 # io -> ram - add R1, R8 - add R2, R8 - store [R2], R3 # ram -> ram - # bytes left to copy - subc R0, 1 - cmp R0, R9 - jz _copy_completed - jmp _copy_more + out 0x10, R1 + in R3, 0x20 + store [R2], R3 + addc R2, 4 + addc R1, 4 + subc R0, 4 - _copy_completed: - jmp _copy_completed + cmp R0, R5 + # jmp to program if copy is completed + jz PROGRAM_DEST + + jmp _copy_more diff --git a/programs/ping_pong.asm b/programs/ping_pong.asm new file mode 100644 index 0000000..8a1f6eb --- /dev/null +++ b/programs/ping_pong.asm @@ -0,0 +1,148 @@ +# Program +# Two Player Ping Pong Game +# +# Controls +# Up/Down button for each player +# Game Reset button +# Display (16*8) +# +# X +# X +# X O X +# X +# X +# +# +# Chip +# H/W: 4+1 buttons, 2 8x8 LED matrix +# OUT[0..7] = LED Matrix Row +# OUT[8..15] = LED Matrix 1 Col +# OUT[16..23] = LED Matrix 2 Col + +# Input(1) = Numpad(0-7) +# Input(2) = Numpad(0-9) +# Output(6) = LED(width=16, low_is_enabled) +# Output(7) = LED(height=8, high_is_enabled) + + +PROGRAM_ORG equ 0x40 + +BAT_H equ 3 +BAT_MAXY equ 5 +OUTPUT_WIDTH equ 6 +OUTPUT_HEGHT equ 7 +INPUT_DEVICE equ 1 + +section .text + main: + + game: + # call print + jmp print + game_after_print: + jmp read_input_p1_up + game_after_read: + jmp sleep + sleep_end: + # only print, TODO: fix + jmp game + + + # call step + # jmp step + game_after_step: + jmp game + hlt_marker: + jmp hlt_marker + + #wait_led_lit: + # wait for 100 ins to let LED powered up + # mov [R0], 100 + # _wait_led_lit_internal: + # sub [R0], 1 + # cmp [R0], 0 + # jneq _wait_led_lit_internal + # ret + + read_input_p1_up: + IN R0, INPUT_DEVICE + movc R1, 0x1 + and R1, R0 + jz read_input_p1_down + subc [bat1_y], 1 + read_input_p1_down: + movc R1, 0x02 + and R1, R0 + jz read_input_p2_up + addc [bat1_y], 1 + read_input_p2_up: + movc R1, 0x04 + and R1, R0 + jz read_input_p2_down + subc [bat2_y], 1 + read_input_p2_down: + movc R1, 0x08 + and R1, R0 + jz game_after_read + addc [bat2_y], 1 + jmp game_after_read + + + + print: + ## Player 1 + # anode col + movc R0, 0x7F + shlc R0, 8 + xorc R0, 0xFF + # cathode row + movc R1, 0x3 + shl R1, [bat1_y] + OUT OUTPUT_WIDTH, R0 + OUT OUTPUT_HEGHT, R1 + + # jmp sleep2 + # sleep_end2: + ## Player 2 + # anode col + movc R0, 0xFF + shlc R0, 8 + xorc R0, 0xFE + # cathode row + movc R1, 0x3 + shl R1, [bat2_y] + OUT OUTPUT_WIDTH, R0 + OUT OUTPUT_HEGHT, R1 + + jmp game_after_print + + sleep: + movc R0, 0xF0 + shlc R0, 2 + _sleep: + subc R0, 1 + jz sleep_end + jmp _sleep + + + #sleep2: + # movc R0, 0xF0 + # shlc R0, 2 + # _sleep2: + # subc R0, 1 + # jz sleep_end2 + # jmp _sleep2 + +section .data + bat1_y dd 2 + bat2_y dd 2 + ball_x dd 7 + ball_y dd 2 + +section .text + step: + addc [ball_x], 1 + andc [ball_x], 0x07 + jmp game_after_step + + diff --git a/programs/ping_pong.not_ready_asm b/programs/ping_pong.not_ready_asm deleted file mode 100644 index 6855492..0000000 --- a/programs/ping_pong.not_ready_asm +++ /dev/null @@ -1,94 +0,0 @@ -# Program -# Two Player Ping Pong Game -# -# Controls -# Up/Down button for each player -# Game Reset button -# Display (16*8) -# -# X -# X -# X O X -# X -# X -# -# -# Chip -# H/W: 4+1 buttons, 2 8x8 LED matrix -# OUT[0..7] = LED Matrix Row -# OUT[8..15] = LED Matrix 1 Col -# OUT[16..23] = LED Matrix 2 Col - - -BAT_H equ 3 -BAT_MAXY equ 5 - -section .text - main: - - game: - call print - call step - jmp game - hlt - - wait_led_lit: - # wait for 100 ins to let LED powered up - mov [R0], 100 - _wait_led_lit_internal: - sub [R0], 1 - cmp [R0], 0 - jneq _wait_led_lit_internal - ret - - print: - # bat1 = 7< Date: Sun, 8 Dec 2024 19:20:33 -0800 Subject: [PATCH 2/7] Support for long jmp --- planner/asm/line_parser.py | 2 -- planner/instruction.py | 71 ++++++++++++++++++++------------------ planner/sim/bin_parser.py | 11 ++---- planner/sim/devices.py | 5 ++- planner/unit.py | 31 +++++++++++------ planner/util.py | 1 + programs/ping_pong.asm | 33 ++++++++++-------- 7 files changed, 82 insertions(+), 72 deletions(-) diff --git a/planner/asm/line_parser.py b/planner/asm/line_parser.py index c5a84cf..ffd6264 100644 --- a/planner/asm/line_parser.py +++ b/planner/asm/line_parser.py @@ -48,8 +48,6 @@ def parse_line(line: str) -> Tuple[Optional[str], Optional[List[Tuple[unit.Opera try: int_val = int(op, 0) - assert int_val < (1<<8), "only 1-bit value is supported" - assert int_val >= 0, "negative value in ASM is not yet supported" value = unit.LazyLabel(util.LABEL_CONSTANT, int_val) # automatically understand base-10 and base-16 except ValueError as e: if util.is_valid_label(op): diff --git a/planner/instruction.py b/planner/instruction.py index d61a4aa..075b531 100644 --- a/planner/instruction.py +++ b/planner/instruction.py @@ -51,21 +51,21 @@ class MBlockSelector_stage2(Enum): ''' bits: [read_ram(vrw_source) else read_ram(vr_value)] ''' - VRW_SOURCE_RAM = 0 - VR_VALUE_RAM = 1 + VRW_SOURCE_CONST = 0 + VRW_SOURCE_RAM = 1 + VR_VALUE_RAM = 2 # last one for reverse lookup DONT_CARE = 0 @classmethod def wire(cls, sel) -> List: assert isinstance(sel, cls) - return [sel.value%2] + return [sel.value%2, sel.value>>1] @classmethod def from_binary(cls, bin: List[int]): - assert len(bin) == 1 - val = bin[0] - assert val >= 0 and val < 2 + assert len(bin) == 2 + val = bin[0]+bin[1]*2 return cls(val) class MBlockSelector_stage3(Enum): @@ -99,6 +99,9 @@ class ALU(Enum): AND = 6 OR = 7 XOR = 8 + # vr_value<<8 | vrw_value + # Pretty specific operation for passing 16-bit long operand value + R_SHL8_RW_OR = 9 @staticmethod def wire(sel) -> List: @@ -133,13 +136,15 @@ def execute(op, rw, r): return MASK&(rw|r) if op == ALU.XOR: return MASK&(rw^r) + if op == ALU.R_SHL8_RW_OR: + return MASK&(rw|(r<<8)) raise Exception(f"unsupported ALU op: {op}") MAPPING = { "alu_op" : [0, 1, 2, 3], "mblock_s1" : [4, 5], - "mblock_s2" : [6], - "mblock_s3" : [7, 8, 9], + "mblock_s2" : [6, 7], + "mblock_s3" : [8, 9, 10], } class EncodedInstruction: @@ -242,20 +247,22 @@ def __init__(self, name: str, type_rw: unit.Operand, type_r: unit.Operand, encod self.type_rw = type_rw self.type_r = type_r self.encoded_instruction = encoded_instruction + self.is_value_16bit = (encoded_instruction.alu_op == ALU.R_SHL8_RW_OR) + if self.is_value_16bit: + assert self.type_rw == self.type_r - def expects_operands(self): - expects = [] - if self.type_rw != unit.Operand.IGNORE: - expects.append(self.type_rw) - if self.type_r != unit.Operand.IGNORE: - expects.append(self.type_r) + def expect_asm_operands(self): + if self.is_value_16bit: + expects = [self.type_r] + else: + expects = [self.type_rw, self.type_r] return expects def parse(self, values: List[Tuple[unit.Operand, int]]): return ParsedInstruction(self, values) def __str__(self) -> str: - expects = ', '.join([str(x) for x in self.expects_operands()]) + expects = ', '.join([str(x) for x in self.expect_asm_operands()]) return "%s %s" % (self.name, expects) @@ -266,20 +273,16 @@ def __init__(self, _parser: ParserInstruction, values: List[Tuple[unit.Operand, self.fully_encoded_instruction = self.get_fully_encoded_instruction(values) def get_fully_encoded_instruction(self, values: List[Tuple[unit.Operand, unit.LazyLabel]]): - if self.parser.expects_operands() != [t[0] for t in values]: - raise ValueError(f"{self.parser.name} want operand type {self.parser.expects_operands()}, given operand values {values}") - values_index = 0 - if self.parser.type_rw != unit.Operand.IGNORE: - address_rw = values[values_index][1] - values_index+=1 + if self.parser.expect_asm_operands() != [t[0] for t in values]: + raise ValueError(f"{self.parser.name} want operand type {self.parser.expect_asm_operands()}, given operand values {values}") + if self.parser.is_value_16bit: + assert len(values) == 1 + address_rw = values[0][1].bin_and(0xFF) + address_r = values[0][1].shr(8) else: - address_rw = unit.LazyLabel(util.LABEL_CONSTANT, 0) - if self.parser.type_r != unit.Operand.IGNORE: - address_r = values[values_index][1] - values_index+=1 - else: - address_r = unit.LazyLabel(util.LABEL_CONSTANT, 0) - assert len(values) == values_index + assert len(values) == 2 + address_rw = values[0][1] + address_r = values[1][1] return self.parser.encoded_instruction.plug(address_rw, address_r) def get_str(self, resolved=False, binary=False): @@ -347,17 +350,17 @@ def size(self): MBlockSelector_stage2.VRW_SOURCE_RAM, MBlockSelector_stage3.NO_WRITE, ALU.SUB)), - ParserInstruction("JMP", unit.Operand.IGNORE, unit.Operand.CONSTANT, + ParserInstruction("JMP", unit.Operand.CONSTANT, unit.Operand.CONSTANT, EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_CONST, - MBlockSelector_stage2.DONT_CARE, + MBlockSelector_stage2.VRW_SOURCE_CONST, MBlockSelector_stage3.PC_NEXT, - ALU.PASS_R)), + ALU.R_SHL8_RW_OR)), # TODO: Ensure flag_alu_zero is updated after stage3. - ParserInstruction("JZ", unit.Operand.IGNORE, unit.Operand.CONSTANT, + ParserInstruction("JZ", unit.Operand.CONSTANT, unit.Operand.CONSTANT, EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_CONST, - MBlockSelector_stage2.DONT_CARE, + MBlockSelector_stage2.VRW_SOURCE_CONST, MBlockSelector_stage3.PC_NEXT_IF_ZERO, - ALU.PASS_R)) + ALU.R_SHL8_RW_OR)) ] + [ ParserInstruction(ins_name, unit.Operand.ADDRESS, unit.Operand.ADDRESS, EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_RAM, diff --git a/planner/sim/bin_parser.py b/planner/sim/bin_parser.py index 9ec8b1e..125e94e 100644 --- a/planner/sim/bin_parser.py +++ b/planner/sim/bin_parser.py @@ -96,6 +96,8 @@ def m_fetch_and_store_stage2( return binary_array_num(self.read_ram(vr_value, 4)) # reading from 32-bit address if sel == instruction.MBlockSelector_stage2.VRW_SOURCE_RAM: return binary_array_num(self.read_ram(vrw_source, 4)) # reading from 8-bit address + if sel == instruction.MBlockSelector_stage2.VRW_SOURCE_CONST: + return vrw_source raise Exception(f"unsupported selector: {sel}") def m_fetch_and_store_stage3( @@ -131,15 +133,6 @@ def m_alu(self, rw: int, r: int, op: instruction.ALU): assert r>=0 and r<(1<<32) return instruction.ALU.execute(op, rw, r) - @staticmethod - def m_pc_next(pc: int, value: int, flag_alu_zero: bool, update_program_counter: bool, is_powered_on: bool): - if not is_powered_on: - return PROGRAM_ORG - if update_program_counter: - return value - # TODO: handle JEQ - return pc+4 - def step(self): self.pc = self.pc_next self.pc_next = self.pc + 4 diff --git a/planner/sim/devices.py b/planner/sim/devices.py index d4a375c..93efc4d 100644 --- a/planner/sim/devices.py +++ b/planner/sim/devices.py @@ -127,6 +127,9 @@ def __init__(self, name: str, width_anode=16, height_cathode=8): self.anodes = [IntegerOutput("anode", bits=self.width)] self.cathodes = [IntegerOutput("cathode", bits=self.height)] self.led_brightness_threshold = 0.1 + # led should be on for 0.02 secs to stay on + self.led_brightness_recompute_lag = 0.01 # secs + # led will stay on for 0.1 secs self.led_brightness_reduce_per_step = 0.1 for _ in range(self.height): self.leds.append([0]*self.width) @@ -151,7 +154,7 @@ def step(self): while True: self.recompute_brightness_step() self.display(only_if_changed=True) - time.sleep(0.01) + time.sleep(self.led_brightness_recompute_lag) def get_anodes(self): return self.anodes diff --git a/planner/unit.py b/planner/unit.py index efcc839..752d418 100644 --- a/planner/unit.py +++ b/planner/unit.py @@ -5,12 +5,13 @@ class LazyLabel: - def __init__(self, name: str, value: Optional[int] = None): + def __init__(self, name: str, value: Optional[int] = None, override_get = None): assert util.is_valid_label(name), ValueError(f"{name} is not a valid label") self.name = name if name == util.LABEL_CONSTANT: assert value is not None self.value = value + self.override_get = override_get def __repr__(self): if self.name == util.LABEL_CONSTANT: @@ -26,7 +27,19 @@ def __eq__(self, o): return self.name == o.name return False + def shr(self, shift): + def new_get(ensure_resolved=False): + return self.get(ensure_resolved)>>shift + return LazyLabel(util.LABEL_TMP, override_get=new_get) + + def bin_and(self, val): + def new_get(ensure_resolved=False): + return self.get(ensure_resolved)&val + return LazyLabel(util.LABEL_TMP, override_get=new_get) + def get(self, ensure_resolved=False): + if self.override_get is not None: + return self.override_get(ensure_resolved=ensure_resolved) if self.value is None: assert not ensure_resolved logging.info("LazyLabel[%s] is empty", self.name) @@ -35,13 +48,7 @@ def get(self, ensure_resolved=False): def assign(self, o): if not isinstance(o.value, int): - raise ValueError(f"{name} resolution failed during assign as {o.value}") - - if 0 > o.value: - raise ValueError("only >=0 label values are supported, got: {o.value}") - if o.value >= (1<<8): - raise ValueError(f"only 1-bit label values are supported, got: {o.value}") - + raise ValueError(f"{self.name} resolution failed during assign as {o.value}") if self.value is None: self.value = o.value else: @@ -59,17 +66,19 @@ def get_str(self, resolved=False): class Operand(Enum): ADDRESS = 1 CONSTANT = 2 - IGNORE = 3 # Address of address, equivalent of pointers - DADDRESS = 4 + DADDRESS = 3 def __repr__(self): return self.name class Data: - def __init__(self, byte: Optional[int]): + def __init__(self, byte: Optional[int] = None): self.byte=byte + if self.byte is not None: + if self.byte >= 256: + raise ValueError(f"data is more than a byte: {byte}") def size(self): return 1 diff --git a/planner/util.py b/planner/util.py index 9ba8b64..ae8c7c9 100644 --- a/planner/util.py +++ b/planner/util.py @@ -1,6 +1,7 @@ import string LABEL_CONSTANT = "constant" +LABEL_TMP = "__tmp__" PROGRAM_ORG = "PROGRAM_ORG" def is_valid_label(msg): diff --git a/programs/ping_pong.asm b/programs/ping_pong.asm index 8a1f6eb..538ca03 100644 --- a/programs/ping_pong.asm +++ b/programs/ping_pong.asm @@ -33,6 +33,15 @@ OUTPUT_WIDTH equ 6 OUTPUT_HEGHT equ 7 INPUT_DEVICE equ 1 +section .text + jmp main + +section .data + bat1_y dd 2 + bat2_y dd 2 + ball_x dd 7 + ball_y dd 2 + section .text main: @@ -101,8 +110,8 @@ section .text OUT OUTPUT_WIDTH, R0 OUT OUTPUT_HEGHT, R1 - # jmp sleep2 - # sleep_end2: + jmp sleep2 + sleep_end2: ## Player 2 # anode col movc R0, 0xFF @@ -125,19 +134,13 @@ section .text jmp _sleep - #sleep2: - # movc R0, 0xF0 - # shlc R0, 2 - # _sleep2: - # subc R0, 1 - # jz sleep_end2 - # jmp _sleep2 - -section .data - bat1_y dd 2 - bat2_y dd 2 - ball_x dd 7 - ball_y dd 2 + sleep2: + movc R0, 0xF0 + shlc R0, 2 + _sleep2: + subc R0, 1 + jz sleep_end2 + jmp _sleep2 section .text step: From 00dba7a003ab2aa01a11d0cd7f8f396b067d91e7 Mon Sep 17 00:00:00 2001 From: Gagan Kumar Date: Sat, 14 Dec 2024 00:29:54 -0800 Subject: [PATCH 3/7] Add multi-step instructions like push/pop with better test coverage --- Makefile | 7 +- planner/asm/line_parser.py | 22 ++- planner/asm/program_parser.py | 33 +++-- planner/instruction.py | 41 +++++- planner/instruction_test.py | 19 ++- planner/memory.py | 18 +++ planner/sim/bin_parser.py | 23 ++- planner/sim/bin_parser_test.py | 250 +++++++++++++++++++++++++-------- planner/util.py | 1 - 9 files changed, 326 insertions(+), 88 deletions(-) create mode 100644 planner/memory.py diff --git a/Makefile b/Makefile index ab029aa..fb960cf 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ SRC_DIR=. BUILD_DIR=build OUTPUT_DIR=output -.PHONY: clean test all +.PHONY: clean test all run_ping_pong all: $(patsubst programs/%.asm, $(OUTPUT_DIR)/programs/%.bin, $(shell find programs/ -name '*.asm')) @@ -13,7 +13,7 @@ clean: include emulator/Makefile.mk pytest: - pytest -s --log-cli-level=DEBUG + pytest -s --log-cli-level=INFO test: pytest test_verilog_modules @@ -21,3 +21,6 @@ $(OUTPUT_DIR)/programs/%.bin: programs/%.asm mkdir -p $(dir $@) python3 -m planner asm -b $^ > $@ + +run_ping_pong: + python3 -m planner -v compile_and_execute ping_pong diff --git a/planner/asm/line_parser.py b/planner/asm/line_parser.py index ffd6264..b551fa1 100644 --- a/planner/asm/line_parser.py +++ b/planner/asm/line_parser.py @@ -1,5 +1,5 @@ from typing import List, Tuple, Optional -from planner import unit, util +from planner import unit, util, memory def address_of(op: unit.Operand): if op == unit.Operand.CONSTANT: @@ -27,7 +27,11 @@ def parse_line(line: str) -> Tuple[Optional[str], Optional[List[Tuple[unit.Opera # Parsing operands line = line[first_space:].strip() - operands = line.split(",") + if len(line) == 0: + # no operand + operands = [] + else: + operands = line.split(",") for op in operands: op = op.strip() if len(op) == 0: @@ -40,12 +44,16 @@ def parse_line(line: str) -> Tuple[Optional[str], Optional[List[Tuple[unit.Opera optype = address_of(optype) op = op[1:-1].strip() - if op.startswith("R"): - if len(op) != 2 or op[1] not in "0123456789": - raise ValueError(f"Invalid mem-register {op} provided, only R0..R9 supported.") + if op.upper() == memory.TOKEN_ESP: optype = address_of(optype) - op = str(int(op[1:])*4) - + op = str(memory.ESP) + elif op.startswith("R"): + optype = address_of(optype) + try: + rindex = int(op[1:]) + except ValueError as e: + raise ValueError(f"Invalid mem-register {op} provided, only R0..R{memory.GENERAL_REGISTERS_COUNT} supported: {e}") + op = str(memory.get_register_address(rindex)) try: int_val = int(op, 0) value = unit.LazyLabel(util.LABEL_CONSTANT, int_val) # automatically understand base-10 and base-16 diff --git a/planner/asm/program_parser.py b/planner/asm/program_parser.py index aacff3e..1d0d006 100644 --- a/planner/asm/program_parser.py +++ b/planner/asm/program_parser.py @@ -1,7 +1,7 @@ import logging from typing import List, Union, Optional -from planner import instruction, unit, util +from planner import instruction, unit, util, memory from planner.asm import line_parser @@ -52,19 +52,19 @@ def get_str(self, resolved=False, rom_binary=False): resolved = True # implict if resolved: self.lm.propogate() - assert util.PROGRAM_ORG in self.lm.labels, f"{util.PROGRAM_ORG} label must be defined" + assert memory.LABEL_PROGRAM_ORG in self.lm.labels, f"{memory.LABEL_PROGRAM_ORG} label must be defined" track_binary_address = None _content = [] if not rom_binary: - _content.append(f"{util.PROGRAM_ORG} equ {self.lm.labels[util.PROGRAM_ORG].get()}") + _content.append(f"{memory.LABEL_PROGRAM_ORG} equ {self.lm.labels[memory.LABEL_PROGRAM_ORG].get()}") for add, x in self.final_bytes: resolved_instruction_with_address = f"{add:03x}: {x.get_str(resolved=resolved, binary=False)}" if not rom_binary: _content.append(resolved_instruction_with_address) else: if track_binary_address is None: - track_binary_address = add # first address can be util.PROGRAM_ORG + track_binary_address = add # first address can be memory.LABEL_PROGRAM_ORG assert track_binary_address == add, "gaps found in binary representation" out = f"{x.get_str(resolved=resolved, binary=True)}" _content.append(out) @@ -134,7 +134,8 @@ def parse_text(self, tokens: List[str], line: str): return for _, token in tokens: self.lm.add_lazy(token) - self.add_ins(instruction.get_parser(ins_name).parse(tokens)) + for ins in instruction.parse(ins_name, tokens): + self.add_ins(ins) def parse_data(self, tokens: List[str]): label = tokens[0] @@ -164,23 +165,25 @@ def parse_data(self, tokens: List[str]): self.add_data(unit.Data(byte)) def parse_bss(self, tokens: List[str]): - if len(tokens) == 3: - assert tokens[0].endswith(":"), f"bss tokens: {tokens}" + nothing_happened = True + if len(tokens) >= 1 and tokens[0].endswith(":"): label = tokens[0][:-1] self.lm.new_label(label, self.get_address()) tokens = tokens[1:] - - assert tokens[0] == "resb" - sz = int(tokens[1]) - for _ in range(sz): - self.add_data(unit.Data(None)) + nothing_happened = False + if len(tokens) >= 1 and tokens[0] == "resb": + sz = int(tokens[1]) + for _ in range(sz): + self.add_data(unit.Data(None)) + nothing_happened = False + assert not nothing_happened def parse_constant(self, tokens: List[str]): assert tokens[1].lower() == "equ" self.lm.new_label(tokens[0], int(tokens[2], 0)) - if tokens[0] == util.PROGRAM_ORG: - assert self.get_address() == 0, f"{util.PROGRAM_ORG} must be the first label defined" - self.add_address(self.lm.labels[util.PROGRAM_ORG].get()) + if tokens[0] == memory.LABEL_PROGRAM_ORG: + assert self.get_address() == 0, f"{memory.LABEL_PROGRAM_ORG} must be the first label defined" + self.add_address(self.lm.labels[memory.LABEL_PROGRAM_ORG].get()) return def parse_line(self, line: str): diff --git a/planner/instruction.py b/planner/instruction.py index 075b531..21655c1 100644 --- a/planner/instruction.py +++ b/planner/instruction.py @@ -1,7 +1,7 @@ from enum import Enum from typing import List, Union, Tuple -from planner import unit, util +from planner import memory, unit, util ''' # Stage 0 @@ -75,6 +75,7 @@ class MBlockSelector_stage3(Enum): VRW_VALUE_RAM = 3 PC_NEXT = 4 PC_NEXT_IF_ZERO = 5 + HLT = 6 @classmethod def wire(cls, sel) -> List: @@ -350,6 +351,11 @@ def size(self): MBlockSelector_stage2.VRW_SOURCE_RAM, MBlockSelector_stage3.NO_WRITE, ALU.SUB)), + ParserInstruction("HLT", unit.Operand.CONSTANT, unit.Operand.CONSTANT, + EncodedInstruction(MBlockSelector_stage1.DONT_CARE, + MBlockSelector_stage2.DONT_CARE, + MBlockSelector_stage3.HLT, + ALU.PASS_R)), ParserInstruction("JMP", unit.Operand.CONSTANT, unit.Operand.CONSTANT, EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_CONST, MBlockSelector_stage2.VRW_SOURCE_CONST, @@ -360,7 +366,7 @@ def size(self): EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_CONST, MBlockSelector_stage2.VRW_SOURCE_CONST, MBlockSelector_stage3.PC_NEXT_IF_ZERO, - ALU.R_SHL8_RW_OR)) + ALU.R_SHL8_RW_OR)), ] + [ ParserInstruction(ins_name, unit.Operand.ADDRESS, unit.Operand.ADDRESS, EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_RAM, @@ -407,3 +413,34 @@ def get_parsers_from_encoding(ins: EncodedInstruction) -> ParserInstruction: if _ins.encoded_instruction == ins: ans.append(_ins) return ans + + +def parse(name: str, tokens: List[Tuple[unit.Operand, int]]): + # preprocess + if name.upper() == "HLT": + return [get_parser(name).parse([ + (unit.Operand.CONSTANT, unit.LazyLabel(util.LABEL_CONSTANT, 0)), + (unit.Operand.CONSTANT, unit.LazyLabel(util.LABEL_CONSTANT, 0)) + ] + tokens)] + + # multi-step instructions + if name == "PUSH": + return [ + get_parser("SUBC").parse([ + (unit.Operand.ADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, memory.ESP)), + (unit.Operand.CONSTANT, unit.LazyLabel(util.LABEL_CONSTANT, 4))]), + get_parser("STORE").parse([ + (unit.Operand.DADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, memory.ESP)), + ] + tokens) + ] + elif name == "POP": + return [ + get_parser("LOAD").parse(tokens + [ + (unit.Operand.DADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, memory.ESP)), + ]), + get_parser("ADDC").parse([ + (unit.Operand.ADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, memory.ESP)), + (unit.Operand.CONSTANT, unit.LazyLabel(util.LABEL_CONSTANT, 4))]) + ] + + return [get_parser(name).parse(tokens)] diff --git a/planner/instruction_test.py b/planner/instruction_test.py index 908b97b..c313afb 100644 --- a/planner/instruction_test.py +++ b/planner/instruction_test.py @@ -30,12 +30,14 @@ def test_all_instructions_validation(self): ("shlc R5, 11", "SHLC [20], 11"), ("shr R6, [12]", "SHR [24], [12]"), ("shrc R7, 12", "SHRC [28], 12"), - ("and R8, [50]", "AND [32], [50]"), - ("andc R9, 50", "ANDC [36], 50"), + ("and R1, [50]", "AND [4], [50]"), + ("andc R2, 50", "ANDC [8], 50"), ("or R0, [65]", "OR [0], [65]"), ("orc R0, 65", "ORC [0], 65"), ("xor R1, [44]", "XOR [4], [44]"), ("xorc R1, 46", "XORC [4], 46"), + + ("hlt 0, 0", "HLT 0, 0"), ] instructions = set() @@ -51,5 +53,18 @@ def test_all_instructions_validation(self): len(instruction.INSTRUCTIONS), msg="Sample instructions not provided for all instructions") + def test_multi_instructions(self): + test_data = [ + ("hlt", ["HLT 0, 0"]), + ("push R2", ["SUBC [32], 4", "STORE [[32]], [8]"]), + ("pop R2", ["LOAD [8], [[32]]", "ADDC [32], 4"]), + ] + for input_line, want in test_data: + name, tokens = line_parser.parse_line(input_line) + self.assertIsNotNone(name) + inss = instruction.parse(name, tokens) + self.assertEqual( + [' '.join(w.split()) for w in want], + [str(ins) for ins in inss]) diff --git a/planner/memory.py b/planner/memory.py new file mode 100644 index 0000000..08d0920 --- /dev/null +++ b/planner/memory.py @@ -0,0 +1,18 @@ +LABEL_PROGRAM_ORG = "PROGRAM_ORG" + +# General Register R0,..R15 +GENERAL_REGISTERS_COUNT = 8 +def get_register_address(index): + assert index >=0 and index < GENERAL_REGISTERS_COUNT + return index*4 + +# Stack Registers +TOKEN_ESP = "ESP" +ESP = 0x20 +ESB = 0x24 + +# free + +# user program + +DEFAULT_PROGRAM_ORG = 0x40 diff --git a/planner/sim/bin_parser.py b/planner/sim/bin_parser.py index 125e94e..d5fa796 100644 --- a/planner/sim/bin_parser.py +++ b/planner/sim/bin_parser.py @@ -2,12 +2,16 @@ import logging from typing import List, Optional -from planner import instruction +from planner import instruction, memory from planner.sim import devices -PROGRAM_ORG = 0x40 +logging.basicConfig(level=logging.INFO) + +PROGRAM_ORG = memory.DEFAULT_PROGRAM_ORG IO_DEVICES = 16 +RAM_SIZE = 0x10000 # 64KB + def binary_array_num(arr: List[int]): return sum([x<<(8*i) for i, x in enumerate(arr)]) @@ -17,7 +21,7 @@ def __init__(self, content): self.ram = [] self.input_devices = [None]*IO_DEVICES self.output_devices = [None]*IO_DEVICES - for _ in range(PROGRAM_ORG): + for _ in range(RAM_SIZE): self.ram.append(random.randint(0, 256)) self.parse(content) @@ -41,8 +45,10 @@ def parse(self, content: str): assert set(content) <= set(['0', '1']) program_size = int(content[:8], 2) assert program_size*8+8 == len(content) + address = PROGRAM_ORG for i in range(1, len(content)//8): - self.ram.append(int(content[i*8:(i+1)*8], 2)) + self.ram[address] = (int(content[i*8:(i+1)*8], 2)) + address += 1 def read_ram(self, addr: int, count: int) -> List[int]: ans = [] @@ -126,6 +132,9 @@ def m_fetch_and_store_stage3( if self.flags[FLAGS_BIT_VW_ZERO] == 1: self.pc_next = vw_value return + if sel == instruction.MBlockSelector_stage3.HLT: + self.is_powered_on = False + return raise Exception(f"unsupported selector: {sel}") def m_alu(self, rw: int, r: int, op: instruction.ALU): @@ -133,7 +142,13 @@ def m_alu(self, rw: int, r: int, op: instruction.ALU): assert r>=0 and r<(1<<32) return instruction.ALU.execute(op, rw, r) + def run_until_hlt(self): + while self.is_powered_on: + self.step() + def step(self): + if not self.is_powered_on: + return self.pc = self.pc_next self.pc_next = self.pc + 4 logging.debug("PC: 0x%x, flags: %s", self.pc, self.flags) diff --git a/planner/sim/bin_parser_test.py b/planner/sim/bin_parser_test.py index 23108a9..2f1e11a 100644 --- a/planner/sim/bin_parser_test.py +++ b/planner/sim/bin_parser_test.py @@ -4,63 +4,203 @@ from unittest import TestCase -PROGRAM = """ -# Sample Program - -PROGRAM_ORG equ 0x40 - -section .text -main: - movc R1, 0 - - # add array0+array1 to R1 - movc R2, array0 - load R3, [R2] - add R1, R3 - addc R2, 4 - load R3, [R2] - add R1, R3 - - movc R2, 15 - # input(0x05)*15 - in R4, 0x05 -loop_start: - cmpc R2, 0 - jz loop_end - subc R2, 1 - add R1, R4 - jmp loop_start -loop_end: - # answer is in R1 - movc R2, 12 # memory address of R3 - store [R2], R1 - out 0x06, R3 -loop_exit: - jmp loop_exit - -section .data -array0 dd 31 -array1 dd 24 - - -""".splitlines() - class BinParserTest(TestCase): + FAKE_INPUT_AT = 0x05 + FAKE_OUPUT_AT = 0x06 + + def setUp(self) -> None: + self.asm = program_parser.AsmParser() + self.fake_input = devices.LatchInput("fake", bits=32) + self.fake_ouput = devices.Device(bits=32) + self.bin = None + + def execute(self, program: str): + self.asm.parse_lines(program.splitlines()) + binary_program = self.asm.get_str(resolved=True, rom_binary=True) + + self.bin = bin_parser.BinRunner(binary_program) + self.bin.set_input_device(self.FAKE_INPUT_AT, self.fake_input) + self.bin.set_output_device(self.FAKE_OUPUT_AT, self.fake_ouput) + self.bin.run_until_hlt() + + def get_ram_byte(self, address: int): + return self.bin.read_ram(address, 1)[0] + + def test_io(self): + self.fake_input.set_input(10) + self.execute(f""" + PROGRAM_ORG equ 0x40 + section .text + main: + in R0, {self.FAKE_INPUT_AT} + out {self.FAKE_OUPUT_AT}, R0 + hlt + """) + self.assertEqual(self.fake_ouput.get(), 10) + + def test_mov(self): + self.execute(f""" + PROGRAM_ORG equ 0x40 + section .text + main: + movc R0, 0x45 + mov R1, R0 + out {self.FAKE_OUPUT_AT}, R1 + hlt + """) + self.assertEqual(self.fake_ouput.get(), 0x45) + + + def test_simple_alu(self): + self.execute(f""" + PROGRAM_ORG equ 0x40 + section .text + main: + movc R0, 1 + movc R1, 2 + movc R2, 2 + movc R3, 5 + movc R4, 5 + movc R5, 7 + movc R6, 7 + add R1, R0 + sub R2, R0 + shl R3, R0 + shr R4, R0 + or R5, R0 + xor R6, R0 + hlt + """) + expect = [ + (4, 3), + (8, 1), + (12, 10), + (16, 2), + (20, 7), + (24, 6) + ] + + for address, value in expect: + self.assertEqual(self.get_ram_byte(address), value) + + def test_simple_aluc(self): + self.execute(f""" + PROGRAM_ORG equ 0x40 + section .text + main: + movc R1, 9 + movc R2, 9 + movc R3, 9 + movc R4, 9 + movc R5, 9 + movc R6, 9 + addc R1, 10 + subc R2, 5 + shlc R3, 3 + shrc R4, 2 + orc R5, 3 + xorc R6, 3 + hlt + """) + expect = [ + (4, 19), + (8, 4), + (12, 72), + (16, 2), + (20, 11), + (24, 10) + ] + + for address, value in expect: + self.assertEqual(self.get_ram_byte(address), value) + + def test_stack(self): + self.fake_input.set_input(45) + self.execute(f""" + PROGRAM_ORG equ 0x40 + + section .text + main: + movc ESP, 0xFC + in R0, {self.FAKE_INPUT_AT} + push R0 + pop R1 + out {self.FAKE_OUPUT_AT}, R1 + hlt + """) + self.assertEqual(self.fake_ouput.get(), 45) + + def test_jmp(self): + self.fake_input.set_input(45) + self.execute(f""" + PROGRAM_ORG equ 0x40 + + section .text + main: + in R0, {self.FAKE_INPUT_AT} + cmpc R0, 10 # incorrect + jz bad + cmpc R0, 45 # correct + jz good1 + jmp bad + + good1: + movc R1, 10 + cmp R0, R1 # incorrect + jz bad + movc R1, 45 + cmp R0, R1 # correct + jz good2 + jmp bad + + good2: + jmp all_good + bad: + hlt + all_good: + out {self.FAKE_OUPUT_AT}, R0 + hlt + """) + self.assertEqual(self.fake_ouput.get(), 45) + + + def test_data_section(self): + self.fake_input.set_input(10) + self.execute(f""" + PROGRAM_ORG equ 0x40 + section .text + main: + shlc [array_0], 2 + mov R0, [array_0] + mov R1, [array_1] + mov R2, [array_2] + add R0, R1 + add R0, R2 + out {self.FAKE_OUPUT_AT}, R0 + hlt - def test_overall(self): - asm = program_parser.AsmParser() - for line in PROGRAM: - asm.parse_line(line) - binary_program = asm.get_str(resolved=True, rom_binary=True) + section .data + array_0 dd 11 + array_1 dd 22 + array_2 dd 30 + """) + self.assertEqual(self.fake_ouput.get(), 96) - _bin = bin_parser.BinRunner(binary_program) - fake_input = devices.LatchInput("fake", bits=32) - fake_ouput = devices.Device(bits=32) - _bin.set_input_device(5, fake_input) - _bin.set_output_device(6, fake_ouput) + def test_bss_section(self): + self.fake_input.set_input(10) + self.execute(f""" + PROGRAM_ORG equ 0x40 + section .text + main: + movc R0, array_end + subc R0, array_start + out {self.FAKE_OUPUT_AT}, R0 + hlt - fake_input.set_input(56) - for _ in range(100): - _bin.step() - self.assertEqual(fake_ouput.get(), 31+24+56*15) + section .bss + array_start: resb 4 + resb 6 + array_end: + """) + self.assertEqual(self.fake_ouput.get(), 10) diff --git a/planner/util.py b/planner/util.py index ae8c7c9..a485688 100644 --- a/planner/util.py +++ b/planner/util.py @@ -2,7 +2,6 @@ LABEL_CONSTANT = "constant" LABEL_TMP = "__tmp__" -PROGRAM_ORG = "PROGRAM_ORG" def is_valid_label(msg): if len(msg) == 0: From 5a13a99a7630d77ec477cc15e4f9853f9de322b7 Mon Sep 17 00:00:00 2001 From: Gagan Kumar Date: Sat, 14 Dec 2024 15:41:32 -0800 Subject: [PATCH 4/7] Implement CALL and RET instruction --- .gitignore | 2 +- Makefile | 13 +++- output/programs/3_led_switch.bin | 4 -- planner/__main__.py | 2 +- planner/asm/program_parser.py | 4 +- planner/instruction.py | 120 ++++++++++++++++++++++++++----- planner/instruction_test.py | 8 ++- planner/memory.py | 2 +- planner/sim/bin_parser.py | 22 ++++-- planner/sim/bin_parser_test.py | 73 ++++++++++++++++--- planner/unit.py | 19 ++++- programs/ping_pong.asm | 79 +++++++------------- 12 files changed, 248 insertions(+), 100 deletions(-) delete mode 100644 output/programs/3_led_switch.bin diff --git a/.gitignore b/.gitignore index d8b2e44..49e924e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .vscode/ __pycache__/ build/ -output/ \ No newline at end of file +output/ diff --git a/Makefile b/Makefile index fb960cf..cd2e88b 100644 --- a/Makefile +++ b/Makefile @@ -2,13 +2,13 @@ SRC_DIR=. BUILD_DIR=build OUTPUT_DIR=output -.PHONY: clean test all run_ping_pong +.PHONY: clean test all run_ping_pong all_programs_binary all_programs_resolved -all: $(patsubst programs/%.asm, $(OUTPUT_DIR)/programs/%.bin, $(shell find programs/ -name '*.asm')) +all: all_programs_binary all_programs_resolved clean: rm -r $(BUILD_DIR) - rm -r (OUTPUT_DIR) + rm -r $(OUTPUT_DIR) include emulator/Makefile.mk @@ -17,10 +17,17 @@ pytest: test: pytest test_verilog_modules +all_programs_binary: $(patsubst programs/%.asm, $(OUTPUT_DIR)/programs/%.bin, $(shell find programs/ -name '*.asm')) + $(OUTPUT_DIR)/programs/%.bin: programs/%.asm mkdir -p $(dir $@) python3 -m planner asm -b $^ > $@ +all_programs_resolved: $(patsubst programs/%.asm, $(OUTPUT_DIR)/programs/%_resolved.asm, $(shell find programs/ -name '*.asm')) + +$(OUTPUT_DIR)/programs/%_resolved.asm: programs/%.asm + mkdir -p $(dir $@) + python3 -m planner asm -r $^ > $@ run_ping_pong: python3 -m planner -v compile_and_execute ping_pong diff --git a/output/programs/3_led_switch.bin b/output/programs/3_led_switch.bin deleted file mode 100644 index cce40d0..0000000 --- a/output/programs/3_led_switch.bin +++ /dev/null @@ -1,4 +0,0 @@ -00001100 01000010 00000001 00000001 -00000101 01001000 00000001 00000110 -00000001 11001111 00000000 00000000 -01000000 diff --git a/planner/__main__.py b/planner/__main__.py index c835d3a..d363b10 100644 --- a/planner/__main__.py +++ b/planner/__main__.py @@ -31,7 +31,7 @@ def args_parser(): def main(): args = args_parser().parse_args() if args.verbose: - logging.basicConfig(level=logging.INFO) + logging.basicConfig(level=logging.DEBUG) if args.source == "asm": asm = program_parser.AsmParser() with open(args.asm_file, "r") as f: diff --git a/planner/asm/program_parser.py b/planner/asm/program_parser.py index 1d0d006..2474253 100644 --- a/planner/asm/program_parser.py +++ b/planner/asm/program_parser.py @@ -81,8 +81,8 @@ def get_str(self, resolved=False, rom_binary=False): ) assert len(_program_content) % 8 == 0 _program_size = len(_program_content) // 8 # bytes - assert _program_size < 2**8 # as we are using 1 byte for metadata - _binary_content = f"{_program_size:08b}" + _program_content + assert _program_size < 2**32 # as we are using 4 bytes for metadata + _binary_content = f"{_program_size:032b}" + _program_content assert len(_binary_content) % 8 == 0 assert set(_binary_content) <= set(['0', '1']), "only binary context is expected" diff --git a/planner/instruction.py b/planner/instruction.py index 21655c1..efc33a1 100644 --- a/planner/instruction.py +++ b/planner/instruction.py @@ -54,28 +54,35 @@ class MBlockSelector_stage2(Enum): VRW_SOURCE_CONST = 0 VRW_SOURCE_RAM = 1 VR_VALUE_RAM = 2 + # vr_source<<8 | vrw_source + # Pretty specific operation for passing 16-bit constant or address + VR_SOURCE_SHL8_VRW_SOURCE_RAM = 3 + VR_SOURCE_SHL8_VRW_SOURCE_CONST = 4 + PC = 5 + # last one for reverse lookup DONT_CARE = 0 @classmethod def wire(cls, sel) -> List: assert isinstance(sel, cls) - return [sel.value%2, sel.value>>1] + return [sel.value%2, (sel.value>>1)%2, sel.value>>2] @classmethod def from_binary(cls, bin: List[int]): - assert len(bin) == 2 - val = bin[0]+bin[1]*2 + assert len(bin) == 3 + val = bin[0]+bin[1]*2+bin[2]*4 return cls(val) class MBlockSelector_stage3(Enum): - NO_WRITE = 0 - VRW_SOURCE_RAM = 1 - VRW_SOURCE_IO = 2 - VRW_VALUE_RAM = 3 - PC_NEXT = 4 - PC_NEXT_IF_ZERO = 5 - HLT = 6 + NO_WRITE = 0 + VRW_SOURCE_RAM = 1 + VRW_SOURCE_IO = 2 + VRW_VALUE_RAM = 3 + PC_NEXT = 4 + PC_NEXT_IF_ZERO = 5 + PC_NEXT_IF_NOT_ZERO = 6 + HLT = 7 @classmethod def wire(cls, sel) -> List: @@ -144,8 +151,8 @@ def execute(op, rw, r): MAPPING = { "alu_op" : [0, 1, 2, 3], "mblock_s1" : [4, 5], - "mblock_s2" : [6, 7], - "mblock_s3" : [8, 9, 10], + "mblock_s2" : [6, 7, 8], + "mblock_s3" : [9, 10, 11], } class EncodedInstruction: @@ -331,6 +338,11 @@ def size(self): MBlockSelector_stage2.DONT_CARE, MBlockSelector_stage3.VRW_SOURCE_RAM, ALU.PASS_R)), + ParserInstruction("PCPLUS", unit.Operand.ADDRESS, unit.Operand.CONSTANT, + EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_CONST, + MBlockSelector_stage2.PC, + MBlockSelector_stage3.VRW_SOURCE_RAM, + ALU.ADD)), ParserInstruction("LOAD", unit.Operand.ADDRESS, unit.Operand.DADDRESS, EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_RAM, MBlockSelector_stage2.VR_VALUE_RAM, @@ -341,6 +353,11 @@ def size(self): MBlockSelector_stage2.VRW_SOURCE_RAM, MBlockSelector_stage3.VRW_VALUE_RAM, ALU.PASS_R)), + ParserInstruction("STOREC", unit.Operand.DADDRESS, unit.Operand.CONSTANT, + EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_CONST, + MBlockSelector_stage2.VRW_SOURCE_RAM, + MBlockSelector_stage3.VRW_VALUE_RAM, + ALU.PASS_R)), ParserInstruction("CMP", unit.Operand.ADDRESS, unit.Operand.ADDRESS, EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_RAM, MBlockSelector_stage2.VRW_SOURCE_RAM, @@ -358,15 +375,24 @@ def size(self): ALU.PASS_R)), ParserInstruction("JMP", unit.Operand.CONSTANT, unit.Operand.CONSTANT, EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_CONST, - MBlockSelector_stage2.VRW_SOURCE_CONST, + MBlockSelector_stage2.VR_SOURCE_SHL8_VRW_SOURCE_CONST, + MBlockSelector_stage3.PC_NEXT, + ALU.PASS_RW)), + ParserInstruction("JMPM", unit.Operand.ADDRESS, unit.Operand.ADDRESS, + EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_CONST, + MBlockSelector_stage2.VR_SOURCE_SHL8_VRW_SOURCE_RAM, MBlockSelector_stage3.PC_NEXT, - ALU.R_SHL8_RW_OR)), - # TODO: Ensure flag_alu_zero is updated after stage3. + ALU.PASS_RW)), ParserInstruction("JZ", unit.Operand.CONSTANT, unit.Operand.CONSTANT, EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_CONST, - MBlockSelector_stage2.VRW_SOURCE_CONST, + MBlockSelector_stage2.VR_SOURCE_SHL8_VRW_SOURCE_CONST, MBlockSelector_stage3.PC_NEXT_IF_ZERO, - ALU.R_SHL8_RW_OR)), + ALU.PASS_RW)), + ParserInstruction("JNZ", unit.Operand.CONSTANT, unit.Operand.CONSTANT, + EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_CONST, + MBlockSelector_stage2.VR_SOURCE_SHL8_VRW_SOURCE_CONST, + MBlockSelector_stage3.PC_NEXT_IF_NOT_ZERO, + ALU.PASS_RW)), ] + [ ParserInstruction(ins_name, unit.Operand.ADDRESS, unit.Operand.ADDRESS, EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_RAM, @@ -415,7 +441,7 @@ def get_parsers_from_encoding(ins: EncodedInstruction) -> ParserInstruction: return ans -def parse(name: str, tokens: List[Tuple[unit.Operand, int]]): +def parse(name: str, tokens: List[Tuple[unit.Operand, unit.LazyLabel]]): # preprocess if name.upper() == "HLT": return [get_parser(name).parse([ @@ -423,6 +449,30 @@ def parse(name: str, tokens: List[Tuple[unit.Operand, int]]): (unit.Operand.CONSTANT, unit.LazyLabel(util.LABEL_CONSTANT, 0)) ] + tokens)] + if name.upper() == "JMP": + assert len(tokens) == 1 + assert tokens[0][0] == unit.Operand.CONSTANT + return [get_parser(name).parse([ + (unit.Operand.CONSTANT, tokens[0][1].bin_and(0xFF)), + (unit.Operand.CONSTANT, tokens[0][1].shr(8)) + ])] + + if name.upper() == "JMPM": + assert len(tokens) == 1 + assert tokens[0][0] == unit.Operand.ADDRESS + return [get_parser(name).parse([ + (unit.Operand.ADDRESS, tokens[0][1].bin_and(0xFF)), + (unit.Operand.ADDRESS, tokens[0][1].shr(8)) + ])] + + if name.upper() == "JZ" or name.upper() == "JNZ": + assert len(tokens) == 1 + assert tokens[0][0] == unit.Operand.CONSTANT + return [get_parser(name).parse([ + (unit.Operand.CONSTANT, tokens[0][1].bin_and(0xFF)), + (unit.Operand.CONSTANT, tokens[0][1].shr(8)) + ])] + # multi-step instructions if name == "PUSH": return [ @@ -442,5 +492,39 @@ def parse(name: str, tokens: List[Tuple[unit.Operand, int]]): (unit.Operand.ADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, memory.ESP)), (unit.Operand.CONSTANT, unit.LazyLabel(util.LABEL_CONSTANT, 4))]) ] + elif name == "CALL": + assert len(tokens) == 1, "call _jmp_address_" + assert tokens[0][0] == unit.Operand.CONSTANT + return [ + get_parser("PCPLUS").parse([ + (unit.Operand.ADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, memory.MSI)), + (unit.Operand.CONSTANT, unit.LazyLabel(util.LABEL_CONSTANT, 16))]), + get_parser("SUBC").parse([ + (unit.Operand.ADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, memory.ESP)), + (unit.Operand.CONSTANT, unit.LazyLabel(util.LABEL_CONSTANT, 4))]), + get_parser("STORE").parse([ + (unit.Operand.DADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, memory.ESP)), + (unit.Operand.ADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, memory.MSI)) + ]), + get_parser("JMP").parse([ + (unit.Operand.CONSTANT, tokens[0][1].bin_and(0xFF)), + (unit.Operand.CONSTANT, tokens[0][1].shr(8)) + ]) + ] + elif name == "RET": + assert len(tokens) == 0 + return [ + get_parser("LOAD").parse([ + (unit.Operand.ADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, memory.MSI)), + (unit.Operand.DADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, memory.ESP)), + ]), + get_parser("ADDC").parse([ + (unit.Operand.ADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, memory.ESP)), + (unit.Operand.CONSTANT, unit.LazyLabel(util.LABEL_CONSTANT, 4))]), + get_parser("JMPM").parse([ + (unit.Operand.ADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, memory.MSI)), + (unit.Operand.ADDRESS, unit.LazyLabel(util.LABEL_CONSTANT, 0)) + ]) + ] return [get_parser(name).parse(tokens)] diff --git a/planner/instruction_test.py b/planner/instruction_test.py index c313afb..2f4b3ab 100644 --- a/planner/instruction_test.py +++ b/planner/instruction_test.py @@ -13,14 +13,17 @@ def test_all_instructions_validation(self): ("movc R4, 0x2", "MOVC [16], 2"), ("cmp R2, [33]", "CMP [8], [33]"), ("cmpc R6, 35", "CMPC [24], 35"), - ("jmp 0x55", "JMP 85"), - ("jz 0x22", "JZ 34"), + ("jmp 0x55, 0", "JMP 85, 0"), + ("jmpm [0x55], [0]", "JMPM [85], [0]"), + ("jz 0x22, 0", "JZ 34, 0"), + ("jnz 0x22, 0", "JNZ 34, 0"), ("load R3, [R1]", "LOAD [12], [[4]]"), ("load R4, [[50]]", "LOAD [16], [[50]]"), ("store [R1], R3", "STORE [[4]], [12]"), ("store [[50]], R4", "STORE [[50]], [16]"), + ("storec [[50]], 10", "STOREC [[50]], 10"), ("add R0, [10]", "ADD [0], [10]"), ("addc R1, 10", "ADDC [4], 10"), @@ -37,6 +40,7 @@ def test_all_instructions_validation(self): ("xor R1, [44]", "XOR [4], [44]"), ("xorc R1, 46", "XORC [4], 46"), + ("pcplus R0, 4", "PCPLUS [0], 4"), ("hlt 0, 0", "HLT 0, 0"), ] diff --git a/planner/memory.py b/planner/memory.py index 08d0920..f7a3b2d 100644 --- a/planner/memory.py +++ b/planner/memory.py @@ -9,7 +9,7 @@ def get_register_address(index): # Stack Registers TOKEN_ESP = "ESP" ESP = 0x20 -ESB = 0x24 +MSI = 0x24 # multi-step instruction state # free diff --git a/planner/sim/bin_parser.py b/planner/sim/bin_parser.py index d5fa796..eb797f9 100644 --- a/planner/sim/bin_parser.py +++ b/planner/sim/bin_parser.py @@ -5,8 +5,6 @@ from planner import instruction, memory from planner.sim import devices -logging.basicConfig(level=logging.INFO) - PROGRAM_ORG = memory.DEFAULT_PROGRAM_ORG IO_DEVICES = 16 @@ -43,10 +41,10 @@ def parse(self, content: str): content = content.replace(" ", "").replace("\n", "") assert len(content)%8 == 0 assert set(content) <= set(['0', '1']) - program_size = int(content[:8], 2) - assert program_size*8+8 == len(content) + program_size = int(content[:32], 2) + assert program_size*8+32 == len(content) address = PROGRAM_ORG - for i in range(1, len(content)//8): + for i in range(4, len(content)//8): self.ram[address] = (int(content[i*8:(i+1)*8], 2)) address += 1 @@ -94,9 +92,11 @@ def m_fetch_and_store_stage1( def m_fetch_and_store_stage2( self, + vr_source: int, vr_value: int, vrw_source: int, sel: instruction.MBlockSelector_stage2): + assert vr_source >= 0 and vr_source < 256 assert vrw_source >= 0 and vrw_source < 256 if sel == instruction.MBlockSelector_stage2.VR_VALUE_RAM: return binary_array_num(self.read_ram(vr_value, 4)) # reading from 32-bit address @@ -104,6 +104,12 @@ def m_fetch_and_store_stage2( return binary_array_num(self.read_ram(vrw_source, 4)) # reading from 8-bit address if sel == instruction.MBlockSelector_stage2.VRW_SOURCE_CONST: return vrw_source + if sel == instruction.MBlockSelector_stage2.VR_SOURCE_SHL8_VRW_SOURCE_RAM: + return binary_array_num(self.read_ram((vr_source<<8) | vrw_source, 4)) # reading from 16-bit address + if sel == instruction.MBlockSelector_stage2.VR_SOURCE_SHL8_VRW_SOURCE_CONST: + return (vr_source<<8) | vrw_source + if sel == instruction.MBlockSelector_stage2.PC: + return self.pc raise Exception(f"unsupported selector: {sel}") def m_fetch_and_store_stage3( @@ -132,6 +138,11 @@ def m_fetch_and_store_stage3( if self.flags[FLAGS_BIT_VW_ZERO] == 1: self.pc_next = vw_value return + if sel == instruction.MBlockSelector_stage3.PC_NEXT_IF_NOT_ZERO: + # check previous vw_value flags + if self.flags[FLAGS_BIT_VW_ZERO] == 0: + self.pc_next = vw_value + return if sel == instruction.MBlockSelector_stage3.HLT: self.is_powered_on = False return @@ -170,6 +181,7 @@ def step(self): vr_source, mblock_s1) vrw_value = self.m_fetch_and_store_stage2( + vr_source, vr_value, vrw_source, mblock_s2) diff --git a/planner/sim/bin_parser_test.py b/planner/sim/bin_parser_test.py index 2f1e11a..bf1c329 100644 --- a/planner/sim/bin_parser_test.py +++ b/planner/sim/bin_parser_test.py @@ -145,16 +145,22 @@ def test_jmp(self): jmp bad good1: - movc R1, 10 - cmp R0, R1 # incorrect - jz bad movc R1, 45 cmp R0, R1 # correct - jz good2 + jnz bad + movc R1, 10 + cmp R0, R1 # incorrect + jnz good2 jmp bad good2: - jmp all_good + jmp good3 + hlt + + good3: + movc R1, all_good + jmpm R1 + bad: hlt all_good: @@ -165,7 +171,6 @@ def test_jmp(self): def test_data_section(self): - self.fake_input.set_input(10) self.execute(f""" PROGRAM_ORG equ 0x40 section .text @@ -186,9 +191,7 @@ def test_data_section(self): """) self.assertEqual(self.fake_ouput.get(), 96) - def test_bss_section(self): - self.fake_input.set_input(10) self.execute(f""" PROGRAM_ORG equ 0x40 section .text @@ -204,3 +207,57 @@ def test_bss_section(self): array_end: """) self.assertEqual(self.fake_ouput.get(), 10) + + def test_load_store(self): + self.execute(f""" + PROGRAM_ORG equ 0x40 + section .text + main: + movc [array_ptr], array_0 + load R0, [[array_ptr]] + addc [array_ptr], 4 + + store [[array_ptr]], R0 + mov R1, [array_1] + storec [[array_ptr]], 10 + add R1, [array_1] + + out {self.FAKE_OUPUT_AT}, R1 + hlt + + section .data + array_0 dd 15 + section .bss + array_1: resb 4 + array_ptr: resb 4 + array_end: + """) + self.assertEqual(self.fake_ouput.get(), 25) + + def test_call(self): + self.fake_input.set_input(45) + self.execute(f""" + PROGRAM_ORG equ 0x40 + + section .text + main: + movc ESP, 0xFC + movc R1, 10 + movc R2, 15 + call sum + jmp end + hlt # guard bad marker + + sum: + push R1 + add R1, R2 + mov R0, R1 + pop R1 + ret + hlt # guard bad marker + + end: + out {self.FAKE_OUPUT_AT}, R0 + hlt + """) + self.assertEqual(self.fake_ouput.get(), 25) diff --git a/planner/unit.py b/planner/unit.py index 752d418..bea5681 100644 --- a/planner/unit.py +++ b/planner/unit.py @@ -29,19 +29,32 @@ def __eq__(self, o): def shr(self, shift): def new_get(ensure_resolved=False): + if not ensure_resolved: + return 0 return self.get(ensure_resolved)>>shift return LazyLabel(util.LABEL_TMP, override_get=new_get) + def add(self, val): + def new_get(ensure_resolved=False): + if not ensure_resolved: + return 0 + return self.get(ensure_resolved)+val + return LazyLabel(util.LABEL_TMP, override_get=new_get) + def bin_and(self, val): def new_get(ensure_resolved=False): + if not ensure_resolved: + return 0 return self.get(ensure_resolved)&val return LazyLabel(util.LABEL_TMP, override_get=new_get) def get(self, ensure_resolved=False): if self.override_get is not None: return self.override_get(ensure_resolved=ensure_resolved) + if ensure_resolved: + assert self.value is not None + return self.value if self.value is None: - assert not ensure_resolved logging.info("LazyLabel[%s] is empty", self.name) return 0 return self.value @@ -59,8 +72,8 @@ def get_str(self, resolved=False): return str(self.value) if not resolved: return self.name - assert isinstance(self.value, int), f"{self.name} is still not resolved" - return str(self.value) + value = self.get(ensure_resolved=True) + return str(value) class Operand(Enum): diff --git a/programs/ping_pong.asm b/programs/ping_pong.asm index 538ca03..52bbae1 100644 --- a/programs/ping_pong.asm +++ b/programs/ping_pong.asm @@ -34,7 +34,11 @@ OUTPUT_HEGHT equ 7 INPUT_DEVICE equ 1 section .text - jmp main + main: + movc ESP, 0xFF + shlc ESP, 8 + orc ESP, 0xF0 + jmp game section .data bat1_y dd 2 @@ -43,35 +47,19 @@ section .data ball_y dd 2 section .text - main: - game: - # call print - jmp print - game_after_print: - jmp read_input_p1_up - game_after_read: - jmp sleep - sleep_end: - # only print, TODO: fix + call print + call read_input_p1_up + call sleep jmp game # call step # jmp step game_after_step: - jmp game - hlt_marker: - jmp hlt_marker - - #wait_led_lit: - # wait for 100 ins to let LED powered up - # mov [R0], 100 - # _wait_led_lit_internal: - # sub [R0], 1 - # cmp [R0], 0 - # jneq _wait_led_lit_internal - # ret + # jmp game + # hlt_marker: + # jmp hlt_marker read_input_p1_up: IN R0, INPUT_DEVICE @@ -92,11 +80,10 @@ section .text read_input_p2_down: movc R1, 0x08 and R1, R0 - jz game_after_read + jz read_input_end addc [bat2_y], 1 - jmp game_after_read - - + read_input_end: + ret print: ## Player 1 @@ -110,8 +97,7 @@ section .text OUT OUTPUT_WIDTH, R0 OUT OUTPUT_HEGHT, R1 - jmp sleep2 - sleep_end2: + call sleep ## Player 2 # anode col movc R0, 0xFF @@ -122,30 +108,19 @@ section .text shl R1, [bat2_y] OUT OUTPUT_WIDTH, R0 OUT OUTPUT_HEGHT, R1 - - jmp game_after_print + ret sleep: - movc R0, 0xF0 - shlc R0, 2 - _sleep: - subc R0, 1 - jz sleep_end - jmp _sleep - - - sleep2: - movc R0, 0xF0 - shlc R0, 2 - _sleep2: - subc R0, 1 - jz sleep_end2 - jmp _sleep2 - -section .text - step: - addc [ball_x], 1 - andc [ball_x], 0x07 - jmp game_after_step + movc R0, 0xF0 + shlc R0, 1 + _sleep: + subc R0, 1 + jnz _sleep + ret + + #step: + # addc [ball_x], 1 + # andc [ball_x], 0x07 + # jmp game_after_step From 99b95c1f5e84ce86b8ac3ea1207dcbe25a5e1bcd Mon Sep 17 00:00:00 2001 From: Gagan Kumar Date: Sat, 14 Dec 2024 15:48:16 -0800 Subject: [PATCH 5/7] Fix general registers token --- planner/asm/line_parser.py | 4 ++-- planner/memory.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/planner/asm/line_parser.py b/planner/asm/line_parser.py index b551fa1..7b28e60 100644 --- a/planner/asm/line_parser.py +++ b/planner/asm/line_parser.py @@ -47,12 +47,12 @@ def parse_line(line: str) -> Tuple[Optional[str], Optional[List[Tuple[unit.Opera if op.upper() == memory.TOKEN_ESP: optype = address_of(optype) op = str(memory.ESP) - elif op.startswith("R"): + elif op in memory.TOKEN_GENERAL_REGISTERS: optype = address_of(optype) try: rindex = int(op[1:]) except ValueError as e: - raise ValueError(f"Invalid mem-register {op} provided, only R0..R{memory.GENERAL_REGISTERS_COUNT} supported: {e}") + raise ValueError(f"Invalid mem-register {op} provided, only R0..R{memory.GENERAL_REGISTERS_COUNT-1} supported: {e}") op = str(memory.get_register_address(rindex)) try: int_val = int(op, 0) diff --git a/planner/memory.py b/planner/memory.py index f7a3b2d..e54f0f5 100644 --- a/planner/memory.py +++ b/planner/memory.py @@ -2,6 +2,7 @@ # General Register R0,..R15 GENERAL_REGISTERS_COUNT = 8 +TOKEN_GENERAL_REGISTERS = [f"R{i}" for i in range(0, GENERAL_REGISTERS_COUNT)] def get_register_address(index): assert index >=0 and index < GENERAL_REGISTERS_COUNT return index*4 From c53321a0dd2ea829f7cd82ffa72828210213a743 Mon Sep 17 00:00:00 2001 From: Gagan Kumar Date: Sun, 15 Dec 2024 12:35:33 -0800 Subject: [PATCH 6/7] Ping pong is ready --- Makefile | 6 +- README.md | 26 ++--- planner/asm/line_parser.py | 3 + planner/asm/program_parser.py | 2 +- planner/memory.py | 4 +- programs/ping_pong.asm | 174 +++++++++++++++++++++++++++++++--- 6 files changed, 178 insertions(+), 37 deletions(-) diff --git a/Makefile b/Makefile index cd2e88b..700b339 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,8 @@ OUTPUT_DIR=output all: all_programs_binary all_programs_resolved clean: - rm -r $(BUILD_DIR) - rm -r $(OUTPUT_DIR) + rm -rf $(BUILD_DIR) + rm -rf $(OUTPUT_DIR) include emulator/Makefile.mk @@ -30,4 +30,4 @@ $(OUTPUT_DIR)/programs/%_resolved.asm: programs/%.asm python3 -m planner asm -r $^ > $@ run_ping_pong: - python3 -m planner -v compile_and_execute ping_pong + python3 -m planner compile_and_execute ping_pong diff --git a/README.md b/README.md index ca70da3..48d6360 100644 --- a/README.md +++ b/README.md @@ -5,28 +5,18 @@ The eventual goal(?) is to build a general-purpose processor integrated with simple input (e.g. buttons) and output devices (8x8 LED display). -## Verification +## Sample Programs -### Emulation - -``` -# Build ROM[boot] -$ python3 -rb assembler/assembler.py programs/boot_sequence.asm | tee build/boot_rom.txt. - -# Write your program in custom assembly. -$ cat programs/ping_pong.asm # we can proceeding with this - -# Use assembler to translate the instructions into machine code. -$ mkdir -p build -$ python3 assembler/assembler.py -r programs/ping_pong.asm | tee build/ping_pong_resolved.txt # optional -$ python3 assembler/assembler.py -rb programs/ping_pong.asm | tee build/ping_pong_rom.txt - -# Use emulater the run the machine code. -$ python3 emulator/emulate.py build/ping_pong_rom.txt -``` +* Ping Pong + * Source: [ping_pong.asm](programs/ping_pong.asm) + * Generate resolved assembly: `python3 -m planner asm -r programs/ping_pong.asm` + * Generate binary: `python3 -m planner asm -b programs/ping_pong.asm` + * Run on emulator: `python3 -m planner compile_and_execute ping_pong` ## Design +This section is not up-to date. + ### Specs * Address Line: 16-bits diff --git a/planner/asm/line_parser.py b/planner/asm/line_parser.py index 7b28e60..39fd5a6 100644 --- a/planner/asm/line_parser.py +++ b/planner/asm/line_parser.py @@ -47,6 +47,9 @@ def parse_line(line: str) -> Tuple[Optional[str], Optional[List[Tuple[unit.Opera if op.upper() == memory.TOKEN_ESP: optype = address_of(optype) op = str(memory.ESP) + if op.upper() == memory.TOKEN_ESB: + optype = address_of(optype) + op = str(memory.ESB) elif op in memory.TOKEN_GENERAL_REGISTERS: optype = address_of(optype) try: diff --git a/planner/asm/program_parser.py b/planner/asm/program_parser.py index 2474253..54af788 100644 --- a/planner/asm/program_parser.py +++ b/planner/asm/program_parser.py @@ -157,7 +157,7 @@ def parse_data(self, tokens: List[str]): raise ValueError("unsupported data size provided") # unsigned integer only for now - val = int(tokens[2]) + val = int(tokens[2], 0) assert val >= 0 and val < (2**(8*sz)) for _ in range(times): diff --git a/planner/memory.py b/planner/memory.py index e54f0f5..1366dff 100644 --- a/planner/memory.py +++ b/planner/memory.py @@ -9,8 +9,10 @@ def get_register_address(index): # Stack Registers TOKEN_ESP = "ESP" +TOKEN_ESB = "ESB" ESP = 0x20 -MSI = 0x24 # multi-step instruction state +ESB = 0x24 +MSI = 0x28 # multi-step instruction state # free diff --git a/programs/ping_pong.asm b/programs/ping_pong.asm index 52bbae1..b53257c 100644 --- a/programs/ping_pong.asm +++ b/programs/ping_pong.asm @@ -33,6 +33,25 @@ OUTPUT_WIDTH equ 6 OUTPUT_HEGHT equ 7 INPUT_DEVICE equ 1 +BALL_STEP_SIZE equ 2 +GAME_OVER_BLINK_STEP equ 5 + +BALL_DIR_RIGHT equ 1 +BALL_DIR_LEFT equ 2 +# BALL_DIR_RIGHT|BALL_DIR_LEFT +BALL_DIR_FLIP_HOR equ 3 +# if not up or down, ball goes horizontally +BALL_DIR_UP equ 4 +BALL_DIR_DOWN equ 8 +# BALL_DIR_UP|BALL_DIR_DOWN +BALL_DIR_FLIP_VER equ 12 + +BALL_MAXX equ 14 +BALL_MINX equ 1 +BALL_MAXY equ 7 +BALL_MINY equ 0 + + section .text main: movc ESP, 0xFF @@ -45,69 +64,110 @@ section .data bat2_y dd 2 ball_x dd 7 ball_y dd 2 + mask16f dd 0xFFFF + ball_step_counter dd 0 + ball_dir dd 6 + + game_over_blink_counter dd 0 + game_over_blink dd 1 section .text game: call print call read_input_p1_up - call sleep + call step jmp game + game_over: + call game_over_step + call print + jmp game_over + + game_over_step: + cmpc [game_over_blink_counter], 0 + jnz _game_over_step_end + xorc [game_over_blink], 1 + movc [game_over_blink_counter], GAME_OVER_BLINK_STEP + _game_over_step_end: + subc [game_over_blink_counter], 1 + ret - # call step - # jmp step - game_after_step: - # jmp game - # hlt_marker: - # jmp hlt_marker read_input_p1_up: IN R0, INPUT_DEVICE movc R1, 0x1 and R1, R0 jz read_input_p1_down + cmpc [bat1_y], 0 + jz read_input_p1_down subc [bat1_y], 1 read_input_p1_down: movc R1, 0x02 and R1, R0 jz read_input_p2_up + cmpc [bat1_y], BAT_MAXY + jz read_input_p2_up addc [bat1_y], 1 read_input_p2_up: movc R1, 0x04 and R1, R0 jz read_input_p2_down + cmpc [bat2_y], 0 + jz read_input_p2_down subc [bat2_y], 1 read_input_p2_down: movc R1, 0x08 and R1, R0 jz read_input_end + cmpc [bat2_y], BAT_MAXY + jz read_input_end addc [bat2_y], 1 read_input_end: ret print: + # if game over draw blink bat animation + cmpc [game_over_blink], 0 + jz _draw_ball + ## Player 1 # anode col movc R0, 0x7F shlc R0, 8 xorc R0, 0xFF # cathode row - movc R1, 0x3 + movc R1, 0x7 shl R1, [bat1_y] OUT OUTPUT_WIDTH, R0 OUT OUTPUT_HEGHT, R1 - call sleep + ## Player 2 # anode col movc R0, 0xFF shlc R0, 8 xorc R0, 0xFE # cathode row - movc R1, 0x3 + movc R1, 0x7 shl R1, [bat2_y] OUT OUTPUT_WIDTH, R0 OUT OUTPUT_HEGHT, R1 + call sleep + + _draw_ball: + ## Ball + mov R0, [ball_x] + movc R1, 0x80 + shlc R1, 8 + shr R1, R0 + xor R1, [mask16f] + mov R0, [ball_y] + movc R2, 1 + shl R2, R0 + OUT OUTPUT_WIDTH, R1 + OUT OUTPUT_HEGHT, R2 + call sleep + ret sleep: @@ -118,9 +178,95 @@ section .text jnz _sleep ret - #step: - # addc [ball_x], 1 - # andc [ball_x], 0x07 - # jmp game_after_step + step: + cmpc [ball_step_counter], 0 + jnz step_over + movc [ball_step_counter], BALL_STEP_SIZE + call step_ball_move + step_over: + subc [ball_step_counter], 1 + ret + + step_ball_move: + cmpc [ball_x], BALL_MAXX + jz _step_ball_collision_hor_right + cmpc [ball_x], BALL_MINX + jz _step_ball_collision_hor_left + _step_ball_collision_hor_over: + cmpc [ball_y], BALL_MAXY + jz _step_ball_collision_ver + cmpc [ball_y], BALL_MINY + jz _step_ball_collision_ver + _step_ball_collision_ver_over: + + mov R0, [ball_dir] + andc R0, BALL_DIR_RIGHT + jz _step_ball_left + # move_right + addc [ball_x], 1 + jmp _step_ball_up + _step_ball_left: + # move left + subc [ball_x], 1 + _step_ball_up: + mov R0, [ball_dir] + andc R0, BALL_DIR_UP + jz _step_ball_down + # move up + subc [ball_y], 1 + _step_ball_down: + mov R0, [ball_dir] + andc R0, BALL_DIR_DOWN + jz _step_ball_move_over + # move down + addc [ball_y], 1 + _step_ball_move_over: + ret + _step_ball_collision_hor_right: + push [bat2_y] + call _step_ball_collision_hor + pop R7 + jmp _step_ball_collision_hor_over + + _step_ball_collision_hor_left: + push [bat1_y] + call _step_ball_collision_hor + pop R7 + jmp _step_ball_collision_hor_over + + _step_ball_collision_hor: + # Argument + # bat_y + mov esb, esp + addc esb, 4 + load R1, [esb] # bat_y + + xorc [ball_dir], BALL_DIR_FLIP_HOR + movc R0, BALL_DIR_FLIP_VER + xorc R0, 0xFF + and [ball_dir], R0 + cmp [ball_y], R1 + jz _step_ball_collision_hor_right_go_up + addc R1, 1 + cmp [ball_y], R1 + jz _step_ball_collision_hor_right_go_hor + addc R1, 1 + cmp [ball_y], R1 + jz _step_ball_collision_hor_right_go_down + jmp game_over + _step_ball_collision_hor_right_go_up: + orc [ball_dir], BALL_DIR_UP + ret + _step_ball_collision_hor_right_go_down: + orc [ball_dir], BALL_DIR_DOWN + ret + _step_ball_collision_hor_right_go_hor: + ret + _step_ball_collision_ver: + mov R0, [ball_dir] + andc R0, BALL_DIR_FLIP_VER + jz _step_ball_collision_ver_over + xorc [ball_dir], BALL_DIR_FLIP_VER + jmp _step_ball_collision_ver_over From 554fedf6902e49bfe3c18a026690f54992fb4cc3 Mon Sep 17 00:00:00 2001 From: Gagan Kumar Date: Sun, 15 Dec 2024 12:36:45 -0800 Subject: [PATCH 7/7] Include output/ under commit --- .github/workflows/ci.yml | 2 +- .gitignore | 1 - README.md | 4 +- output/programs/3_led_switch.bin | 4 + output/programs/3_led_switch_resolved.asm | 4 + output/programs/boot_sequence.bin | 16 ++ output/programs/boot_sequence_resolved.asm | 16 ++ output/programs/ping_pong.bin | 206 ++++++++++++++++++ output/programs/ping_pong_resolved.asm | 233 +++++++++++++++++++++ planner/sim/gui_devices.py | 3 + 10 files changed, 485 insertions(+), 4 deletions(-) create mode 100644 output/programs/3_led_switch.bin create mode 100644 output/programs/3_led_switch_resolved.asm create mode 100644 output/programs/boot_sequence.bin create mode 100644 output/programs/boot_sequence_resolved.asm create mode 100644 output/programs/ping_pong.bin create mode 100644 output/programs/ping_pong_resolved.asm diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd10787..5cc1031 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: sudo apt-get update sudo apt-get install iverilog - name: make test - run: make test + run: make clean && make test - name: make run: make all - name: Upload artifacts diff --git a/.gitignore b/.gitignore index 49e924e..a9a0b95 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ .vscode/ __pycache__/ build/ -output/ diff --git a/README.md b/README.md index 48d6360..a8d5e04 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ The eventual goal(?) is to build a general-purpose processor integrated with sim * Ping Pong * Source: [ping_pong.asm](programs/ping_pong.asm) - * Generate resolved assembly: `python3 -m planner asm -r programs/ping_pong.asm` - * Generate binary: `python3 -m planner asm -b programs/ping_pong.asm` + * Generate resolved assembly: `python3 -m planner asm -r programs/ping_pong.asm` [[example](output/programs/ping_pong_resolved.asm)] + * Generate binary: `python3 -m planner asm -b programs/ping_pong.asm` [[example](output/programs/ping_pong.bin)] * Run on emulator: `python3 -m planner compile_and_execute ping_pong` ## Design diff --git a/output/programs/3_led_switch.bin b/output/programs/3_led_switch.bin new file mode 100644 index 0000000..140edcd --- /dev/null +++ b/output/programs/3_led_switch.bin @@ -0,0 +1,4 @@ +00000000 00000000 00000000 00001100 +00100100 00000010 00000100 00000101 +00000100 00000100 00000110 00000100 +00110101 00001001 01000000 00000000 diff --git a/output/programs/3_led_switch_resolved.asm b/output/programs/3_led_switch_resolved.asm new file mode 100644 index 0000000..f9b0e42 --- /dev/null +++ b/output/programs/3_led_switch_resolved.asm @@ -0,0 +1,4 @@ +PROGRAM_ORG equ 64 +040: IN [4], 5 +044: OUT 6, [4] +048: JMP 64, 0 diff --git a/output/programs/boot_sequence.bin b/output/programs/boot_sequence.bin new file mode 100644 index 0000000..08c42f7 --- /dev/null +++ b/output/programs/boot_sequence.bin @@ -0,0 +1,16 @@ +00000000 00000000 00000000 00111100 +00110100 00000010 00000000 00000000 +00000100 00000100 00010000 00000000 +00100100 00000010 00000000 00000000 +00110100 00000010 00010100 00000000 +00110100 00000010 00000100 00000001 +00110100 00000010 00001000 01000000 +00000100 00000100 00010000 00000100 +00100100 00000010 00001100 00100000 +01000100 00000110 00001000 00001100 +01110000 00000010 00001000 00000100 +01110000 00000010 00000100 00000100 +01110001 00000010 00000000 00000100 +01000001 00000000 00000000 00010100 +00110101 00001011 01000000 00000000 +00110101 00001001 10011000 00000000 diff --git a/output/programs/boot_sequence_resolved.asm b/output/programs/boot_sequence_resolved.asm new file mode 100644 index 0000000..84b1fd5 --- /dev/null +++ b/output/programs/boot_sequence_resolved.asm @@ -0,0 +1,16 @@ +PROGRAM_ORG equ 128 +080: MOVC [0], 0 +084: OUT 16, [0] +088: IN [0], 0 +08c: MOVC [20], 0 +090: MOVC [4], 1 +094: MOVC [8], 64 +098: OUT 16, [4] +09c: IN [12], 32 +0a0: STORE [[8]], [12] +0a4: ADDC [8], 4 +0a8: ADDC [4], 4 +0ac: SUBC [0], 4 +0b0: CMP [0], [20] +0b4: JZ 64, 0 +0b8: JMP 152, 0 diff --git a/output/programs/ping_pong.bin b/output/programs/ping_pong.bin new file mode 100644 index 0000000..246aca5 --- /dev/null +++ b/output/programs/ping_pong.bin @@ -0,0 +1,206 @@ +00000000 00000000 00000011 00110100 +00110100 00000010 00100000 11111111 +01110010 00000010 00100000 00001000 +01110111 00000010 00100000 11110000 +00110101 00001001 01110100 00000000 +00000010 00000000 00000000 00000000 +00000010 00000000 00000000 00000000 +00000111 00000000 00000000 00000000 +00000010 00000000 00000000 00000000 +11111111 11111111 00000000 00000000 +00000000 00000000 00000000 00000000 +00000110 00000000 00000000 00000000 +00000000 00000000 00000000 00000000 +00000001 00000000 00000000 00000000 +01110000 00000011 00101000 00010000 +01110001 00000010 00100000 00000100 +01000100 00000110 00100000 00101000 +00110101 00001001 01011100 00000001 +01110000 00000011 00101000 00010000 +01110001 00000010 00100000 00000100 +01000100 00000110 00100000 00101000 +00110101 00001001 11101100 00000000 +01110000 00000011 00101000 00010000 +01110001 00000010 00100000 00000100 +01000100 00000110 00100000 00101000 +00110101 00001001 00011100 00000010 +00110101 00001001 01110100 00000000 +01110000 00000011 00101000 00010000 +01110001 00000010 00100000 00000100 +01000100 00000110 00100000 00101000 +00110101 00001001 11001100 00000000 +01110000 00000011 00101000 00010000 +01110001 00000010 00100000 00000100 +01000100 00000110 00100000 00101000 +00110101 00001001 01011100 00000001 +00110101 00001001 10101000 00000000 +01110001 00000000 01101100 00000000 +00110101 00001101 11011100 00000000 +01111000 00000010 01110000 00000001 +00110100 00000010 01101100 00000101 +01110001 00000010 01101100 00000001 +10000101 00000010 00101000 00100000 +01110000 00000010 00100000 00000100 +11110101 00001000 00101000 00000000 +00100100 00000010 00000000 00000001 +00110100 00000010 00000100 00000001 +01000110 00000010 00000100 00000000 +00110101 00001011 00001000 00000001 +01110001 00000000 01010000 00000000 +00110101 00001011 00001000 00000001 +01110001 00000010 01010000 00000001 +00110100 00000010 00000100 00000010 +01000110 00000010 00000100 00000000 +00110101 00001011 00100000 00000001 +01110001 00000000 01010000 00000101 +00110101 00001011 00100000 00000001 +01110000 00000010 01010000 00000001 +00110100 00000010 00000100 00000100 +01000110 00000010 00000100 00000000 +00110101 00001011 00111000 00000001 +01110001 00000000 01010100 00000000 +00110101 00001011 00111000 00000001 +01110001 00000010 01010100 00000001 +00110100 00000010 00000100 00001000 +01000110 00000010 00000100 00000000 +00110101 00001011 01010000 00000001 +01110001 00000000 01010100 00000101 +00110101 00001011 01010000 00000001 +01110000 00000010 01010100 00000001 +10000101 00000010 00101000 00100000 +01110000 00000010 00100000 00000100 +11110101 00001000 00101000 00000000 +01110001 00000000 01110000 00000000 +00110101 00001011 10111100 00000001 +00110100 00000010 00000000 01111111 +01110010 00000010 00000000 00001000 +01111000 00000010 00000000 11111111 +00110100 00000010 00000100 00000111 +01000010 00000010 00000100 01010000 +00000100 00000100 00000110 00000000 +00000100 00000100 00000111 00000100 +01110000 00000011 00101000 00010000 +01110001 00000010 00100000 00000100 +01000100 00000110 00100000 00101000 +00110101 00001001 00000000 00000010 +00110100 00000010 00000000 11111111 +01110010 00000010 00000000 00001000 +01111000 00000010 00000000 11111110 +00110100 00000010 00000100 00000111 +01000010 00000010 00000100 01010100 +00000100 00000100 00000110 00000000 +00000100 00000100 00000111 00000100 +01110000 00000011 00101000 00010000 +01110001 00000010 00100000 00000100 +01000100 00000110 00100000 00101000 +00110101 00001001 00000000 00000010 +00000100 00000010 00000000 01011000 +00110100 00000010 00000100 10000000 +01110010 00000010 00000100 00001000 +01000011 00000010 00000100 00000000 +01001000 00000010 00000100 01100000 +00000100 00000010 00000000 01011100 +00110100 00000010 00001000 00000001 +01000010 00000010 00001000 00000000 +00000100 00000100 00000110 00000100 +00000100 00000100 00000111 00001000 +01110000 00000011 00101000 00010000 +01110001 00000010 00100000 00000100 +01000100 00000110 00100000 00101000 +00110101 00001001 00000000 00000010 +10000101 00000010 00101000 00100000 +01110000 00000010 00100000 00000100 +11110101 00001000 00101000 00000000 +00110100 00000010 00000000 11110000 +01110010 00000010 00000000 00000001 +01110001 00000010 00000000 00000001 +00110101 00001101 00001000 00000010 +10000101 00000010 00101000 00100000 +01110000 00000010 00100000 00000100 +11110101 00001000 00101000 00000000 +01110001 00000000 01100100 00000000 +00110101 00001101 00111000 00000010 +00110100 00000010 01100100 00000010 +01110000 00000011 00101000 00010000 +01110001 00000010 00100000 00000100 +01000100 00000110 00100000 00101000 +00110101 00001001 01001000 00000010 +01110001 00000010 01100100 00000001 +10000101 00000010 00101000 00100000 +01110000 00000010 00100000 00000100 +11110101 00001000 00101000 00000000 +01110001 00000000 01011000 00001110 +00110101 00001011 10101100 00000010 +01110001 00000000 01011000 00000001 +00110101 00001011 11010000 00000010 +01110001 00000000 01011100 00000111 +00110101 00001011 01100000 00000011 +01110001 00000000 01011100 00000000 +00110101 00001011 01100000 00000011 +00000100 00000010 00000000 01101000 +01110110 00000010 00000000 00000001 +00110101 00001011 01111100 00000010 +01110000 00000010 01011000 00000001 +00110101 00001001 10000000 00000010 +01110001 00000010 01011000 00000001 +00000100 00000010 00000000 01101000 +01110110 00000010 00000000 00000100 +00110101 00001011 10010000 00000010 +01110001 00000010 01011100 00000001 +00000100 00000010 00000000 01101000 +01110110 00000010 00000000 00001000 +00110101 00001011 10100000 00000010 +01110000 00000010 01011100 00000001 +10000101 00000010 00101000 00100000 +01110000 00000010 00100000 00000100 +11110101 00001000 00101000 00000000 +01110001 00000010 00100000 00000100 +01000100 00000110 00100000 01010100 +01110000 00000011 00101000 00010000 +01110001 00000010 00100000 00000100 +01000100 00000110 00100000 00101000 +00110101 00001001 11110100 00000010 +10000101 00000010 00011100 00100000 +01110000 00000010 00100000 00000100 +00110101 00001001 01011000 00000010 +01110001 00000010 00100000 00000100 +01000100 00000110 00100000 01010000 +01110000 00000011 00101000 00010000 +01110001 00000010 00100000 00000100 +01000100 00000110 00100000 00101000 +00110101 00001001 11110100 00000010 +10000101 00000010 00011100 00100000 +01110000 00000010 00100000 00000100 +00110101 00001001 01011000 00000010 +00000100 00000010 00100100 00100000 +01110000 00000010 00100100 00000100 +10000101 00000010 00000100 00100100 +01111000 00000010 01101000 00000011 +00110100 00000010 00000000 00001100 +01111000 00000010 00000000 11111111 +01000110 00000010 01101000 00000000 +01000001 00000000 01011100 00000100 +00110101 00001011 00110100 00000011 +01110000 00000010 00000100 00000001 +01000001 00000000 01011100 00000100 +00110101 00001011 01010100 00000011 +01110000 00000010 00000100 00000001 +01000001 00000000 01011100 00000100 +00110101 00001011 01000100 00000011 +00110101 00001001 10101000 00000000 +01110111 00000010 01101000 00000100 +10000101 00000010 00101000 00100000 +01110000 00000010 00100000 00000100 +11110101 00001000 00101000 00000000 +01110111 00000010 01101000 00001000 +10000101 00000010 00101000 00100000 +01110000 00000010 00100000 00000100 +11110101 00001000 00101000 00000000 +10000101 00000010 00101000 00100000 +01110000 00000010 00100000 00000100 +11110101 00001000 00101000 00000000 +00000100 00000010 00000000 01101000 +01110110 00000010 00000000 00001100 +00110101 00001011 01101000 00000010 +01111000 00000010 01101000 00001100 +00110101 00001001 01101000 00000010 diff --git a/output/programs/ping_pong_resolved.asm b/output/programs/ping_pong_resolved.asm new file mode 100644 index 0000000..4273b54 --- /dev/null +++ b/output/programs/ping_pong_resolved.asm @@ -0,0 +1,233 @@ +PROGRAM_ORG equ 64 +040: MOVC [32], 255 +044: SHLC [32], 8 +048: ORC [32], 240 +04c: JMP 116, 0 +050: 02 +051: 00 +052: 00 +053: 00 +054: 02 +055: 00 +056: 00 +057: 00 +058: 07 +059: 00 +05a: 00 +05b: 00 +05c: 02 +05d: 00 +05e: 00 +05f: 00 +060: 255 +061: 255 +062: 00 +063: 00 +064: 00 +065: 00 +066: 00 +067: 00 +068: 06 +069: 00 +06a: 00 +06b: 00 +06c: 00 +06d: 00 +06e: 00 +06f: 00 +070: 01 +071: 00 +072: 00 +073: 00 +074: PCPLUS [40], 16 +078: SUBC [32], 4 +07c: STORE [[32]], [40] +080: JMP 92, 1 +084: PCPLUS [40], 16 +088: SUBC [32], 4 +08c: STORE [[32]], [40] +090: JMP 236, 0 +094: PCPLUS [40], 16 +098: SUBC [32], 4 +09c: STORE [[32]], [40] +0a0: JMP 28, 2 +0a4: JMP 116, 0 +0a8: PCPLUS [40], 16 +0ac: SUBC [32], 4 +0b0: STORE [[32]], [40] +0b4: JMP 204, 0 +0b8: PCPLUS [40], 16 +0bc: SUBC [32], 4 +0c0: STORE [[32]], [40] +0c4: JMP 92, 1 +0c8: JMP 168, 0 +0cc: CMPC [108], 0 +0d0: JNZ 220, 0 +0d4: XORC [112], 1 +0d8: MOVC [108], 5 +0dc: SUBC [108], 1 +0e0: LOAD [40], [[32]] +0e4: ADDC [32], 4 +0e8: JMPM [40], [0] +0ec: IN [0], 1 +0f0: MOVC [4], 1 +0f4: AND [4], [0] +0f8: JZ 8, 1 +0fc: CMPC [80], 0 +100: JZ 8, 1 +104: SUBC [80], 1 +108: MOVC [4], 2 +10c: AND [4], [0] +110: JZ 32, 1 +114: CMPC [80], 5 +118: JZ 32, 1 +11c: ADDC [80], 1 +120: MOVC [4], 4 +124: AND [4], [0] +128: JZ 56, 1 +12c: CMPC [84], 0 +130: JZ 56, 1 +134: SUBC [84], 1 +138: MOVC [4], 8 +13c: AND [4], [0] +140: JZ 80, 1 +144: CMPC [84], 5 +148: JZ 80, 1 +14c: ADDC [84], 1 +150: LOAD [40], [[32]] +154: ADDC [32], 4 +158: JMPM [40], [0] +15c: CMPC [112], 0 +160: JZ 188, 1 +164: MOVC [0], 127 +168: SHLC [0], 8 +16c: XORC [0], 255 +170: MOVC [4], 7 +174: SHL [4], [80] +178: OUT 6, [0] +17c: OUT 7, [4] +180: PCPLUS [40], 16 +184: SUBC [32], 4 +188: STORE [[32]], [40] +18c: JMP 0, 2 +190: MOVC [0], 255 +194: SHLC [0], 8 +198: XORC [0], 254 +19c: MOVC [4], 7 +1a0: SHL [4], [84] +1a4: OUT 6, [0] +1a8: OUT 7, [4] +1ac: PCPLUS [40], 16 +1b0: SUBC [32], 4 +1b4: STORE [[32]], [40] +1b8: JMP 0, 2 +1bc: MOV [0], [88] +1c0: MOVC [4], 128 +1c4: SHLC [4], 8 +1c8: SHR [4], [0] +1cc: XOR [4], [96] +1d0: MOV [0], [92] +1d4: MOVC [8], 1 +1d8: SHL [8], [0] +1dc: OUT 6, [4] +1e0: OUT 7, [8] +1e4: PCPLUS [40], 16 +1e8: SUBC [32], 4 +1ec: STORE [[32]], [40] +1f0: JMP 0, 2 +1f4: LOAD [40], [[32]] +1f8: ADDC [32], 4 +1fc: JMPM [40], [0] +200: MOVC [0], 240 +204: SHLC [0], 1 +208: SUBC [0], 1 +20c: JNZ 8, 2 +210: LOAD [40], [[32]] +214: ADDC [32], 4 +218: JMPM [40], [0] +21c: CMPC [100], 0 +220: JNZ 56, 2 +224: MOVC [100], 2 +228: PCPLUS [40], 16 +22c: SUBC [32], 4 +230: STORE [[32]], [40] +234: JMP 72, 2 +238: SUBC [100], 1 +23c: LOAD [40], [[32]] +240: ADDC [32], 4 +244: JMPM [40], [0] +248: CMPC [88], 14 +24c: JZ 172, 2 +250: CMPC [88], 1 +254: JZ 208, 2 +258: CMPC [92], 7 +25c: JZ 96, 3 +260: CMPC [92], 0 +264: JZ 96, 3 +268: MOV [0], [104] +26c: ANDC [0], 1 +270: JZ 124, 2 +274: ADDC [88], 1 +278: JMP 128, 2 +27c: SUBC [88], 1 +280: MOV [0], [104] +284: ANDC [0], 4 +288: JZ 144, 2 +28c: SUBC [92], 1 +290: MOV [0], [104] +294: ANDC [0], 8 +298: JZ 160, 2 +29c: ADDC [92], 1 +2a0: LOAD [40], [[32]] +2a4: ADDC [32], 4 +2a8: JMPM [40], [0] +2ac: SUBC [32], 4 +2b0: STORE [[32]], [84] +2b4: PCPLUS [40], 16 +2b8: SUBC [32], 4 +2bc: STORE [[32]], [40] +2c0: JMP 244, 2 +2c4: LOAD [28], [[32]] +2c8: ADDC [32], 4 +2cc: JMP 88, 2 +2d0: SUBC [32], 4 +2d4: STORE [[32]], [80] +2d8: PCPLUS [40], 16 +2dc: SUBC [32], 4 +2e0: STORE [[32]], [40] +2e4: JMP 244, 2 +2e8: LOAD [28], [[32]] +2ec: ADDC [32], 4 +2f0: JMP 88, 2 +2f4: MOV [36], [32] +2f8: ADDC [36], 4 +2fc: LOAD [4], [[36]] +300: XORC [104], 3 +304: MOVC [0], 12 +308: XORC [0], 255 +30c: AND [104], [0] +310: CMP [92], [4] +314: JZ 52, 3 +318: ADDC [4], 1 +31c: CMP [92], [4] +320: JZ 84, 3 +324: ADDC [4], 1 +328: CMP [92], [4] +32c: JZ 68, 3 +330: JMP 168, 0 +334: ORC [104], 4 +338: LOAD [40], [[32]] +33c: ADDC [32], 4 +340: JMPM [40], [0] +344: ORC [104], 8 +348: LOAD [40], [[32]] +34c: ADDC [32], 4 +350: JMPM [40], [0] +354: LOAD [40], [[32]] +358: ADDC [32], 4 +35c: JMPM [40], [0] +360: MOV [0], [104] +364: ANDC [0], 12 +368: JZ 104, 2 +36c: XORC [104], 12 +370: JMP 104, 2 diff --git a/planner/sim/gui_devices.py b/planner/sim/gui_devices.py index 8e61b4e..e7ae95f 100644 --- a/planner/sim/gui_devices.py +++ b/planner/sim/gui_devices.py @@ -1,5 +1,8 @@ from planner.sim import devices import logging + +import os +os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" import pygame class GUIDevice: