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 d8b2e44..a9a0b95 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ .vscode/ __pycache__/ build/ -output/ \ No newline at end of file diff --git a/Makefile b/Makefile index 1a87f0f..700b339 100644 --- a/Makefile +++ b/Makefile @@ -2,21 +2,32 @@ SRC_DIR=. BUILD_DIR=build OUTPUT_DIR=output -.PHONY: clean test +.PHONY: clean test all run_ping_pong all_programs_binary all_programs_resolved + +all: all_programs_binary all_programs_resolved clean: - rm -r $(BUILD_DIR) + rm -rf $(BUILD_DIR) + rm -rf $(OUTPUT_DIR) include emulator/Makefile.mk pytest: - pytest -s --log-cli-level=DEBUG + pytest -s --log-cli-level=INFO 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 $^ > $@ -all: $(patsubst programs/%.asm, $(OUTPUT_DIR)/programs/%.bin, $(shell find programs/ -name '*.asm')) +run_ping_pong: + python3 -m planner compile_and_execute ping_pong diff --git a/README.md b/README.md index ca70da3..a8d5e04 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` [[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 +This section is not up-to date. + ### Specs * Address Line: 16-bits diff --git a/output/programs/3_led_switch.bin b/output/programs/3_led_switch.bin index cce40d0..140edcd 100644 --- a/output/programs/3_led_switch.bin +++ b/output/programs/3_led_switch.bin @@ -1,4 +1,4 @@ -00001100 01000010 00000001 00000001 -00000101 01001000 00000001 00000110 -00000001 11001111 00000000 00000000 -01000000 +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/__main__.py b/planner/__main__.py index 83d7b51..d363b10 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,26 +23,34 @@ 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(): 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: - 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..39fd5a6 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: @@ -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] @@ -26,11 +27,15 @@ 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: - 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) @@ -39,14 +44,22 @@ 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("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) + 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: + rindex = int(op[1:]) + except ValueError as 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: - value = unit.LazyLabel(util.LABEL_CONSTANT, int(op, 0)) # automatically understand base-10 and base-16 + int_val = int(op, 0) + 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..54af788 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,22 +52,23 @@ 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(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 + 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) - 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) @@ -80,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" @@ -133,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] @@ -155,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): @@ -163,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): @@ -212,3 +216,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..efc33a1 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 @@ -51,30 +51,38 @@ 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 + # 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] + return [sel.value%2, (sel.value>>1)%2, sel.value>>2] @classmethod def from_binary(cls, bin: List[int]): - assert len(bin) == 1 - val = bin[0] - assert val >= 0 and val < 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 + 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: @@ -98,23 +106,53 @@ class ALU(Enum): PASS_RW = 5 # vrw_value 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: - 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) + 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], - "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, 7, 8], + "mblock_s3" : [9, 10, 11], } class EncodedInstruction: @@ -217,20 +255,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) @@ -241,20 +281,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 - 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 + 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_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): @@ -302,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, @@ -312,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, @@ -322,17 +368,31 @@ def size(self): MBlockSelector_stage2.VRW_SOURCE_RAM, MBlockSelector_stage3.NO_WRITE, ALU.SUB)), - ParserInstruction("JMP", unit.Operand.IGNORE, unit.Operand.CONSTANT, - EncodedInstruction(MBlockSelector_stage1.VR_SOURCE_CONST, + ParserInstruction("HLT", unit.Operand.CONSTANT, unit.Operand.CONSTANT, + EncodedInstruction(MBlockSelector_stage1.DONT_CARE, MBlockSelector_stage2.DONT_CARE, - MBlockSelector_stage3.PC_NEXT, + MBlockSelector_stage3.HLT, ALU.PASS_R)), - # TODO: Ensure flag_alu_zero is updated after stage3. - ParserInstruction("JZ", 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.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.PASS_RW)), + ParserInstruction("JZ", 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_ZERO, - ALU.PASS_R)) + 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, @@ -346,6 +406,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 +421,7 @@ def size(self): ("SHRC", ALU.SHR), ("ANDC", ALU.AND), ("ORC", ALU.OR), + ("XORC", ALU.XOR), ] ] @@ -377,3 +439,92 @@ 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, unit.LazyLabel]]): + # 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)] + + 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 [ + 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))]) + ] + 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 0f9eb4f..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"), @@ -30,10 +33,15 @@ 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"), + + ("pcplus R0, 4", "PCPLUS [0], 4"), + ("hlt 0, 0", "HLT 0, 0"), ] instructions = set() @@ -49,5 +57,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..1366dff --- /dev/null +++ b/planner/memory.py @@ -0,0 +1,21 @@ +LABEL_PROGRAM_ORG = "PROGRAM_ORG" + +# 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 + +# Stack Registers +TOKEN_ESP = "ESP" +TOKEN_ESB = "ESB" +ESP = 0x20 +ESB = 0x24 +MSI = 0x28 # multi-step instruction state + +# free + +# user program + +DEFAULT_PROGRAM_ORG = 0x40 diff --git a/planner/sim/bin_parser.py b/planner/sim/bin_parser.py index b8a60e5..eb797f9 100644 --- a/planner/sim/bin_parser.py +++ b/planner/sim/bin_parser.py @@ -2,12 +2,14 @@ import logging from typing import List, Optional -from planner import instruction +from planner import instruction, memory from planner.sim import devices -PROGRAM_ORG = 0x40 +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 +19,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) @@ -39,10 +41,12 @@ 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) - for i in range(1, len(content)//8): - self.ram.append(int(content[i*8:(i+1)*8], 2)) + program_size = int(content[:32], 2) + assert program_size*8+32 == len(content) + address = PROGRAM_ORG + for i in range(4, len(content)//8): + self.ram[address] = (int(content[i*8:(i+1)*8], 2)) + address += 1 def read_ram(self, addr: int, count: int) -> List[int]: ans = [] @@ -53,7 +57,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 +70,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, @@ -88,14 +92,24 @@ 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 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 + 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( @@ -124,48 +138,35 @@ 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 raise Exception(f"unsupported selector: {sel}") 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}") - - @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 + 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.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 @@ -180,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 23108a9..bf1c329 100644 --- a/planner/sim/bin_parser_test.py +++ b/planner/sim/bin_parser_test.py @@ -4,63 +4,260 @@ 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, 45 + cmp R0, R1 # correct + jnz bad + movc R1, 10 + cmp R0, R1 # incorrect + jnz good2 + jmp bad + + good2: + jmp good3 + hlt + + good3: + movc R1, all_good + jmpm R1 + + bad: + hlt + all_good: + out {self.FAKE_OUPUT_AT}, R0 + hlt + """) + self.assertEqual(self.fake_ouput.get(), 45) + + + def test_data_section(self): + 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 + + section .data + array_0 dd 11 + array_1 dd 22 + array_2 dd 30 + """) + self.assertEqual(self.fake_ouput.get(), 96) + + def test_bss_section(self): + 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 + + section .bss + array_start: resb 4 + resb 6 + 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_overall(self): - asm = program_parser.AsmParser() - for line in PROGRAM: - asm.parse_line(line) - binary_program = asm.get_str(resolved=True, rom_binary=True) + def test_call(self): + self.fake_input.set_input(45) + self.execute(f""" + PROGRAM_ORG equ 0x40 - _bin = bin_parser.BinRunner(binary_program) - fake_input = devices.LatchInput("fake", bits=32) - fake_ouput = devices.Device(bits=32) + section .text + main: + movc ESP, 0xFC + movc R1, 10 + movc R2, 15 + call sum + jmp end + hlt # guard bad marker - _bin.set_input_device(5, fake_input) - _bin.set_output_device(6, fake_ouput) + sum: + push R1 + add R1, R2 + mov R0, R1 + pop R1 + ret + hlt # guard bad marker - fake_input.set_input(56) - for _ in range(100): - _bin.step() - self.assertEqual(fake_ouput.get(), 31+24+56*15) + end: + out {self.FAKE_OUPUT_AT}, R0 + hlt + """) + self.assertEqual(self.fake_ouput.get(), 25) diff --git a/planner/sim/devices.py b/planner/sim/devices.py index b8b570c..93efc4d 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..e7ae95f --- /dev/null +++ b/planner/sim/gui_devices.py @@ -0,0 +1,102 @@ +from planner.sim import devices +import logging + +import os +os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" +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..bea5681 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,17 +27,41 @@ def __eq__(self, o): return self.name == o.name return False + 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 def assign(self, o): if not isinstance(o.value, int): - raise ValueError(f"{name} resolution failed during assign as {o.value}") - + raise ValueError(f"{self.name} resolution failed during assign as {o.value}") if self.value is None: self.value = o.value else: @@ -47,24 +72,26 @@ 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): 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..a485688 100644 --- a/planner/util.py +++ b/planner/util.py @@ -1,7 +1,7 @@ import string LABEL_CONSTANT = "constant" -PROGRAM_ORG = "PROGRAM_ORG" +LABEL_TMP = "__tmp__" def is_valid_label(msg): if len(msg) == 0: 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..b53257c --- /dev/null +++ b/programs/ping_pong.asm @@ -0,0 +1,272 @@ +# 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 + +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 + shlc ESP, 8 + orc ESP, 0xF0 + jmp game + +section .data + bat1_y dd 2 + 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 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 + + + 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, 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, 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: + movc R0, 0xF0 + shlc R0, 1 + _sleep: + subc R0, 1 + jnz _sleep + ret + + 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 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<