├── cucu ├── __init__.py ├── Makefile ├── gen.c ├── cucu.py └── cucu.c ├── assets ├── .gitignore └── style.css ├── src ├── app.js ├── ui │ ├── flag.filter.js │ ├── start.filter.js │ ├── number.filter.js │ ├── tab-support.directive.js │ ├── select-line.directive.js │ └── controller.js ├── emulator │ ├── opcodes.js │ ├── printer.js │ ├── memory.js │ └── cpu.js └── assembler │ └── asm.js ├── favicon.ico ├── examples ├── 21-addition.txt ├── 63-c-1.txt ├── 64-c-2.txt ├── 65-c-3.txt ├── qz21.txt ├── qz22.txt ├── qz23.txt ├── 22-addition-assembly.txt ├── ex23-jump.txt ├── ex21-add.txt ├── ex22-fp.txt ├── scandir.php ├── 51-insertion-sort.txt ├── 61-call-by-value.txt ├── 71-simple.txt ├── 62-call-by-reference.txt ├── 31-time-sharing-2-processes.txt ├── 52-binary-search.txt └── 72-seq-merge.txt ├── .gitignore ├── package.json ├── Gruntfile.js ├── Simserver.py ├── README.md ├── instruction-set.html ├── controller.js └── index.html /cucu/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/.gitignore: -------------------------------------------------------------------------------- 1 | *.js -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('ASMSimulator', []); 2 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chyyuu/v8-cpu/HEAD/favicon.ico -------------------------------------------------------------------------------- /examples/21-addition.txt: -------------------------------------------------------------------------------- 1 | ; 2.1 R0 = R1 + R2 2 | 3 | 2134 4 | 2218 5 | 5012 6 | C000 7 | -------------------------------------------------------------------------------- /examples/63-c-1.txt: -------------------------------------------------------------------------------- 1 | ; 6.3 C example (1) 2 | 3 | int main() 4 | { 5 | int i = 3; 6 | int j = 5; 7 | i = i + j; 8 | } 9 | -------------------------------------------------------------------------------- /examples/64-c-2.txt: -------------------------------------------------------------------------------- 1 | ; 6.4 C example (2) 2 | 3 | int main() 4 | { 5 | int i = 5; 6 | while(i != 10) 7 | { 8 | i = i + 1; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | node_modules/* 4 | ASMSimulator.iml 5 | *~ 6 | *.new 7 | *.old 8 | after.txt 9 | cucu-cc 10 | 11 | *.o 12 | *.pyc 13 | -------------------------------------------------------------------------------- /examples/65-c-3.txt: -------------------------------------------------------------------------------- 1 | ; 6.5 C Example (3) 2 | 3 | int main() 4 | { 5 | int i = 3; 6 | int j = 5; 7 | if(i != 5) 8 | { 9 | j = 6; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ui/flag.filter.js: -------------------------------------------------------------------------------- 1 | app.filter('flag', function() { 2 | return function(input) { 3 | return input.toString().toUpperCase(); 4 | }; 5 | }); 6 | -------------------------------------------------------------------------------- /src/ui/start.filter.js: -------------------------------------------------------------------------------- 1 | app.filter('startFrom', function() { 2 | return function(input, start) { 3 | start = +start; //parse to int 4 | return input.slice(start); 5 | }; 6 | }); 7 | -------------------------------------------------------------------------------- /examples/qz21.txt: -------------------------------------------------------------------------------- 1 | ; Quiz(A) 2.2 Calculate -1 + 2 - 3 + 4 ... 2 | 3 | 2008 4 | 2101 5 | 22FF 6 | 2300 7 | 24FF 8 | 25FF 9 | B41C 10 | 5334 11 | 5445 12 | 9442 13 | 5441 14 | 9552 15 | 5551 16 | B00C 17 | C000 18 | -------------------------------------------------------------------------------- /examples/qz22.txt: -------------------------------------------------------------------------------- 1 | ; Quiz(B) 2.2 Calculate 1 - 2 + 3 - 4 ... 2 | 3 | 2007 4 | 2A01 5 | 2BFF 6 | 2C00 7 | 2D01 8 | 2E01 9 | BD1C 10 | 5CCD 11 | 5DDE 12 | 9DDB 13 | 5DDA 14 | 9EEB 15 | 5EEA 16 | B00C 17 | C000 18 | -------------------------------------------------------------------------------- /examples/qz23.txt: -------------------------------------------------------------------------------- 1 | ; Quiz(C) 2.2 Calculate 0 - 1 + 2 - 3 + 4 ... 2 | 3 | 2006 4 | 2101 5 | 22FF 6 | 2C00 7 | 2D00 8 | 2E01 9 | BD1C 10 | 5CCD 11 | 5DDE 12 | 9DD2 13 | 5DD1 14 | 9EE2 15 | 5EE1 16 | B00C 17 | C000 18 | -------------------------------------------------------------------------------- /examples/22-addition-assembly.txt: -------------------------------------------------------------------------------- 1 | ; 2.2 c = a + b (in assembly) 2 | 3 | .data@0x6C: 4 | DB 1 5 | DB 2 6 | 7 | ;; The program starts from 0xA0 8 | .entry@0xA0: 9 | LOADM R5, 0x6C 10 | LOADM R6, 0x6D 11 | ADDI R0, R5, R6 12 | STOREM R0, 0x6E 13 | HALT 14 | -------------------------------------------------------------------------------- /cucu/Makefile: -------------------------------------------------------------------------------- 1 | #CFLAGS := -Wall -W -std=c89 2 | 3 | all: cucu-cc 4 | 5 | cucu-cc: cucu.o 6 | $(CC) -o $@ $< 7 | 8 | cucu.o: cucu.c gen.c 9 | $(CC) -c $< -DGEN=\"gen.c\" -o $@ 10 | 11 | install: cucu-cc 12 | cp cucu-cc .. 13 | 14 | clean: 15 | rm -f cucu 16 | rm -f *.o 17 | 18 | .PHONY: all 19 | -------------------------------------------------------------------------------- /examples/ex23-jump.txt: -------------------------------------------------------------------------------- 1 | ; Exercise-2.3 Calculate 1 + 2 + ... + 10 2 | ; 3 | ; Requirement: 4 | ; * calculate 1 + 2 + ... + 10 and store the result in RC 5 | ; 6 | ; The following program does not meet the above requirements. Try correcting it. 7 | 8 | 2010 9 | 2101 10 | 2C00 11 | 2201 12 | B210 13 | 5CC2 14 | 5221 15 | B006 16 | C000 17 | -------------------------------------------------------------------------------- /src/ui/number.filter.js: -------------------------------------------------------------------------------- 1 | app.filter('number', function() { 2 | return function(input, isHex) { 3 | if (input === 0 || input === undefined) 4 | return "00"; 5 | if (isHex) { 6 | var hex = input.toString(16).toUpperCase(); 7 | return hex.length == 1 ? "0" + hex: hex; 8 | } else { 9 | return input.toString(10); 10 | } 11 | }; 12 | }); 13 | -------------------------------------------------------------------------------- /examples/ex21-add.txt: -------------------------------------------------------------------------------- 1 | ; Exercise-2.1 Integer Addition 2 | ; 3 | ; Requirement: 4 | ; * store 38 (in decimal) in R1 5 | ; * store 52 (in decimal) in R2 6 | ; * add numbers in R1 and R2 and store the result in R0 7 | ; * when the machine halts, R0 should hold the bit patterns representing 90. 8 | ; 9 | ; The following program does not meet the above requirements. Try correcting it. 10 | 11 | 2138 12 | 2252 13 | 5120 14 | C000 15 | -------------------------------------------------------------------------------- /examples/ex22-fp.txt: -------------------------------------------------------------------------------- 1 | ; Exercise-2.2 Floating-point Addition 2 | ; 3 | ; Requirement: 4 | ; * store -0.5 in R3 5 | ; * store 1.25 in R5 6 | ; * add numbers in R3 and R5 and store the result in R8 7 | ; * when the machine halts, R8 should hold the bit patterns representing 0.75 in 8 | ; floating point. 9 | ; 10 | ; The following program does not meet the above requirements. Try correcting it. 11 | 12 | 2348 13 | 255A 14 | 6835 15 | C000 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asmsimulator", 3 | "version": "0.5.1", 4 | "description": "Simple 8-bit Assembler Simulator in Javascript", 5 | "author": "Marco Schweighauser", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "angular": "~1.4.5", 9 | "grunt": "~0.4.1", 10 | "grunt-contrib-concat": "~0.3.0", 11 | "grunt-contrib-jshint": "~0.7.0", 12 | "grunt-contrib-uglify": "~0.2.4", 13 | "grunt-contrib-watch": "~0.5.3", 14 | "grunt-php": "^1.5.1", 15 | "load-grunt-tasks": "^3.2.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/emulator/opcodes.js: -------------------------------------------------------------------------------- 1 | app.service('opcodes', [function() { 2 | var opcodes = { 3 | NONE: 0, 4 | LOAD_FROM_MEMORY: 1, 5 | LOAD_WITH_CONSTANT: 2, 6 | STORE_TO_MEMORY: 3, 7 | MOVE: 4, 8 | ADD_INT: 5, 9 | ADD_FLOAT: 6, 10 | OR: 7, 11 | AND: 8, 12 | XOR: 9, 13 | ROTATE: 10, 14 | JUMP_IF_EQUAL: 11, 15 | HALT: 12, 16 | LOAD_FROM_POINTER: 13, 17 | STORE_TO_POINTER: 14, 18 | JUMP_IF_LESS: 15 19 | }; 20 | 21 | return opcodes; 22 | }]); 23 | 24 | /* 25 | * Local variables: 26 | * c-basic-offset: 4 27 | * tab-width: 4 28 | * indent-tabs-mode: nil 29 | * End: 30 | */ 31 | -------------------------------------------------------------------------------- /examples/scandir.php: -------------------------------------------------------------------------------- 1 | = 0 && strpos($file, ".txt", $tmp) !== FALSE) { 10 | $comment = substr(fgets(fopen($file, 'r')), 2); 11 | if (strpos($comment, 'HIDE') === FALSE) { 12 | array_push($files, $file . '|' . $comment); 13 | } 14 | } 15 | } 16 | @closedir($handle); 17 | sort($files); //uksort($files, "strnatcasecmp"); 18 | 19 | $files = json_encode($files); 20 | 21 | unset($handle,$ext,$file,$path); 22 | ?> 23 | 24 | -------------------------------------------------------------------------------- /src/ui/tab-support.directive.js: -------------------------------------------------------------------------------- 1 | app.directive('tabSupport', [function () { 2 | return { 3 | restrict: 'A', 4 | link: function (scope, element, attrs, controller) { 5 | element.bind("keydown", function (e) { 6 | if (e.keyCode === 9) { 7 | var val = this.value; 8 | var start = this.selectionStart; 9 | var end = this.selectionEnd; 10 | 11 | this.value = val.substring(0, start) + '\t' + val.substring(end); 12 | this.selectionStart = this.selectionEnd = start + 1; 13 | 14 | e.preventDefault(); 15 | return false; 16 | } 17 | }); 18 | } 19 | }; 20 | }]); 21 | -------------------------------------------------------------------------------- /src/emulator/printer.js: -------------------------------------------------------------------------------- 1 | app.service('printer', [function () { 2 | var printer = { 3 | data: '', 4 | load: function() { 5 | return 0; 6 | }, 7 | store: function(value) { 8 | var self = this; 9 | if (value < 16) 10 | self.data += '0' + value.toString(16).toUpperCase() + " "; 11 | else 12 | self.data += value.toString(16).toUpperCase() + " "; 13 | }, 14 | reset: function () { 15 | var self = this; 16 | self.data = ''; 17 | } 18 | }; 19 | 20 | printer.reset(); 21 | return printer; 22 | }]); 23 | 24 | /* 25 | * Local variables: 26 | * c-basic-offset: 4 27 | * tab-width: 4 28 | * indent-tabs-mode: nil 29 | * End: 30 | */ 31 | -------------------------------------------------------------------------------- /examples/51-insertion-sort.txt: -------------------------------------------------------------------------------- 1 | ; 5.1 Insertion Sort 2 | ; 3 | ; The list of numbers to be sorted is stored from .data 4 | 5 | .entry: 6 | ;; Initialize some constants: 7 | LOADB R1, 1 8 | LOADB R2, -1 9 | 10 | LOADB R3, .data+1 ;; loop variable of the outer loop 11 | outer_loop: 12 | LOADB R0, .data_end 13 | JUMP R3, outer_loop_end 14 | LOADP R4, R3 ;; the pivot 15 | 16 | MOVE R5, R3 ;; loop variable of the outer loop 17 | inner_loop: 18 | LOADB R0, .data 19 | JUMP R5, inner_loop_end 20 | MOVE R6, R5 21 | ADDI R5, R5, R2 22 | LOADP R0, R5 23 | JUMPL R4, tmp1 24 | ADDI R5, R5, R1 25 | JUMP R0, inner_loop_end 26 | tmp1: STOREP R0, R6 27 | JUMP R0, inner_loop 28 | 29 | inner_loop_end: 30 | STOREP R4, R5 31 | ADDI R3, R3, R1 32 | JUMP R0, outer_loop 33 | 34 | outer_loop_end: 35 | HALT 36 | 37 | .data@0x40: 38 | DB 0x20 39 | DB 0x34 40 | DB 0xDE 41 | DB 0x65 42 | DB 0xAA 43 | DB 0xC0 44 | DB 0x00 45 | DB 0xF1 46 | .data_end: 47 | -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | .source-code { 2 | font-family: source-code-pro, Courier New; 3 | } 4 | 5 | .output, .output-bg { 6 | background-color: #DFDFDF; 7 | } 8 | 9 | .output { 10 | width: 1em; 11 | margin-right: 1px; 12 | margin-bottom: 1px; 13 | text-align: center; 14 | } 15 | 16 | .instr-bg { 17 | background-color: #E6F3FF; 18 | } 19 | 20 | .instr-bg a { 21 | color: inherit; 22 | cursor: pointer; 23 | } 24 | 25 | .stack-bg { 26 | background-color: #F8DCB4; 27 | } 28 | 29 | .memory-block { 30 | width: 1.8em; 31 | display: inline-block; 32 | text-align: center; 33 | } 34 | 35 | .marker { 36 | width: 1.8em; 37 | color: #FFF; 38 | border-radius: 2px; 39 | } 40 | 41 | .marker-ip { 42 | background-color: #428BCA; 43 | } 44 | 45 | .marker-sp { 46 | background-color: #EEA236; 47 | } 48 | 49 | .marker-a { 50 | background-color: #4D8C20; 51 | } 52 | 53 | .marker-b { 54 | background-color: #22D19D; 55 | } 56 | 57 | .marker-c { 58 | background-color: #A137ED; 59 | } 60 | 61 | .marker-d { 62 | background-color: #E81F1F; 63 | } 64 | 65 | .codelabel-line a { 66 | cursor: pointer; 67 | } 68 | -------------------------------------------------------------------------------- /examples/61-call-by-value.txt: -------------------------------------------------------------------------------- 1 | ; 6.1 Passing Parameters by Value 2 | ; 3 | ; The corresponding C code looks like this: 4 | ; 5 | ; int a = 0xDE, b = 0xFA; 6 | ; int swap (int a, int b) { 7 | ; int c = a; 8 | ; a = b; 9 | ; b = c; 10 | ; return (a + b); 11 | ; } 12 | ; main() { 13 | ; swap(a, b); 14 | ; } 15 | 16 | .entry: 17 | ;; initialize some constants 18 | LOADB R1, 1 19 | LOADB R2, -1 20 | ;; initialize the stack 21 | LOADB RE, .stack 22 | ;; push a slot for the return value 23 | ADDI RE, RE, R2 24 | ;; push the second parameter 25 | ADDI RE, RE, R2 26 | LOADM R3, .data+1 27 | STOREP R3, RE 28 | ;; push the first parameter 29 | ADDI RE, RE, R2 30 | LOADM R3, .data 31 | STOREP R3, RE 32 | ;; push the return address 33 | ADDI RE, RE, R2 34 | LOADB R3, next 35 | STOREP R3, RE 36 | JUMP R0, swap 37 | next: LOADB R4, 3 38 | ADDI RE, RE, R4 39 | LOADP R0, RE 40 | STOREM R0, 0xFE 41 | ADDI RE, RE, R1 42 | HALT 43 | 44 | swap@0x40: 45 | ADDI R8, RE, R1 46 | LOADP RA, R8 ; the first parameter 47 | ADDI R8, R8, R1 48 | LOADP RB, R8 ; the second parameter 49 | ;; int c = a; 50 | MOVE RC, RA 51 | ;; a = b; 52 | MOVE RA, RB 53 | ;; b = c; 54 | MOVE RB, RC 55 | ;; return (a + b); 56 | ADDI RD, RA, RB 57 | ADDI R8, R8, R1 58 | STOREP RD, R8 59 | LOADP R8, RE 60 | STOREM R8, ret+1 61 | ret: JUMP R0, 00 62 | 63 | 64 | .stack@0x80: 65 | .data@0x80: 66 | DB 0xDE 67 | DB 0xFA 68 | -------------------------------------------------------------------------------- /examples/71-simple.txt: -------------------------------------------------------------------------------- 1 | ; 7.1 Disk Operation Demo 2 | ; 3 | ; This small program loads sector 0 from the disk, place 4 | ; the data in memory cells whose addresses range from 0x20 5 | ; to 0x2F, and store the same data to sector 1 on the disk. 6 | ; 7 | ; To load a sector from the disk, you should: 8 | ; 9 | ; 1) Store to the memory address 0xFF, which is the register 10 | ; of the disk in V8 architecture, the sector address you 11 | ; want to load. 12 | ; 2) Store to 0xFF the memory address where the data should 13 | ; be stored in. 14 | ; 3) Store to 0xFF the constant 0, indicating that this is a 15 | ; LOAD operation. 16 | ; 4) Load the bit pattern in 0xFF repeatedly until the pattern 17 | ; is not zero. 18 | ; 19 | ; Similar operations should be done to store a sector to the disk. 20 | ; 21 | 22 | LOADB R0, 0 23 | LOADB R1, 1 24 | LOADB R2, 0x20 25 | 26 | ; Load from sector 0 27 | STOREM R0, 0xFF 28 | STOREM R2, 0xFF 29 | STOREM R0, 0xFF 30 | .again_load: 31 | LOADM RE, 0xFF 32 | JUMP RE, .again_load 33 | 34 | ; Store to sector 1 35 | STOREM R1, 0xFF 36 | STOREM R2, 0xFF 37 | STOREM R1, 0xFF 38 | .again_store: 39 | LOADM RE, 0xFF 40 | JUMP RE, .again_store 41 | 42 | HALT 43 | 44 | ========== 45 | ; Data in the disk are listed below. 46 | 47 | 04 0C 05 0F 00 00 00 00 00 00 00 00 00 00 00 00 48 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 49 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 50 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 51 | -------------------------------------------------------------------------------- /examples/62-call-by-reference.txt: -------------------------------------------------------------------------------- 1 | ; 6.2 Passing Parameters by Reference 2 | ; 3 | ; The corresponding C code looks like this: 4 | ; 5 | ; int a = 0xDE, b = 0xFA; 6 | ; int swap (int *a, int *b) { 7 | ; int c = *a; 8 | ; *a = *b; 9 | ; *b = c; 10 | ; return (*a + *b); 11 | ; } 12 | ; main() { 13 | ; print(swap(&a, &b)); 14 | ; } 15 | 16 | .entry: 17 | ;; initialize some constants 18 | LOADB R1, 1 19 | LOADB R2, -1 20 | ;; initialize the stack 21 | LOADB RE, .stack 22 | ;; push a slot for the return value 23 | ADDI RE, RE, R2 24 | ;; push the second parameter 25 | ADDI RE, RE, R2 26 | LOADB R3, .data+1 27 | STOREP R3, RE 28 | ;; push the first parameter 29 | ADDI RE, RE, R2 30 | LOADB R3, .data 31 | STOREP R3, RE 32 | ;; push the return address 33 | ADDI RE, RE, R2 34 | LOADB R3, next 35 | STOREP R3, RE 36 | JUMP R0, swap 37 | next: LOADB R4, 3 38 | ADDI RE, RE, R4 39 | LOADP R0, RE 40 | STOREM R0, 0xFE 41 | ADDI RE, RE, R1 42 | HALT 43 | 44 | swap@0x40: 45 | ADDI R8, RE, R1 46 | LOADP RA, R8 ; the first parameter 47 | ADDI R8, R8, R1 48 | LOADP RB, R8 ; the second parameter 49 | ;; int c = *a; 50 | LOADP RC, RA 51 | ;; *a = *b; 52 | LOADP RD, RB 53 | STOREP RD, RA 54 | ;; *b = c; 55 | STOREP RC, RB 56 | ;; return (*a + *b); 57 | LOADP RC, RA 58 | LOADP RD, RB 59 | ADDI RD, RC, RD 60 | ADDI R8, R8, R1 61 | STOREP RD, R8 62 | LOADP R8, RE 63 | STOREM R8, ret+1 64 | ret: JUMP R0, 00 65 | 66 | .stack@0x80: 67 | .data@0x80: 68 | DB 0xDE 69 | DB 0xFA 70 | -------------------------------------------------------------------------------- /src/ui/select-line.directive.js: -------------------------------------------------------------------------------- 1 | // Source: http://lostsource.com/2012/11/30/selecting-textarea-line.html 2 | app.directive('selectLine', [function () { 3 | return { 4 | restrict: 'A', 5 | link: function (scope, element, attrs, controller) { 6 | scope.$watch('selectedLine', function () { 7 | if (scope.selectedLine >= 0) { 8 | var lines = element[0].value.split("\n"); 9 | 10 | // Calculate start/end 11 | var startPos = 0; 12 | for (var x = 0; x < lines.length; x++) { 13 | if (x == scope.selectedLine) { 14 | break; 15 | } 16 | startPos += (lines[x].length + 1); 17 | } 18 | 19 | var endPos = lines[scope.selectedLine].length + startPos; 20 | 21 | // Chrome / Firefox 22 | if (typeof(element[0].selectionStart) != "undefined") { 23 | element[0].focus(); 24 | element[0].selectionStart = startPos; 25 | element[0].selectionEnd = endPos; 26 | } 27 | 28 | // IE 29 | if (document.selection && document.selection.createRange) { 30 | element[0].focus(); 31 | element[0].select(); 32 | var range = document.selection.createRange(); 33 | range.collapse(true); 34 | range.moveEnd("character", endPos); 35 | range.moveStart("character", startPos); 36 | range.select(); 37 | } 38 | } 39 | }); 40 | } 41 | }; 42 | }]); 43 | -------------------------------------------------------------------------------- /examples/31-time-sharing-2-processes.txt: -------------------------------------------------------------------------------- 1 | ; 3.1 Time-sharing Between Two Processes 2 | 3 | .programA@0x00: 4 | LOADB R0, 0 5 | LOADB R1, 0 6 | LOADB R2, 1 7 | loop_a: 8 | ADDI R0, R0, R1 9 | ADDI R1, R1, R2 10 | JUMP R0, loop_a 11 | 12 | .programB@0x40: 13 | LOADB R0, 0x00 14 | LOADB R1, 0x00 15 | LOADB R2, 0x58 16 | loop_b: 17 | ADDF R0, R0, R1 18 | ADDF R1, R1, R2 19 | JUMP R0, loop_b 20 | 21 | interrupt_handler@0x80: 22 | STOREM R0, tmp2 23 | LOADM R0, saved_regs 24 | STOREM R0, tmp1 25 | LOADM R0, tmp2 26 | STOREM R0, saved_regs 27 | 28 | LOADM R0, saved_pc 29 | STOREM R0, jmp+1 30 | 31 | LOADM R0, interrupted_pc 32 | STOREM R0, saved_pc 33 | 34 | MOVE R0, R1 35 | LOADM R1, saved_regs+1 36 | STOREM R0, saved_regs+1 37 | MOVE R0, R2 38 | LOADM R2, saved_regs+2 39 | STOREM R0, saved_regs+2 40 | MOVE R0, R3 41 | LOADM R3, saved_regs+3 42 | STOREM R0, saved_regs+3 43 | MOVE R0, R4 44 | LOADM R4, saved_regs+4 45 | STOREM R0, saved_regs+4 46 | MOVE R0, R5 47 | LOADM R5, saved_regs+5 48 | STOREM R0, saved_regs+5 49 | MOVE R0, R6 50 | LOADM R6, saved_regs+6 51 | STOREM R0, saved_regs+6 52 | MOVE R0, R7 53 | LOADM R7, saved_regs+7 54 | STOREM R0, saved_regs+7 55 | MOVE R0, R8 56 | LOADM R8, saved_regs+8 57 | STOREM R0, saved_regs+8 58 | MOVE R0, R9 59 | LOADM R9, saved_regs+9 60 | STOREM R0, saved_regs+9 61 | MOVE R0, RA 62 | LOADM RA, saved_regs+10 63 | STOREM R0, saved_regs+10 64 | MOVE R0, RB 65 | LOADM RB, saved_regs+11 66 | STOREM R0, saved_regs+11 67 | MOVE R0, RC 68 | LOADM RC, saved_regs+12 69 | STOREM R0, saved_regs+12 70 | MOVE R0, RD 71 | LOADM RD, saved_regs+13 72 | STOREM R0, saved_regs+13 73 | MOVE R0, RE 74 | LOADM RE, saved_regs+14 75 | STOREM R0, saved_regs+14 76 | 77 | LOADM R0, tmp1 78 | jmp: JUMP R0, 00 79 | 80 | saved_regs@0xEA: 81 | saved_pc@0xF9: 82 | tmp1@0xFA: 83 | tmp2@0xFB: 84 | interrupted_pc@0xFD: 85 | 86 | .entry@0xF0: 87 | LOADB RF, 0x80 88 | LOADB R0, 0x40 89 | STOREM R0, saved_pc 90 | JUMP R0, 0x00 91 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | require('load-grunt-tasks')(grunt); 4 | 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | concat: { 8 | options: { 9 | separator: ';' 10 | }, 11 | dist: { 12 | src: ['src/app.js', 'src/**/*.js'], 13 | dest: 'assets/<%= pkg.name %>.js' 14 | } 15 | }, 16 | uglify: { 17 | options: { 18 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n' 19 | }, 20 | dist: { 21 | files: { 22 | 'assets/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>'] 23 | } 24 | } 25 | }, 26 | jshint: { 27 | files: ['Gruntfile.js', 'src/**/*.js'], 28 | options: { 29 | // options here to override JSHint defaults 30 | trailing: true, 31 | globals: { 32 | browser: true, 33 | console: true 34 | } 35 | } 36 | }, 37 | watch: { 38 | files: ['<%= jshint.files %>'], 39 | tasks: ['jshint', 'concat'] 40 | }, 41 | php: { 42 | dist: { 43 | options: { 44 | port: 8082, 45 | hostname: '0.0.0.0', 46 | base: '.', 47 | keepalive: true 48 | } 49 | } 50 | } 51 | }); 52 | 53 | grunt.loadNpmTasks('grunt-contrib-uglify'); 54 | grunt.loadNpmTasks('grunt-contrib-jshint'); 55 | grunt.loadNpmTasks('grunt-contrib-watch'); 56 | grunt.loadNpmTasks('grunt-contrib-concat'); 57 | 58 | grunt.registerTask('default', ['jshint', 'concat', 'uglify']); 59 | grunt.registerTask('http', ['php']); 60 | 61 | }; 62 | 63 | /* 64 | * Local variables: 65 | * c-basic-offset: 4 66 | * tab-width: 4 67 | * indent-tabs-mode: nil 68 | * End: 69 | */ 70 | -------------------------------------------------------------------------------- /Simserver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import BaseHTTPServer 4 | import urlparse 5 | import os 6 | import unittest 7 | import json 8 | from cucu.cucu import CucuVM 9 | 10 | class WebRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 11 | def do_POST(self): 12 | payload = json.loads(self.rfile.read(int(self.headers['content-length']))) 13 | source = payload["source"].split('\n') 14 | real_source = '' 15 | for line in source: 16 | if not line.startswith(';'): 17 | real_source += line + '\n' 18 | c = CucuVM(real_source) 19 | parsed_path = urlparse.urlparse(self.path) 20 | message_parts = [ 21 | 'CLIENT VALUES:', 22 | 'client_address=%s (%s)' % (self.client_address, 23 | self.address_string()), 24 | 'command=%s' % self.command, 25 | 'path=%s' % self.path, 26 | 'real path=%s' % parsed_path.path, 27 | 'query=%s' % parsed_path.query, 28 | 'request_version=%s' % self.request_version, 29 | '', 30 | 'SERVER VALUES:', 31 | 'server_version=%s' % self.server_version, 32 | 'sys_version=%s' % self.sys_version, 33 | 'protocol_version=%s' % self.protocol_version, 34 | '', 35 | 'HEADERS RECEIVED:', 36 | ] 37 | for name, value in sorted(self.headers.items()): 38 | message_parts.append('%s=%s' % (name, value.rstrip())) 39 | message_parts.append('') 40 | message = '\n'.join(message_parts) 41 | self.send_response(200) 42 | self.end_headers() 43 | self.wfile.write(c.code) 44 | def do_GET(self): 45 | parsed_path = urlparse.urlparse(self.path) 46 | pa = self.path; 47 | self.send_response(200) 48 | self.end_headers() 49 | if(pa[0:17] == "/examples/scandir"): 50 | info = os.getcwd(); 51 | arr = sorted(os.listdir(info+"/examples")); 52 | results = [] 53 | for fn in arr: 54 | if not fn.endswith('.txt'): 55 | continue 56 | f = open('examples/' + fn, 'r') 57 | line = f.readlines()[0].strip() 58 | if 'HIDE' in line: 59 | continue 60 | results.append('"%s|%s"' % (fn, line[2:])) 61 | text = '[%s]' % ','.join(results) 62 | self.wfile.write(text); 63 | else: 64 | if pa == "/": 65 | pa = "/index.html" 66 | input = open(pa[1:],"r"); 67 | self.wfile.write(input.read()); 68 | input.close() 69 | 70 | server = BaseHTTPServer.HTTPServer(('0.0.0.0',8082), WebRequestHandler) 71 | server.serve_forever() 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple 8-bit V8-CPU Simulator with Assembler/C-subset Compiler 2 | V8-CPU simulator is used for labs& exercises on first-year(freshman) undergraduate CS course "An Overview of Computer Science" in Dept. of CS in Tsinghua Univ. This simulator provides a simplified assembler/C syntax and is simulating a simple 8-bit RISC cpu. Press Help inside the simulator to see an overview about the supported instructions, which is based on Appendix C contents in Computer Science: An Overview textbook. 3 | 4 | # TRY IT ONLINE 5 | 6 | ### Features 7 | - 8-bit RISC CPU 8 | - 15 general purpose registers 9 | - program counter register 10 | - 1 timer count-downregister 11 | - 256 bytes of memory 12 | - a printer(like a output serial) 13 | - assembler (see 'assemble' button) 14 | - binary code/data uploader(see 'upload' button) 15 | - several assembly code example(see 'Examples' list) 16 | - 4KB disk (To Be Done) 17 | - C-subset compiler (To Be Done) 18 | 19 | ### How to build 20 | Make sure you have Grunt installed to compile the `asmsimulator.js` script. 21 | 22 | steps in ubuntu 16.04 x64 23 | ``` 24 | sudo apt install npm nodejs-legacy php7.0-cli 25 | alias cnpm="npm --registry=https://registry.npm.taobao.org \ 26 | --cache=$HOME/.npm/.cache/cnpm \ 27 | --disturl=https://npm.taobao.org/dist \ 28 | --userconfig=$HOME/.cnpmrc" 29 | cd v8-cpu 30 | cnpm install 31 | cnpm grunt-cli 32 | ./node_modules/.bin/grunt 33 | cd cucu 34 | make install 35 | cd .. 36 | ./node_modules/.bin/grunt http 37 | ``` 38 | 39 | Run `npm install && grunt` to build the project and `grunt http` to run. 40 | 41 | ### Background 42 | A technical introduction is available on Marco Schweighauser's blog: [www.mschweighauser.com](https://www.mschweighauser.com/make-your-own-assembler-simulator-in-javascript-part1/). 43 | 44 | ### License 45 | **The MIT License** 46 | 47 | Copyright (c) 2015 Yuanchun Shi, Yu Chen, Junjie Mao, Yukang Yan 48 | 49 | Copyright (c) 2015 Marco Schweighauser 50 | 51 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 52 | 53 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 54 | 55 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 56 | -------------------------------------------------------------------------------- /src/emulator/memory.js: -------------------------------------------------------------------------------- 1 | app.service('memory', ['printer', function (printer) { 2 | var memory = { 3 | data: Array(256), 4 | lastAccess: -1, 5 | diskdata: Array(256), 6 | statusnow: 0, 7 | sector_address: 0, 8 | memory_address: 0, 9 | disk_operation: 0, 10 | disk_latency: 0, 11 | sectorlength: 16, 12 | 13 | load: function (address) { 14 | var self = this; 15 | 16 | if (address < 0 || address >= self.data.length) { 17 | throw "Memory access violation at " + address; 18 | } 19 | 20 | self.lastAccess = address; 21 | 22 | if (address == 0xFE) { 23 | return printer.load(); 24 | } else if (address == 0xFF) { 25 | if (self.disk_latency > 0) { 26 | self.disk_latency = self.disk_latency - 1; 27 | return 0; 28 | } 29 | if (self.disk_operation === 0x00) { 30 | for(var i = 0; i < self.sectorlength; i++) 31 | self.data[self.memory_address + i] = self.diskdata[self.sector_address * self.sectorlength + i]; 32 | } else if (self.disk_operation === 0x01) { 33 | for(var j = 0; j < self.sectorlength; j++) 34 | self.diskdata[self.sector_address * self.sectorlength + j] = self.data[self.memory_address + j]; 35 | } 36 | self.statusnow = 0; 37 | self.sector_address = 0; 38 | self.memory_address = 0; 39 | return 0xFF; 40 | } 41 | return self.data[address]; 42 | }, 43 | store: function (address, value) { 44 | var self = this; 45 | 46 | if (address < 0 || address >= self.data.length) { 47 | throw "Memory access violation at " + address; 48 | } 49 | 50 | self.lastAccess = address; 51 | 52 | if (address == 0xFE) { 53 | return printer.store(value); 54 | } 55 | if (address == 0xFF) 56 | { 57 | if (self.statusnow === 0) 58 | { 59 | self.statusnow = 1; 60 | self.sector_address = value; 61 | } else if (self.statusnow === 1) { 62 | self.statusnow = 2; 63 | self.memory_address = value; 64 | } else if(self.statusnow === 2) { 65 | self.disk_operation = value; 66 | self.disk_latency = 3; 67 | } 68 | return; 69 | } 70 | self.data[address] = value; 71 | }, 72 | reset: function () { 73 | var self = this; 74 | 75 | self.lastAccess = -1; 76 | self.statusnow = 0; 77 | self.sector_address = 0; 78 | self.memory_address = 0; 79 | self.disk_operation = 0; 80 | self.disk_latency = 0; 81 | for (var i = 0, l = self.data.length; i < l; i++) { 82 | self.data[i] = 0; 83 | } 84 | for(i = 0;i < self.diskdata.length;i++) 85 | { 86 | self.diskdata[i] = 0; 87 | } 88 | } 89 | }; 90 | 91 | memory.reset(); 92 | return memory; 93 | }]); 94 | 95 | /* 96 | * Local variables: 97 | * c-basic-offset: 4 98 | * tab-width: 4 99 | * indent-tabs-mode: nil 100 | * End: 101 | */ 102 | -------------------------------------------------------------------------------- /examples/52-binary-search.txt: -------------------------------------------------------------------------------- 1 | ; 5.2 Binary Search 2 | ; 3 | ; This example implements the following function: 4 | ; 5 | ; int list[]; 6 | ; int binary_search(int begin, int end, int target) { 7 | ; if (end < begin) 8 | ; return -1; 9 | ; int mid = begin + (end - begin) / 2; 10 | ; int mid_val = list[mid]; 11 | ; if (target == mid_val) 12 | ; return mid; 13 | ; else if (target < mid_val) 14 | ; return binary_search(begin, mid - 1, target); 15 | ; else 16 | ; return binary_search(mid + 1, end, target); 17 | ; } 18 | ; 19 | ; which will search for 'target' in the range [begin, end] 20 | ; of the global list. The function returns the index of the 21 | ; element if 'target' is found in the list. Otherwise it 22 | ; returns -1. 23 | 24 | 25 | .entry: 26 | ;; initialize some constants 27 | LOADB R1, 1 28 | LOADB R2, -1 29 | ;; initialize the stack 30 | LOADB RE, .stack 31 | 32 | ADDI RE, RE, R2 33 | ;; 3rd parameter 'target' = 0x34 34 | ADDI RE, RE, R2 35 | LOADB R3, 0x34 36 | STOREP R3, RE 37 | ;; 2nd parameter 'end' = .data_end - .data - 1 38 | ADDI RE, RE, R2 39 | LOADB R3, .data 40 | XOR R3, R3, R2 41 | LOADB R4, .data_end 42 | ADDI R3, R3, R4 43 | STOREP R3, RE 44 | ;; 1st parameter 'begin' = 0 45 | ADDI RE, RE, R2 46 | LOADB R3, 0 47 | STOREP R3, RE 48 | ;; return address 49 | ADDI RE, RE, R2 50 | LOADB R3, tmp1 51 | STOREP R3, RE 52 | JUMP R0, binary_search 53 | tmp1: LOADB R3, 4 54 | ADDI RE, RE, R3 55 | LOADP R0, RE 56 | STOREM R0, 0xFE ; Send the result to the printer 57 | ADDI RE, RE, R1 58 | HALT 59 | 60 | binary_search: 61 | ADDI R3, RE, R1 62 | LOADP RA, R3 ; 1st argument 'begin' 63 | ADDI R3, R3, R1 64 | LOADP RB, R3 ; 2nd argument 'end' 65 | ADDI R3, R3, R1 66 | LOADP RC, R3 ; 3rd argument 'target' 67 | LOADB RD, -1 ; RD has value to be returned 68 | 69 | ;; if (end < begin) 70 | ;; return -1; 71 | MOVE R0, RA 72 | JUMPL RB, end 73 | 74 | ;; int mid = begin + (end - begin) / 2; 75 | XOR R4, RA, R2 76 | ADDI R4, R4, R1 77 | ADDI R4, R4, RB 78 | LOADB R3, 0xFE 79 | AND R4, R4, R3 80 | ROT R4, 1 81 | ADDI R4, R4, RA 82 | 83 | ;; int mid_val = list[middle]; 84 | LOADB R5, .data 85 | ADDI R5, R5, R4 86 | LOADP R5, R5 87 | 88 | MOVE R0, R5 89 | JUMP RC, equal 90 | JUMPL RC, target_less_than_mid 91 | JUMP R0, target_greater_than_mid 92 | 93 | equal: 94 | ;; if (target == mid_val) 95 | ;; return mid; 96 | MOVE RD, R4 97 | JUMP R0, end 98 | 99 | target_less_than_mid: 100 | ;; else if (target < mid_val) 101 | ;; return binary_search(begin, mid - 1, target); 102 | MOVE R8, RA 103 | MOVE R9, R4 104 | ADDI R9, R9, R2 105 | JUMP R0, recursive_call 106 | 107 | target_greater_than_mid: 108 | ;; else 109 | ;; return binary_search(mid + 1, end, target); 110 | MOVE R8, R4 111 | ADDI R8, R8, R1 112 | MOVE R9, RB 113 | 114 | recursive_call: 115 | ADDI RE, RE, R2 116 | ADDI RE, RE, R2 117 | STOREP RC, RE 118 | ADDI RE, RE, R2 119 | STOREP R9, RE 120 | ADDI RE, RE, R2 121 | STOREP R8, RE 122 | ADDI RE, RE, R2 123 | LOADB R4, tmp2 124 | STOREP R4, RE 125 | JUMP R0, binary_search 126 | tmp2: LOADB R4, 4 127 | ADDI RE, RE, R4 128 | LOADP RD, RE 129 | ADDI RE, RE, R1 130 | end: 131 | LOADB R3, 4 132 | ADDI R3, RE, R3 133 | STOREP RD, R3 134 | LOADP R3, RE 135 | STOREM R3, ret+1 136 | ret: JUMP R0, 0x00 137 | 138 | .data@0xA0: 139 | DB 0xAA 140 | DB 0xC0 141 | DB 0xDE 142 | DB 0xF1 143 | DB 0x00 144 | DB 0x20 145 | DB 0x34 146 | DB 0x65 147 | .data_end: 148 | .stack@0xF8: 149 | -------------------------------------------------------------------------------- /cucu/gen.c: -------------------------------------------------------------------------------- 1 | #define emits(s) emit(s, strlen(s)) 2 | 3 | #define TYPE_NUM_SIZE 2 4 | static int mem_pos = 0; 5 | 6 | #define GEN_ADD "LOADP RB,RE\nADDI RE,RE,R1\nADDI RA,RA,RB\n" 7 | #define GEN_ADDSZ strlen(GEN_ADD) 8 | 9 | #define GEN_SUB "LOADP RB,RE\nADDI RE,RE,R1\nA:=B-A \n" 10 | #define GEN_SUBSZ strlen(GEN_SUB) 11 | 12 | #define GEN_SHL "LOADP RB,RE\nADDI RE,RE,R1\nA:=B<addr); 53 | //memcpy(code+27, s, 4); 54 | emits("HALT \n"); 55 | emits(".stack@0xC0:"); 56 | printf("%s", code); 57 | FILE* fp = fopen("after.txt", "w"); 58 | fputs(code, fp); 59 | fclose(fp); 60 | } 61 | 62 | static void gen_ret() { 63 | emits("HALT \n"); 64 | emits(".stack@0xC0:"); 65 | stack_pos = stack_pos - 1; 66 | } 67 | 68 | static void gen_const(int n) { 69 | char s[32]; 70 | sprintf(s, "LOADB RA,%02d\n",n); 71 | emits(s); 72 | } 73 | 74 | static void gen_sym(struct sym *sym) { 75 | if (sym->type == 'G') { 76 | sym->addr = mem_pos; 77 | mem_pos = mem_pos + TYPE_NUM_SIZE; 78 | } 79 | } 80 | 81 | static void gen_loop_start() {} 82 | 83 | static void gen_sym_addr(struct sym *sym) { 84 | //gen_const(sym->addr); 85 | } 86 | 87 | static void gen_push() { 88 | emits("ADDI RE,RE,R2\nSTOREP RA,RE\n"); 89 | stack_pos = stack_pos + 1; 90 | } 91 | 92 | static void gen_pop(int n) { 93 | char s[32]; 94 | if (n > 0) { 95 | sprintf(s, "ADDI RE,RE,R1\n"); 96 | emits(s); 97 | stack_pos = stack_pos - n; 98 | } 99 | } 100 | 101 | static void gen_stack_addr(int addr) { 102 | char s[32]; 103 | sprintf(s, "LOADB RC, %02x\nADDI RA,RE,RC\n", addr); 104 | emits(s); 105 | } 106 | 107 | static void gen_unref(int type) { 108 | if (type == TYPE_INTVAR) { 109 | emits("LOADP RA,RA\n"); 110 | } else if (type == TYPE_CHARVAR) { 111 | emits("LOADP RA,RA\n"); 112 | } 113 | } 114 | 115 | static void gen_call() { 116 | emits("call A\n"); 117 | } 118 | 119 | static void gen_array(char *array, int size) { 120 | int i = size; 121 | char *tok = array; 122 | /* put token on stack */ 123 | for (; i >= 0; i-=2) { 124 | //gen_const((tok[i] << 8 | tok[i-1])); 125 | gen_push(); 126 | } 127 | /* put token address on stack */ 128 | gen_stack_addr(0); 129 | } 130 | 131 | 132 | static void gen_patch(uint8_t *op, int value) { 133 | char s[32]; 134 | sprintf(s, "%04x", value); 135 | memcpy(op-5, s, 4); 136 | } 137 | 138 | static void gen_patch_str(uint8_t *op, char* address) 139 | { 140 | memcpy(op-6,address,5); 141 | } 142 | -------------------------------------------------------------------------------- /cucu/cucu.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | import time 4 | 5 | # 6 | # Interpret VM instruction 7 | # 8 | class CucuVM: 9 | CUCU_PATH='./cucu-cc' 10 | def __init__(self, src, debug=False): 11 | self.A = 0 12 | self.B = 0 13 | self.PC = 0 14 | self.SP = 16 15 | self.mem = [0 for i in range(0, 16)] 16 | print(src.encode('ascii')) 17 | self.compile(src.encode('ascii')) 18 | self.debug = debug 19 | #if debug: 20 | print(self.code) 21 | print(self.PC) 22 | print(len(self.code)) 23 | self.debug = True 24 | while (self.PC < len(self.code)): 25 | self.step() 26 | if debug: 27 | self.dump() 28 | 29 | def compile(self, src): 30 | p = subprocess.Popen(self.CUCU_PATH, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 31 | self.code = p.communicate(input=src)[0].replace('\r\n', '\n') 32 | 33 | def getint(self, addr): 34 | return self.mem[addr] + self.mem[addr+1] * 256 35 | def putint(self, addr, n): 36 | self.mem[addr] = n & 0xff 37 | self.mem[addr+1] = (n & 0xff00) >> 8 38 | 39 | def step(self): 40 | op = (self.code[self.PC:self.PC+8]).decode('ascii') 41 | #op = op.replace('\n','') 42 | #op = op.replace('\r','') 43 | #op = op.replace(' ', '') 44 | if (self.debug): 45 | print("op", op) 46 | 47 | self.PC = self.PC + 8 48 | if (op.startswith(';')): 49 | return 50 | elif (op.startswith('ret')): 51 | try: 52 | addr = self.getint(self.SP) 53 | #addr = self.mem[self.SP+1]*256 + self.mem[self.SP] 54 | self.SP = self.SP + 2 55 | self.PC = addr 56 | except IndexError: 57 | self.PC = 0xffffff 58 | elif (op.startswith('A:=m[A]')): 59 | self.A = self.mem[self.A] 60 | elif (op.startswith('A:=M[A]')): 61 | self.A = self.getint(self.A) 62 | #self.A = self.mem[self.A] + self.mem[self.A + 1] * 256 63 | elif (op.startswith('m[B]:=A')): 64 | self.mem[self.B] = self.A & 0xff 65 | elif (op.startswith('M[B]:=A')): 66 | self.putint(self.B, self.A) 67 | #self.mem[self.B] = self.A & 0xff 68 | #self.mem[self.B+1] = (self.A & 0xff00) >> 8 69 | elif (op.startswith('push A')): 70 | self.SP = self.SP - 2 71 | self.putint(self.SP, self.A) 72 | #self.mem[self.SP] = self.A & 0xff 73 | #self.mem[self.SP+1] = (self.A & 0xff00) >> 8 74 | elif (op.startswith('pop B')): 75 | self.B = self.getint(self.SP) 76 | #self.B = self.mem[self.SP+1]*256 + self.mem[self.SP] 77 | self.SP = self.SP + 2 78 | elif (op.startswith('A:=B+A')): 79 | self.A = (self.B + self.A) & 0xffff 80 | elif (op.startswith('A:=B-A')): 81 | self.A = (self.B - self.A) & 0xffff 82 | elif (op.startswith('A:=B&A')): 83 | self.A = (self.B & self.A) & 0xffff 84 | elif (op.startswith('A:=B<>A')): 87 | self.A = (self.B >> self.A) & 0xffff 88 | elif (op.startswith('A:=B|A')): 89 | self.A = (self.B | self.A) & 0xffff 90 | elif (op.startswith('A:=B self.B: 92 | self.A = 1 93 | else: 94 | self.A = 0 95 | elif (op.startswith('A:=B==A')): 96 | if self.A == self.B: 97 | self.A = 1 98 | else: 99 | self.A = 0 100 | elif (op.startswith('A:=B!=A')): 101 | if self.A != self.B: 102 | self.A = 1 103 | else: 104 | self.A = 0 105 | elif (op.startswith('pop')): 106 | n = int(op[3:], 16) 107 | self.SP = self.SP + n*2 108 | elif (op.startswith('A:=')): 109 | self.A = int(op[3:], 16) 110 | elif (op.startswith('sp@')): 111 | self.A = self.SP + int(op[3:], 16)*2 # TODO 112 | #print(self.A) 113 | elif (op.startswith('jmp')): 114 | self.PC = int(op[3:], 16) 115 | elif (op.startswith('jmz')): 116 | if self.A == 0: 117 | self.PC = int(op[3:], 16) 118 | elif (op.startswith('call A')): 119 | self.SP = self.SP - 2 120 | self.putint(self.SP, self.PC) 121 | #self.mem[self.SP] = (self.PC & 0xff) 122 | #self.mem[self.SP+1] = ((self.PC & 0xff00) >> 8) 123 | self.PC = self.A 124 | else: 125 | print("UNKNOWN OPERATOR STRING: " + op) 126 | 127 | def dump(self): 128 | print("A:%04x B:%04x PC:%x SP:%x" % (self.A, self.B, self.PC, self.SP)) 129 | print("Mem:", self.mem) 130 | print() 131 | -------------------------------------------------------------------------------- /examples/72-seq-merge.txt: -------------------------------------------------------------------------------- 1 | ; 7.2 Merge two sequential files 2 | ; 3 | ; Organization of the disk 4 | ; 5 | ; On the disk, each file is represented by a file allocation 6 | ; table (FAT). The sector address 0 is used to represent the 7 | ; end of a file (EOF). 8 | ; 9 | ; In this example, there are two files on the disk, namely 10 | ; file A and file B, whose FATs are located at sector 11 | ; address 1 and 2 respectively. Each record in these files 12 | ; uses exactly one sector. The first byte of each record is 13 | ; the key used for comparison. Given two ordered sequential 14 | ; files, the following program merges the two files and 15 | ; create a new file, namely file C, on the disk. The FAT of 16 | ; file C is located at sector address 3 on the disk. 17 | ; 18 | ; Register usage in the program 19 | ; R0 - For comparison 20 | ; R1 - The constant 1 21 | ; Sector address of FAT of file A 22 | ; The 'write to disk' command 23 | ; R2 - The constant 2 24 | ; Sector address of FAT of file B 25 | ; R3 - Pointer to in-memory FAT of file A 26 | ; R4 - Pointer to in-memory FAT of file B 27 | ; R5 - Pointer to in-memory FAT of file C 28 | ; R6 - Buffer for data in file A 29 | ; R7 - Buffer for data in file B 30 | ; R8 - Sector address of current record in file A 31 | ; R9 - Sector address of current record in file B 32 | ; RA - Key of current record of file A 33 | ; RB - Key of current record of file B 34 | ; RE - Status of the disk 35 | 36 | .entry: 37 | LOADB R0, 0 38 | LOADB R1, 1 39 | LOADB R2, 2 40 | LOADB R3, fat_a 41 | LOADB R4, fat_b 42 | LOADB R5, fat_c 43 | LOADB R6, buffer_a 44 | LOADB R7, buffer_b 45 | 46 | ; load FAT of file A 47 | STOREM R1, 0xFF 48 | STOREM R3, 0xFF 49 | STOREM R0, 0xFF 50 | .again_load_fat_a: 51 | LOADM RE, 0xFF 52 | JUMP RE, .again_load_fat_a 53 | 54 | ; load FAT of file B 55 | STOREM R2, 0xFF 56 | STOREM R4, 0xFF 57 | STOREM R0, 0xFF 58 | .again_load_fat_b: 59 | LOADM RE, 0xFF 60 | JUMP RE, .again_load_fat_b 61 | 62 | ; load the first sector of file A 63 | LOADP R8, R3 64 | STOREM R8, 0xFF 65 | STOREM R6, 0xFF 66 | STOREM R0, 0xFF 67 | .again_load_sec_a: 68 | LOADM RE, 0xFF 69 | JUMP RE, .again_load_sec_a 70 | 71 | ; load the first sector of file B 72 | LOADP R9, R4 73 | STOREM R9, 0xFF 74 | STOREM R7, 0xFF 75 | STOREM R0, 0xFF 76 | .again_load_sec_b: 77 | LOADM RE, 0xFF 78 | JUMP RE, .again_load_sec_b 79 | 80 | ; begin of the main loop 81 | .loop: 82 | ; have we encounter an EOF (i.e. sector 00)? 83 | JUMP R8, .eof_a 84 | JUMP R9, .eof_b_1 85 | JUMP R0, .compare 86 | .eof_b_1: 87 | JUMP R0, .insert_record_of_a 88 | .eof_a: 89 | JUMP R9, .eof_b_2 90 | JUMP R0, .insert_record_of_b 91 | .eof_b_2: 92 | JUMP R0, .exit 93 | 94 | .compare: 95 | LOADP RA, R6 96 | LOADP RB, R7 97 | MOVE R0, RA 98 | JUMPL RB, .insert_record_of_b 99 | 100 | .insert_record_of_a: 101 | STOREP R8, R5 102 | ADDI R5, R5, R1 103 | ADDI R3, R3, R1 104 | LOADP R8, R3 105 | STOREM R8, 0xFF 106 | STOREM R6, 0xFF 107 | JUMP R0, .load_next 108 | 109 | .insert_record_of_b: 110 | STOREP R9, R5 111 | ADDI R5, R5, R1 112 | ADDI R4, R4, R1 113 | LOADP R9, R4 114 | STOREM R9, 0xFF 115 | STOREM R7, 0xFF 116 | 117 | .load_next: 118 | LOADB R0, 0 119 | STOREM R0, 0xFF 120 | .again_load_next: 121 | LOADM RE, 0xFF 122 | JUMP RE, .again_load_next 123 | JUMP R0, .loop 124 | 125 | .exit: 126 | ; store FAT of file C 127 | LOADB R2, 3 128 | STOREM R2, 0xFF 129 | LOADB R5, fat_c 130 | STOREM R5, 0xFF 131 | STOREM R1, 0xFF 132 | .again_write_fat_c: 133 | LOADM RE, 0xFF 134 | JUMP RE, .again_write_fat_c 135 | 136 | HALT 137 | 138 | fat_a@0xA0: 139 | 140 | fat_b@0xB0: 141 | 142 | fat_c@0xC0: 143 | 144 | buffer_a@0xD0: 145 | 146 | buffer_b@0xE0: 147 | 148 | ========== 149 | ; Data in the disk are listed below. 150 | 151 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 152 | 06 0A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 153 | 04 0C 05 0F 00 00 00 00 00 00 00 00 00 00 00 00 154 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 155 | 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 156 | 45 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 157 | 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 158 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 159 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 160 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 161 | 44 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 162 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 163 | 43 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 164 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 165 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 166 | 46 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 167 | -------------------------------------------------------------------------------- /instruction-set.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Simple 8-bit V8-CPU Assembler - Instruction Set Help 6 | 7 | 8 | 9 | 10 | 20 |
21 |

Introduction

22 |

This simulator provides a simplified assembler syntax (based on TEXT BOOK "Computer Science: An Overview") and is simulating a 8-bit cpu.

23 |

The simulator consists of a 8-bit cpu and 256 bytes of memory. All instructions (code) and variables (data) needs to fit inside the memory. For simplicity every instruction (and operand) is 2 byte.

24 |

Syntax

25 |

The syntax is similar as most assemblers are using. Every instruction must be on their own line. Labels are optional and must either start with a letter or a dot (.) and end with a colon.

26 |
label: instruction operands	; Comment
27 |

Valid number formats for constants are:

28 |
 29 | Decimal: 200
 30 | Hex: 0xA4
 31 | 
32 |

It is possible to define a number using a character or multiple numbers (see instruction DB) by using a string.

33 |
 34 | Character: 'A'
 35 | String: "Hello World!"
 36 | 
37 |

Operands can either be one of the 16 general purpose registers, a memory address or a constant. 38 | Instead of defining an address as a constant or by using a register you can use labels. The assembler will then replace the label with the corresponding constant.

39 |
 40 | General purpose (GP) register: R0-RE
 41 | Timer interrupt register: RF
 42 | Address using a constant: 100
 43 | Address using a label: label
 44 | 
45 |

MOVE S, R

46 |

MOVE the bit pattern found in register R to register S.

47 |
 48 | MOVE reg, reg
 49 | 
50 |

DB - Variable

51 |

Defines a variable. A variable can either be a single number, character or a string.

52 |
 53 | DB constant
 54 | 
55 |

integer/float Addition

56 |

Adds two numbers together.

57 |
 58 | ADDI regR, regS, regT ; regR=regS+regT integer
 59 | ADDF regR, regS, regT ; regR=regS+regT float
 60 | 
61 | Logical instructions 62 |

The following logical instructions are supported: AND, OR, XOR.

63 |
 64 | AND regR, regS, regT ; R=S & T
 65 | OR  regR, regS, regT ; R=S | T
 66 | XOR regR, regS, regT ; R=S ^ T
 67 | 
68 | Shift instructions 69 |

The following shift instructions are supported: ROTATE to right.

70 |
 71 | ROT regR, numX  ; regR=regR rotate-right numX times
 72 | 
73 |

JUMP - jump if equal

74 |

JUMP to the instruction located in the memory cell at address XY if the bit pattern in register R is equal to the bit 75 | pattern in register 0. Otherwise, continue with the normal sequence of execution. (The jump is implemented by 76 | copying XY into the program counter during the execute phase.) 77 |

78 |
 79 | JUMP regR, numXY
 80 | 
81 |

JUMPL - jump if less

82 |

JUMPL to the instruction located in the memory cell at address XY if the bit pattern in register R is less than the bit 83 | pattern in register 0. Otherwise, continue with the normal sequence of execution. (The jump is implemented by 84 | copying XY into the program counter during the execute phase.) 85 |

86 |
 87 | JUMPL regR, numXY
 88 | 
89 | 90 |

HALT

91 |

Stops operation of the processor. Hit Reset button to reset IP before restarting.

92 |
 93 | HALT
 94 | 
95 |

LOADM (Load from Memory)

96 |

LOAD the register R with the bit pattern found in the memory cell whose address is XY.

97 |
 98 | LOADM regR, numXY
 99 | 
100 |

LOADB (Load with Bit Pattern)

101 |

LOAD the register R with the bit pattern XY.

102 |
103 | LOADB regR, numXY
104 | 
105 |

LOADP (Load via Pointer)

106 |

LOAD the register R with the contents of the memory cell whose address is found in register S.

107 |
108 | LOADP regR, regS
109 | 
110 |

STOREM (Store to Memory)

111 |

STORE the bit pattern found in register R in the memory cell whose address is XY.

112 |
113 | STOREM regR, numXY
114 | 
115 |

STOREP (Store via Pointer)

116 |

STORE the contents of register R in the memory cell whose address is found in register S.

117 |
118 | STOREP regR, regS
119 | 
120 | 121 | 122 |
123 |

by Yuanchun Shi, Yu Chen, Junjie Mao, Yukang Yan (2015) | MIT License | Source Code

124 |

by Marco Schweighauser (2015) | MIT License | Blog

125 |
126 | 127 | 128 | -------------------------------------------------------------------------------- /src/emulator/cpu.js: -------------------------------------------------------------------------------- 1 | function log(msg) { 2 | setTimeout(function() { 3 | throw new Error(msg); 4 | }, 0); 5 | } 6 | 7 | app.service('cpu', ['opcodes', 'memory', function(opcodes, memory) { 8 | var cpu = { 9 | step: function() { 10 | var self = this; 11 | 12 | var byteToNumber = function(val) { 13 | if (val < 128) { 14 | return val; 15 | } else { 16 | return val - 255; 17 | } 18 | }; 19 | 20 | var readReg = function(id) { 21 | return self.gpr[id]; 22 | }; 23 | 24 | var writeReg = function(id, val) { 25 | self.gpr[id] = val; 26 | if (id == 15) { 27 | self.updateTimer = true; 28 | if (val > 0) { 29 | self.countdown = val; 30 | } else { 31 | self.countdown = 0; 32 | } 33 | } 34 | }; 35 | 36 | var findHighestBit = function(bits, max) { 37 | var i; 38 | for (i = max; i >= 0; i--) { 39 | if ((bits >> i) !== 0) 40 | break; 41 | } 42 | return i; 43 | }; 44 | 45 | var floatingAdd = function(a, b) { 46 | var a_sign = (a & 0x80) >> 7, a_expo = ((a & 0x70) >> 4), a_mant = a & 0x0F; 47 | var b_sign = (b & 0x80) >> 7, b_expo = ((b & 0x70) >> 4), b_mant = b & 0x0F; 48 | var a_fix = (a_mant << a_expo), b_fix = (b_mant << b_expo); 49 | var result_sign, result_fix; 50 | if (a_sign == b_sign) { 51 | result_sign = a_sign; 52 | result_fix = a_fix + b_fix; 53 | } else { 54 | if (a_fix > b_fix) { 55 | result_sign = a_sign; 56 | result_fix = a_fix - b_fix; 57 | } else if (a_fix < b_fix) { 58 | result_sign = b_sign; 59 | result_fix = b_fix - a_fix; 60 | } else { 61 | result_fix = 0; 62 | result_sign = 0; 63 | } 64 | } 65 | var result_expo = findHighestBit(result_fix, 16) - 3; 66 | if (result_expo > 7) { 67 | result_expo = 7; 68 | } else if (result_expo < 0) { 69 | result_expo = 0; 70 | } 71 | var result_mant = (result_fix >> result_expo) & 0xF; 72 | var result = (result_sign << 7) | (result_expo << 4) | result_mant; 73 | return result; 74 | }; 75 | 76 | var updateIR = function(instr) { 77 | self.ir = ''; 78 | if (instr[0] <= 15) 79 | self.ir += '0' + instr[0].toString(16); 80 | else 81 | self.ir += instr[0].toString(16); 82 | if (instr[1] <= 15) 83 | self.ir += '0' + instr[1].toString(16); 84 | else 85 | self.ir += instr[1].toString(16); 86 | }; 87 | 88 | self.updateTimer = false; 89 | self.status = ''; 90 | 91 | var instr = [memory.load(self.ip), memory.load(self.ip + 1)]; 92 | var opcode = instr[0] >> 4; 93 | var regDest = instr[0] & 0x0F, regSource1 = instr[1] >> 4, regSource2 = instr[1] & 0x0F; 94 | var mem = instr[1], num = instr[1]; 95 | updateIR(instr); 96 | self.ip = (self.ip + 2) & 0xFF; 97 | switch(opcode) { 98 | case opcodes.LOAD_FROM_MEMORY: 99 | writeReg(regDest, memory.load(mem)); 100 | break; 101 | case opcodes.LOAD_WITH_CONSTANT: 102 | writeReg(regDest, num); 103 | break; 104 | case opcodes.STORE_TO_MEMORY: 105 | memory.store(mem, readReg(regDest)); 106 | break; 107 | case opcodes.MOVE: 108 | writeReg(regSource2, readReg(regSource1)); 109 | break; 110 | case opcodes.ADD_INT: 111 | writeReg(regDest, (readReg(regSource1) + readReg(regSource2)) & 0xFF); 112 | break; 113 | case opcodes.ADD_FLOAT: 114 | writeReg(regDest, floatingAdd(readReg(regSource1), readReg(regSource2))); 115 | break; 116 | case opcodes.OR: 117 | writeReg(regDest, readReg(regSource1) | readReg(regSource2)); 118 | break; 119 | case opcodes.AND: 120 | writeReg(regDest, readReg(regSource1) & readReg(regSource2)); 121 | break; 122 | case opcodes.XOR: 123 | writeReg(regDest, readReg(regSource1) ^ readReg(regSource2)); 124 | break; 125 | case opcodes.ROTATE: 126 | var delta = num % 8, val = readReg(regDest); 127 | writeReg(regDest, (val >> delta) + ((val & ((1 << delta) - 1)) << (8 - delta))); 128 | break; 129 | case opcodes.JUMP_IF_EQUAL: 130 | if (readReg(regDest) == readReg(0)) { 131 | self.ip = mem; 132 | } 133 | break; 134 | case opcodes.HALT: 135 | self.ip = (self.ip - 2) & 0xFF; 136 | return false; 137 | case opcodes.LOAD_FROM_POINTER: 138 | writeReg(regDest, memory.load(readReg(regSource2))); 139 | break; 140 | case opcodes.STORE_TO_POINTER: 141 | memory.store(readReg(regSource2), readReg(regDest)); 142 | break; 143 | case opcodes.JUMP_IF_LESS: 144 | if (byteToNumber(readReg(regDest)) < byteToNumber(readReg(0))) { 145 | self.ip = mem; 146 | } 147 | break; 148 | } 149 | 150 | if (self.countdown > 0 && !self.updateTimer) { 151 | self.countdown -= 1; 152 | if (self.countdown === 0) { 153 | memory.store(0xFD, self.ip); 154 | self.ip = 0x80; 155 | self.countdown = readReg(15); 156 | self.status = '(Interrupted!)'; 157 | } 158 | } 159 | 160 | return true; 161 | }, 162 | reset: function() { 163 | var self = this; 164 | 165 | self.gpr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 166 | self.ip = 0; 167 | self.ir = '0000'; 168 | self.status = ''; 169 | 170 | self.countdown = 0; 171 | self.updateTimer = false; 172 | } 173 | }; 174 | 175 | cpu.reset(); 176 | return cpu; 177 | }]); 178 | 179 | /* 180 | * Local variables: 181 | * c-basic-offset: 4 182 | * tab-width: 4 183 | * indent-tabs-mode: nil 184 | * End: 185 | */ 186 | -------------------------------------------------------------------------------- /controller.js: -------------------------------------------------------------------------------- 1 | app.controller('Ctrl', ['$document', '$scope', '$timeout', '$http', 'cpu', 'memory', 'printer', 'assembler', 'uploader', function ($document, $scope, $timeout, $http, cpu, memory, printer, assembler, uploader) { 2 | $scope.printer = printer; 3 | $scope.memory = memory; 4 | $scope.cpu = cpu; 5 | $scope.error = ''; 6 | $scope.isRunning = false; 7 | $scope.displayHex = true; 8 | $scope.displayInstr = true; 9 | $scope.displayA = false; 10 | $scope.displayB = false; 11 | $scope.displayC = false; 12 | $scope.displayD = false; 13 | $scope.speeds = [{speed: 1, desc: "1 HZ"}, 14 | {speed: 4, desc: "4 HZ"}, 15 | {speed: 8, desc: "8 HZ"}, 16 | {speed: 16, desc: "16 HZ"}]; 17 | $scope.speed = 4; 18 | $scope.example = ''; 19 | $scope.examples = []; 20 | 21 | $scope.code = ";; Choose an example above or write your own code here :)"; 22 | $scope.reset = function () { 23 | cpu.reset(); 24 | memory.reset(); 25 | printer.reset(); 26 | $scope.error = ''; 27 | $scope.selectedLine = -1; 28 | $scope.mapping = undefined; 29 | }; 30 | 31 | $scope.executeStep = function () { 32 | if (!$scope.checkPrgrmLoaded()) { 33 | $scope.assemble(); 34 | } 35 | 36 | try { 37 | // Execute 38 | var res = cpu.step(); 39 | 40 | // Mark in code 41 | if (cpu.ip in $scope.mapping) { 42 | $scope.selectedLine = $scope.mapping[cpu.ip]; 43 | } 44 | 45 | return res; 46 | } catch (e) { 47 | $scope.error = e; 48 | return false; 49 | } 50 | }; 51 | 52 | var runner; 53 | $scope.run = function () { 54 | if (!$scope.checkPrgrmLoaded()) { 55 | $scope.assemble(); 56 | } 57 | 58 | $scope.isRunning = true; 59 | runner = $timeout(function () { 60 | if ($scope.executeStep() === true) { 61 | $scope.run(); 62 | } else { 63 | $scope.isRunning = false; 64 | } 65 | }, 1000 / $scope.speed); 66 | }; 67 | 68 | $scope.stop = function () { 69 | $timeout.cancel(runner); 70 | $scope.isRunning = false; 71 | }; 72 | 73 | $scope.checkPrgrmLoaded = function () { 74 | for (var i = 0, l = memory.data.length; i < l; i++) { 75 | if (memory.data[i] !== 0) { 76 | return true; 77 | } 78 | } 79 | 80 | return false; 81 | }; 82 | 83 | $scope.getChar = function (value) { 84 | var text = String.fromCharCode(value); 85 | 86 | if (text.trim() === '') { 87 | return '\u00A0\u00A0'; 88 | } else { 89 | return text; 90 | } 91 | }; 92 | 93 | $scope.assemble = function () { 94 | try { 95 | $scope.reset(); 96 | 97 | var assembly = assembler.go($scope.code); 98 | $scope.mapping = assembly.mapping; 99 | var binary = assembly.code; 100 | $scope.labels = assembly.labels; 101 | 102 | if (binary.length > memory.data.length) 103 | throw "Binary code does not fit into the memory. Max " + memory.data.length + " bytes are allowed"; 104 | 105 | for (var i = 0, l = binary.length; i < l; i++) { 106 | memory.data[i] = binary[i]; 107 | } 108 | 109 | if ($scope.labels['.entry'] !== undefined) { 110 | cpu.ip = $scope.labels['.entry']; 111 | } 112 | } catch (e) { 113 | if (e.line !== undefined) { 114 | $scope.error = e.line + " | " + e.error; 115 | $scope.selectedLine = e.line; 116 | } else { 117 | $scope.error = e.error; 118 | } 119 | } 120 | }; 121 | 122 | $scope.upload = function () { 123 | try { 124 | $scope.reset(); 125 | 126 | var binarycode = uploader.go($scope.code); 127 | $scope.mapping = binarycode.mapping; 128 | var binary = binarycode.code; 129 | $scope.labels = binarycode.labels; 130 | 131 | if (binary.length > memory.data.length) 132 | throw "Binary code does not fit into the memory. Max " + memory.data.length + " bytes are allowed"; 133 | 134 | for (var i = 0, l = binary.length; i < l; i++) { 135 | memory.data[i] = binary[i]; 136 | } 137 | } catch (e) { 138 | if (e.line !== undefined) { 139 | $scope.error = e.line + " | " + e.error; 140 | $scope.selectedLine = e.line; 141 | } else { 142 | $scope.error = e.error; 143 | } 144 | } 145 | }; 146 | 147 | $scope.compile = function () { 148 | $.post('/',{"1":$scope.code},function(response){ 149 | console.debug("called"); $scope.code = response; $scope.assemble();}); 150 | }; 151 | 152 | $scope.initExamples = function() { 153 | var response = $http.get('examples/scandir.php'); 154 | response.success(function(data, status, headers, config) { 155 | var filelist = String(data).split(','); 156 | for (var i = 0, l = filelist.length; i < l; i++) { 157 | var contents = filelist[i].split('|'); 158 | var filename = contents[0], desc = contents[1]; 159 | $scope.examples.push({id: filename, desc: desc}); 160 | } 161 | }); 162 | response.error(function(data, status, headers, config) { 163 | console.error("ajax failed"); 164 | }); 165 | }; 166 | 167 | $scope.showExample = function(key) { 168 | var response = $http.get('examples/' + $scope.example); 169 | 170 | response.success(function(data, status, headers, config) { 171 | $scope.code = data; 172 | }); 173 | response.error(function(data, status, headers, config) { 174 | console.error("ajax failed"); 175 | }); 176 | }; 177 | 178 | $scope.jumpToLine = function (index) { 179 | $document[0].getElementById('sourceCode').scrollIntoView(); 180 | $scope.selectedLine = $scope.mapping[index]; 181 | }; 182 | 183 | 184 | $scope.isInstruction = function (index) { 185 | return $scope.mapping !== undefined && 186 | $scope.mapping[index] !== undefined && 187 | $scope.displayInstr; 188 | }; 189 | 190 | $scope.getMemoryCellCss = function (index) { 191 | if ($scope.isInstruction(index)) { 192 | return 'instr-bg'; 193 | } else { 194 | return ''; 195 | } 196 | }; 197 | 198 | $scope.getMemoryInnerCellCss = function (index) { 199 | if (index === cpu.ip) { 200 | return 'marker marker-ip'; 201 | } else if (index === cpu.sp) { 202 | return 'marker marker-sp'; 203 | } else if (index === cpu.gpr[0] && $scope.displayA) { 204 | return 'marker marker-a'; 205 | } else if (index === cpu.gpr[1] && $scope.displayB) { 206 | return 'marker marker-b'; 207 | } else if (index === cpu.gpr[2] && $scope.displayC) { 208 | return 'marker marker-c'; 209 | } else if (index === cpu.gpr[3] && $scope.displayD) { 210 | return 'marker marker-d'; 211 | } else { 212 | return ''; 213 | } 214 | }; 215 | }]); 216 | 217 | /* 218 | * Local variables: 219 | * c-basic-offset: 4 220 | * tab-width: 4 221 | * indent-tabs-mode: nil 222 | * End: 223 | */ 224 | -------------------------------------------------------------------------------- /src/ui/controller.js: -------------------------------------------------------------------------------- 1 | app.controller('Ctrl', ['$document', '$scope', '$timeout', '$http', 'cpu', 'memory', 'printer', 'assembler', 'uploader', function ($document, $scope, $timeout, $http, cpu, memory, printer, assembler, uploader) { 2 | $scope.printer = printer; 3 | $scope.memory = memory; 4 | $scope.cpu = cpu; 5 | $scope.error = ''; 6 | $scope.isRunning = false; 7 | $scope.displayHex = true; 8 | $scope.displayInstr = true; 9 | $scope.displayA = false; 10 | $scope.displayB = false; 11 | $scope.displayC = false; 12 | $scope.displayD = false; 13 | $scope.speeds = [{speed: 1, desc: "1 HZ"}, 14 | {speed: 4, desc: "4 HZ"}, 15 | {speed: 8, desc: "8 HZ"}, 16 | {speed: 16, desc: "16 HZ"}]; 17 | $scope.speed = 4; 18 | $scope.example = ''; 19 | $scope.examples = []; 20 | 21 | $scope.code = ";; Choose an example above or write your own code here :)"; 22 | $scope.reset = function () { 23 | cpu.reset(); 24 | memory.reset(); 25 | printer.reset(); 26 | $scope.error = ''; 27 | $scope.selectedLine = -1; 28 | $scope.mapping = undefined; 29 | }; 30 | 31 | $scope.executeStep = function () { 32 | if (!$scope.checkPrgrmLoaded()) { 33 | $scope.assemble(); 34 | } 35 | 36 | try { 37 | // Execute 38 | var res = cpu.step(); 39 | 40 | // Mark in code 41 | if (cpu.ip in $scope.mapping) { 42 | $scope.selectedLine = $scope.mapping[cpu.ip]; 43 | } 44 | 45 | return res; 46 | } catch (e) { 47 | $scope.error = e; 48 | return false; 49 | } 50 | }; 51 | 52 | var runner; 53 | $scope.run = function () { 54 | if (!$scope.checkPrgrmLoaded()) { 55 | $scope.assemble(); 56 | } 57 | 58 | $scope.isRunning = true; 59 | runner = $timeout(function () { 60 | if ($scope.executeStep() === true) { 61 | $scope.run(); 62 | } else { 63 | $scope.isRunning = false; 64 | } 65 | }, 1000 / $scope.speed); 66 | }; 67 | 68 | $scope.stop = function () { 69 | $timeout.cancel(runner); 70 | $scope.isRunning = false; 71 | }; 72 | 73 | $scope.checkPrgrmLoaded = function () { 74 | for (var i = 0, l = memory.data.length; i < l; i++) { 75 | if (memory.data[i] !== 0) { 76 | return true; 77 | } 78 | } 79 | 80 | return false; 81 | }; 82 | 83 | $scope.getChar = function (value) { 84 | var text = String.fromCharCode(value); 85 | 86 | if (text.trim() === '') { 87 | return '\u00A0\u00A0'; 88 | } else { 89 | return text; 90 | } 91 | }; 92 | 93 | $scope.assemble = function () { 94 | try { 95 | $scope.reset(); 96 | 97 | var assembly = assembler.go($scope.code); 98 | $scope.mapping = assembly.mapping; 99 | var binary = assembly.code; 100 | var disk = assembly.disk; 101 | $scope.labels = assembly.labels; 102 | 103 | if (binary.length > memory.data.length) 104 | throw {error: "Binary code does not fit into the memory. Max " + memory.data.length + " bytes are allowed"}; 105 | 106 | if (disk.length > memory.diskdata.length) 107 | throw {error: "Disk data does not fit into the disk. Max " + memory.diskdata.length + " bytes are allowed"}; 108 | 109 | for (var i = 0, l = binary.length; i < l; i++) { 110 | memory.data[i] = binary[i]; 111 | } 112 | 113 | for (i = 0, l = disk.length; i < l; i++) { 114 | memory.diskdata[i] = disk[i]; 115 | } 116 | 117 | if ($scope.labels['.entry'] !== undefined) { 118 | cpu.ip = $scope.labels['.entry']; 119 | } 120 | } catch (e) { 121 | if (e.line !== undefined) { 122 | $scope.error = e.line + " | " + e.error; 123 | $scope.selectedLine = e.line; 124 | } else { 125 | $scope.error = e.error; 126 | } 127 | } 128 | }; 129 | 130 | $scope.upload = function () { 131 | try { 132 | $scope.reset(); 133 | 134 | var binarycode = uploader.go($scope.code); 135 | $scope.mapping = binarycode.mapping; 136 | var binary = binarycode.code; 137 | $scope.labels = binarycode.labels; 138 | 139 | if (binary.length > memory.data.length) 140 | throw "Binary code does not fit into the memory. Max " + memory.data.length + " bytes are allowed"; 141 | 142 | for (var i = 0, l = binary.length; i < l; i++) { 143 | memory.data[i] = binary[i]; 144 | } 145 | } catch (e) { 146 | if (e.line !== undefined) { 147 | $scope.error = e.line + " | " + e.error; 148 | $scope.selectedLine = e.line; 149 | } else { 150 | $scope.error = e.error; 151 | } 152 | } 153 | }; 154 | 155 | $scope.compile = function () { 156 | $http.post('/', {"source": $scope.code}).success(function(response){ 157 | $scope.code = response; $scope.assemble();}); 158 | }; 159 | 160 | $scope.initExamples = function() { 161 | var response = $http.get('examples/scandir.php'); 162 | response.success(function(data, status, headers, config) { 163 | var filelist = String(data).split(','); 164 | for (var i = 0, l = filelist.length; i < l; i++) { 165 | var contents = filelist[i].split('|'); 166 | var filename = contents[0], desc = contents[1]; 167 | $scope.examples.push({id: filename, desc: desc}); 168 | } 169 | }); 170 | response.error(function(data, status, headers, config) { 171 | console.error("ajax failed"); 172 | }); 173 | }; 174 | 175 | $scope.showExample = function(key) { 176 | var response = $http.get('examples/' + $scope.example); 177 | 178 | response.success(function(data, status, headers, config) { 179 | $scope.code = data; 180 | }); 181 | response.error(function(data, status, headers, config) { 182 | console.error("ajax failed"); 183 | }); 184 | }; 185 | 186 | $scope.jumpToLine = function (index) { 187 | $document[0].getElementById('sourceCode').scrollIntoView(); 188 | $scope.selectedLine = $scope.mapping[index]; 189 | }; 190 | 191 | 192 | $scope.isInstruction = function (index) { 193 | return $scope.mapping !== undefined && 194 | $scope.mapping[index] !== undefined && 195 | $scope.displayInstr; 196 | }; 197 | 198 | $scope.getMemoryCellCss = function (index) { 199 | if ($scope.isInstruction(index)) { 200 | return 'instr-bg'; 201 | } else { 202 | return ''; 203 | } 204 | }; 205 | 206 | $scope.getMemoryInnerCellCss = function (index) { 207 | if (index === cpu.ip) { 208 | return 'marker marker-ip'; 209 | } else if (index === cpu.sp) { 210 | return 'marker marker-sp'; 211 | } else if (index === cpu.gpr[0] && $scope.displayA) { 212 | return 'marker marker-a'; 213 | } else if (index === cpu.gpr[1] && $scope.displayB) { 214 | return 'marker marker-b'; 215 | } else if (index === cpu.gpr[2] && $scope.displayC) { 216 | return 'marker marker-c'; 217 | } else if (index === cpu.gpr[3] && $scope.displayD) { 218 | return 'marker marker-d'; 219 | } else { 220 | return ''; 221 | } 222 | }; 223 | }]); 224 | 225 | /* 226 | * Local variables: 227 | * c-basic-offset: 4 228 | * tab-width: 4 229 | * indent-tabs-mode: nil 230 | * End: 231 | */ 232 | -------------------------------------------------------------------------------- /cucu/cucu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /* print fatal error message and exit */ 9 | static void error(const char *fmt, ...) { 10 | va_list args; 11 | va_start(args, fmt); 12 | vfprintf(stderr, fmt, args); 13 | va_end(args); 14 | exit(1); 15 | } 16 | 17 | /* 18 | * LEXER 19 | */ 20 | #define MAXTOKSZ 256 21 | static FILE *f; /* input source file */ 22 | static char tok[MAXTOKSZ]; /* current token */ 23 | static int tokpos; /* offset inside the current token */ 24 | static int nextc; /* next char to be pushed into token */ 25 | 26 | /* read next char */ 27 | void readchr() { 28 | if (tokpos == MAXTOKSZ - 1) { 29 | tok[tokpos] = '\0'; 30 | error("Token too long: %s\n", tok); 31 | } 32 | tok[tokpos++] = nextc; 33 | nextc = fgetc(f); 34 | } 35 | 36 | /* read single token */ 37 | void readtok() { 38 | for (;;) { 39 | /* skip spaces */ 40 | while (isspace(nextc)) { 41 | nextc = fgetc(f); 42 | } 43 | /* try to read a literal token */ 44 | tokpos = 0; 45 | while (isalnum(nextc) || nextc == '_') { 46 | readchr(); 47 | } 48 | /* if it's not a literal token */ 49 | if (tokpos == 0) { 50 | while (nextc == '<' || nextc == '=' || nextc == '>' 51 | || nextc == '!' || nextc == '&' || nextc == '|') { 52 | readchr(); 53 | } 54 | } 55 | /* if it's not special chars that looks like an operator */ 56 | if (tokpos == 0) { 57 | /* try strings and chars inside quotes */ 58 | if (nextc == '\'' || nextc == '"') { 59 | char c = nextc; 60 | readchr(); 61 | while (nextc != c) { 62 | readchr(); 63 | } 64 | readchr(); 65 | } else if (nextc == '/') { /* skip comments */ 66 | readchr(); 67 | if (nextc == '*') { 68 | nextc = fgetc(f); 69 | while (nextc != '/') { 70 | while (nextc != '*') { 71 | nextc = fgetc(f); 72 | } 73 | nextc = fgetc(f); 74 | } 75 | nextc = fgetc(f); 76 | continue; 77 | } 78 | } else if (nextc != EOF) { 79 | /* otherwise it looks like a single-char symbol, like '+', '-' etc */ 80 | readchr(); 81 | } 82 | } 83 | break; 84 | } 85 | tok[tokpos] = '\0'; 86 | } 87 | 88 | /* check if the current token machtes the string */ 89 | int peek(char *s) { 90 | return (strcmp(tok, s) == 0); 91 | } 92 | 93 | /* read the next token if the current token machtes the string */ 94 | int accept(char *s) { 95 | if (peek(s)) { 96 | readtok(); 97 | return 1; 98 | } 99 | return 0; 100 | } 101 | 102 | /* throw fatal error if the current token doesn't match the string */ 103 | void expect(char *s) { 104 | if (accept(s) == 0) { 105 | error("Error: expected '%s', but found: %s\n", s, tok); 106 | } 107 | } 108 | 109 | /* 110 | * SYMBOLS 111 | */ 112 | #define MAXSYMBOLS 4096 113 | static struct sym { 114 | char type; 115 | int addr; 116 | char name[MAXTOKSZ]; 117 | } sym[MAXSYMBOLS]; 118 | static int sympos = 0; 119 | 120 | int stack_pos = 0; 121 | 122 | static struct sym *sym_find(char *s) { 123 | int i; 124 | struct sym *symbol = NULL; 125 | for (i = 0; i < sympos; i++) { 126 | if (strcmp(sym[i].name, s) == 0) { 127 | symbol = &sym[i]; 128 | } 129 | } 130 | return symbol; 131 | } 132 | 133 | static struct sym *sym_declare(char *name, char type, int addr) { 134 | strncpy(sym[sympos].name, name, MAXTOKSZ); 135 | sym[sympos].addr = addr; 136 | sym[sympos].type = type; 137 | sympos++; 138 | if (sympos > MAXSYMBOLS) { 139 | error("Too many symbols\n"); 140 | } 141 | return &sym[sympos-1]; 142 | } 143 | 144 | /* 145 | * BACKEND 146 | */ 147 | #define MAXCODESZ 4096 148 | static char code[MAXCODESZ]; 149 | static int codepos = 0; 150 | 151 | static void emit(void *buf, size_t len) { 152 | memcpy(code + codepos, buf, len); 153 | codepos += len; 154 | } 155 | 156 | #define TYPE_NUM 0 157 | #define TYPE_CHARVAR 1 158 | #define TYPE_INTVAR 2 159 | 160 | #ifndef GEN 161 | #error "A code generator (backend) must be provided (use -DGEN=...)" 162 | #endif 163 | 164 | #include GEN 165 | 166 | /* 167 | * PARSER AND COMPILER 168 | */ 169 | 170 | static int expr(); 171 | 172 | /* read type name: int, char and pointers are supported */ 173 | static int typename() { 174 | if (peek("int") || peek("char")) { 175 | readtok(); 176 | while (accept("*")); 177 | return 1; 178 | } 179 | return 0; 180 | } 181 | 182 | static int prim_expr() { 183 | int type = TYPE_NUM; 184 | if (isdigit(tok[0])) { 185 | int n = strtol(tok, NULL, 10); /* TODO: parse 0x.. */ 186 | gen_const(n); 187 | } else if (isalpha(tok[0])) { 188 | struct sym *s = sym_find(tok); 189 | //printf("%s",s->addr); 190 | if (s == NULL) { 191 | error("Undeclared symbol: %s\n", tok); 192 | } 193 | if (s->type == 'L') { 194 | gen_stack_addr(stack_pos - s->addr - 1); 195 | //regnow = s->addr; 196 | } else { 197 | gen_sym_addr(s); 198 | } 199 | type = TYPE_INTVAR; 200 | } else if (accept("(")) { 201 | type = expr(); 202 | expect(")"); 203 | } else if (tok[0] == '"') { 204 | int i, j; 205 | i = 0; j = 1; 206 | while (tok[j] != '"') { 207 | if (tok[j] == '\\' && tok[j+1] == 'x') { 208 | char s[3] = {tok[j+2], tok[j+3], 0}; 209 | uint8_t n = strtol(s, NULL, 16); 210 | tok[i++] = n; 211 | j += 4; 212 | } else { 213 | tok[i++] = tok[j++]; 214 | } 215 | } 216 | tok[i] = 0; 217 | if (i % 2 == 0) { 218 | i++; 219 | tok[i] = 0; 220 | } 221 | gen_array(tok, i); 222 | type = TYPE_NUM; 223 | } else { 224 | error("Unexpected primary expression: %s\n", tok); 225 | } 226 | readtok(); 227 | return type; 228 | } 229 | 230 | static int binary(int type, int (*f)(), char *buf, size_t len) { 231 | if (type != TYPE_NUM) { 232 | gen_unref(type); 233 | } 234 | gen_push(); 235 | type = f(); 236 | if (type != TYPE_NUM) { 237 | gen_unref(type); 238 | } 239 | emit(buf, len); 240 | stack_pos = stack_pos - 1; /* assume that buffer contains a "pop" */ 241 | return TYPE_NUM; 242 | } 243 | 244 | static int postfix_expr() { 245 | int type = prim_expr(); 246 | if (type == TYPE_INTVAR && accept("[")) { 247 | binary(type, expr, GEN_ADD, GEN_ADDSZ); 248 | expect("]"); 249 | type = TYPE_CHARVAR; 250 | } else if (accept("(")) { 251 | int prev_stack_pos = stack_pos; 252 | gen_push(); /* store function address */ 253 | int call_addr = stack_pos - 1; 254 | if (accept(")") == 0) { 255 | expr(); 256 | gen_push(); 257 | while (accept(",")) { 258 | expr(); 259 | gen_push(); 260 | } 261 | expect(")"); 262 | } 263 | type = TYPE_NUM; 264 | gen_stack_addr(stack_pos - call_addr - 1); 265 | gen_unref(TYPE_INTVAR); 266 | gen_call(); 267 | /* remove function address and args */ 268 | gen_pop(stack_pos - prev_stack_pos); 269 | stack_pos = prev_stack_pos; 270 | } 271 | return type; 272 | } 273 | 274 | static int add_expr() { 275 | int type = postfix_expr(); 276 | while (peek("+") || peek("-")) { 277 | if (accept("+")) { 278 | type = binary(type, postfix_expr, GEN_ADD, GEN_ADDSZ); 279 | } else if (accept("-")) { 280 | type = binary(type, postfix_expr, GEN_SUB, GEN_SUBSZ); 281 | } 282 | } 283 | return type; 284 | } 285 | 286 | static int shift_expr() { 287 | int type = add_expr(); 288 | while (peek("<<") || peek(">>")) { 289 | if (accept("<<")) { 290 | type = binary(type, add_expr, GEN_SHL, GEN_SHLSZ); 291 | } else if (accept(">>")) { 292 | type = binary(type, add_expr, GEN_SHR, GEN_SHRSZ); 293 | } 294 | } 295 | return type; 296 | } 297 | 298 | static int rel_expr() { 299 | int type = shift_expr(); 300 | while (peek("<")) { 301 | if (accept("<")) { 302 | type = binary(type, shift_expr, GEN_LESS, GEN_LESSSZ); 303 | } 304 | } 305 | return type; 306 | } 307 | 308 | static int eq_expr() { 309 | int type = rel_expr(); 310 | while (peek("==") || peek("!=")) { 311 | if (accept("==")) { 312 | type = binary(type, rel_expr, GEN_EQ, GEN_EQSZ); 313 | } else if (accept("!=")) { 314 | type = binary(type, rel_expr, GEN_NEQ, GEN_NEQSZ); 315 | } 316 | } 317 | return type; 318 | } 319 | 320 | static int bitwise_expr() { 321 | int type = eq_expr(); 322 | while (peek("|") || peek("&")) { 323 | if (accept("|")) { 324 | type = binary(type, eq_expr, GEN_OR, GEN_ORSZ); 325 | } else if (accept("&")) { 326 | type = binary(type, eq_expr, GEN_AND, GEN_ANDSZ); 327 | } 328 | } 329 | return type; 330 | } 331 | 332 | static int expr() { 333 | int type = bitwise_expr(); 334 | if (type != TYPE_NUM) { 335 | if (accept("=")) { 336 | gen_push(); expr(); 337 | if (type == TYPE_INTVAR) { 338 | emit(GEN_ASSIGN, GEN_ASSIGNSZ); 339 | } else { 340 | emit(GEN_ASSIGN8, GEN_ASSIGN8SZ); 341 | } 342 | stack_pos = stack_pos - 1; /* assume ASSIGN contains pop */ 343 | type = TYPE_NUM; 344 | } else { 345 | gen_unref(type); 346 | } 347 | } 348 | return type; 349 | } 350 | 351 | static void statement() { 352 | if (accept("{")) { 353 | int prev_stack_pos = stack_pos; 354 | while (accept("}") == 0) { 355 | statement(); 356 | } 357 | gen_pop(stack_pos-prev_stack_pos); 358 | stack_pos = prev_stack_pos; 359 | } else if (typename()) { 360 | struct sym *var = sym_declare(tok, 'L', stack_pos); 361 | readtok(); 362 | if (accept("=")) { 363 | expr(); 364 | } 365 | gen_push(); /* make room for new local variable */ 366 | var->addr = stack_pos - 1; 367 | expect(";"); 368 | } else if (accept("if")) { 369 | expect("("); 370 | expr(); 371 | emit(GEN_JZ, GEN_JZSZ); 372 | int p1 = codepos; 373 | expect(")"); 374 | int prev_stack_pos = stack_pos; 375 | statement(); 376 | gen_patch_str(code + p1, ".else"); 377 | //emits("hello\n"); 378 | emit(GEN_JMP, GEN_JMPSZ); 379 | int p2 = codepos; 380 | emits(".else:\n"); 381 | if (accept("else")) { 382 | stack_pos = prev_stack_pos; 383 | statement(); 384 | } 385 | emits(".end:\n"); 386 | stack_pos = prev_stack_pos; 387 | gen_patch_str(code + p2, ".end "); 388 | } else if (accept("while")) { 389 | expect("("); 390 | emits(".loop:\n"); 391 | int p1 = codepos; 392 | gen_loop_start(); 393 | expr(); 394 | emit(GEN_JZ, GEN_JZSZ); 395 | int p2 = codepos; 396 | expect(")"); 397 | statement(); 398 | emit(GEN_JMP, GEN_JMPSZ); 399 | emits(".end:\n"); 400 | //gen_patch(code + codepos, p1); 401 | //gen_patch(code + p2, codepos); 402 | gen_patch_str(code + codepos-6, ".loop"); 403 | gen_patch_str(code + p2, ".end "); 404 | } else if (accept("return")) { 405 | if (peek(";") == 0) { 406 | expr(); 407 | } 408 | expect(";"); 409 | gen_pop(stack_pos); /* remove all locals from stack (except return address) */ 410 | gen_ret(); 411 | } else { 412 | expr(); 413 | expect(";"); 414 | } 415 | } 416 | 417 | static void compile() { 418 | while (tok[0] != 0) { /* until EOF */ 419 | if (typename() == 0) { 420 | error("Error: type name expected\n"); 421 | } 422 | struct sym *var = sym_declare(tok, 'U', 0); 423 | readtok(); 424 | if (accept(";")) { 425 | var->type = 'G'; 426 | gen_sym(var); 427 | continue; 428 | } 429 | expect("("); 430 | int argc = 0; 431 | for (;;) { 432 | argc++; 433 | if (typename() == 0) { 434 | break; 435 | } 436 | sym_declare(tok, 'L', -argc-1); 437 | readtok(); 438 | if (peek(")")) { 439 | break; 440 | } 441 | expect(","); 442 | } 443 | expect(")"); 444 | if (accept(";") == 0) { 445 | stack_pos = 0; 446 | var->addr = codepos; 447 | var->type = 'F'; 448 | gen_sym(var); 449 | statement(); /* function body */ 450 | //gen_ret(); /* another ret if user forgets to put 'return' */ 451 | } 452 | } 453 | } 454 | 455 | int main(int argc, char *argv[]) { 456 | f = stdin; 457 | /* prefetch first char and first token */ 458 | nextc = fgetc(f); 459 | readtok(); 460 | gen_start(); 461 | compile(); 462 | gen_finish(); 463 | return 0; 464 | } 465 | 466 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple 8-bit V8-CPU in Javascript 5 | 6 | 7 | 8 | 9 | 10 | 25 |
26 |
{{ error }}
27 |
28 |
29 |
30 |
31 |

Code (Instruction Set) (Specification)

32 |
33 | 34 | 44 | 45 | 46 | Examples: 47 | 48 | 49 | 50 | 51 | 52 |
53 | 60 |
61 |
62 |
63 |

Labels

64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
NameAddressValue
{{ name }}{{ value | number:displayHex }}{{ memory.data[value] | number:displayHex }}
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |

CPU & Memory

87 |
88 |
89 |

General Registers

90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 |
0123456789ABCDEF
{{ cpu.gpr[0] | number:displayHex }}
{{ cpu.gpr[1] | number:displayHex }}
{{ cpu.gpr[2] | number:displayHex }}
{{ cpu.gpr[3] | number:displayHex }}
{{ cpu.gpr[4] | number:displayHex }}
{{ cpu.gpr[5] | number:displayHex }}
{{ cpu.gpr[6] | number:displayHex }}
{{ cpu.gpr[7] | number:displayHex }}
{{ cpu.gpr[8] | number:displayHex }}
{{ cpu.gpr[9] | number:displayHex }}
{{ cpu.gpr[10] | number:displayHex }}
{{ cpu.gpr[11] | number:displayHex }}
{{ cpu.gpr[12] | number:displayHex }}
{{ cpu.gpr[13] | number:displayHex }}
{{ cpu.gpr[14] | number:displayHex }}
{{ cpu.gpr[15] | number:displayHex }}
132 |

Other Registers

133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 |
Program counter {{ cpu.status }}
{{ cpu.ip | number:displayHex }}
Instruction register
{{ cpu.ir | number:displayHex }}
Timer countdown
{{ cpu.countdown | number:displayHex }}
149 |

RAM

150 |
151 |
154 |
155 | {{ m | number:displayHex }} 156 | 157 | {{ m | number:displayHex }} 158 | 159 |
160 |
161 |
162 |

163 | 164 | Clock speed: 165 | 166 | Instructions: 167 | Show 168 | Hide 169 | 170 |

171 |
172 |
173 |
174 |
175 |

Printer

176 |
177 |
{{ printer.data }}
178 |
179 |
180 |
181 |

Disk

182 |
183 |
184 |
186 |
187 | {{ m | number:displayHex }} 188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |

by Yuanchun Shi, Yu Chen, Junjie Mao, Yukang Yan (2015) | MIT License | Source Code

196 |

by Marco Schweighauser (2015) | MIT License | Blog

197 |
198 | 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /src/assembler/asm.js: -------------------------------------------------------------------------------- 1 | app.service('uploader', ['opcodes', function (opcodes) { 2 | return { 3 | go: function (input) { 4 | // Contains the program code & data generated by the assembler 5 | var code = []; 6 | // Contains data on disk 7 | var disk = []; 8 | // Contains the mapping from instructions to assembler line 9 | var mapping = {}; 10 | // Hash map of label used to replace the labels after the assembler generated the code 11 | var labels = {}; 12 | // Split text into code lines 13 | var lines = input.split('\n'); 14 | for (var i = 0, l = lines.length; i < l; i++) { 15 | var line = lines[i]; 16 | var end_of_line = line.indexOf(';'); 17 | if (end_of_line > 0) 18 | line = line.slice(0, end_of_line - 1); 19 | else if (end_of_line === 0) 20 | line = ''; 21 | var codes = line.split(' '); 22 | for (var j = 0, codenum = codes.length; j < codenum; j++) { 23 | if (codes[j] === '') 24 | continue; 25 | for (var k = 1; k < codes[j].length; k += 2) { 26 | var codevalue = parseInt(codes[j].slice(k - 1, k + 1), 16); 27 | if (codevalue < 0 || codevalue > 255) { 28 | throw {error: "code must be a value between 0...255"}; 29 | } 30 | code.push(codevalue); 31 | } 32 | } 33 | } 34 | return {code: code, disk: disk, mapping: mapping, labels: labels}; 35 | } 36 | }; 37 | }]); 38 | 39 | app.service('assembler', ['opcodes', function (opcodes) { 40 | return { 41 | go: function (input) { 42 | // Use https://www.debuggex.com/ 43 | // Matches: "label: INSTRUCTION OPERAND1, OPERAND2, OPERAND3 44 | // GROUPS: 1 3 4 7 10 45 | var regex = /^[\t ]*(?:([.A-Za-z]\w*)(@\w+)?[:])?(?:[\t ]*([A-Za-z]{2,6})(?:[\t ]+([-.A-Za-z0-9]\w*((\+|-)\d+)?)(?:[\t ]*[,][\t ]*([-.A-Za-z0-9]\w*((\+|-)\d+)?)(?:[\t ]*[,][\t ]*([-.A-Za-z0-9]\w*((\+|-)\d+)?))?)?)?)?/; 46 | //^[\t ]*(?:([.A-Za-z]\w*)(@\w+)?[:])? -- label: or nothing 47 | //(?:[\t ]*([A-Za-z]{2,6}) -- instruction 48 | //([.A-Za-z0-9]\w*((\+|-)\d+)?) -- (OPERAND1) 49 | // Regex group indexes for operands 50 | var op1_group = 4; 51 | var op2_group = 7; 52 | var op3_group = 10; 53 | // MATCHES: "(+|-)INTEGER" 54 | var regexNum = /^[-+]?[0-9]+$/; 55 | // MATCHES: "(.L)abel" 56 | var regexLabel = /^([.A-Za-z_]\w*)((\+|-)\d+)?$/; 57 | // Contains the program code & data generated by the assembler 58 | var memory = []; 59 | // Contains data on disk 60 | var disk = []; 61 | // The address where the next instruction/data will be placed at 62 | var current = 0; 63 | // Contains the mapping from instructions to assembler line 64 | var mapping = {}; 65 | // Hash map of label used to replace the labels after the assembler generated the code 66 | var labels = {}; 67 | // Hash of uppercase labels used to detect duplicates 68 | var normalizedLabels = {}; 69 | 70 | var diskpart = false; 71 | 72 | // Split text into code lines 73 | var lines = input.split('\n'); 74 | 75 | // Allowed formats: 200, 200d, 0xA4, 0o48, 101b 76 | var parseNumber = function (input) { 77 | if (input.slice(0, 2) === "0x") { 78 | return parseInt(input.slice(2), 16); 79 | } else if (regexNum.exec(input)) { 80 | return parseInt(input, 10) & 0xFF; 81 | } else { 82 | return undefined; 83 | } 84 | }; 85 | 86 | // Allowed registers: R0 - RF 87 | var parseRegister = function (input) { 88 | input = input.toUpperCase(); 89 | if (input === 'R0') { 90 | return 0; 91 | } else if (input === 'R1') { 92 | return 1; 93 | } else if (input === 'R2') { 94 | return 2; 95 | } else if (input === 'R3') { 96 | return 3; 97 | } else if (input === 'R4') { 98 | return 4; 99 | } else if (input === 'R5') { 100 | return 5; 101 | } else if (input === 'R6') { 102 | return 6; 103 | } else if (input === 'R7') { 104 | return 7; 105 | } else if (input === 'R8') { 106 | return 8; 107 | } else if (input === 'R9') { 108 | return 9; 109 | } else if (input === 'RA') { 110 | return 10; 111 | } else if (input === 'RB') { 112 | return 11; 113 | } else if (input === 'RC') { 114 | return 12; 115 | } else if (input === 'RD') { 116 | return 13; 117 | } else if (input === 'RE') { 118 | return 14; 119 | } else if (input === 'RF') { 120 | return 15; 121 | } else { 122 | return undefined; 123 | } 124 | }; 125 | 126 | var parseAddress = function (input) { 127 | var number = parseNumber(input); 128 | if (number !== undefined) { 129 | if (number >= 0 && number <= 255) 130 | return number; 131 | throw "addresses must have a value between 0-255"; 132 | } 133 | 134 | var match = regexLabel.exec(input); 135 | if (match[1] === undefined) 136 | return undefined; 137 | var offset = 0; 138 | if (match[2] !== undefined) { 139 | var sign = match[2][0] === '-' ? -1 : 1; 140 | offset = sign * parseInt(match[2].slice(1), 10); 141 | } 142 | return {label: match[1], offset: offset}; 143 | }; 144 | 145 | var addLabel = function (label, address) { 146 | var upperLabel = label.toUpperCase(); 147 | if (upperLabel in normalizedLabels) 148 | throw "Duplicate label: " + label; 149 | 150 | if (address === undefined) { 151 | labels[label] = current; 152 | } else if (address >= 0 && address <= 255) { 153 | labels[label] = address; 154 | current = address; 155 | while (memory.length < current) 156 | memory.push(0); 157 | } else { 158 | throw "addresses must have a value between 0-255"; 159 | } 160 | }; 161 | 162 | var checkNoExtraArg = function (instr, arg) { 163 | if (arg !== undefined) { 164 | throw instr + ": too many arguments"; 165 | } 166 | }; 167 | 168 | var generate = function () { 169 | for (var arg in arguments) { 170 | var datum = arguments[arg]; 171 | if (current < 0 || current > 255) 172 | throw "Too many code/data"; 173 | if (current == memory.length) 174 | memory.push(datum); 175 | else if (current < memory.length) 176 | memory[current] = datum; 177 | current += 1; 178 | } 179 | }; 180 | 181 | for (var i = 0, l = lines.length; i < l; i++) { 182 | var line = lines[i].trim(); 183 | 184 | // Comments are not parsed 185 | if (line === "" || line.slice(0, 1) == ";") 186 | continue; 187 | 188 | // Fastpath: if the line contains data in disk, simply parse them as a sequence of hex numbers 189 | if (diskpart) { 190 | var bytes = line.split(' '); 191 | for (var j = 0, codenum = bytes.length; j < codenum; j++) { 192 | if (bytes[j] === '') 193 | continue; 194 | disk.push(parseInt(bytes[j], 16) % 256); 195 | } 196 | continue; 197 | } else if (line === "==========") { 198 | diskpart = true; 199 | continue; 200 | } 201 | 202 | try { 203 | var match = regex.exec(lines[i]); 204 | if (match[1] !== undefined || match[3] !== undefined) { 205 | if (match[1] !== undefined) { 206 | // TODO: Support hardcoded addresses 207 | var address = match[2]; 208 | if (address !== undefined) 209 | address = parseNumber(address.slice(1)); 210 | addLabel(match[1], address); 211 | } 212 | 213 | if (match[3] !== undefined) { 214 | var instr = match[3].toUpperCase(); 215 | var p1, p2, p3, opCode; 216 | 217 | // Add mapping instr pos to line number 218 | // Don't do it for DB as this is not a real instruction 219 | if (instr !== 'DB') { 220 | mapping[current] = i; 221 | } 222 | 223 | switch (instr) { 224 | case 'DB': 225 | p1 = parseNumber(match[op1_group]); 226 | if (p1 !== undefined) 227 | generate(p1 & 0xFF); 228 | else 229 | throw "DB does not support this operand"; 230 | break; 231 | case 'HALT': 232 | checkNoExtraArg('HALT', match[op1_group]); 233 | checkNoExtraArg('HALT', match[op2_group]); 234 | checkNoExtraArg('HALT', match[op3_group]); 235 | generate(opcodes.HALT << 4, 0); 236 | break; 237 | case 'MOVE': 238 | p1 = parseRegister(match[op1_group]); 239 | p2 = parseRegister(match[op2_group]); 240 | checkNoExtraArg('MOVE', match[op3_group]); 241 | if (p1 !== undefined && p2 !== undefined) 242 | generate(opcodes.MOVE << 4, (p2 << 4) | p1); 243 | else 244 | throw "MOVE does not support this operands"; 245 | break; 246 | case 'ADDI': 247 | p1 = parseRegister(match[op1_group]); 248 | p2 = parseRegister(match[op2_group]); 249 | p3 = parseRegister(match[op3_group]); 250 | if (p1 !== undefined && p2 !== undefined && p3 !== undefined) 251 | generate(opcodes.ADD_INT << 4 | p1, (p2 << 4) | p3); 252 | else 253 | throw "ADDI does not support this operands"; 254 | break; 255 | case 'ADDF': 256 | p1 = parseRegister(match[op1_group]); 257 | p2 = parseRegister(match[op2_group]); 258 | p3 = parseRegister(match[op3_group]); 259 | if (p1 !== undefined && p2 !== undefined && p3 !== undefined) 260 | generate(opcodes.ADD_FLOAT << 4 | p1, (p2 << 4) | p3); 261 | else 262 | throw "ADDF does not support this operands"; 263 | break; 264 | case 'LOADM': 265 | p1 = parseRegister(match[op1_group]); 266 | p2 = parseAddress(match[op2_group]); 267 | checkNoExtraArg('LOADM', match[op3_group]); 268 | if (p1 !== undefined && p2 !== undefined) 269 | generate(opcodes.LOAD_FROM_MEMORY << 4 | p1, p2); 270 | else 271 | throw "LOADM does not support this operands"; 272 | break; 273 | case 'LOADB': 274 | p1 = parseRegister(match[op1_group]); 275 | p2 = parseAddress(match[op2_group]); 276 | checkNoExtraArg('LOADB', match[op3_group]); 277 | if (p1 !== undefined && p2 !== undefined) 278 | generate(opcodes.LOAD_WITH_CONSTANT << 4 | p1, p2); 279 | else 280 | throw "LOADB does not support this operands"; 281 | break; 282 | case 'LOADP': 283 | p1 = parseRegister(match[op1_group]); 284 | p2 = parseRegister(match[op2_group]); 285 | checkNoExtraArg('LOADP', match[op3_group]); 286 | if (p1 !== undefined && p2 !== undefined) 287 | generate(opcodes.LOAD_FROM_POINTER << 4 | p1, p2); 288 | else 289 | throw "LOADP does not support this operands"; 290 | break; 291 | case 'STOREM': 292 | p1 = parseRegister(match[op1_group]); 293 | p2 = parseAddress(match[op2_group]); 294 | checkNoExtraArg('STOREM', match[op3_group]); 295 | if (p1 !== undefined && p2 !== undefined) 296 | generate(opcodes.STORE_TO_MEMORY << 4 | p1, p2); 297 | else 298 | throw "STOREM does not support this operands"; 299 | break; 300 | case 'STOREP': 301 | p1 = parseRegister(match[op1_group]); 302 | p2 = parseRegister(match[op2_group]); 303 | checkNoExtraArg('STOREP', match[op3_group]); 304 | if (p1 !== undefined && p2 !== undefined) 305 | generate(opcodes.STORE_TO_POINTER << 4 | p1, p2); 306 | else 307 | throw "STOREP does not support this operands"; 308 | break; 309 | case 'JUMP': 310 | p1 = parseRegister(match[op1_group]); 311 | p2 = parseAddress(match[op2_group]); 312 | checkNoExtraArg('JUMP', match[op3_group]); 313 | if (p1 !== undefined && p2 !== undefined) 314 | generate(opcodes.JUMP_IF_EQUAL << 4 | p1, p2); 315 | else 316 | throw "JUMP does not support this operands"; 317 | break; 318 | case 'JUMPL': 319 | p1 = parseRegister(match[op1_group]); 320 | p2 = parseAddress(match[op2_group]); 321 | checkNoExtraArg('JUMPL', match[op3_group]); 322 | if (p1 !== undefined && p2 !== undefined) 323 | generate(opcodes.JUMP_IF_LESS << 4 | p1, p2); 324 | else 325 | throw "JUMPL does not support this operands"; 326 | break; 327 | case 'AND': 328 | p1 = parseRegister(match[op1_group]); 329 | p2 = parseRegister(match[op2_group]); 330 | p3 = parseRegister(match[op3_group]); 331 | if (p1 !== undefined && p2 !== undefined && p3 !== undefined) 332 | generate(opcodes.AND << 4 | p1, (p2 << 4) | p3); 333 | else 334 | throw "AND does not support this operands"; 335 | break; 336 | case 'OR': 337 | p1 = parseRegister(match[op1_group]); 338 | p2 = parseRegister(match[op2_group]); 339 | p3 = parseRegister(match[op3_group]); 340 | if (p1 !== undefined && p2 !== undefined && p3 !== undefined) 341 | generate(opcodes.OR << 4 | p1, (p2 << 4) | p3); 342 | else 343 | throw "OR does not support this operands"; 344 | break; 345 | case 'XOR': 346 | p1 = parseRegister(match[op1_group]); 347 | p2 = parseRegister(match[op2_group]); 348 | p3 = parseRegister(match[op3_group]); 349 | if (p1 !== undefined && p2 !== undefined && p3 !== undefined) 350 | generate(opcodes.XOR << 4 | p1, (p2 << 4) | p3); 351 | else 352 | throw "XOR does not support this operands"; 353 | break; 354 | case 'ROT': 355 | p1 = parseRegister(match[op1_group]); 356 | p2 = parseNumber(match[op2_group]); 357 | checkNoExtraArg('ROT', match[op3_group]); 358 | if (p1 !== undefined && p2 !== undefined) 359 | generate(opcodes.ROTATE << 4 | p1, p2); 360 | else 361 | throw "LOADB does not support this operands"; 362 | break; 363 | default: 364 | throw "Invalid instruction: " + match[2]; 365 | } 366 | } 367 | } else { 368 | throw "Syntax error"; 369 | } 370 | } catch (e) { 371 | throw {error: e, line: i}; 372 | } 373 | } 374 | 375 | // Replace label 376 | for (i = 0, l = memory.length; i < l; i++) { 377 | if (!angular.isNumber(memory[i])) { 378 | var pair = memory[i]; 379 | if (pair.label in labels) { 380 | memory[i] = (labels[pair.label] + pair.offset) & 0xFF; 381 | } else { 382 | throw {error: "Undefined label: " + pair.label}; 383 | } 384 | } 385 | } 386 | 387 | return {code: memory, disk: disk, mapping: mapping, labels: labels}; 388 | } 389 | }; 390 | }]); 391 | 392 | /* 393 | * Local variables: 394 | * c-basic-offset: 4 395 | * tab-width: 4 396 | * indent-tabs-mode: nil 397 | * End: 398 | */ 399 | --------------------------------------------------------------------------------