├── src
├── app.js
├── ui
│ ├── flag.filter.js
│ ├── start.filter.js
│ ├── number.filter.js
│ ├── tab-support.directive.js
│ ├── select-line.directive.js
│ └── controller.js
├── emulator
│ ├── memory.js
│ ├── opcodes.js
│ └── cpu.js
└── assembler
│ └── asm.js
├── .gitignore
├── package.json
├── assets
├── style.css
├── asmsimulator.min.js
└── asmsimulator.js
├── Gruntfile.js
├── README.md
├── index.html
└── instruction-set.html
/src/app.js:
--------------------------------------------------------------------------------
1 | var app = angular.module('ASMSimulator', []);
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gitignore
2 | .idea
3 | .DS_Store
4 | node_modules/*
5 | ASMSimulator.iml
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/ui/number.filter.js:
--------------------------------------------------------------------------------
1 | app.filter('number', function() {
2 | return function(input, isHex) {
3 | if (isHex) {
4 | var hex = input.toString(16).toUpperCase();
5 | return hex.length == 1 ? "0" + hex: hex;
6 | } else {
7 | return input.toString(10);
8 | }
9 | };
10 | });
11 |
--------------------------------------------------------------------------------
/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 | "grunt": "~0.4.1",
9 | "grunt-contrib-concat": "~0.3.0",
10 | "grunt-contrib-uglify": "~0.2.4",
11 | "grunt-contrib-jshint": "~0.7.0",
12 | "grunt-contrib-watch": "~0.5.3"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/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/memory.js:
--------------------------------------------------------------------------------
1 | app.service('memory', [function () {
2 | var memory = {
3 | data: Array(256),
4 | lastAccess: -1,
5 | load: function (address) {
6 | var self = this;
7 |
8 | if (address < 0 || address >= self.data.length) {
9 | throw "Memory access violation at " + address;
10 | }
11 |
12 | self.lastAccess = address;
13 | return self.data[address];
14 | },
15 | store: function (address, value) {
16 | var self = this;
17 |
18 | if (address < 0 || address >= self.data.length) {
19 | throw "Memory access violation at " + address;
20 | }
21 |
22 | self.lastAccess = address;
23 | self.data[address] = value;
24 | },
25 | reset: function () {
26 | var self = this;
27 |
28 | self.lastAccess = -1;
29 | for (var i = 0, l = self.data.length; i < l; i++) {
30 | self.data[i] = 0;
31 | }
32 | }
33 | };
34 |
35 | memory.reset();
36 | return memory;
37 | }]);
38 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 |
3 | grunt.initConfig({
4 | pkg: grunt.file.readJSON('package.json'),
5 | concat: {
6 | options: {
7 | separator: ';'
8 | },
9 | dist: {
10 | src: ['src/app.js', 'src/**/*.js'],
11 | dest: 'assets/<%= pkg.name %>.js'
12 | }
13 | },
14 | uglify: {
15 | options: {
16 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
17 | },
18 | dist: {
19 | files: {
20 | 'assets/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
21 | }
22 | }
23 | },
24 | jshint: {
25 | files: ['Gruntfile.js', 'src/**/*.js'],
26 | options: {
27 | // options here to override JSHint defaults
28 | trailing: true,
29 | globals: {
30 | browser: true,
31 | console: true
32 | }
33 | }
34 | },
35 | watch: {
36 | files: ['<%= jshint.files %>'],
37 | tasks: ['jshint', 'concat']
38 | }
39 | });
40 |
41 | grunt.loadNpmTasks('grunt-contrib-uglify');
42 | grunt.loadNpmTasks('grunt-contrib-jshint');
43 | grunt.loadNpmTasks('grunt-contrib-watch');
44 | grunt.loadNpmTasks('grunt-contrib-concat');
45 |
46 | grunt.registerTask('default', ['jshint', 'concat', 'uglify']);
47 |
48 | };
49 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Simple 8-bit Assembler Simulator
2 | A simulator which provides a simplified assembler syntax (based on NASM) and is simulating a x86 like cpu. Press Help inside the simulator to see an overview about the supported instructions.
3 |
4 | ####TRY IT ONLINE
5 |
6 | ### Features
7 | - 8-bit CPU
8 | - 4 general purpose registers
9 | - 256 bytes of memory
10 | - Console output
11 |
12 | ### How to build
13 | Make sure you have Grunt installed to compile the `asmsimulator.js` script.
14 | Run `grunt` to build the project.
15 |
16 | ### Background
17 | A technical introduction is available on my blog: [www.mschweighauser.com](https://www.mschweighauser.com/make-your-own-assembler-simulator-in-javascript-part1/).
18 |
19 | ### License
20 | **The MIT License**
21 |
22 | Copyright (c) 2015 Marco Schweighauser
23 |
24 | 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:
25 |
26 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
27 |
28 | 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.
29 |
--------------------------------------------------------------------------------
/src/emulator/opcodes.js:
--------------------------------------------------------------------------------
1 | app.service('opcodes', [function() {
2 | var opcodes = {
3 | NONE: 0,
4 | MOV_REG_TO_REG: 1,
5 | MOV_ADDRESS_TO_REG: 2,
6 | MOV_REGADDRESS_TO_REG: 3,
7 | MOV_REG_TO_ADDRESS: 4,
8 | MOV_REG_TO_REGADDRESS: 5,
9 | MOV_NUMBER_TO_REG: 6,
10 | MOV_NUMBER_TO_ADDRESS: 7,
11 | MOV_NUMBER_TO_REGADDRESS: 8,
12 | ADD_REG_TO_REG: 10,
13 | ADD_REGADDRESS_TO_REG: 11,
14 | ADD_ADDRESS_TO_REG: 12,
15 | ADD_NUMBER_TO_REG: 13,
16 | SUB_REG_FROM_REG: 14,
17 | SUB_REGADDRESS_FROM_REG: 15,
18 | SUB_ADDRESS_FROM_REG: 16,
19 | SUB_NUMBER_FROM_REG: 17,
20 | INC_REG: 18,
21 | DEC_REG: 19,
22 | CMP_REG_WITH_REG: 20,
23 | CMP_REGADDRESS_WITH_REG: 21,
24 | CMP_ADDRESS_WITH_REG: 22,
25 | CMP_NUMBER_WITH_REG: 23,
26 | JMP_REGADDRESS: 30,
27 | JMP_ADDRESS: 31,
28 | JC_REGADDRESS: 32,
29 | JC_ADDRESS: 33,
30 | JNC_REGADDRESS: 34,
31 | JNC_ADDRESS: 35,
32 | JZ_REGADDRESS: 36,
33 | JZ_ADDRESS: 37,
34 | JNZ_REGADDRESS: 38,
35 | JNZ_ADDRESS: 39,
36 | JA_REGADDRESS: 40,
37 | JA_ADDRESS: 41,
38 | JNA_REGADDRESS: 42,
39 | JNA_ADDRESS: 43,
40 | PUSH_REG: 50,
41 | PUSH_REGADDRESS: 51,
42 | PUSH_ADDRESS: 52,
43 | PUSH_NUMBER: 53,
44 | POP_REG: 54,
45 | CALL_REGADDRESS: 55,
46 | CALL_ADDRESS: 56,
47 | RET: 57,
48 | MUL_REG: 60,
49 | MUL_REGADDRESS: 61,
50 | MUL_ADDRESS: 62,
51 | MUL_NUMBER: 63,
52 | DIV_REG: 64,
53 | DIV_REGADDRESS: 65,
54 | DIV_ADDRESS: 66,
55 | DIV_NUMBER: 67,
56 | AND_REG_WITH_REG: 70,
57 | AND_REGADDRESS_WITH_REG: 71,
58 | AND_ADDRESS_WITH_REG: 72,
59 | AND_NUMBER_WITH_REG: 73,
60 | OR_REG_WITH_REG: 74,
61 | OR_REGADDRESS_WITH_REG: 75,
62 | OR_ADDRESS_WITH_REG: 76,
63 | OR_NUMBER_WITH_REG: 77,
64 | XOR_REG_WITH_REG: 78,
65 | XOR_REGADDRESS_WITH_REG: 79,
66 | XOR_ADDRESS_WITH_REG: 80,
67 | XOR_NUMBER_WITH_REG: 81,
68 | NOT_REG: 82,
69 | SHL_REG_WITH_REG: 90,
70 | SHL_REGADDRESS_WITH_REG: 91,
71 | SHL_ADDRESS_WITH_REG: 92,
72 | SHL_NUMBER_WITH_REG: 93,
73 | SHR_REG_WITH_REG: 94,
74 | SHR_REGADDRESS_WITH_REG: 95,
75 | SHR_ADDRESS_WITH_REG: 96,
76 | SHR_NUMBER_WITH_REG: 97
77 | };
78 |
79 | return opcodes;
80 | }]);
81 |
--------------------------------------------------------------------------------
/src/ui/controller.js:
--------------------------------------------------------------------------------
1 | app.controller('Ctrl', ['$document', '$scope', '$timeout', 'cpu', 'memory', 'assembler', function ($document, $scope, $timeout, cpu, memory, assembler) {
2 | $scope.memory = memory;
3 | $scope.cpu = cpu;
4 | $scope.error = '';
5 | $scope.isRunning = false;
6 | $scope.displayHex = true;
7 | $scope.displayInstr = true;
8 | $scope.displayA = false;
9 | $scope.displayB = false;
10 | $scope.displayC = false;
11 | $scope.displayD = false;
12 | $scope.speeds = [{speed: 1, desc: "1 HZ"},
13 | {speed: 4, desc: "4 HZ"},
14 | {speed: 8, desc: "8 HZ"},
15 | {speed: 16, desc: "16 HZ"}];
16 | $scope.speed = 4;
17 | $scope.outputStartIndex = 232;
18 |
19 | $scope.code = "; Simple example\n; Writes Hello World to the output\n\n JMP start\nhello: DB \"Hello World!\" ; Variable\n DB 0 ; String terminator\n\nstart:\n MOV C, hello ; Point to var \n MOV D, 232 ; Point to output\n CALL print\n HLT ; Stop execution\n\nprint: ; print(C:*from, D:*to)\n PUSH A\n PUSH B\n MOV B, 0\n.loop:\n MOV A, [C] ; Get char from var\n MOV [D], A ; Write to output\n INC C\n INC D \n CMP B, [C] ; Check if end\n JNZ .loop ; jump if not\n\n POP B\n POP A\n RET";
20 |
21 | $scope.reset = function () {
22 | cpu.reset();
23 | memory.reset();
24 | $scope.error = '';
25 | $scope.selectedLine = -1;
26 | };
27 |
28 | $scope.executeStep = function () {
29 | if (!$scope.checkPrgrmLoaded()) {
30 | $scope.assemble();
31 | }
32 |
33 | try {
34 | // Execute
35 | var res = cpu.step();
36 |
37 | // Mark in code
38 | if (cpu.ip in $scope.mapping) {
39 | $scope.selectedLine = $scope.mapping[cpu.ip];
40 | }
41 |
42 | return res;
43 | } catch (e) {
44 | $scope.error = e;
45 | return false;
46 | }
47 | };
48 |
49 | var runner;
50 | $scope.run = function () {
51 | if (!$scope.checkPrgrmLoaded()) {
52 | $scope.assemble();
53 | }
54 |
55 | $scope.isRunning = true;
56 | runner = $timeout(function () {
57 | if ($scope.executeStep() === true) {
58 | $scope.run();
59 | } else {
60 | $scope.isRunning = false;
61 | }
62 | }, 1000 / $scope.speed);
63 | };
64 |
65 | $scope.stop = function () {
66 | $timeout.cancel(runner);
67 | $scope.isRunning = false;
68 | };
69 |
70 | $scope.checkPrgrmLoaded = function () {
71 | for (var i = 0, l = memory.data.length; i < l; i++) {
72 | if (memory.data[i] !== 0) {
73 | return true;
74 | }
75 | }
76 |
77 | return false;
78 | };
79 |
80 | $scope.getChar = function (value) {
81 | var text = String.fromCharCode(value);
82 |
83 | if (text.trim() === '') {
84 | return '\u00A0\u00A0';
85 | } else {
86 | return text;
87 | }
88 | };
89 |
90 | $scope.assemble = function () {
91 | try {
92 | $scope.reset();
93 |
94 | var assembly = assembler.go($scope.code);
95 | $scope.mapping = assembly.mapping;
96 | var binary = assembly.code;
97 | $scope.labels = assembly.labels;
98 |
99 | if (binary.length > memory.data.length)
100 | throw "Binary code does not fit into the memory. Max " + memory.data.length + " bytes are allowed";
101 |
102 | for (var i = 0, l = binary.length; i < l; i++) {
103 | memory.data[i] = binary[i];
104 | }
105 | } catch (e) {
106 | if (e.line !== undefined) {
107 | $scope.error = e.line + " | " + e.error;
108 | $scope.selectedLine = e.line;
109 | } else {
110 | $scope.error = e.error;
111 | }
112 | }
113 | };
114 |
115 | $scope.jumpToLine = function (index) {
116 | $document[0].getElementById('sourceCode').scrollIntoView();
117 | $scope.selectedLine = $scope.mapping[index];
118 | };
119 |
120 |
121 | $scope.isInstruction = function (index) {
122 | return $scope.mapping !== undefined &&
123 | $scope.mapping[index] !== undefined &&
124 | $scope.displayInstr;
125 | };
126 |
127 | $scope.getMemoryCellCss = function (index) {
128 | if (index >= $scope.outputStartIndex) {
129 | return 'output-bg';
130 | } else if ($scope.isInstruction(index)) {
131 | return 'instr-bg';
132 | } else if (index > cpu.sp && index <= cpu.maxSP) {
133 | return 'stack-bg';
134 | } else {
135 | return '';
136 | }
137 | };
138 |
139 | $scope.getMemoryInnerCellCss = function (index) {
140 | if (index === cpu.ip) {
141 | return 'marker marker-ip';
142 | } else if (index === cpu.sp) {
143 | return 'marker marker-sp';
144 | } else if (index === cpu.gpr[0] && $scope.displayA) {
145 | return 'marker marker-a';
146 | } else if (index === cpu.gpr[1] && $scope.displayB) {
147 | return 'marker marker-b';
148 | } else if (index === cpu.gpr[2] && $scope.displayC) {
149 | return 'marker marker-c';
150 | } else if (index === cpu.gpr[3] && $scope.displayD) {
151 | return 'marker marker-d';
152 | } else {
153 | return '';
154 | }
155 | };
156 | }]);
157 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Simple 8-bit Assembler Simulator in Javascript
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
23 |
26 |
27 |
28 |
29 |
{{ error }}
30 |
31 |
50 |
51 |
52 |
53 |
54 |
Output
55 |
56 |
57 |
59 | {{ getChar(m) }}
60 |
61 |
62 |
63 |
64 |
65 |
CPU & Memory
66 |
67 |
68 |
Registers / Flags
69 |
70 |
71 |
72 | A
73 | B
74 | C
75 | D
76 | IP
77 | SP
78 | Z
79 | C
80 | F
81 |
82 |
83 |
84 |
85 | {{ cpu.gpr[0] | number:displayHex }}
86 | {{ cpu.gpr[1] | number:displayHex }}
87 | {{ cpu.gpr[2] | number:displayHex }}
88 | {{ cpu.gpr[3] | number:displayHex }}
89 | {{ cpu.ip | number:displayHex }}
90 | {{ cpu.sp | number:displayHex }}
91 | {{ cpu.zero | flag }}
92 | {{ cpu.carry | flag }}
93 | {{ cpu.fault | flag }}
94 |
95 |
96 |
97 |
RAM
98 |
110 |
111 |
112 | Clock speed:
113 |
114 | Instructions:
115 | Show
116 | Hide
117 | View:
118 | Hex
119 | Decimal
120 |
121 | Register addressing:
122 | A:
123 | Show
124 | Hide
125 | B:
126 | Show
127 | Hide
128 | C:
129 | Show
130 | Hide
131 | D:
132 | Show
133 | Hide
134 |
135 |
136 |
137 |
138 |
139 |
140 |
Labels
141 |
142 |
143 |
144 |
145 | Name
146 | Address
147 | Value
148 |
149 |
150 | {{ name }}
151 | {{ value | number:displayHex }}
152 | {{ memory.data[value] | number:displayHex }}
153 |
154 | ('{{ getChar(memory.data[value]) }}')
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
by Marco Schweighauser (2015) | MIT License | Blog
165 |
166 |
167 |
168 |
169 |
170 |
--------------------------------------------------------------------------------
/instruction-set.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Simple 8-bit Assembler - Instruction Set Help
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
19 |
20 |
21 |
22 |
Introduction
23 |
This simulator provides a simplified assembler syntax (based on NASM ) and is simulating a x86 like cpu. In depth documentation and introduction to assembler can be found on the following websites:
24 |
29 |
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 1 byte. Therefore a MOV instruction will use 3 bytes of memory. The simulator provides a console output which is memory mapped from 0xE8 to 0xFF. Memory mapped means that every value written to this memory block is visible on the console.
30 |
Syntax
31 |
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.
32 |
label: instruction operands ; Comment
33 |
Valid number formats for constants are:
34 |
35 | Decimal: 200
36 | Decimal: 200d
37 | Hex: 0xA4
38 | Octal: 0o48
39 | Binary: 101b
40 |
41 |
It is possible to define a number using a character or multiple numbers (see instruction DB ) by using a string.
42 |
43 | Character: 'A'
44 | String: "Hello World!"
45 |
46 |
Operands can either be one of the four general purpose registers, stack pointer register, a memory address or a constant.
47 | Stack pointer register can only be used as operand in MOV, ADD, SUB, CMP, INC and DEC instructions.
48 | 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.
49 |
50 | General purpose (GP) register: A, B, C, D
51 | Stack pointer register: SP
52 | Address using a GP register: [A]
53 | Address using a GP register and offset: [D-3]
54 | Address using SP register and offset: [SP+2]
55 | Address using a constant: [100]
56 | Address using a label: label
57 | Constant: Any number between 0..255 (8bit unsigned)
58 | Offset for indirect addressing: Integer between -16..+15 (sign is mandatory)
59 |
60 |
MOV - Copy a value
61 |
Copies a value from src to dest . The MOV instruction is the only one able to directly modify the memory. SP can be used as operand with MOV.
62 |
63 | MOV reg, reg
64 | MOV reg, address
65 | MOV reg, constant
66 | MOV address, reg
67 | MOV address, constant
68 |
69 |
DB - Variable
70 |
Defines a variable. A variable can either be a single number, character or a string.
71 |
72 | DB constant
73 |
74 |
Math operations
75 |
Addition and Subtraction
76 |
Adds two numbers together or subtract one number form another. This operations will modify the carry and zero flag. SP can be used as operand with ADD and SUB.
77 |
78 | ADD reg, reg
79 | ADD reg, address
80 | ADD reg, constant
81 | SUB reg, reg
82 | SUB reg, address
83 | SUB reg, constant
84 |
85 |
Increment and Decrement
86 |
Increments or decrements a register by one. This operations will modify the carry and zero flag. SP can be used as operand with INC and DEC.
87 |
88 | INC reg
89 | DEC reg
90 |
91 |
Multiplication and division
92 |
Multiplies or divides the A register with the given value. This operations will modify the carry and zero flag.
93 |
94 | MUL reg
95 | MUL address
96 | MUL constant
97 | DIV reg
98 | DIV address
99 | DIV constant
100 |
101 |
Logical instructions
102 |
The following logical instructions are supported: AND, OR, XOR, NOT. This operations will modify the carry and zero flag.
103 |
104 | AND reg, reg
105 | AND reg, address
106 | AND reg, constant
107 | OR reg, reg
108 | OR reg, address
109 | OR reg, constant
110 | XOR reg, reg
111 | XOR reg, address
112 | XOR reg, constant
113 | NOT reg
114 |
115 |
Shift instructions
116 |
The following shift instructions are supported: SHL/SAL and SHR/SAR. As this simulator only supports unsigned numbers SHR and SAR yield the same result. This operations will modify the carry and zero flag.
117 |
118 | SHL reg, reg
119 | SHL reg, address
120 | SHL reg, constant
121 | SHR reg, reg
122 | SHR reg, address
123 | SHR reg, constant
124 |
125 |
CMP - Compare
126 |
Compares two values and sets the zero flag to true if they are equal. SP can be used as operand with CMP. Use this instruction before a conditional jump.
127 |
128 | CMP reg, reg
129 | CMP reg, address
130 | CMP reg, constant
131 |
132 |
Jumps
133 |
JMP - Unconditional jump
134 |
Let the instruction pointer do a unconditional jump to the defined address.
135 |
136 | JMP address
137 |
138 |
Conditional jumps
139 |
Let the instruction pointer do a conditional jump to the defined address. See the table below for the available conditions.
140 |
141 |
142 |
143 | Instruction
144 | Description
145 | Condition
146 | Alternatives
147 |
148 |
149 |
150 |
151 | JC
152 | Jump if carry
153 | Carry = TRUE
154 | JB, JNAE
155 |
156 |
157 | JNC
158 | Jump if no carry
159 | Carry = FALSE
160 | JNB, JAE
161 |
162 |
163 | JZ
164 | Jump if zero
165 | Zero = TRUE
166 | JB, JE
167 |
168 |
169 | JNZ
170 | Jump if no zero
171 | Zero = FALSE
172 | JNE
173 |
174 |
175 | JA
176 | >
177 | Carry = FALSE && Zero = FALSE
178 | JNBE
179 |
180 |
181 | JNBE
182 | not <=
183 | Carry = FALSE && Zero = FALSE
184 | JA
185 |
186 |
187 | JAE
188 | >=
189 | Carry = FALSE
190 | JNC, JNB
191 |
192 |
193 | JNB
194 | not <
195 | Carry = FALSE
196 | JNC, JAE
197 |
198 |
199 | JB
200 | <
201 | Carry = TRUE
202 | JC, JNAE
203 |
204 |
205 | JNAE
206 | not >=
207 | Carry = TRUE
208 | JC, JB
209 |
210 |
211 | JBE
212 | <=
213 | C = TRUE or Z = TRUE
214 | JNA
215 |
216 |
217 | JNA
218 | not >
219 | C = TRUE or Z = TRUE
220 | JBE
221 |
222 |
223 | JE
224 | =
225 | Z = TRUE
226 | JZ
227 |
228 |
229 | JNE
230 | !=
231 | Z = FALSE
232 | JNZ
233 |
234 |
235 |
236 |
CALL - Function call
237 |
Call can be used to jump into a subroutine (function). Pushes the instruction address of the next instruction to the stack and jumps to the specified address.
238 |
239 | CALL address
240 |
241 |
RET - Exit a subroutine
242 |
Exits a subroutines by popping the return address previously pushed by the CALL instruction. Make sure the SP is balanced before calling RET otherwise the instruction pointer will have an ambiguous value.
243 |
244 | RET
245 |
246 |
Stack instructions
247 |
PUSH - Push to stack
248 |
Pushes a value to the stack. The stack grows down and the current position is available in the stack pointer register (SP). This instruction will decrease the SP.
249 |
250 | PUSH reg
251 | PUSH address
252 | PUSH constant
253 |
254 |
POP - Pop from stack
255 |
Pops a value from the stack to a register. This instruction will increase the SP.
256 |
257 | POP reg
258 |
259 |
Other instructions
260 |
HLT - Stops the processor.
261 |
Stops operation of the processor. Hit Reset button to reset IP before restarting.
262 |
263 | HLT
264 |
265 |
266 |
by Marco Schweighauser (2015) | MIT License | Blog
267 |
268 |
269 |
270 |
--------------------------------------------------------------------------------
/assets/asmsimulator.min.js:
--------------------------------------------------------------------------------
1 | /*! asmsimulator 19-07-2015 */
2 | var app=angular.module("ASMSimulator",[]);app.service("assembler",["opcodes",function(a){return{go:function(b){for(var c=/^[\t ]*(?:([.A-Za-z]\w*)[:])?(?:[\t ]*([A-Za-z]{2,4})(?:[\t ]+(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*)(?:[\t ]*[,][\t ]*(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*))?)?)?/,d=3,e=7,f=/^[-+]?[0-9]+$/,g=/^[.A-Za-z]\w*$/,h=[],i={},j={},k={},l=b.split("\n"),m=function(a){if("0x"===a.slice(0,2))return parseInt(a.slice(2),16);if("0o"===a.slice(0,2))return parseInt(a.slice(2),8);if("b"===a.slice(a.length-1))return parseInt(a.slice(0,a.length-1),2);if("d"===a.slice(a.length-1))return parseInt(a.slice(0,a.length-1),10);if(f.exec(a))return parseInt(a,10);throw"Invalid number format"},n=function(a){return a=a.toUpperCase(),"A"===a?0:"B"===a?1:"C"===a?2:"D"===a?3:"SP"===a?4:void 0},o=function(a){a=a.toUpperCase();var b=0,c=0;if("A"===a[0])c=0;else if("B"===a[0])c=1;else if("C"===a[0])c=2;else if("D"===a[0])c=3;else{if("SP"!==a.slice(0,2))return void 0;c=4}var d=1;if(4===c&&(d=2),"-"===a[d])b=-1;else{if("+"!==a[d])return void 0;b=1}var e=b*parseInt(a.slice(d+1),10);if(-16>e||e>15)throw"offset must be a value between -16...+15";return 0>e&&(e=32+e),8*e+c},p=function(a,b,c){var d=n(a);if(void 0!==d)return{type:b,value:d};var e=q(a);if(void 0!==e)return{type:c,value:e};if("regaddress"===b&&(d=o(a),void 0!==d))return{type:b,value:d};var f=m(a);if(isNaN(f))throw"Not a "+c+": "+f;if(0>f||f>255)throw c+" must have a value between 0-255";return{type:c,value:f}},q=function(a){return g.exec(a)?a:void 0},r=function(a){switch(a.slice(0,1)){case"[":var b=a.slice(1,a.length-1);return p(b,"regaddress","address");case'"':for(var c=a.slice(1,a.length-1),d=[],e=0,f=c.length;f>e;e++)d.push(c.charCodeAt(e));return{type:"numbers",value:d};case"'":var g=a.slice(1,a.length-1);if(g.length>1)throw"Only one character is allowed. Use String instead";return{type:"number",value:g.charCodeAt(0)};default:return p(a,"register","number")}},s=(function(a){var b=a.toUpperCase();if(b in k)throw"Duplicate label: "+a;if("A"===b||"B"===b||"C"===b||"D"===b)throw"Label contains keyword: "+b;j[a]=h.length}),t=function(a,b){if(void 0!==b)throw a+": too many arguments"},u=0,v=l.length;v>u;u++)try{var w=c.exec(l[u]);if(void 0!==w[1]||void 0!==w[2]){if(void 0!==w[1]&&s(w[1]),void 0!==w[2]){var x,y,z,A=w[2].toUpperCase();switch("DB"!==A&&(i[h.length]=u),A){case"DB":if(x=r(w[d]),"number"===x.type)h.push(x.value);else{if("numbers"!==x.type)throw"DB does not support this operand";for(var B=0,C=x.value.length;C>B;B++)h.push(x.value[B])}break;case"HLT":t("HLT",w[d]),z=a.NONE,h.push(z);break;case"MOV":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.MOV_REG_TO_REG;else if("register"===x.type&&"address"===y.type)z=a.MOV_ADDRESS_TO_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.MOV_REGADDRESS_TO_REG;else if("address"===x.type&&"register"===y.type)z=a.MOV_REG_TO_ADDRESS;else if("regaddress"===x.type&&"register"===y.type)z=a.MOV_REG_TO_REGADDRESS;else if("register"===x.type&&"number"===y.type)z=a.MOV_NUMBER_TO_REG;else if("address"===x.type&&"number"===y.type)z=a.MOV_NUMBER_TO_ADDRESS;else{if("regaddress"!==x.type||"number"!==y.type)throw"MOV does not support this operands";z=a.MOV_NUMBER_TO_REGADDRESS}h.push(z,x.value,y.value);break;case"ADD":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.ADD_REG_TO_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.ADD_REGADDRESS_TO_REG;else if("register"===x.type&&"address"===y.type)z=a.ADD_ADDRESS_TO_REG;else{if("register"!==x.type||"number"!==y.type)throw"ADD does not support this operands";z=a.ADD_NUMBER_TO_REG}h.push(z,x.value,y.value);break;case"SUB":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.SUB_REG_FROM_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.SUB_REGADDRESS_FROM_REG;else if("register"===x.type&&"address"===y.type)z=a.SUB_ADDRESS_FROM_REG;else{if("register"!==x.type||"number"!==y.type)throw"SUB does not support this operands";z=a.SUB_NUMBER_FROM_REG}h.push(z,x.value,y.value);break;case"INC":if(x=r(w[d]),t("INC",w[e]),"register"!==x.type)throw"INC does not support this operand";z=a.INC_REG,h.push(z,x.value);break;case"DEC":if(x=r(w[d]),t("DEC",w[e]),"register"!==x.type)throw"DEC does not support this operand";z=a.DEC_REG,h.push(z,x.value);break;case"CMP":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.CMP_REG_WITH_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.CMP_REGADDRESS_WITH_REG;else if("register"===x.type&&"address"===y.type)z=a.CMP_ADDRESS_WITH_REG;else{if("register"!==x.type||"number"!==y.type)throw"CMP does not support this operands";z=a.CMP_NUMBER_WITH_REG}h.push(z,x.value,y.value);break;case"JMP":if(x=r(w[d]),t("JMP",w[e]),"register"===x.type)z=a.JMP_REGADDRESS;else{if("number"!==x.type)throw"JMP does not support this operands";z=a.JMP_ADDRESS}h.push(z,x.value);break;case"JC":case"JB":case"JNAE":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.JC_REGADDRESS;else{if("number"!==x.type)throw A+" does not support this operand";z=a.JC_ADDRESS}h.push(z,x.value);break;case"JNC":case"JNB":case"JAE":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.JNC_REGADDRESS;else{if("number"!==x.type)throw A+"does not support this operand";z=a.JNC_ADDRESS}h.push(z,x.value);break;case"JZ":case"JE":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.JZ_REGADDRESS;else{if("number"!==x.type)throw A+" does not support this operand";z=a.JZ_ADDRESS}h.push(z,x.value);break;case"JNZ":case"JNE":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.JNZ_REGADDRESS;else{if("number"!==x.type)throw A+" does not support this operand";z=a.JNZ_ADDRESS}h.push(z,x.value);break;case"JA":case"JNBE":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.JA_REGADDRESS;else{if("number"!==x.type)throw A+" does not support this operand";z=a.JA_ADDRESS}h.push(z,x.value);break;case"JNA":case"JBE":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.JNA_REGADDRESS;else{if("number"!==x.type)throw A+" does not support this operand";z=a.JNA_ADDRESS}h.push(z,x.value);break;case"PUSH":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.PUSH_REG;else if("regaddress"===x.type)z=a.PUSH_REGADDRESS;else if("address"===x.type)z=a.PUSH_ADDRESS;else{if("number"!==x.type)throw"PUSH does not support this operand";z=a.PUSH_NUMBER}h.push(z,x.value);break;case"POP":if(x=r(w[d]),t(A,w[e]),"register"!==x.type)throw"PUSH does not support this operand";z=a.POP_REG,h.push(z,x.value);break;case"CALL":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.CALL_REGADDRESS;else{if("number"!==x.type)throw"CALL does not support this operand";z=a.CALL_ADDRESS}h.push(z,x.value);break;case"RET":t(A,w[d]),z=a.RET,h.push(z);break;case"MUL":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.MUL_REG;else if("regaddress"===x.type)z=a.MUL_REGADDRESS;else if("address"===x.type)z=a.MUL_ADDRESS;else{if("number"!==x.type)throw"MULL does not support this operand";z=a.MUL_NUMBER}h.push(z,x.value);break;case"DIV":if(x=r(w[d]),t(A,w[e]),"register"===x.type)z=a.DIV_REG;else if("regaddress"===x.type)z=a.DIV_REGADDRESS;else if("address"===x.type)z=a.DIV_ADDRESS;else{if("number"!==x.type)throw"DIV does not support this operand";z=a.DIV_NUMBER}h.push(z,x.value);break;case"AND":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.AND_REG_WITH_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.AND_REGADDRESS_WITH_REG;else if("register"===x.type&&"address"===y.type)z=a.AND_ADDRESS_WITH_REG;else{if("register"!==x.type||"number"!==y.type)throw"AND does not support this operands";z=a.AND_NUMBER_WITH_REG}h.push(z,x.value,y.value);break;case"OR":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.OR_REG_WITH_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.OR_REGADDRESS_WITH_REG;else if("register"===x.type&&"address"===y.type)z=a.OR_ADDRESS_WITH_REG;else{if("register"!==x.type||"number"!==y.type)throw"OR does not support this operands";z=a.OR_NUMBER_WITH_REG}h.push(z,x.value,y.value);break;case"XOR":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.XOR_REG_WITH_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.XOR_REGADDRESS_WITH_REG;else if("register"===x.type&&"address"===y.type)z=a.XOR_ADDRESS_WITH_REG;else{if("register"!==x.type||"number"!==y.type)throw"XOR does not support this operands";z=a.XOR_NUMBER_WITH_REG}h.push(z,x.value,y.value);break;case"NOT":if(x=r(w[d]),t(A,w[e]),"register"!==x.type)throw"NOT does not support this operand";z=a.NOT_REG,h.push(z,x.value);break;case"SHL":case"SAL":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.SHL_REG_WITH_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.SHL_REGADDRESS_WITH_REG;else if("register"===x.type&&"address"===y.type)z=a.SHL_ADDRESS_WITH_REG;else{if("register"!==x.type||"number"!==y.type)throw A+" does not support this operands";z=a.SHL_NUMBER_WITH_REG}h.push(z,x.value,y.value);break;case"SHR":case"SAR":if(x=r(w[d]),y=r(w[e]),"register"===x.type&&"register"===y.type)z=a.SHR_REG_WITH_REG;else if("register"===x.type&&"regaddress"===y.type)z=a.SHR_REGADDRESS_WITH_REG;else if("register"===x.type&&"address"===y.type)z=a.SHR_ADDRESS_WITH_REG;else{if("register"!==x.type||"number"!==y.type)throw A+" does not support this operands";z=a.SHR_NUMBER_WITH_REG}h.push(z,x.value,y.value);break;default:throw"Invalid instruction: "+w[2]}}}else{var D=l[u].trim();if(""!==D&&";"!==D.slice(0,1))throw"Syntax error"}}catch(E){throw{error:E,line:u}}for(u=0,v=h.length;v>u;u++)if(!angular.isNumber(h[u])){if(!(h[u]in j))throw{error:"Undefined label: "+h[u]};h[u]=j[h[u]]}return{code:h,mapping:i,labels:j}}}}]),app.service("cpu",["opcodes","memory",function(a,b){var c={step:function(){var c=this;if(c.fault===!0)throw"FAULT. Reset to continue.";try{var d=function(a){if(0>a||a>=c.gpr.length)throw"Invalid register: "+a;return a},e=function(a){if(0>a||a>=1+c.gpr.length)throw"Invalid register: "+a;return a},f=function(a,b){if(a>=0&&ac.maxSP)throw"Stack underflow"}},g=function(a){if(a>=0&&a15&&(e-=32),b+e},i=function(a){return c.zero=!1,c.carry=!1,a>=256?(c.carry=!0,a%=256):0===a?c.zero=!0:0>a&&(c.carry=!0,a=256- -a%256),a},j=function(a){if(0>a||a>=b.data.length)throw"IP outside memory";c.ip=a},k=function(a){if(b.store(c.sp--,a),c.spc.maxSP)throw"Stack underflow";return a},m=function(a){if(0===a)throw"Division by 0";return Math.floor(c.gpr[0]/a)};if(c.ip<0||c.ip>=b.data.length)throw"Instruction pointer is outside of memory";var n,o,p,q,r,s=b.load(c.ip);switch(s){case a.NONE:return!1;case a.MOV_REG_TO_REG:n=e(b.load(++c.ip)),o=e(b.load(++c.ip)),f(n,g(o)),c.ip++;break;case a.MOV_ADDRESS_TO_REG:n=e(b.load(++c.ip)),p=b.load(++c.ip),f(n,b.load(p)),c.ip++;break;case a.MOV_REGADDRESS_TO_REG:n=e(b.load(++c.ip)),o=b.load(++c.ip),f(n,b.load(h(o))),c.ip++;break;case a.MOV_REG_TO_ADDRESS:q=b.load(++c.ip),o=e(b.load(++c.ip)),b.store(q,g(o)),c.ip++;break;case a.MOV_REG_TO_REGADDRESS:n=b.load(++c.ip),o=e(b.load(++c.ip)),b.store(h(n),g(o)),c.ip++;break;case a.MOV_NUMBER_TO_REG:n=e(b.load(++c.ip)),r=b.load(++c.ip),f(n,r),c.ip++;break;case a.MOV_NUMBER_TO_ADDRESS:q=b.load(++c.ip),r=b.load(++c.ip),b.store(q,r),c.ip++;break;case a.MOV_NUMBER_TO_REGADDRESS:n=b.load(++c.ip),r=b.load(++c.ip),b.store(h(n),r),c.ip++;break;case a.ADD_REG_TO_REG:n=e(b.load(++c.ip)),o=e(b.load(++c.ip)),f(n,i(g(n)+g(o))),c.ip++;break;case a.ADD_REGADDRESS_TO_REG:n=e(b.load(++c.ip)),o=b.load(++c.ip),f(n,i(g(n)+b.load(h(o)))),c.ip++;break;case a.ADD_ADDRESS_TO_REG:n=e(b.load(++c.ip)),p=b.load(++c.ip),f(n,i(g(n)+b.load(p))),c.ip++;break;case a.ADD_NUMBER_TO_REG:n=e(b.load(++c.ip)),r=b.load(++c.ip),f(n,i(g(n)+r)),c.ip++;break;case a.SUB_REG_FROM_REG:n=e(b.load(++c.ip)),o=e(b.load(++c.ip)),f(n,i(g(n)-c.gpr[o])),c.ip++;break;case a.SUB_REGADDRESS_FROM_REG:n=e(b.load(++c.ip)),o=b.load(++c.ip),f(n,i(g(n)-b.load(h(o)))),c.ip++;break;case a.SUB_ADDRESS_FROM_REG:n=e(b.load(++c.ip)),p=b.load(++c.ip),f(n,i(g(n)-b.load(p))),c.ip++;break;case a.SUB_NUMBER_FROM_REG:n=e(b.load(++c.ip)),r=b.load(++c.ip),f(n,i(g(n)-r)),c.ip++;break;case a.INC_REG:n=e(b.load(++c.ip)),f(n,i(g(n)+1)),c.ip++;break;case a.DEC_REG:n=e(b.load(++c.ip)),f(n,i(g(n)-1)),c.ip++;break;case a.CMP_REG_WITH_REG:n=e(b.load(++c.ip)),o=e(b.load(++c.ip)),i(g(n)-g(o)),c.ip++;break;case a.CMP_REGADDRESS_WITH_REG:n=e(b.load(++c.ip)),o=b.load(++c.ip),i(g(n)-b.load(h(o))),c.ip++;break;case a.CMP_ADDRESS_WITH_REG:n=e(b.load(++c.ip)),p=b.load(++c.ip),i(g(n)-b.load(p)),c.ip++;break;case a.CMP_NUMBER_WITH_REG:n=e(b.load(++c.ip)),r=b.load(++c.ip),i(g(n)-r),c.ip++;break;case a.JMP_REGADDRESS:n=d(b.load(++c.ip)),j(c.gpr[n]);break;case a.JMP_ADDRESS:r=b.load(++c.ip),j(r);break;case a.JC_REGADDRESS:n=d(b.load(++c.ip)),c.carry?j(c.gpr[n]):c.ip++;break;case a.JC_ADDRESS:r=b.load(++c.ip),c.carry?j(r):c.ip++;break;case a.JNC_REGADDRESS:n=d(b.load(++c.ip)),c.carry?c.ip++:j(c.gpr[n]);break;case a.JNC_ADDRESS:r=b.load(++c.ip),c.carry?c.ip++:j(r);break;case a.JZ_REGADDRESS:n=d(b.load(++c.ip)),c.zero?j(c.gpr[n]):c.ip++;break;case a.JZ_ADDRESS:r=b.load(++c.ip),c.zero?j(r):c.ip++;break;case a.JNZ_REGADDRESS:n=d(b.load(++c.ip)),c.zero?c.ip++:j(c.gpr[n]);break;case a.JNZ_ADDRESS:r=b.load(++c.ip),c.zero?c.ip++:j(r);break;case a.JA_REGADDRESS:n=d(b.load(++c.ip)),c.zero||c.carry?c.ip++:j(c.gpr[n]);break;case a.JA_ADDRESS:r=b.load(++c.ip),c.zero||c.carry?c.ip++:j(r);break;case a.JNA_REGADDRESS:n=d(b.load(++c.ip)),c.zero||c.carry?j(c.gpr[n]):c.ip++;break;case a.JNA_ADDRESS:r=b.load(++c.ip),c.zero||c.carry?j(r):c.ip++;break;case a.PUSH_REG:o=d(b.load(++c.ip)),k(c.gpr[o]),c.ip++;break;case a.PUSH_REGADDRESS:o=b.load(++c.ip),k(b.load(h(o))),c.ip++;break;case a.PUSH_ADDRESS:p=b.load(++c.ip),k(b.load(p)),c.ip++;break;case a.PUSH_NUMBER:r=b.load(++c.ip),k(r),c.ip++;break;case a.POP_REG:n=d(b.load(++c.ip)),c.gpr[n]=l(),c.ip++;break;case a.CALL_REGADDRESS:n=d(b.load(++c.ip)),k(c.ip+1),j(c.gpr[n]);break;case a.CALL_ADDRESS:r=b.load(++c.ip),k(c.ip+1),j(r);break;case a.RET:j(l());break;case a.MUL_REG:o=d(b.load(++c.ip)),c.gpr[0]=i(c.gpr[0]*c.gpr[o]),c.ip++;break;case a.MUL_REGADDRESS:o=b.load(++c.ip),c.gpr[0]=i(c.gpr[0]*b.load(h(o))),c.ip++;break;case a.MUL_ADDRESS:p=b.load(++c.ip),c.gpr[0]=i(c.gpr[0]*b.load(p)),c.ip++;break;case a.MUL_NUMBER:r=b.load(++c.ip),c.gpr[0]=i(c.gpr[0]*r),c.ip++;break;case a.DIV_REG:o=d(b.load(++c.ip)),c.gpr[0]=i(m(c.gpr[o])),c.ip++;break;case a.DIV_REGADDRESS:o=b.load(++c.ip),c.gpr[0]=i(m(b.load(h(o)))),c.ip++;break;case a.DIV_ADDRESS:p=b.load(++c.ip),c.gpr[0]=i(m(b.load(p))),c.ip++;break;case a.DIV_NUMBER:r=b.load(++c.ip),c.gpr[0]=i(m(r)),c.ip++;break;case a.AND_REG_WITH_REG:n=d(b.load(++c.ip)),o=d(b.load(++c.ip)),c.gpr[n]=i(c.gpr[n]&c.gpr[o]),c.ip++;break;case a.AND_REGADDRESS_WITH_REG:n=d(b.load(++c.ip)),o=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]&b.load(h(o))),c.ip++;break;case a.AND_ADDRESS_WITH_REG:n=d(b.load(++c.ip)),p=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]&b.load(p)),c.ip++;break;case a.AND_NUMBER_WITH_REG:n=d(b.load(++c.ip)),r=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]&r),c.ip++;break;case a.OR_REG_WITH_REG:n=d(b.load(++c.ip)),o=d(b.load(++c.ip)),c.gpr[n]=i(c.gpr[n]|c.gpr[o]),c.ip++;break;case a.OR_REGADDRESS_WITH_REG:n=d(b.load(++c.ip)),o=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]|b.load(h(o))),c.ip++;break;case a.OR_ADDRESS_WITH_REG:n=d(b.load(++c.ip)),p=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]|b.load(p)),c.ip++;break;case a.OR_NUMBER_WITH_REG:n=d(b.load(++c.ip)),r=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]|r),c.ip++;break;case a.XOR_REG_WITH_REG:n=d(b.load(++c.ip)),o=d(b.load(++c.ip)),c.gpr[n]=i(c.gpr[n]^c.gpr[o]),c.ip++;break;case a.XOR_REGADDRESS_WITH_REG:n=d(b.load(++c.ip)),o=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]^b.load(h(o))),c.ip++;break;case a.XOR_ADDRESS_WITH_REG:n=d(b.load(++c.ip)),p=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]^b.load(p)),c.ip++;break;case a.XOR_NUMBER_WITH_REG:n=d(b.load(++c.ip)),r=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]^r),c.ip++;break;case a.NOT_REG:n=d(b.load(++c.ip)),c.gpr[n]=i(~c.gpr[n]),c.ip++;break;case a.SHL_REG_WITH_REG:n=d(b.load(++c.ip)),o=d(b.load(++c.ip)),c.gpr[n]=i(c.gpr[n]<>>c.gpr[o]),c.ip++;break;case a.SHR_REGADDRESS_WITH_REG:n=d(b.load(++c.ip)),o=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]>>>b.load(h(o))),c.ip++;break;case a.SHR_ADDRESS_WITH_REG:n=d(b.load(++c.ip)),p=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]>>>b.load(p)),c.ip++;break;case a.SHR_NUMBER_WITH_REG:n=d(b.load(++c.ip)),r=b.load(++c.ip),c.gpr[n]=i(c.gpr[n]>>>r),c.ip++;break;default:throw"Invalid op code: "+s}return!0}catch(t){throw c.fault=!0,t}},reset:function(){var a=this;a.maxSP=231,a.minSP=0,a.gpr=[0,0,0,0],a.sp=a.maxSP,a.ip=0,a.zero=!1,a.carry=!1,a.fault=!1}};return c.reset(),c}]),app.service("memory",[function(){var a={data:Array(256),lastAccess:-1,load:function(a){var b=this;if(0>a||a>=b.data.length)throw"Memory access violation at "+a;return b.lastAccess=a,b.data[a]},store:function(a,b){var c=this;if(0>a||a>=c.data.length)throw"Memory access violation at "+a;c.lastAccess=a,c.data[a]=b},reset:function(){var a=this;a.lastAccess=-1;for(var b=0,c=a.data.length;c>b;b++)a.data[b]=0}};return a.reset(),a}]),app.service("opcodes",[function(){var a={NONE:0,MOV_REG_TO_REG:1,MOV_ADDRESS_TO_REG:2,MOV_REGADDRESS_TO_REG:3,MOV_REG_TO_ADDRESS:4,MOV_REG_TO_REGADDRESS:5,MOV_NUMBER_TO_REG:6,MOV_NUMBER_TO_ADDRESS:7,MOV_NUMBER_TO_REGADDRESS:8,ADD_REG_TO_REG:10,ADD_REGADDRESS_TO_REG:11,ADD_ADDRESS_TO_REG:12,ADD_NUMBER_TO_REG:13,SUB_REG_FROM_REG:14,SUB_REGADDRESS_FROM_REG:15,SUB_ADDRESS_FROM_REG:16,SUB_NUMBER_FROM_REG:17,INC_REG:18,DEC_REG:19,CMP_REG_WITH_REG:20,CMP_REGADDRESS_WITH_REG:21,CMP_ADDRESS_WITH_REG:22,CMP_NUMBER_WITH_REG:23,JMP_REGADDRESS:30,JMP_ADDRESS:31,JC_REGADDRESS:32,JC_ADDRESS:33,JNC_REGADDRESS:34,JNC_ADDRESS:35,JZ_REGADDRESS:36,JZ_ADDRESS:37,JNZ_REGADDRESS:38,JNZ_ADDRESS:39,JA_REGADDRESS:40,JA_ADDRESS:41,JNA_REGADDRESS:42,JNA_ADDRESS:43,PUSH_REG:50,PUSH_REGADDRESS:51,PUSH_ADDRESS:52,PUSH_NUMBER:53,POP_REG:54,CALL_REGADDRESS:55,CALL_ADDRESS:56,RET:57,MUL_REG:60,MUL_REGADDRESS:61,MUL_ADDRESS:62,MUL_NUMBER:63,DIV_REG:64,DIV_REGADDRESS:65,DIV_ADDRESS:66,DIV_NUMBER:67,AND_REG_WITH_REG:70,AND_REGADDRESS_WITH_REG:71,AND_ADDRESS_WITH_REG:72,AND_NUMBER_WITH_REG:73,OR_REG_WITH_REG:74,OR_REGADDRESS_WITH_REG:75,OR_ADDRESS_WITH_REG:76,OR_NUMBER_WITH_REG:77,XOR_REG_WITH_REG:78,XOR_REGADDRESS_WITH_REG:79,XOR_ADDRESS_WITH_REG:80,XOR_NUMBER_WITH_REG:81,NOT_REG:82,SHL_REG_WITH_REG:90,SHL_REGADDRESS_WITH_REG:91,SHL_ADDRESS_WITH_REG:92,SHL_NUMBER_WITH_REG:93,SHR_REG_WITH_REG:94,SHR_REGADDRESS_WITH_REG:95,SHR_ADDRESS_WITH_REG:96,SHR_NUMBER_WITH_REG:97};return a}]),app.controller("Ctrl",["$document","$scope","$timeout","cpu","memory","assembler",function(a,b,c,d,e,f){b.memory=e,b.cpu=d,b.error="",b.isRunning=!1,b.displayHex=!0,b.displayInstr=!0,b.displayA=!1,b.displayB=!1,b.displayC=!1,b.displayD=!1,b.speeds=[{speed:1,desc:"1 HZ"},{speed:4,desc:"4 HZ"},{speed:8,desc:"8 HZ"},{speed:16,desc:"16 HZ"}],b.speed=4,b.outputStartIndex=232,b.code='; Simple example\n; Writes Hello World to the output\n\n JMP start\nhello: DB "Hello World!" ; Variable\n DB 0 ; String terminator\n\nstart:\n MOV C, hello ; Point to var \n MOV D, 232 ; Point to output\n CALL print\n HLT ; Stop execution\n\nprint: ; print(C:*from, D:*to)\n PUSH A\n PUSH B\n MOV B, 0\n.loop:\n MOV A, [C] ; Get char from var\n MOV [D], A ; Write to output\n INC C\n INC D \n CMP B, [C] ; Check if end\n JNZ .loop ; jump if not\n\n POP B\n POP A\n RET',b.reset=function(){d.reset(),e.reset(),b.error="",b.selectedLine=-1},b.executeStep=function(){b.checkPrgrmLoaded()||b.assemble();try{var a=d.step();return d.ip in b.mapping&&(b.selectedLine=b.mapping[d.ip]),a}catch(c){return b.error=c,!1}};var g;b.run=function(){b.checkPrgrmLoaded()||b.assemble(),b.isRunning=!0,g=c(function(){b.executeStep()===!0?b.run():b.isRunning=!1},1e3/b.speed)},b.stop=function(){c.cancel(g),b.isRunning=!1},b.checkPrgrmLoaded=function(){for(var a=0,b=e.data.length;b>a;a++)if(0!==e.data[a])return!0;return!1},b.getChar=function(a){var b=String.fromCharCode(a);return""===b.trim()?" ":b},b.assemble=function(){try{b.reset();var a=f.go(b.code);b.mapping=a.mapping;var c=a.code;if(b.labels=a.labels,c.length>e.data.length)throw"Binary code does not fit into the memory. Max "+e.data.length+" bytes are allowed";for(var d=0,g=c.length;g>d;d++)e.data[d]=c[d]}catch(h){void 0!==h.line?(b.error=h.line+" | "+h.error,b.selectedLine=h.line):b.error=h.error}},b.jumpToLine=function(c){a[0].getElementById("sourceCode").scrollIntoView(),b.selectedLine=b.mapping[c]},b.isInstruction=function(a){return void 0!==b.mapping&&void 0!==b.mapping[a]&&b.displayInstr},b.getMemoryCellCss=function(a){return a>=b.outputStartIndex?"output-bg":b.isInstruction(a)?"instr-bg":a>d.sp&&a<=d.maxSP?"stack-bg":""},b.getMemoryInnerCellCss=function(a){return a===d.ip?"marker marker-ip":a===d.sp?"marker marker-sp":a===d.gpr[0]&&b.displayA?"marker marker-a":a===d.gpr[1]&&b.displayB?"marker marker-b":a===d.gpr[2]&&b.displayC?"marker marker-c":a===d.gpr[3]&&b.displayD?"marker marker-d":""}}]),app.filter("flag",function(){return function(a){return a.toString().toUpperCase()}}),app.filter("number",function(){return function(a,b){if(b){var c=a.toString(16).toUpperCase();return 1==c.length?"0"+c:c}return a.toString(10)}}),app.directive("selectLine",[function(){return{restrict:"A",link:function(a,b,c,d){a.$watch("selectedLine",function(){if(a.selectedLine>=0){for(var c=b[0].value.split("\n"),d=0,e=0;e= self.gpr.length) {
13 | throw "Invalid register: " + reg;
14 | } else {
15 | return reg;
16 | }
17 | };
18 |
19 | var checkGPR_SP = function(reg) {
20 | if (reg < 0 || reg >= 1 + self.gpr.length) {
21 | throw "Invalid register: " + reg;
22 | } else {
23 | return reg;
24 | }
25 | };
26 |
27 | var setGPR_SP = function(reg,value)
28 | {
29 | if(reg >= 0 && reg < self.gpr.length) {
30 | self.gpr[reg] = value;
31 | } else if(reg == self.gpr.length) {
32 | self.sp = value;
33 |
34 | // Not likely to happen, since we always get here after checkOpertion().
35 | if (self.sp < self.minSP) {
36 | throw "Stack overflow";
37 | } else if (self.sp > self.maxSP) {
38 | throw "Stack underflow";
39 | }
40 | } else {
41 | throw "Invalid register: " + reg;
42 | }
43 | };
44 |
45 | var getGPR_SP = function(reg)
46 | {
47 | if(reg >= 0 && reg < self.gpr.length) {
48 | return self.gpr[reg];
49 | } else if(reg == self.gpr.length) {
50 | return self.sp;
51 | } else {
52 | throw "Invalid register: " + reg;
53 | }
54 | };
55 |
56 | var indirectRegisterAddress = function(value) {
57 | var reg = value % 8;
58 |
59 | var base;
60 | if (reg < self.gpr.length) {
61 | base = self.gpr[reg];
62 | } else {
63 | base = self.sp;
64 | }
65 |
66 | var offset = Math.floor(value / 8);
67 | if ( offset > 15 ) {
68 | offset = offset - 32;
69 | }
70 |
71 | return base+offset;
72 | };
73 |
74 | var checkOperation = function(value) {
75 | self.zero = false;
76 | self.carry = false;
77 |
78 | if (value >= 256) {
79 | self.carry = true;
80 | value = value % 256;
81 | } else if (value === 0) {
82 | self.zero = true;
83 | } else if (value < 0) {
84 | self.carry = true;
85 | value = 256 - (-value) % 256;
86 | }
87 |
88 | return value;
89 | };
90 |
91 | var jump = function(newIP) {
92 | if (newIP < 0 || newIP >= memory.data.length) {
93 | throw "IP outside memory";
94 | } else {
95 | self.ip = newIP;
96 | }
97 | };
98 |
99 | var push = function(value) {
100 | memory.store(self.sp--, value);
101 | if (self.sp < self.minSP) {
102 | throw "Stack overflow";
103 | }
104 | };
105 |
106 | var pop = function() {
107 | var value = memory.load(++self.sp);
108 | if (self.sp > self.maxSP) {
109 | throw "Stack underflow";
110 | }
111 |
112 | return value;
113 | };
114 |
115 | var division = function(divisor) {
116 | if (divisor === 0) {
117 | throw "Division by 0";
118 | }
119 |
120 | return Math.floor(self.gpr[0] / divisor);
121 | };
122 |
123 | if (self.ip < 0 || self.ip >= memory.data.length) {
124 | throw "Instruction pointer is outside of memory";
125 | }
126 |
127 | var regTo, regFrom, memFrom, memTo, number;
128 | var instr = memory.load(self.ip);
129 | switch(instr) {
130 | case opcodes.NONE:
131 | return false; // Abort step
132 | case opcodes.MOV_REG_TO_REG:
133 | regTo = checkGPR_SP(memory.load(++self.ip));
134 | regFrom = checkGPR_SP(memory.load(++self.ip));
135 | setGPR_SP(regTo,getGPR_SP(regFrom));
136 | self.ip++;
137 | break;
138 | case opcodes.MOV_ADDRESS_TO_REG:
139 | regTo = checkGPR_SP(memory.load(++self.ip));
140 | memFrom = memory.load(++self.ip);
141 | setGPR_SP(regTo,memory.load(memFrom));
142 | self.ip++;
143 | break;
144 | case opcodes.MOV_REGADDRESS_TO_REG:
145 | regTo = checkGPR_SP(memory.load(++self.ip));
146 | regFrom = memory.load(++self.ip);
147 | setGPR_SP(regTo,memory.load(indirectRegisterAddress(regFrom)));
148 | self.ip++;
149 | break;
150 | case opcodes.MOV_REG_TO_ADDRESS:
151 | memTo = memory.load(++self.ip);
152 | regFrom = checkGPR_SP(memory.load(++self.ip));
153 | memory.store(memTo, getGPR_SP(regFrom));
154 | self.ip++;
155 | break;
156 | case opcodes.MOV_REG_TO_REGADDRESS:
157 | regTo = memory.load(++self.ip);
158 | regFrom = checkGPR_SP(memory.load(++self.ip));
159 | memory.store(indirectRegisterAddress(regTo), getGPR_SP(regFrom));
160 | self.ip++;
161 | break;
162 | case opcodes.MOV_NUMBER_TO_REG:
163 | regTo = checkGPR_SP(memory.load(++self.ip));
164 | number = memory.load(++self.ip);
165 | setGPR_SP(regTo,number);
166 | self.ip++;
167 | break;
168 | case opcodes.MOV_NUMBER_TO_ADDRESS:
169 | memTo = memory.load(++self.ip);
170 | number = memory.load(++self.ip);
171 | memory.store(memTo, number);
172 | self.ip++;
173 | break;
174 | case opcodes.MOV_NUMBER_TO_REGADDRESS:
175 | regTo = memory.load(++self.ip);
176 | number = memory.load(++self.ip);
177 | memory.store(indirectRegisterAddress(regTo), number);
178 | self.ip++;
179 | break;
180 | case opcodes.ADD_REG_TO_REG:
181 | regTo = checkGPR_SP(memory.load(++self.ip));
182 | regFrom = checkGPR_SP(memory.load(++self.ip));
183 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + getGPR_SP(regFrom)));
184 | self.ip++;
185 | break;
186 | case opcodes.ADD_REGADDRESS_TO_REG:
187 | regTo = checkGPR_SP(memory.load(++self.ip));
188 | regFrom = memory.load(++self.ip);
189 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + memory.load(indirectRegisterAddress(regFrom))));
190 | self.ip++;
191 | break;
192 | case opcodes.ADD_ADDRESS_TO_REG:
193 | regTo = checkGPR_SP(memory.load(++self.ip));
194 | memFrom = memory.load(++self.ip);
195 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + memory.load(memFrom)));
196 | self.ip++;
197 | break;
198 | case opcodes.ADD_NUMBER_TO_REG:
199 | regTo = checkGPR_SP(memory.load(++self.ip));
200 | number = memory.load(++self.ip);
201 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + number));
202 | self.ip++;
203 | break;
204 | case opcodes.SUB_REG_FROM_REG:
205 | regTo = checkGPR_SP(memory.load(++self.ip));
206 | regFrom = checkGPR_SP(memory.load(++self.ip));
207 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - self.gpr[regFrom]));
208 | self.ip++;
209 | break;
210 | case opcodes.SUB_REGADDRESS_FROM_REG:
211 | regTo = checkGPR_SP(memory.load(++self.ip));
212 | regFrom = memory.load(++self.ip);
213 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - memory.load(indirectRegisterAddress(regFrom))));
214 | self.ip++;
215 | break;
216 | case opcodes.SUB_ADDRESS_FROM_REG:
217 | regTo = checkGPR_SP(memory.load(++self.ip));
218 | memFrom = memory.load(++self.ip);
219 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - memory.load(memFrom)));
220 | self.ip++;
221 | break;
222 | case opcodes.SUB_NUMBER_FROM_REG:
223 | regTo = checkGPR_SP(memory.load(++self.ip));
224 | number = memory.load(++self.ip);
225 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - number));
226 | self.ip++;
227 | break;
228 | case opcodes.INC_REG:
229 | regTo = checkGPR_SP(memory.load(++self.ip));
230 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + 1));
231 | self.ip++;
232 | break;
233 | case opcodes.DEC_REG:
234 | regTo = checkGPR_SP(memory.load(++self.ip));
235 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - 1));
236 | self.ip++;
237 | break;
238 | case opcodes.CMP_REG_WITH_REG:
239 | regTo = checkGPR_SP(memory.load(++self.ip));
240 | regFrom = checkGPR_SP(memory.load(++self.ip));
241 | checkOperation(getGPR_SP(regTo) - getGPR_SP(regFrom));
242 | self.ip++;
243 | break;
244 | case opcodes.CMP_REGADDRESS_WITH_REG:
245 | regTo = checkGPR_SP(memory.load(++self.ip));
246 | regFrom = memory.load(++self.ip);
247 | checkOperation(getGPR_SP(regTo) - memory.load(indirectRegisterAddress(regFrom)));
248 | self.ip++;
249 | break;
250 | case opcodes.CMP_ADDRESS_WITH_REG:
251 | regTo = checkGPR_SP(memory.load(++self.ip));
252 | memFrom = memory.load(++self.ip);
253 | checkOperation(getGPR_SP(regTo) - memory.load(memFrom));
254 | self.ip++;
255 | break;
256 | case opcodes.CMP_NUMBER_WITH_REG:
257 | regTo = checkGPR_SP(memory.load(++self.ip));
258 | number = memory.load(++self.ip);
259 | checkOperation(getGPR_SP(regTo) - number);
260 | self.ip++;
261 | break;
262 | case opcodes.JMP_REGADDRESS:
263 | regTo = checkGPR(memory.load(++self.ip));
264 | jump(self.gpr[regTo]);
265 | break;
266 | case opcodes.JMP_ADDRESS:
267 | number = memory.load(++self.ip);
268 | jump(number);
269 | break;
270 | case opcodes.JC_REGADDRESS:
271 | regTo = checkGPR(memory.load(++self.ip));
272 | if (self.carry) {
273 | jump(self.gpr[regTo]);
274 | } else {
275 | self.ip++;
276 | }
277 | break;
278 | case opcodes.JC_ADDRESS:
279 | number = memory.load(++self.ip);
280 | if (self.carry) {
281 | jump(number);
282 | } else {
283 | self.ip++;
284 | }
285 | break;
286 | case opcodes.JNC_REGADDRESS:
287 | regTo = checkGPR(memory.load(++self.ip));
288 | if (!self.carry) {
289 | jump(self.gpr[regTo]);
290 | } else {
291 | self.ip++;
292 | }
293 | break;
294 | case opcodes.JNC_ADDRESS:
295 | number = memory.load(++self.ip);
296 | if (!self.carry) {
297 | jump(number);
298 | } else {
299 | self.ip++;
300 | }
301 | break;
302 | case opcodes.JZ_REGADDRESS:
303 | regTo = checkGPR(memory.load(++self.ip));
304 | if (self.zero) {
305 | jump(self.gpr[regTo]);
306 | } else {
307 | self.ip++;
308 | }
309 | break;
310 | case opcodes.JZ_ADDRESS:
311 | number = memory.load(++self.ip);
312 | if (self.zero) {
313 | jump(number);
314 | } else {
315 | self.ip++;
316 | }
317 | break;
318 | case opcodes.JNZ_REGADDRESS:
319 | regTo = checkGPR(memory.load(++self.ip));
320 | if (!self.zero) {
321 | jump(self.gpr[regTo]);
322 | } else {
323 | self.ip++;
324 | }
325 | break;
326 | case opcodes.JNZ_ADDRESS:
327 | number = memory.load(++self.ip);
328 | if (!self.zero) {
329 | jump(number);
330 | } else {
331 | self.ip++;
332 | }
333 | break;
334 | case opcodes.JA_REGADDRESS:
335 | regTo = checkGPR(memory.load(++self.ip));
336 | if (!self.zero && !self.carry) {
337 | jump(self.gpr[regTo]);
338 | } else {
339 | self.ip++;
340 | }
341 | break;
342 | case opcodes.JA_ADDRESS:
343 | number = memory.load(++self.ip);
344 | if (!self.zero && !self.carry) {
345 | jump(number);
346 | } else {
347 | self.ip++;
348 | }
349 | break;
350 | case opcodes.JNA_REGADDRESS: // JNA REG
351 | regTo = checkGPR(memory.load(++self.ip));
352 | if (self.zero || self.carry) {
353 | jump(self.gpr[regTo]);
354 | } else {
355 | self.ip++;
356 | }
357 | break;
358 | case opcodes.JNA_ADDRESS:
359 | number = memory.load(++self.ip);
360 | if (self.zero || self.carry) {
361 | jump(number);
362 | } else {
363 | self.ip++;
364 | }
365 | break;
366 | case opcodes.PUSH_REG:
367 | regFrom = checkGPR(memory.load(++self.ip));
368 | push(self.gpr[regFrom]);
369 | self.ip++;
370 | break;
371 | case opcodes.PUSH_REGADDRESS:
372 | regFrom = memory.load(++self.ip);
373 | push(memory.load(indirectRegisterAddress(regFrom)));
374 | self.ip++;
375 | break;
376 | case opcodes.PUSH_ADDRESS:
377 | memFrom = memory.load(++self.ip);
378 | push(memory.load(memFrom));
379 | self.ip++;
380 | break;
381 | case opcodes.PUSH_NUMBER:
382 | number = memory.load(++self.ip);
383 | push(number);
384 | self.ip++;
385 | break;
386 | case opcodes.POP_REG:
387 | regTo = checkGPR(memory.load(++self.ip));
388 | self.gpr[regTo] = pop();
389 | self.ip++;
390 | break;
391 | case opcodes.CALL_REGADDRESS:
392 | regTo = checkGPR(memory.load(++self.ip));
393 | push(self.ip+1);
394 | jump(self.gpr[regTo]);
395 | break;
396 | case opcodes.CALL_ADDRESS:
397 | number = memory.load(++self.ip);
398 | push(self.ip+1);
399 | jump(number);
400 | break;
401 | case opcodes.RET:
402 | jump(pop());
403 | break;
404 | case opcodes.MUL_REG: // A = A * REG
405 | regFrom = checkGPR(memory.load(++self.ip));
406 | self.gpr[0] = checkOperation(self.gpr[0] * self.gpr[regFrom]);
407 | self.ip++;
408 | break;
409 | case opcodes.MUL_REGADDRESS: // A = A * [REG]
410 | regFrom = memory.load(++self.ip);
411 | self.gpr[0] = checkOperation(self.gpr[0] * memory.load(indirectRegisterAddress(regFrom)));
412 | self.ip++;
413 | break;
414 | case opcodes.MUL_ADDRESS: // A = A * [NUMBER]
415 | memFrom = memory.load(++self.ip);
416 | self.gpr[0] = checkOperation(self.gpr[0] * memory.load(memFrom));
417 | self.ip++;
418 | break;
419 | case opcodes.MUL_NUMBER: // A = A * NUMBER
420 | number = memory.load(++self.ip);
421 | self.gpr[0] = checkOperation(self.gpr[0] * number);
422 | self.ip++;
423 | break;
424 | case opcodes.DIV_REG: // A = A / REG
425 | regFrom = checkGPR(memory.load(++self.ip));
426 | self.gpr[0] = checkOperation(division(self.gpr[regFrom]));
427 | self.ip++;
428 | break;
429 | case opcodes.DIV_REGADDRESS: // A = A / [REG]
430 | regFrom = memory.load(++self.ip);
431 | self.gpr[0] = checkOperation(division(memory.load(indirectRegisterAddress(regFrom))));
432 | self.ip++;
433 | break;
434 | case opcodes.DIV_ADDRESS: // A = A / [NUMBER]
435 | memFrom = memory.load(++self.ip);
436 | self.gpr[0] = checkOperation(division(memory.load(memFrom)));
437 | self.ip++;
438 | break;
439 | case opcodes.DIV_NUMBER: // A = A / NUMBER
440 | number = memory.load(++self.ip);
441 | self.gpr[0] = checkOperation(division(number));
442 | self.ip++;
443 | break;
444 | case opcodes.AND_REG_WITH_REG:
445 | regTo = checkGPR(memory.load(++self.ip));
446 | regFrom = checkGPR(memory.load(++self.ip));
447 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & self.gpr[regFrom]);
448 | self.ip++;
449 | break;
450 | case opcodes.AND_REGADDRESS_WITH_REG:
451 | regTo = checkGPR(memory.load(++self.ip));
452 | regFrom = memory.load(++self.ip);
453 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & memory.load(indirectRegisterAddress(regFrom)));
454 | self.ip++;
455 | break;
456 | case opcodes.AND_ADDRESS_WITH_REG:
457 | regTo = checkGPR(memory.load(++self.ip));
458 | memFrom = memory.load(++self.ip);
459 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & memory.load(memFrom));
460 | self.ip++;
461 | break;
462 | case opcodes.AND_NUMBER_WITH_REG:
463 | regTo = checkGPR(memory.load(++self.ip));
464 | number = memory.load(++self.ip);
465 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & number);
466 | self.ip++;
467 | break;
468 | case opcodes.OR_REG_WITH_REG:
469 | regTo = checkGPR(memory.load(++self.ip));
470 | regFrom = checkGPR(memory.load(++self.ip));
471 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | self.gpr[regFrom]);
472 | self.ip++;
473 | break;
474 | case opcodes.OR_REGADDRESS_WITH_REG:
475 | regTo = checkGPR(memory.load(++self.ip));
476 | regFrom = memory.load(++self.ip);
477 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | memory.load(indirectRegisterAddress(regFrom)));
478 | self.ip++;
479 | break;
480 | case opcodes.OR_ADDRESS_WITH_REG:
481 | regTo = checkGPR(memory.load(++self.ip));
482 | memFrom = memory.load(++self.ip);
483 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | memory.load(memFrom));
484 | self.ip++;
485 | break;
486 | case opcodes.OR_NUMBER_WITH_REG:
487 | regTo = checkGPR(memory.load(++self.ip));
488 | number = memory.load(++self.ip);
489 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | number);
490 | self.ip++;
491 | break;
492 | case opcodes.XOR_REG_WITH_REG:
493 | regTo = checkGPR(memory.load(++self.ip));
494 | regFrom = checkGPR(memory.load(++self.ip));
495 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ self.gpr[regFrom]);
496 | self.ip++;
497 | break;
498 | case opcodes.XOR_REGADDRESS_WITH_REG:
499 | regTo = checkGPR(memory.load(++self.ip));
500 | regFrom = memory.load(++self.ip);
501 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ memory.load(indirectRegisterAddress(regFrom)));
502 | self.ip++;
503 | break;
504 | case opcodes.XOR_ADDRESS_WITH_REG:
505 | regTo = checkGPR(memory.load(++self.ip));
506 | memFrom = memory.load(++self.ip);
507 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ memory.load(memFrom));
508 | self.ip++;
509 | break;
510 | case opcodes.XOR_NUMBER_WITH_REG:
511 | regTo = checkGPR(memory.load(++self.ip));
512 | number = memory.load(++self.ip);
513 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ number);
514 | self.ip++;
515 | break;
516 | case opcodes.NOT_REG:
517 | regTo = checkGPR(memory.load(++self.ip));
518 | self.gpr[regTo] = checkOperation(~self.gpr[regTo]);
519 | self.ip++;
520 | break;
521 | case opcodes.SHL_REG_WITH_REG:
522 | regTo = checkGPR(memory.load(++self.ip));
523 | regFrom = checkGPR(memory.load(++self.ip));
524 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << self.gpr[regFrom]);
525 | self.ip++;
526 | break;
527 | case opcodes.SHL_REGADDRESS_WITH_REG:
528 | regTo = checkGPR(memory.load(++self.ip));
529 | regFrom = memory.load(++self.ip);
530 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << memory.load(indirectRegisterAddress(regFrom)));
531 | self.ip++;
532 | break;
533 | case opcodes.SHL_ADDRESS_WITH_REG:
534 | regTo = checkGPR(memory.load(++self.ip));
535 | memFrom = memory.load(++self.ip);
536 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << memory.load(memFrom));
537 | self.ip++;
538 | break;
539 | case opcodes.SHL_NUMBER_WITH_REG:
540 | regTo = checkGPR(memory.load(++self.ip));
541 | number = memory.load(++self.ip);
542 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << number);
543 | self.ip++;
544 | break;
545 | case opcodes.SHR_REG_WITH_REG:
546 | regTo = checkGPR(memory.load(++self.ip));
547 | regFrom = checkGPR(memory.load(++self.ip));
548 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> self.gpr[regFrom]);
549 | self.ip++;
550 | break;
551 | case opcodes.SHR_REGADDRESS_WITH_REG:
552 | regTo = checkGPR(memory.load(++self.ip));
553 | regFrom = memory.load(++self.ip);
554 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> memory.load(indirectRegisterAddress(regFrom)));
555 | self.ip++;
556 | break;
557 | case opcodes.SHR_ADDRESS_WITH_REG:
558 | regTo = checkGPR(memory.load(++self.ip));
559 | memFrom = memory.load(++self.ip);
560 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> memory.load(memFrom));
561 | self.ip++;
562 | break;
563 | case opcodes.SHR_NUMBER_WITH_REG:
564 | regTo = checkGPR(memory.load(++self.ip));
565 | number = memory.load(++self.ip);
566 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> number);
567 | self.ip++;
568 | break;
569 | default:
570 | throw "Invalid op code: " + instr;
571 | }
572 |
573 | return true;
574 | } catch(e) {
575 | self.fault = true;
576 | throw e;
577 | }
578 | },
579 | reset: function() {
580 | var self = this;
581 | self.maxSP = 231;
582 | self.minSP = 0;
583 |
584 | self.gpr = [0, 0, 0, 0];
585 | self.sp = self.maxSP;
586 | self.ip = 0;
587 | self.zero = false;
588 | self.carry = false;
589 | self.fault = false;
590 | }
591 | };
592 |
593 | cpu.reset();
594 | return cpu;
595 | }]);
596 |
--------------------------------------------------------------------------------
/src/assembler/asm.js:
--------------------------------------------------------------------------------
1 | app.service('assembler', ['opcodes', function (opcodes) {
2 | return {
3 | go: function (input) {
4 | // Use https://www.debuggex.com/
5 | // Matches: "label: INSTRUCTION (["')OPERAND1(]"'), (["')OPERAND2(]"')
6 | // GROUPS: 1 2 3 7
7 | var regex = /^[\t ]*(?:([.A-Za-z]\w*)[:])?(?:[\t ]*([A-Za-z]{2,4})(?:[\t ]+(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*)(?:[\t ]*[,][\t ]*(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*))?)?)?/;
8 |
9 | // Regex group indexes for operands
10 | var op1_group = 3;
11 | var op2_group = 7;
12 |
13 | // MATCHES: "(+|-)INTEGER"
14 | var regexNum = /^[-+]?[0-9]+$/;
15 | // MATCHES: "(.L)abel"
16 | var regexLabel = /^[.A-Za-z]\w*$/;
17 | // Contains the program code & data generated by the assembler
18 | var code = [];
19 | // Contains the mapping from instructions to assembler line
20 | var mapping = {};
21 | // Hash map of label used to replace the labels after the assembler generated the code
22 | var labels = {};
23 | // Hash of uppercase labels used to detect duplicates
24 | var normalizedLabels = {};
25 |
26 | // Split text into code lines
27 | var lines = input.split('\n');
28 |
29 | // Allowed formats: 200, 200d, 0xA4, 0o48, 101b
30 | var parseNumber = function (input) {
31 | if (input.slice(0, 2) === "0x") {
32 | return parseInt(input.slice(2), 16);
33 | } else if (input.slice(0, 2) === "0o") {
34 | return parseInt(input.slice(2), 8);
35 | } else if (input.slice(input.length - 1) === "b") {
36 | return parseInt(input.slice(0, input.length - 1), 2);
37 | } else if (input.slice(input.length - 1) === "d") {
38 | return parseInt(input.slice(0, input.length - 1), 10);
39 | } else if (regexNum.exec(input)) {
40 | return parseInt(input, 10);
41 | } else {
42 | throw "Invalid number format";
43 | }
44 | };
45 |
46 | // Allowed registers: A, B, C, D, SP
47 | var parseRegister = function (input) {
48 | input = input.toUpperCase();
49 |
50 | if (input === 'A') {
51 | return 0;
52 | } else if (input === 'B') {
53 | return 1;
54 | } else if (input === 'C') {
55 | return 2;
56 | } else if (input === 'D') {
57 | return 3;
58 | } else if (input === 'SP') {
59 | return 4;
60 | } else {
61 | return undefined;
62 | }
63 | };
64 |
65 | var parseOffsetAddressing = function (input) {
66 | input = input.toUpperCase();
67 | var m = 0;
68 | var base = 0;
69 |
70 | if (input[0] === 'A') {
71 | base = 0;
72 | } else if (input[0] === 'B') {
73 | base = 1;
74 | } else if (input[0] === 'C') {
75 | base = 2;
76 | } else if (input[0] === 'D') {
77 | base = 3;
78 | } else if (input.slice(0, 2) === "SP") {
79 | base = 4;
80 | } else {
81 | return undefined;
82 | }
83 | var offset_start = 1;
84 | if (base === 4) {
85 | offset_start = 2;
86 | }
87 |
88 | if (input[offset_start] === '-') {
89 | m = -1;
90 | } else if (input[offset_start] === '+') {
91 | m = 1;
92 | } else {
93 | return undefined;
94 | }
95 |
96 | var offset = m * parseInt(input.slice(offset_start + 1), 10);
97 |
98 | if (offset < -16 || offset > 15)
99 | throw "offset must be a value between -16...+15";
100 |
101 | if (offset < 0) {
102 | offset = 32 + offset; // two's complement representation in 5-bit
103 | }
104 |
105 | return offset * 8 + base; // shift offset 3 bits right and add code for register
106 | };
107 |
108 | // Allowed: Register, Label or Number; SP+/-Number is allowed for 'regaddress' type
109 | var parseRegOrNumber = function (input, typeReg, typeNumber) {
110 | var register = parseRegister(input);
111 |
112 | if (register !== undefined) {
113 | return {type: typeReg, value: register};
114 | } else {
115 | var label = parseLabel(input);
116 | if (label !== undefined) {
117 | return {type: typeNumber, value: label};
118 | } else {
119 | if (typeReg === "regaddress") {
120 |
121 | register = parseOffsetAddressing(input);
122 |
123 | if (register !== undefined) {
124 | return {type: typeReg, value: register};
125 | }
126 | }
127 |
128 | var value = parseNumber(input);
129 |
130 | if (isNaN(value)) {
131 | throw "Not a " + typeNumber + ": " + value;
132 | }
133 | else if (value < 0 || value > 255)
134 | throw typeNumber + " must have a value between 0-255";
135 |
136 | return {type: typeNumber, value: value};
137 | }
138 | }
139 | };
140 |
141 | var parseLabel = function (input) {
142 | return regexLabel.exec(input) ? input : undefined;
143 | };
144 |
145 | var getValue = function (input) {
146 | switch (input.slice(0, 1)) {
147 | case '[': // [number] or [register]
148 | var address = input.slice(1, input.length - 1);
149 | return parseRegOrNumber(address, "regaddress", "address");
150 | case '"': // "String"
151 | var text = input.slice(1, input.length - 1);
152 | var chars = [];
153 |
154 | for (var i = 0, l = text.length; i < l; i++) {
155 | chars.push(text.charCodeAt(i));
156 | }
157 |
158 | return {type: "numbers", value: chars};
159 | case "'": // 'C'
160 | var character = input.slice(1, input.length - 1);
161 | if (character.length > 1)
162 | throw "Only one character is allowed. Use String instead";
163 |
164 | return {type: "number", value: character.charCodeAt(0)};
165 | default: // REGISTER, NUMBER or LABEL
166 | return parseRegOrNumber(input, "register", "number");
167 | }
168 | };
169 |
170 | var addLabel = function (label) {
171 | var upperLabel = label.toUpperCase();
172 | if (upperLabel in normalizedLabels)
173 | throw "Duplicate label: " + label;
174 |
175 | if (upperLabel === "A" || upperLabel === "B" || upperLabel === "C" || upperLabel === "D")
176 | throw "Label contains keyword: " + upperLabel;
177 |
178 | labels[label] = code.length;
179 | };
180 |
181 | var checkNoExtraArg = function (instr, arg) {
182 | if (arg !== undefined) {
183 | throw instr + ": too many arguments";
184 | }
185 | };
186 |
187 | for (var i = 0, l = lines.length; i < l; i++) {
188 | try {
189 | var match = regex.exec(lines[i]);
190 | if (match[1] !== undefined || match[2] !== undefined) {
191 | if (match[1] !== undefined) {
192 | addLabel(match[1]);
193 | }
194 |
195 | if (match[2] !== undefined) {
196 | var instr = match[2].toUpperCase();
197 | var p1, p2, opCode;
198 |
199 | // Add mapping instr pos to line number
200 | // Don't do it for DB as this is not a real instruction
201 | if (instr !== 'DB') {
202 | mapping[code.length] = i;
203 | }
204 |
205 | switch (instr) {
206 | case 'DB':
207 | p1 = getValue(match[op1_group]);
208 |
209 | if (p1.type === "number")
210 | code.push(p1.value);
211 | else if (p1.type === "numbers")
212 | for (var j = 0, k = p1.value.length; j < k; j++) {
213 | code.push(p1.value[j]);
214 | }
215 | else
216 | throw "DB does not support this operand";
217 |
218 | break;
219 | case 'HLT':
220 | checkNoExtraArg('HLT', match[op1_group]);
221 | opCode = opcodes.NONE;
222 | code.push(opCode);
223 | break;
224 |
225 | case 'MOV':
226 | p1 = getValue(match[op1_group]);
227 | p2 = getValue(match[op2_group]);
228 |
229 | if (p1.type === "register" && p2.type === "register")
230 | opCode = opcodes.MOV_REG_TO_REG;
231 | else if (p1.type === "register" && p2.type === "address")
232 | opCode = opcodes.MOV_ADDRESS_TO_REG;
233 | else if (p1.type === "register" && p2.type === "regaddress")
234 | opCode = opcodes.MOV_REGADDRESS_TO_REG;
235 | else if (p1.type === "address" && p2.type === "register")
236 | opCode = opcodes.MOV_REG_TO_ADDRESS;
237 | else if (p1.type === "regaddress" && p2.type === "register")
238 | opCode = opcodes.MOV_REG_TO_REGADDRESS;
239 | else if (p1.type === "register" && p2.type === "number")
240 | opCode = opcodes.MOV_NUMBER_TO_REG;
241 | else if (p1.type === "address" && p2.type === "number")
242 | opCode = opcodes.MOV_NUMBER_TO_ADDRESS;
243 | else if (p1.type === "regaddress" && p2.type === "number")
244 | opCode = opcodes.MOV_NUMBER_TO_REGADDRESS;
245 | else
246 | throw "MOV does not support this operands";
247 |
248 | code.push(opCode, p1.value, p2.value);
249 | break;
250 | case 'ADD':
251 | p1 = getValue(match[op1_group]);
252 | p2 = getValue(match[op2_group]);
253 |
254 | if (p1.type === "register" && p2.type === "register")
255 | opCode = opcodes.ADD_REG_TO_REG;
256 | else if (p1.type === "register" && p2.type === "regaddress")
257 | opCode = opcodes.ADD_REGADDRESS_TO_REG;
258 | else if (p1.type === "register" && p2.type === "address")
259 | opCode = opcodes.ADD_ADDRESS_TO_REG;
260 | else if (p1.type === "register" && p2.type === "number")
261 | opCode = opcodes.ADD_NUMBER_TO_REG;
262 | else
263 | throw "ADD does not support this operands";
264 |
265 | code.push(opCode, p1.value, p2.value);
266 | break;
267 | case 'SUB':
268 | p1 = getValue(match[op1_group]);
269 | p2 = getValue(match[op2_group]);
270 |
271 | if (p1.type === "register" && p2.type === "register")
272 | opCode = opcodes.SUB_REG_FROM_REG;
273 | else if (p1.type === "register" && p2.type === "regaddress")
274 | opCode = opcodes.SUB_REGADDRESS_FROM_REG;
275 | else if (p1.type === "register" && p2.type === "address")
276 | opCode = opcodes.SUB_ADDRESS_FROM_REG;
277 | else if (p1.type === "register" && p2.type === "number")
278 | opCode = opcodes.SUB_NUMBER_FROM_REG;
279 | else
280 | throw "SUB does not support this operands";
281 |
282 | code.push(opCode, p1.value, p2.value);
283 | break;
284 | case 'INC':
285 | p1 = getValue(match[op1_group]);
286 | checkNoExtraArg('INC', match[op2_group]);
287 |
288 | if (p1.type === "register")
289 | opCode = opcodes.INC_REG;
290 | else
291 | throw "INC does not support this operand";
292 |
293 | code.push(opCode, p1.value);
294 |
295 | break;
296 | case 'DEC':
297 | p1 = getValue(match[op1_group]);
298 | checkNoExtraArg('DEC', match[op2_group]);
299 |
300 | if (p1.type === "register")
301 | opCode = opcodes.DEC_REG;
302 | else
303 | throw "DEC does not support this operand";
304 |
305 | code.push(opCode, p1.value);
306 |
307 | break;
308 | case 'CMP':
309 | p1 = getValue(match[op1_group]);
310 | p2 = getValue(match[op2_group]);
311 |
312 | if (p1.type === "register" && p2.type === "register")
313 | opCode = opcodes.CMP_REG_WITH_REG;
314 | else if (p1.type === "register" && p2.type === "regaddress")
315 | opCode = opcodes.CMP_REGADDRESS_WITH_REG;
316 | else if (p1.type === "register" && p2.type === "address")
317 | opCode = opcodes.CMP_ADDRESS_WITH_REG;
318 | else if (p1.type === "register" && p2.type === "number")
319 | opCode = opcodes.CMP_NUMBER_WITH_REG;
320 | else
321 | throw "CMP does not support this operands";
322 |
323 | code.push(opCode, p1.value, p2.value);
324 | break;
325 | case 'JMP':
326 | p1 = getValue(match[op1_group]);
327 | checkNoExtraArg('JMP', match[op2_group]);
328 |
329 | if (p1.type === "register")
330 | opCode = opcodes.JMP_REGADDRESS;
331 | else if (p1.type === "number")
332 | opCode = opcodes.JMP_ADDRESS;
333 | else
334 | throw "JMP does not support this operands";
335 |
336 | code.push(opCode, p1.value);
337 | break;
338 | case 'JC':
339 | case 'JB':
340 | case 'JNAE':
341 | p1 = getValue(match[op1_group]);
342 | checkNoExtraArg(instr, match[op2_group]);
343 |
344 | if (p1.type === "register")
345 | opCode = opcodes.JC_REGADDRESS;
346 | else if (p1.type === "number")
347 | opCode = opcodes.JC_ADDRESS;
348 | else
349 | throw instr + " does not support this operand";
350 |
351 | code.push(opCode, p1.value);
352 | break;
353 | case 'JNC':
354 | case 'JNB':
355 | case 'JAE':
356 | p1 = getValue(match[op1_group]);
357 | checkNoExtraArg(instr, match[op2_group]);
358 |
359 | if (p1.type === "register")
360 | opCode = opcodes.JNC_REGADDRESS;
361 | else if (p1.type === "number")
362 | opCode = opcodes.JNC_ADDRESS;
363 | else
364 | throw instr + "does not support this operand";
365 |
366 | code.push(opCode, p1.value);
367 | break;
368 | case 'JZ':
369 | case 'JE':
370 | p1 = getValue(match[op1_group]);
371 | checkNoExtraArg(instr, match[op2_group]);
372 |
373 | if (p1.type === "register")
374 | opCode = opcodes.JZ_REGADDRESS;
375 | else if (p1.type === "number")
376 | opCode = opcodes.JZ_ADDRESS;
377 | else
378 | throw instr + " does not support this operand";
379 |
380 | code.push(opCode, p1.value);
381 | break;
382 | case 'JNZ':
383 | case 'JNE':
384 | p1 = getValue(match[op1_group]);
385 | checkNoExtraArg(instr, match[op2_group]);
386 |
387 | if (p1.type === "register")
388 | opCode = opcodes.JNZ_REGADDRESS;
389 | else if (p1.type === "number")
390 | opCode = opcodes.JNZ_ADDRESS;
391 | else
392 | throw instr + " does not support this operand";
393 |
394 | code.push(opCode, p1.value);
395 | break;
396 | case 'JA':
397 | case 'JNBE':
398 | p1 = getValue(match[op1_group]);
399 | checkNoExtraArg(instr, match[op2_group]);
400 |
401 | if (p1.type === "register")
402 | opCode = opcodes.JA_REGADDRESS;
403 | else if (p1.type === "number")
404 | opCode = opcodes.JA_ADDRESS;
405 | else
406 | throw instr + " does not support this operand";
407 |
408 | code.push(opCode, p1.value);
409 | break;
410 | case 'JNA':
411 | case 'JBE':
412 | p1 = getValue(match[op1_group]);
413 | checkNoExtraArg(instr, match[op2_group]);
414 |
415 | if (p1.type === "register")
416 | opCode = opcodes.JNA_REGADDRESS;
417 | else if (p1.type === "number")
418 | opCode = opcodes.JNA_ADDRESS;
419 | else
420 | throw instr + " does not support this operand";
421 |
422 | code.push(opCode, p1.value);
423 | break;
424 | case 'PUSH':
425 | p1 = getValue(match[op1_group]);
426 | checkNoExtraArg(instr, match[op2_group]);
427 |
428 | if (p1.type === "register")
429 | opCode = opcodes.PUSH_REG;
430 | else if (p1.type === "regaddress")
431 | opCode = opcodes.PUSH_REGADDRESS;
432 | else if (p1.type === "address")
433 | opCode = opcodes.PUSH_ADDRESS;
434 | else if (p1.type === "number")
435 | opCode = opcodes.PUSH_NUMBER;
436 | else
437 | throw "PUSH does not support this operand";
438 |
439 | code.push(opCode, p1.value);
440 | break;
441 | case 'POP':
442 | p1 = getValue(match[op1_group]);
443 | checkNoExtraArg(instr, match[op2_group]);
444 |
445 | if (p1.type === "register")
446 | opCode = opcodes.POP_REG;
447 | else
448 | throw "PUSH does not support this operand";
449 |
450 | code.push(opCode, p1.value);
451 | break;
452 | case 'CALL':
453 | p1 = getValue(match[op1_group]);
454 | checkNoExtraArg(instr, match[op2_group]);
455 |
456 | if (p1.type === "register")
457 | opCode = opcodes.CALL_REGADDRESS;
458 | else if (p1.type === "number")
459 | opCode = opcodes.CALL_ADDRESS;
460 | else
461 | throw "CALL does not support this operand";
462 |
463 | code.push(opCode, p1.value);
464 | break;
465 | case 'RET':
466 | checkNoExtraArg(instr, match[op1_group]);
467 |
468 | opCode = opcodes.RET;
469 |
470 | code.push(opCode);
471 | break;
472 |
473 | case 'MUL':
474 | p1 = getValue(match[op1_group]);
475 | checkNoExtraArg(instr, match[op2_group]);
476 |
477 | if (p1.type === "register")
478 | opCode = opcodes.MUL_REG;
479 | else if (p1.type === "regaddress")
480 | opCode = opcodes.MUL_REGADDRESS;
481 | else if (p1.type === "address")
482 | opCode = opcodes.MUL_ADDRESS;
483 | else if (p1.type === "number")
484 | opCode = opcodes.MUL_NUMBER;
485 | else
486 | throw "MULL does not support this operand";
487 |
488 | code.push(opCode, p1.value);
489 | break;
490 | case 'DIV':
491 | p1 = getValue(match[op1_group]);
492 | checkNoExtraArg(instr, match[op2_group]);
493 |
494 | if (p1.type === "register")
495 | opCode = opcodes.DIV_REG;
496 | else if (p1.type === "regaddress")
497 | opCode = opcodes.DIV_REGADDRESS;
498 | else if (p1.type === "address")
499 | opCode = opcodes.DIV_ADDRESS;
500 | else if (p1.type === "number")
501 | opCode = opcodes.DIV_NUMBER;
502 | else
503 | throw "DIV does not support this operand";
504 |
505 | code.push(opCode, p1.value);
506 | break;
507 | case 'AND':
508 | p1 = getValue(match[op1_group]);
509 | p2 = getValue(match[op2_group]);
510 |
511 | if (p1.type === "register" && p2.type === "register")
512 | opCode = opcodes.AND_REG_WITH_REG;
513 | else if (p1.type === "register" && p2.type === "regaddress")
514 | opCode = opcodes.AND_REGADDRESS_WITH_REG;
515 | else if (p1.type === "register" && p2.type === "address")
516 | opCode = opcodes.AND_ADDRESS_WITH_REG;
517 | else if (p1.type === "register" && p2.type === "number")
518 | opCode = opcodes.AND_NUMBER_WITH_REG;
519 | else
520 | throw "AND does not support this operands";
521 |
522 | code.push(opCode, p1.value, p2.value);
523 | break;
524 | case 'OR':
525 | p1 = getValue(match[op1_group]);
526 | p2 = getValue(match[op2_group]);
527 |
528 | if (p1.type === "register" && p2.type === "register")
529 | opCode = opcodes.OR_REG_WITH_REG;
530 | else if (p1.type === "register" && p2.type === "regaddress")
531 | opCode = opcodes.OR_REGADDRESS_WITH_REG;
532 | else if (p1.type === "register" && p2.type === "address")
533 | opCode = opcodes.OR_ADDRESS_WITH_REG;
534 | else if (p1.type === "register" && p2.type === "number")
535 | opCode = opcodes.OR_NUMBER_WITH_REG;
536 | else
537 | throw "OR does not support this operands";
538 |
539 | code.push(opCode, p1.value, p2.value);
540 | break;
541 | case 'XOR':
542 | p1 = getValue(match[op1_group]);
543 | p2 = getValue(match[op2_group]);
544 |
545 | if (p1.type === "register" && p2.type === "register")
546 | opCode = opcodes.XOR_REG_WITH_REG;
547 | else if (p1.type === "register" && p2.type === "regaddress")
548 | opCode = opcodes.XOR_REGADDRESS_WITH_REG;
549 | else if (p1.type === "register" && p2.type === "address")
550 | opCode = opcodes.XOR_ADDRESS_WITH_REG;
551 | else if (p1.type === "register" && p2.type === "number")
552 | opCode = opcodes.XOR_NUMBER_WITH_REG;
553 | else
554 | throw "XOR does not support this operands";
555 |
556 | code.push(opCode, p1.value, p2.value);
557 | break;
558 | case 'NOT':
559 | p1 = getValue(match[op1_group]);
560 | checkNoExtraArg(instr, match[op2_group]);
561 |
562 | if (p1.type === "register")
563 | opCode = opcodes.NOT_REG;
564 | else
565 | throw "NOT does not support this operand";
566 |
567 | code.push(opCode, p1.value);
568 | break;
569 | case 'SHL':
570 | case 'SAL':
571 | p1 = getValue(match[op1_group]);
572 | p2 = getValue(match[op2_group]);
573 |
574 | if (p1.type === "register" && p2.type === "register")
575 | opCode = opcodes.SHL_REG_WITH_REG;
576 | else if (p1.type === "register" && p2.type === "regaddress")
577 | opCode = opcodes.SHL_REGADDRESS_WITH_REG;
578 | else if (p1.type === "register" && p2.type === "address")
579 | opCode = opcodes.SHL_ADDRESS_WITH_REG;
580 | else if (p1.type === "register" && p2.type === "number")
581 | opCode = opcodes.SHL_NUMBER_WITH_REG;
582 | else
583 | throw instr + " does not support this operands";
584 |
585 | code.push(opCode, p1.value, p2.value);
586 | break;
587 | case 'SHR':
588 | case 'SAR':
589 | p1 = getValue(match[op1_group]);
590 | p2 = getValue(match[op2_group]);
591 |
592 | if (p1.type === "register" && p2.type === "register")
593 | opCode = opcodes.SHR_REG_WITH_REG;
594 | else if (p1.type === "register" && p2.type === "regaddress")
595 | opCode = opcodes.SHR_REGADDRESS_WITH_REG;
596 | else if (p1.type === "register" && p2.type === "address")
597 | opCode = opcodes.SHR_ADDRESS_WITH_REG;
598 | else if (p1.type === "register" && p2.type === "number")
599 | opCode = opcodes.SHR_NUMBER_WITH_REG;
600 | else
601 | throw instr + " does not support this operands";
602 |
603 | code.push(opCode, p1.value, p2.value);
604 | break;
605 | default:
606 | throw "Invalid instruction: " + match[2];
607 | }
608 | }
609 | } else {
610 | // Check if line starts with a comment otherwise the line contains an error and can not be parsed
611 | var line = lines[i].trim();
612 | if (line !== "" && line.slice(0, 1) !== ";") {
613 | throw "Syntax error";
614 | }
615 | }
616 | } catch (e) {
617 | throw {error: e, line: i};
618 | }
619 | }
620 |
621 | // Replace label
622 | for (i = 0, l = code.length; i < l; i++) {
623 | if (!angular.isNumber(code[i])) {
624 | if (code[i] in labels) {
625 | code[i] = labels[code[i]];
626 | } else {
627 |
628 | throw {error: "Undefined label: " + code[i]};
629 | }
630 | }
631 | }
632 |
633 | return {code: code, mapping: mapping, labels: labels};
634 | }
635 | };
636 | }]);
637 |
--------------------------------------------------------------------------------
/assets/asmsimulator.js:
--------------------------------------------------------------------------------
1 | var app = angular.module('ASMSimulator', []);
2 | ;app.service('assembler', ['opcodes', function (opcodes) {
3 | return {
4 | go: function (input) {
5 | // Use https://www.debuggex.com/
6 | // Matches: "label: INSTRUCTION (["')OPERAND1(]"'), (["')OPERAND2(]"')
7 | // GROUPS: 1 2 3 7
8 | var regex = /^[\t ]*(?:([.A-Za-z]\w*)[:])?(?:[\t ]*([A-Za-z]{2,4})(?:[\t ]+(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*)(?:[\t ]*[,][\t ]*(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*))?)?)?/;
9 |
10 | // Regex group indexes for operands
11 | var op1_group = 3;
12 | var op2_group = 7;
13 |
14 | // MATCHES: "(+|-)INTEGER"
15 | var regexNum = /^[-+]?[0-9]+$/;
16 | // MATCHES: "(.L)abel"
17 | var regexLabel = /^[.A-Za-z]\w*$/;
18 | // Contains the program code & data generated by the assembler
19 | var code = [];
20 | // Contains the mapping from instructions to assembler line
21 | var mapping = {};
22 | // Hash map of label used to replace the labels after the assembler generated the code
23 | var labels = {};
24 | // Hash of uppercase labels used to detect duplicates
25 | var normalizedLabels = {};
26 |
27 | // Split text into code lines
28 | var lines = input.split('\n');
29 |
30 | // Allowed formats: 200, 200d, 0xA4, 0o48, 101b
31 | var parseNumber = function (input) {
32 | if (input.slice(0, 2) === "0x") {
33 | return parseInt(input.slice(2), 16);
34 | } else if (input.slice(0, 2) === "0o") {
35 | return parseInt(input.slice(2), 8);
36 | } else if (input.slice(input.length - 1) === "b") {
37 | return parseInt(input.slice(0, input.length - 1), 2);
38 | } else if (input.slice(input.length - 1) === "d") {
39 | return parseInt(input.slice(0, input.length - 1), 10);
40 | } else if (regexNum.exec(input)) {
41 | return parseInt(input, 10);
42 | } else {
43 | throw "Invalid number format";
44 | }
45 | };
46 |
47 | // Allowed registers: A, B, C, D, SP
48 | var parseRegister = function (input) {
49 | input = input.toUpperCase();
50 |
51 | if (input === 'A') {
52 | return 0;
53 | } else if (input === 'B') {
54 | return 1;
55 | } else if (input === 'C') {
56 | return 2;
57 | } else if (input === 'D') {
58 | return 3;
59 | } else if (input === 'SP') {
60 | return 4;
61 | } else {
62 | return undefined;
63 | }
64 | };
65 |
66 | var parseOffsetAddressing = function (input) {
67 | input = input.toUpperCase();
68 | var m = 0;
69 | var base = 0;
70 |
71 | if (input[0] === 'A') {
72 | base = 0;
73 | } else if (input[0] === 'B') {
74 | base = 1;
75 | } else if (input[0] === 'C') {
76 | base = 2;
77 | } else if (input[0] === 'D') {
78 | base = 3;
79 | } else if (input.slice(0, 2) === "SP") {
80 | base = 4;
81 | } else {
82 | return undefined;
83 | }
84 | var offset_start = 1;
85 | if (base === 4) {
86 | offset_start = 2;
87 | }
88 |
89 | if (input[offset_start] === '-') {
90 | m = -1;
91 | } else if (input[offset_start] === '+') {
92 | m = 1;
93 | } else {
94 | return undefined;
95 | }
96 |
97 | var offset = m * parseInt(input.slice(offset_start + 1), 10);
98 |
99 | if (offset < -16 || offset > 15)
100 | throw "offset must be a value between -16...+15";
101 |
102 | if (offset < 0) {
103 | offset = 32 + offset; // two's complement representation in 5-bit
104 | }
105 |
106 | return offset * 8 + base; // shift offset 3 bits right and add code for register
107 | };
108 |
109 | // Allowed: Register, Label or Number; SP+/-Number is allowed for 'regaddress' type
110 | var parseRegOrNumber = function (input, typeReg, typeNumber) {
111 | var register = parseRegister(input);
112 |
113 | if (register !== undefined) {
114 | return {type: typeReg, value: register};
115 | } else {
116 | var label = parseLabel(input);
117 | if (label !== undefined) {
118 | return {type: typeNumber, value: label};
119 | } else {
120 | if (typeReg === "regaddress") {
121 |
122 | register = parseOffsetAddressing(input);
123 |
124 | if (register !== undefined) {
125 | return {type: typeReg, value: register};
126 | }
127 | }
128 |
129 | var value = parseNumber(input);
130 |
131 | if (isNaN(value)) {
132 | throw "Not a " + typeNumber + ": " + value;
133 | }
134 | else if (value < 0 || value > 255)
135 | throw typeNumber + " must have a value between 0-255";
136 |
137 | return {type: typeNumber, value: value};
138 | }
139 | }
140 | };
141 |
142 | var parseLabel = function (input) {
143 | return regexLabel.exec(input) ? input : undefined;
144 | };
145 |
146 | var getValue = function (input) {
147 | switch (input.slice(0, 1)) {
148 | case '[': // [number] or [register]
149 | var address = input.slice(1, input.length - 1);
150 | return parseRegOrNumber(address, "regaddress", "address");
151 | case '"': // "String"
152 | var text = input.slice(1, input.length - 1);
153 | var chars = [];
154 |
155 | for (var i = 0, l = text.length; i < l; i++) {
156 | chars.push(text.charCodeAt(i));
157 | }
158 |
159 | return {type: "numbers", value: chars};
160 | case "'": // 'C'
161 | var character = input.slice(1, input.length - 1);
162 | if (character.length > 1)
163 | throw "Only one character is allowed. Use String instead";
164 |
165 | return {type: "number", value: character.charCodeAt(0)};
166 | default: // REGISTER, NUMBER or LABEL
167 | return parseRegOrNumber(input, "register", "number");
168 | }
169 | };
170 |
171 | var addLabel = function (label) {
172 | var upperLabel = label.toUpperCase();
173 | if (upperLabel in normalizedLabels)
174 | throw "Duplicate label: " + label;
175 |
176 | if (upperLabel === "A" || upperLabel === "B" || upperLabel === "C" || upperLabel === "D")
177 | throw "Label contains keyword: " + upperLabel;
178 |
179 | labels[label] = code.length;
180 | };
181 |
182 | var checkNoExtraArg = function (instr, arg) {
183 | if (arg !== undefined) {
184 | throw instr + ": too many arguments";
185 | }
186 | };
187 |
188 | for (var i = 0, l = lines.length; i < l; i++) {
189 | try {
190 | var match = regex.exec(lines[i]);
191 | if (match[1] !== undefined || match[2] !== undefined) {
192 | if (match[1] !== undefined) {
193 | addLabel(match[1]);
194 | }
195 |
196 | if (match[2] !== undefined) {
197 | var instr = match[2].toUpperCase();
198 | var p1, p2, opCode;
199 |
200 | // Add mapping instr pos to line number
201 | // Don't do it for DB as this is not a real instruction
202 | if (instr !== 'DB') {
203 | mapping[code.length] = i;
204 | }
205 |
206 | switch (instr) {
207 | case 'DB':
208 | p1 = getValue(match[op1_group]);
209 |
210 | if (p1.type === "number")
211 | code.push(p1.value);
212 | else if (p1.type === "numbers")
213 | for (var j = 0, k = p1.value.length; j < k; j++) {
214 | code.push(p1.value[j]);
215 | }
216 | else
217 | throw "DB does not support this operand";
218 |
219 | break;
220 | case 'HLT':
221 | checkNoExtraArg('HLT', match[op1_group]);
222 | opCode = opcodes.NONE;
223 | code.push(opCode);
224 | break;
225 |
226 | case 'MOV':
227 | p1 = getValue(match[op1_group]);
228 | p2 = getValue(match[op2_group]);
229 |
230 | if (p1.type === "register" && p2.type === "register")
231 | opCode = opcodes.MOV_REG_TO_REG;
232 | else if (p1.type === "register" && p2.type === "address")
233 | opCode = opcodes.MOV_ADDRESS_TO_REG;
234 | else if (p1.type === "register" && p2.type === "regaddress")
235 | opCode = opcodes.MOV_REGADDRESS_TO_REG;
236 | else if (p1.type === "address" && p2.type === "register")
237 | opCode = opcodes.MOV_REG_TO_ADDRESS;
238 | else if (p1.type === "regaddress" && p2.type === "register")
239 | opCode = opcodes.MOV_REG_TO_REGADDRESS;
240 | else if (p1.type === "register" && p2.type === "number")
241 | opCode = opcodes.MOV_NUMBER_TO_REG;
242 | else if (p1.type === "address" && p2.type === "number")
243 | opCode = opcodes.MOV_NUMBER_TO_ADDRESS;
244 | else if (p1.type === "regaddress" && p2.type === "number")
245 | opCode = opcodes.MOV_NUMBER_TO_REGADDRESS;
246 | else
247 | throw "MOV does not support this operands";
248 |
249 | code.push(opCode, p1.value, p2.value);
250 | break;
251 | case 'ADD':
252 | p1 = getValue(match[op1_group]);
253 | p2 = getValue(match[op2_group]);
254 |
255 | if (p1.type === "register" && p2.type === "register")
256 | opCode = opcodes.ADD_REG_TO_REG;
257 | else if (p1.type === "register" && p2.type === "regaddress")
258 | opCode = opcodes.ADD_REGADDRESS_TO_REG;
259 | else if (p1.type === "register" && p2.type === "address")
260 | opCode = opcodes.ADD_ADDRESS_TO_REG;
261 | else if (p1.type === "register" && p2.type === "number")
262 | opCode = opcodes.ADD_NUMBER_TO_REG;
263 | else
264 | throw "ADD does not support this operands";
265 |
266 | code.push(opCode, p1.value, p2.value);
267 | break;
268 | case 'SUB':
269 | p1 = getValue(match[op1_group]);
270 | p2 = getValue(match[op2_group]);
271 |
272 | if (p1.type === "register" && p2.type === "register")
273 | opCode = opcodes.SUB_REG_FROM_REG;
274 | else if (p1.type === "register" && p2.type === "regaddress")
275 | opCode = opcodes.SUB_REGADDRESS_FROM_REG;
276 | else if (p1.type === "register" && p2.type === "address")
277 | opCode = opcodes.SUB_ADDRESS_FROM_REG;
278 | else if (p1.type === "register" && p2.type === "number")
279 | opCode = opcodes.SUB_NUMBER_FROM_REG;
280 | else
281 | throw "SUB does not support this operands";
282 |
283 | code.push(opCode, p1.value, p2.value);
284 | break;
285 | case 'INC':
286 | p1 = getValue(match[op1_group]);
287 | checkNoExtraArg('INC', match[op2_group]);
288 |
289 | if (p1.type === "register")
290 | opCode = opcodes.INC_REG;
291 | else
292 | throw "INC does not support this operand";
293 |
294 | code.push(opCode, p1.value);
295 |
296 | break;
297 | case 'DEC':
298 | p1 = getValue(match[op1_group]);
299 | checkNoExtraArg('DEC', match[op2_group]);
300 |
301 | if (p1.type === "register")
302 | opCode = opcodes.DEC_REG;
303 | else
304 | throw "DEC does not support this operand";
305 |
306 | code.push(opCode, p1.value);
307 |
308 | break;
309 | case 'CMP':
310 | p1 = getValue(match[op1_group]);
311 | p2 = getValue(match[op2_group]);
312 |
313 | if (p1.type === "register" && p2.type === "register")
314 | opCode = opcodes.CMP_REG_WITH_REG;
315 | else if (p1.type === "register" && p2.type === "regaddress")
316 | opCode = opcodes.CMP_REGADDRESS_WITH_REG;
317 | else if (p1.type === "register" && p2.type === "address")
318 | opCode = opcodes.CMP_ADDRESS_WITH_REG;
319 | else if (p1.type === "register" && p2.type === "number")
320 | opCode = opcodes.CMP_NUMBER_WITH_REG;
321 | else
322 | throw "CMP does not support this operands";
323 |
324 | code.push(opCode, p1.value, p2.value);
325 | break;
326 | case 'JMP':
327 | p1 = getValue(match[op1_group]);
328 | checkNoExtraArg('JMP', match[op2_group]);
329 |
330 | if (p1.type === "register")
331 | opCode = opcodes.JMP_REGADDRESS;
332 | else if (p1.type === "number")
333 | opCode = opcodes.JMP_ADDRESS;
334 | else
335 | throw "JMP does not support this operands";
336 |
337 | code.push(opCode, p1.value);
338 | break;
339 | case 'JC':
340 | case 'JB':
341 | case 'JNAE':
342 | p1 = getValue(match[op1_group]);
343 | checkNoExtraArg(instr, match[op2_group]);
344 |
345 | if (p1.type === "register")
346 | opCode = opcodes.JC_REGADDRESS;
347 | else if (p1.type === "number")
348 | opCode = opcodes.JC_ADDRESS;
349 | else
350 | throw instr + " does not support this operand";
351 |
352 | code.push(opCode, p1.value);
353 | break;
354 | case 'JNC':
355 | case 'JNB':
356 | case 'JAE':
357 | p1 = getValue(match[op1_group]);
358 | checkNoExtraArg(instr, match[op2_group]);
359 |
360 | if (p1.type === "register")
361 | opCode = opcodes.JNC_REGADDRESS;
362 | else if (p1.type === "number")
363 | opCode = opcodes.JNC_ADDRESS;
364 | else
365 | throw instr + "does not support this operand";
366 |
367 | code.push(opCode, p1.value);
368 | break;
369 | case 'JZ':
370 | case 'JE':
371 | p1 = getValue(match[op1_group]);
372 | checkNoExtraArg(instr, match[op2_group]);
373 |
374 | if (p1.type === "register")
375 | opCode = opcodes.JZ_REGADDRESS;
376 | else if (p1.type === "number")
377 | opCode = opcodes.JZ_ADDRESS;
378 | else
379 | throw instr + " does not support this operand";
380 |
381 | code.push(opCode, p1.value);
382 | break;
383 | case 'JNZ':
384 | case 'JNE':
385 | p1 = getValue(match[op1_group]);
386 | checkNoExtraArg(instr, match[op2_group]);
387 |
388 | if (p1.type === "register")
389 | opCode = opcodes.JNZ_REGADDRESS;
390 | else if (p1.type === "number")
391 | opCode = opcodes.JNZ_ADDRESS;
392 | else
393 | throw instr + " does not support this operand";
394 |
395 | code.push(opCode, p1.value);
396 | break;
397 | case 'JA':
398 | case 'JNBE':
399 | p1 = getValue(match[op1_group]);
400 | checkNoExtraArg(instr, match[op2_group]);
401 |
402 | if (p1.type === "register")
403 | opCode = opcodes.JA_REGADDRESS;
404 | else if (p1.type === "number")
405 | opCode = opcodes.JA_ADDRESS;
406 | else
407 | throw instr + " does not support this operand";
408 |
409 | code.push(opCode, p1.value);
410 | break;
411 | case 'JNA':
412 | case 'JBE':
413 | p1 = getValue(match[op1_group]);
414 | checkNoExtraArg(instr, match[op2_group]);
415 |
416 | if (p1.type === "register")
417 | opCode = opcodes.JNA_REGADDRESS;
418 | else if (p1.type === "number")
419 | opCode = opcodes.JNA_ADDRESS;
420 | else
421 | throw instr + " does not support this operand";
422 |
423 | code.push(opCode, p1.value);
424 | break;
425 | case 'PUSH':
426 | p1 = getValue(match[op1_group]);
427 | checkNoExtraArg(instr, match[op2_group]);
428 |
429 | if (p1.type === "register")
430 | opCode = opcodes.PUSH_REG;
431 | else if (p1.type === "regaddress")
432 | opCode = opcodes.PUSH_REGADDRESS;
433 | else if (p1.type === "address")
434 | opCode = opcodes.PUSH_ADDRESS;
435 | else if (p1.type === "number")
436 | opCode = opcodes.PUSH_NUMBER;
437 | else
438 | throw "PUSH does not support this operand";
439 |
440 | code.push(opCode, p1.value);
441 | break;
442 | case 'POP':
443 | p1 = getValue(match[op1_group]);
444 | checkNoExtraArg(instr, match[op2_group]);
445 |
446 | if (p1.type === "register")
447 | opCode = opcodes.POP_REG;
448 | else
449 | throw "PUSH does not support this operand";
450 |
451 | code.push(opCode, p1.value);
452 | break;
453 | case 'CALL':
454 | p1 = getValue(match[op1_group]);
455 | checkNoExtraArg(instr, match[op2_group]);
456 |
457 | if (p1.type === "register")
458 | opCode = opcodes.CALL_REGADDRESS;
459 | else if (p1.type === "number")
460 | opCode = opcodes.CALL_ADDRESS;
461 | else
462 | throw "CALL does not support this operand";
463 |
464 | code.push(opCode, p1.value);
465 | break;
466 | case 'RET':
467 | checkNoExtraArg(instr, match[op1_group]);
468 |
469 | opCode = opcodes.RET;
470 |
471 | code.push(opCode);
472 | break;
473 |
474 | case 'MUL':
475 | p1 = getValue(match[op1_group]);
476 | checkNoExtraArg(instr, match[op2_group]);
477 |
478 | if (p1.type === "register")
479 | opCode = opcodes.MUL_REG;
480 | else if (p1.type === "regaddress")
481 | opCode = opcodes.MUL_REGADDRESS;
482 | else if (p1.type === "address")
483 | opCode = opcodes.MUL_ADDRESS;
484 | else if (p1.type === "number")
485 | opCode = opcodes.MUL_NUMBER;
486 | else
487 | throw "MULL does not support this operand";
488 |
489 | code.push(opCode, p1.value);
490 | break;
491 | case 'DIV':
492 | p1 = getValue(match[op1_group]);
493 | checkNoExtraArg(instr, match[op2_group]);
494 |
495 | if (p1.type === "register")
496 | opCode = opcodes.DIV_REG;
497 | else if (p1.type === "regaddress")
498 | opCode = opcodes.DIV_REGADDRESS;
499 | else if (p1.type === "address")
500 | opCode = opcodes.DIV_ADDRESS;
501 | else if (p1.type === "number")
502 | opCode = opcodes.DIV_NUMBER;
503 | else
504 | throw "DIV does not support this operand";
505 |
506 | code.push(opCode, p1.value);
507 | break;
508 | case 'AND':
509 | p1 = getValue(match[op1_group]);
510 | p2 = getValue(match[op2_group]);
511 |
512 | if (p1.type === "register" && p2.type === "register")
513 | opCode = opcodes.AND_REG_WITH_REG;
514 | else if (p1.type === "register" && p2.type === "regaddress")
515 | opCode = opcodes.AND_REGADDRESS_WITH_REG;
516 | else if (p1.type === "register" && p2.type === "address")
517 | opCode = opcodes.AND_ADDRESS_WITH_REG;
518 | else if (p1.type === "register" && p2.type === "number")
519 | opCode = opcodes.AND_NUMBER_WITH_REG;
520 | else
521 | throw "AND does not support this operands";
522 |
523 | code.push(opCode, p1.value, p2.value);
524 | break;
525 | case 'OR':
526 | p1 = getValue(match[op1_group]);
527 | p2 = getValue(match[op2_group]);
528 |
529 | if (p1.type === "register" && p2.type === "register")
530 | opCode = opcodes.OR_REG_WITH_REG;
531 | else if (p1.type === "register" && p2.type === "regaddress")
532 | opCode = opcodes.OR_REGADDRESS_WITH_REG;
533 | else if (p1.type === "register" && p2.type === "address")
534 | opCode = opcodes.OR_ADDRESS_WITH_REG;
535 | else if (p1.type === "register" && p2.type === "number")
536 | opCode = opcodes.OR_NUMBER_WITH_REG;
537 | else
538 | throw "OR does not support this operands";
539 |
540 | code.push(opCode, p1.value, p2.value);
541 | break;
542 | case 'XOR':
543 | p1 = getValue(match[op1_group]);
544 | p2 = getValue(match[op2_group]);
545 |
546 | if (p1.type === "register" && p2.type === "register")
547 | opCode = opcodes.XOR_REG_WITH_REG;
548 | else if (p1.type === "register" && p2.type === "regaddress")
549 | opCode = opcodes.XOR_REGADDRESS_WITH_REG;
550 | else if (p1.type === "register" && p2.type === "address")
551 | opCode = opcodes.XOR_ADDRESS_WITH_REG;
552 | else if (p1.type === "register" && p2.type === "number")
553 | opCode = opcodes.XOR_NUMBER_WITH_REG;
554 | else
555 | throw "XOR does not support this operands";
556 |
557 | code.push(opCode, p1.value, p2.value);
558 | break;
559 | case 'NOT':
560 | p1 = getValue(match[op1_group]);
561 | checkNoExtraArg(instr, match[op2_group]);
562 |
563 | if (p1.type === "register")
564 | opCode = opcodes.NOT_REG;
565 | else
566 | throw "NOT does not support this operand";
567 |
568 | code.push(opCode, p1.value);
569 | break;
570 | case 'SHL':
571 | case 'SAL':
572 | p1 = getValue(match[op1_group]);
573 | p2 = getValue(match[op2_group]);
574 |
575 | if (p1.type === "register" && p2.type === "register")
576 | opCode = opcodes.SHL_REG_WITH_REG;
577 | else if (p1.type === "register" && p2.type === "regaddress")
578 | opCode = opcodes.SHL_REGADDRESS_WITH_REG;
579 | else if (p1.type === "register" && p2.type === "address")
580 | opCode = opcodes.SHL_ADDRESS_WITH_REG;
581 | else if (p1.type === "register" && p2.type === "number")
582 | opCode = opcodes.SHL_NUMBER_WITH_REG;
583 | else
584 | throw instr + " does not support this operands";
585 |
586 | code.push(opCode, p1.value, p2.value);
587 | break;
588 | case 'SHR':
589 | case 'SAR':
590 | p1 = getValue(match[op1_group]);
591 | p2 = getValue(match[op2_group]);
592 |
593 | if (p1.type === "register" && p2.type === "register")
594 | opCode = opcodes.SHR_REG_WITH_REG;
595 | else if (p1.type === "register" && p2.type === "regaddress")
596 | opCode = opcodes.SHR_REGADDRESS_WITH_REG;
597 | else if (p1.type === "register" && p2.type === "address")
598 | opCode = opcodes.SHR_ADDRESS_WITH_REG;
599 | else if (p1.type === "register" && p2.type === "number")
600 | opCode = opcodes.SHR_NUMBER_WITH_REG;
601 | else
602 | throw instr + " does not support this operands";
603 |
604 | code.push(opCode, p1.value, p2.value);
605 | break;
606 | default:
607 | throw "Invalid instruction: " + match[2];
608 | }
609 | }
610 | } else {
611 | // Check if line starts with a comment otherwise the line contains an error and can not be parsed
612 | var line = lines[i].trim();
613 | if (line !== "" && line.slice(0, 1) !== ";") {
614 | throw "Syntax error";
615 | }
616 | }
617 | } catch (e) {
618 | throw {error: e, line: i};
619 | }
620 | }
621 |
622 | // Replace label
623 | for (i = 0, l = code.length; i < l; i++) {
624 | if (!angular.isNumber(code[i])) {
625 | if (code[i] in labels) {
626 | code[i] = labels[code[i]];
627 | } else {
628 |
629 | throw {error: "Undefined label: " + code[i]};
630 | }
631 | }
632 | }
633 |
634 | return {code: code, mapping: mapping, labels: labels};
635 | }
636 | };
637 | }]);
638 | ;app.service('cpu', ['opcodes', 'memory', function(opcodes, memory) {
639 | var cpu = {
640 | step: function() {
641 | var self = this;
642 |
643 | if (self.fault === true) {
644 | throw "FAULT. Reset to continue.";
645 | }
646 |
647 | try {
648 | var checkGPR = function(reg) {
649 | if (reg < 0 || reg >= self.gpr.length) {
650 | throw "Invalid register: " + reg;
651 | } else {
652 | return reg;
653 | }
654 | };
655 |
656 | var checkGPR_SP = function(reg) {
657 | if (reg < 0 || reg >= 1 + self.gpr.length) {
658 | throw "Invalid register: " + reg;
659 | } else {
660 | return reg;
661 | }
662 | };
663 |
664 | var setGPR_SP = function(reg,value)
665 | {
666 | if(reg >= 0 && reg < self.gpr.length) {
667 | self.gpr[reg] = value;
668 | } else if(reg == self.gpr.length) {
669 | self.sp = value;
670 |
671 | // Not likely to happen, since we always get here after checkOpertion().
672 | if (self.sp < self.minSP) {
673 | throw "Stack overflow";
674 | } else if (self.sp > self.maxSP) {
675 | throw "Stack underflow";
676 | }
677 | } else {
678 | throw "Invalid register: " + reg;
679 | }
680 | };
681 |
682 | var getGPR_SP = function(reg)
683 | {
684 | if(reg >= 0 && reg < self.gpr.length) {
685 | return self.gpr[reg];
686 | } else if(reg == self.gpr.length) {
687 | return self.sp;
688 | } else {
689 | throw "Invalid register: " + reg;
690 | }
691 | };
692 |
693 | var indirectRegisterAddress = function(value) {
694 | var reg = value % 8;
695 |
696 | var base;
697 | if (reg < self.gpr.length) {
698 | base = self.gpr[reg];
699 | } else {
700 | base = self.sp;
701 | }
702 |
703 | var offset = Math.floor(value / 8);
704 | if ( offset > 15 ) {
705 | offset = offset - 32;
706 | }
707 |
708 | return base+offset;
709 | };
710 |
711 | var checkOperation = function(value) {
712 | self.zero = false;
713 | self.carry = false;
714 |
715 | if (value >= 256) {
716 | self.carry = true;
717 | value = value % 256;
718 | } else if (value === 0) {
719 | self.zero = true;
720 | } else if (value < 0) {
721 | self.carry = true;
722 | value = 256 - (-value) % 256;
723 | }
724 |
725 | return value;
726 | };
727 |
728 | var jump = function(newIP) {
729 | if (newIP < 0 || newIP >= memory.data.length) {
730 | throw "IP outside memory";
731 | } else {
732 | self.ip = newIP;
733 | }
734 | };
735 |
736 | var push = function(value) {
737 | memory.store(self.sp--, value);
738 | if (self.sp < self.minSP) {
739 | throw "Stack overflow";
740 | }
741 | };
742 |
743 | var pop = function() {
744 | var value = memory.load(++self.sp);
745 | if (self.sp > self.maxSP) {
746 | throw "Stack underflow";
747 | }
748 |
749 | return value;
750 | };
751 |
752 | var division = function(divisor) {
753 | if (divisor === 0) {
754 | throw "Division by 0";
755 | }
756 |
757 | return Math.floor(self.gpr[0] / divisor);
758 | };
759 |
760 | if (self.ip < 0 || self.ip >= memory.data.length) {
761 | throw "Instruction pointer is outside of memory";
762 | }
763 |
764 | var regTo, regFrom, memFrom, memTo, number;
765 | var instr = memory.load(self.ip);
766 | switch(instr) {
767 | case opcodes.NONE:
768 | return false; // Abort step
769 | case opcodes.MOV_REG_TO_REG:
770 | regTo = checkGPR_SP(memory.load(++self.ip));
771 | regFrom = checkGPR_SP(memory.load(++self.ip));
772 | setGPR_SP(regTo,getGPR_SP(regFrom));
773 | self.ip++;
774 | break;
775 | case opcodes.MOV_ADDRESS_TO_REG:
776 | regTo = checkGPR_SP(memory.load(++self.ip));
777 | memFrom = memory.load(++self.ip);
778 | setGPR_SP(regTo,memory.load(memFrom));
779 | self.ip++;
780 | break;
781 | case opcodes.MOV_REGADDRESS_TO_REG:
782 | regTo = checkGPR_SP(memory.load(++self.ip));
783 | regFrom = memory.load(++self.ip);
784 | setGPR_SP(regTo,memory.load(indirectRegisterAddress(regFrom)));
785 | self.ip++;
786 | break;
787 | case opcodes.MOV_REG_TO_ADDRESS:
788 | memTo = memory.load(++self.ip);
789 | regFrom = checkGPR_SP(memory.load(++self.ip));
790 | memory.store(memTo, getGPR_SP(regFrom));
791 | self.ip++;
792 | break;
793 | case opcodes.MOV_REG_TO_REGADDRESS:
794 | regTo = memory.load(++self.ip);
795 | regFrom = checkGPR_SP(memory.load(++self.ip));
796 | memory.store(indirectRegisterAddress(regTo), getGPR_SP(regFrom));
797 | self.ip++;
798 | break;
799 | case opcodes.MOV_NUMBER_TO_REG:
800 | regTo = checkGPR_SP(memory.load(++self.ip));
801 | number = memory.load(++self.ip);
802 | setGPR_SP(regTo,number);
803 | self.ip++;
804 | break;
805 | case opcodes.MOV_NUMBER_TO_ADDRESS:
806 | memTo = memory.load(++self.ip);
807 | number = memory.load(++self.ip);
808 | memory.store(memTo, number);
809 | self.ip++;
810 | break;
811 | case opcodes.MOV_NUMBER_TO_REGADDRESS:
812 | regTo = memory.load(++self.ip);
813 | number = memory.load(++self.ip);
814 | memory.store(indirectRegisterAddress(regTo), number);
815 | self.ip++;
816 | break;
817 | case opcodes.ADD_REG_TO_REG:
818 | regTo = checkGPR_SP(memory.load(++self.ip));
819 | regFrom = checkGPR_SP(memory.load(++self.ip));
820 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + getGPR_SP(regFrom)));
821 | self.ip++;
822 | break;
823 | case opcodes.ADD_REGADDRESS_TO_REG:
824 | regTo = checkGPR_SP(memory.load(++self.ip));
825 | regFrom = memory.load(++self.ip);
826 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + memory.load(indirectRegisterAddress(regFrom))));
827 | self.ip++;
828 | break;
829 | case opcodes.ADD_ADDRESS_TO_REG:
830 | regTo = checkGPR_SP(memory.load(++self.ip));
831 | memFrom = memory.load(++self.ip);
832 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + memory.load(memFrom)));
833 | self.ip++;
834 | break;
835 | case opcodes.ADD_NUMBER_TO_REG:
836 | regTo = checkGPR_SP(memory.load(++self.ip));
837 | number = memory.load(++self.ip);
838 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + number));
839 | self.ip++;
840 | break;
841 | case opcodes.SUB_REG_FROM_REG:
842 | regTo = checkGPR_SP(memory.load(++self.ip));
843 | regFrom = checkGPR_SP(memory.load(++self.ip));
844 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - self.gpr[regFrom]));
845 | self.ip++;
846 | break;
847 | case opcodes.SUB_REGADDRESS_FROM_REG:
848 | regTo = checkGPR_SP(memory.load(++self.ip));
849 | regFrom = memory.load(++self.ip);
850 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - memory.load(indirectRegisterAddress(regFrom))));
851 | self.ip++;
852 | break;
853 | case opcodes.SUB_ADDRESS_FROM_REG:
854 | regTo = checkGPR_SP(memory.load(++self.ip));
855 | memFrom = memory.load(++self.ip);
856 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - memory.load(memFrom)));
857 | self.ip++;
858 | break;
859 | case opcodes.SUB_NUMBER_FROM_REG:
860 | regTo = checkGPR_SP(memory.load(++self.ip));
861 | number = memory.load(++self.ip);
862 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - number));
863 | self.ip++;
864 | break;
865 | case opcodes.INC_REG:
866 | regTo = checkGPR_SP(memory.load(++self.ip));
867 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) + 1));
868 | self.ip++;
869 | break;
870 | case opcodes.DEC_REG:
871 | regTo = checkGPR_SP(memory.load(++self.ip));
872 | setGPR_SP(regTo,checkOperation(getGPR_SP(regTo) - 1));
873 | self.ip++;
874 | break;
875 | case opcodes.CMP_REG_WITH_REG:
876 | regTo = checkGPR_SP(memory.load(++self.ip));
877 | regFrom = checkGPR_SP(memory.load(++self.ip));
878 | checkOperation(getGPR_SP(regTo) - getGPR_SP(regFrom));
879 | self.ip++;
880 | break;
881 | case opcodes.CMP_REGADDRESS_WITH_REG:
882 | regTo = checkGPR_SP(memory.load(++self.ip));
883 | regFrom = memory.load(++self.ip);
884 | checkOperation(getGPR_SP(regTo) - memory.load(indirectRegisterAddress(regFrom)));
885 | self.ip++;
886 | break;
887 | case opcodes.CMP_ADDRESS_WITH_REG:
888 | regTo = checkGPR_SP(memory.load(++self.ip));
889 | memFrom = memory.load(++self.ip);
890 | checkOperation(getGPR_SP(regTo) - memory.load(memFrom));
891 | self.ip++;
892 | break;
893 | case opcodes.CMP_NUMBER_WITH_REG:
894 | regTo = checkGPR_SP(memory.load(++self.ip));
895 | number = memory.load(++self.ip);
896 | checkOperation(getGPR_SP(regTo) - number);
897 | self.ip++;
898 | break;
899 | case opcodes.JMP_REGADDRESS:
900 | regTo = checkGPR(memory.load(++self.ip));
901 | jump(self.gpr[regTo]);
902 | break;
903 | case opcodes.JMP_ADDRESS:
904 | number = memory.load(++self.ip);
905 | jump(number);
906 | break;
907 | case opcodes.JC_REGADDRESS:
908 | regTo = checkGPR(memory.load(++self.ip));
909 | if (self.carry) {
910 | jump(self.gpr[regTo]);
911 | } else {
912 | self.ip++;
913 | }
914 | break;
915 | case opcodes.JC_ADDRESS:
916 | number = memory.load(++self.ip);
917 | if (self.carry) {
918 | jump(number);
919 | } else {
920 | self.ip++;
921 | }
922 | break;
923 | case opcodes.JNC_REGADDRESS:
924 | regTo = checkGPR(memory.load(++self.ip));
925 | if (!self.carry) {
926 | jump(self.gpr[regTo]);
927 | } else {
928 | self.ip++;
929 | }
930 | break;
931 | case opcodes.JNC_ADDRESS:
932 | number = memory.load(++self.ip);
933 | if (!self.carry) {
934 | jump(number);
935 | } else {
936 | self.ip++;
937 | }
938 | break;
939 | case opcodes.JZ_REGADDRESS:
940 | regTo = checkGPR(memory.load(++self.ip));
941 | if (self.zero) {
942 | jump(self.gpr[regTo]);
943 | } else {
944 | self.ip++;
945 | }
946 | break;
947 | case opcodes.JZ_ADDRESS:
948 | number = memory.load(++self.ip);
949 | if (self.zero) {
950 | jump(number);
951 | } else {
952 | self.ip++;
953 | }
954 | break;
955 | case opcodes.JNZ_REGADDRESS:
956 | regTo = checkGPR(memory.load(++self.ip));
957 | if (!self.zero) {
958 | jump(self.gpr[regTo]);
959 | } else {
960 | self.ip++;
961 | }
962 | break;
963 | case opcodes.JNZ_ADDRESS:
964 | number = memory.load(++self.ip);
965 | if (!self.zero) {
966 | jump(number);
967 | } else {
968 | self.ip++;
969 | }
970 | break;
971 | case opcodes.JA_REGADDRESS:
972 | regTo = checkGPR(memory.load(++self.ip));
973 | if (!self.zero && !self.carry) {
974 | jump(self.gpr[regTo]);
975 | } else {
976 | self.ip++;
977 | }
978 | break;
979 | case opcodes.JA_ADDRESS:
980 | number = memory.load(++self.ip);
981 | if (!self.zero && !self.carry) {
982 | jump(number);
983 | } else {
984 | self.ip++;
985 | }
986 | break;
987 | case opcodes.JNA_REGADDRESS: // JNA REG
988 | regTo = checkGPR(memory.load(++self.ip));
989 | if (self.zero || self.carry) {
990 | jump(self.gpr[regTo]);
991 | } else {
992 | self.ip++;
993 | }
994 | break;
995 | case opcodes.JNA_ADDRESS:
996 | number = memory.load(++self.ip);
997 | if (self.zero || self.carry) {
998 | jump(number);
999 | } else {
1000 | self.ip++;
1001 | }
1002 | break;
1003 | case opcodes.PUSH_REG:
1004 | regFrom = checkGPR(memory.load(++self.ip));
1005 | push(self.gpr[regFrom]);
1006 | self.ip++;
1007 | break;
1008 | case opcodes.PUSH_REGADDRESS:
1009 | regFrom = memory.load(++self.ip);
1010 | push(memory.load(indirectRegisterAddress(regFrom)));
1011 | self.ip++;
1012 | break;
1013 | case opcodes.PUSH_ADDRESS:
1014 | memFrom = memory.load(++self.ip);
1015 | push(memory.load(memFrom));
1016 | self.ip++;
1017 | break;
1018 | case opcodes.PUSH_NUMBER:
1019 | number = memory.load(++self.ip);
1020 | push(number);
1021 | self.ip++;
1022 | break;
1023 | case opcodes.POP_REG:
1024 | regTo = checkGPR(memory.load(++self.ip));
1025 | self.gpr[regTo] = pop();
1026 | self.ip++;
1027 | break;
1028 | case opcodes.CALL_REGADDRESS:
1029 | regTo = checkGPR(memory.load(++self.ip));
1030 | push(self.ip+1);
1031 | jump(self.gpr[regTo]);
1032 | break;
1033 | case opcodes.CALL_ADDRESS:
1034 | number = memory.load(++self.ip);
1035 | push(self.ip+1);
1036 | jump(number);
1037 | break;
1038 | case opcodes.RET:
1039 | jump(pop());
1040 | break;
1041 | case opcodes.MUL_REG: // A = A * REG
1042 | regFrom = checkGPR(memory.load(++self.ip));
1043 | self.gpr[0] = checkOperation(self.gpr[0] * self.gpr[regFrom]);
1044 | self.ip++;
1045 | break;
1046 | case opcodes.MUL_REGADDRESS: // A = A * [REG]
1047 | regFrom = memory.load(++self.ip);
1048 | self.gpr[0] = checkOperation(self.gpr[0] * memory.load(indirectRegisterAddress(regFrom)));
1049 | self.ip++;
1050 | break;
1051 | case opcodes.MUL_ADDRESS: // A = A * [NUMBER]
1052 | memFrom = memory.load(++self.ip);
1053 | self.gpr[0] = checkOperation(self.gpr[0] * memory.load(memFrom));
1054 | self.ip++;
1055 | break;
1056 | case opcodes.MUL_NUMBER: // A = A * NUMBER
1057 | number = memory.load(++self.ip);
1058 | self.gpr[0] = checkOperation(self.gpr[0] * number);
1059 | self.ip++;
1060 | break;
1061 | case opcodes.DIV_REG: // A = A / REG
1062 | regFrom = checkGPR(memory.load(++self.ip));
1063 | self.gpr[0] = checkOperation(division(self.gpr[regFrom]));
1064 | self.ip++;
1065 | break;
1066 | case opcodes.DIV_REGADDRESS: // A = A / [REG]
1067 | regFrom = memory.load(++self.ip);
1068 | self.gpr[0] = checkOperation(division(memory.load(indirectRegisterAddress(regFrom))));
1069 | self.ip++;
1070 | break;
1071 | case opcodes.DIV_ADDRESS: // A = A / [NUMBER]
1072 | memFrom = memory.load(++self.ip);
1073 | self.gpr[0] = checkOperation(division(memory.load(memFrom)));
1074 | self.ip++;
1075 | break;
1076 | case opcodes.DIV_NUMBER: // A = A / NUMBER
1077 | number = memory.load(++self.ip);
1078 | self.gpr[0] = checkOperation(division(number));
1079 | self.ip++;
1080 | break;
1081 | case opcodes.AND_REG_WITH_REG:
1082 | regTo = checkGPR(memory.load(++self.ip));
1083 | regFrom = checkGPR(memory.load(++self.ip));
1084 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & self.gpr[regFrom]);
1085 | self.ip++;
1086 | break;
1087 | case opcodes.AND_REGADDRESS_WITH_REG:
1088 | regTo = checkGPR(memory.load(++self.ip));
1089 | regFrom = memory.load(++self.ip);
1090 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & memory.load(indirectRegisterAddress(regFrom)));
1091 | self.ip++;
1092 | break;
1093 | case opcodes.AND_ADDRESS_WITH_REG:
1094 | regTo = checkGPR(memory.load(++self.ip));
1095 | memFrom = memory.load(++self.ip);
1096 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & memory.load(memFrom));
1097 | self.ip++;
1098 | break;
1099 | case opcodes.AND_NUMBER_WITH_REG:
1100 | regTo = checkGPR(memory.load(++self.ip));
1101 | number = memory.load(++self.ip);
1102 | self.gpr[regTo] = checkOperation(self.gpr[regTo] & number);
1103 | self.ip++;
1104 | break;
1105 | case opcodes.OR_REG_WITH_REG:
1106 | regTo = checkGPR(memory.load(++self.ip));
1107 | regFrom = checkGPR(memory.load(++self.ip));
1108 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | self.gpr[regFrom]);
1109 | self.ip++;
1110 | break;
1111 | case opcodes.OR_REGADDRESS_WITH_REG:
1112 | regTo = checkGPR(memory.load(++self.ip));
1113 | regFrom = memory.load(++self.ip);
1114 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | memory.load(indirectRegisterAddress(regFrom)));
1115 | self.ip++;
1116 | break;
1117 | case opcodes.OR_ADDRESS_WITH_REG:
1118 | regTo = checkGPR(memory.load(++self.ip));
1119 | memFrom = memory.load(++self.ip);
1120 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | memory.load(memFrom));
1121 | self.ip++;
1122 | break;
1123 | case opcodes.OR_NUMBER_WITH_REG:
1124 | regTo = checkGPR(memory.load(++self.ip));
1125 | number = memory.load(++self.ip);
1126 | self.gpr[regTo] = checkOperation(self.gpr[regTo] | number);
1127 | self.ip++;
1128 | break;
1129 | case opcodes.XOR_REG_WITH_REG:
1130 | regTo = checkGPR(memory.load(++self.ip));
1131 | regFrom = checkGPR(memory.load(++self.ip));
1132 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ self.gpr[regFrom]);
1133 | self.ip++;
1134 | break;
1135 | case opcodes.XOR_REGADDRESS_WITH_REG:
1136 | regTo = checkGPR(memory.load(++self.ip));
1137 | regFrom = memory.load(++self.ip);
1138 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ memory.load(indirectRegisterAddress(regFrom)));
1139 | self.ip++;
1140 | break;
1141 | case opcodes.XOR_ADDRESS_WITH_REG:
1142 | regTo = checkGPR(memory.load(++self.ip));
1143 | memFrom = memory.load(++self.ip);
1144 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ memory.load(memFrom));
1145 | self.ip++;
1146 | break;
1147 | case opcodes.XOR_NUMBER_WITH_REG:
1148 | regTo = checkGPR(memory.load(++self.ip));
1149 | number = memory.load(++self.ip);
1150 | self.gpr[regTo] = checkOperation(self.gpr[regTo] ^ number);
1151 | self.ip++;
1152 | break;
1153 | case opcodes.NOT_REG:
1154 | regTo = checkGPR(memory.load(++self.ip));
1155 | self.gpr[regTo] = checkOperation(~self.gpr[regTo]);
1156 | self.ip++;
1157 | break;
1158 | case opcodes.SHL_REG_WITH_REG:
1159 | regTo = checkGPR(memory.load(++self.ip));
1160 | regFrom = checkGPR(memory.load(++self.ip));
1161 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << self.gpr[regFrom]);
1162 | self.ip++;
1163 | break;
1164 | case opcodes.SHL_REGADDRESS_WITH_REG:
1165 | regTo = checkGPR(memory.load(++self.ip));
1166 | regFrom = memory.load(++self.ip);
1167 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << memory.load(indirectRegisterAddress(regFrom)));
1168 | self.ip++;
1169 | break;
1170 | case opcodes.SHL_ADDRESS_WITH_REG:
1171 | regTo = checkGPR(memory.load(++self.ip));
1172 | memFrom = memory.load(++self.ip);
1173 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << memory.load(memFrom));
1174 | self.ip++;
1175 | break;
1176 | case opcodes.SHL_NUMBER_WITH_REG:
1177 | regTo = checkGPR(memory.load(++self.ip));
1178 | number = memory.load(++self.ip);
1179 | self.gpr[regTo] = checkOperation(self.gpr[regTo] << number);
1180 | self.ip++;
1181 | break;
1182 | case opcodes.SHR_REG_WITH_REG:
1183 | regTo = checkGPR(memory.load(++self.ip));
1184 | regFrom = checkGPR(memory.load(++self.ip));
1185 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> self.gpr[regFrom]);
1186 | self.ip++;
1187 | break;
1188 | case opcodes.SHR_REGADDRESS_WITH_REG:
1189 | regTo = checkGPR(memory.load(++self.ip));
1190 | regFrom = memory.load(++self.ip);
1191 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> memory.load(indirectRegisterAddress(regFrom)));
1192 | self.ip++;
1193 | break;
1194 | case opcodes.SHR_ADDRESS_WITH_REG:
1195 | regTo = checkGPR(memory.load(++self.ip));
1196 | memFrom = memory.load(++self.ip);
1197 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> memory.load(memFrom));
1198 | self.ip++;
1199 | break;
1200 | case opcodes.SHR_NUMBER_WITH_REG:
1201 | regTo = checkGPR(memory.load(++self.ip));
1202 | number = memory.load(++self.ip);
1203 | self.gpr[regTo] = checkOperation(self.gpr[regTo] >>> number);
1204 | self.ip++;
1205 | break;
1206 | default:
1207 | throw "Invalid op code: " + instr;
1208 | }
1209 |
1210 | return true;
1211 | } catch(e) {
1212 | self.fault = true;
1213 | throw e;
1214 | }
1215 | },
1216 | reset: function() {
1217 | var self = this;
1218 | self.maxSP = 231;
1219 | self.minSP = 0;
1220 |
1221 | self.gpr = [0, 0, 0, 0];
1222 | self.sp = self.maxSP;
1223 | self.ip = 0;
1224 | self.zero = false;
1225 | self.carry = false;
1226 | self.fault = false;
1227 | }
1228 | };
1229 |
1230 | cpu.reset();
1231 | return cpu;
1232 | }]);
1233 | ;app.service('memory', [function () {
1234 | var memory = {
1235 | data: Array(256),
1236 | lastAccess: -1,
1237 | load: function (address) {
1238 | var self = this;
1239 |
1240 | if (address < 0 || address >= self.data.length) {
1241 | throw "Memory access violation at " + address;
1242 | }
1243 |
1244 | self.lastAccess = address;
1245 | return self.data[address];
1246 | },
1247 | store: function (address, value) {
1248 | var self = this;
1249 |
1250 | if (address < 0 || address >= self.data.length) {
1251 | throw "Memory access violation at " + address;
1252 | }
1253 |
1254 | self.lastAccess = address;
1255 | self.data[address] = value;
1256 | },
1257 | reset: function () {
1258 | var self = this;
1259 |
1260 | self.lastAccess = -1;
1261 | for (var i = 0, l = self.data.length; i < l; i++) {
1262 | self.data[i] = 0;
1263 | }
1264 | }
1265 | };
1266 |
1267 | memory.reset();
1268 | return memory;
1269 | }]);
1270 | ;app.service('opcodes', [function() {
1271 | var opcodes = {
1272 | NONE: 0,
1273 | MOV_REG_TO_REG: 1,
1274 | MOV_ADDRESS_TO_REG: 2,
1275 | MOV_REGADDRESS_TO_REG: 3,
1276 | MOV_REG_TO_ADDRESS: 4,
1277 | MOV_REG_TO_REGADDRESS: 5,
1278 | MOV_NUMBER_TO_REG: 6,
1279 | MOV_NUMBER_TO_ADDRESS: 7,
1280 | MOV_NUMBER_TO_REGADDRESS: 8,
1281 | ADD_REG_TO_REG: 10,
1282 | ADD_REGADDRESS_TO_REG: 11,
1283 | ADD_ADDRESS_TO_REG: 12,
1284 | ADD_NUMBER_TO_REG: 13,
1285 | SUB_REG_FROM_REG: 14,
1286 | SUB_REGADDRESS_FROM_REG: 15,
1287 | SUB_ADDRESS_FROM_REG: 16,
1288 | SUB_NUMBER_FROM_REG: 17,
1289 | INC_REG: 18,
1290 | DEC_REG: 19,
1291 | CMP_REG_WITH_REG: 20,
1292 | CMP_REGADDRESS_WITH_REG: 21,
1293 | CMP_ADDRESS_WITH_REG: 22,
1294 | CMP_NUMBER_WITH_REG: 23,
1295 | JMP_REGADDRESS: 30,
1296 | JMP_ADDRESS: 31,
1297 | JC_REGADDRESS: 32,
1298 | JC_ADDRESS: 33,
1299 | JNC_REGADDRESS: 34,
1300 | JNC_ADDRESS: 35,
1301 | JZ_REGADDRESS: 36,
1302 | JZ_ADDRESS: 37,
1303 | JNZ_REGADDRESS: 38,
1304 | JNZ_ADDRESS: 39,
1305 | JA_REGADDRESS: 40,
1306 | JA_ADDRESS: 41,
1307 | JNA_REGADDRESS: 42,
1308 | JNA_ADDRESS: 43,
1309 | PUSH_REG: 50,
1310 | PUSH_REGADDRESS: 51,
1311 | PUSH_ADDRESS: 52,
1312 | PUSH_NUMBER: 53,
1313 | POP_REG: 54,
1314 | CALL_REGADDRESS: 55,
1315 | CALL_ADDRESS: 56,
1316 | RET: 57,
1317 | MUL_REG: 60,
1318 | MUL_REGADDRESS: 61,
1319 | MUL_ADDRESS: 62,
1320 | MUL_NUMBER: 63,
1321 | DIV_REG: 64,
1322 | DIV_REGADDRESS: 65,
1323 | DIV_ADDRESS: 66,
1324 | DIV_NUMBER: 67,
1325 | AND_REG_WITH_REG: 70,
1326 | AND_REGADDRESS_WITH_REG: 71,
1327 | AND_ADDRESS_WITH_REG: 72,
1328 | AND_NUMBER_WITH_REG: 73,
1329 | OR_REG_WITH_REG: 74,
1330 | OR_REGADDRESS_WITH_REG: 75,
1331 | OR_ADDRESS_WITH_REG: 76,
1332 | OR_NUMBER_WITH_REG: 77,
1333 | XOR_REG_WITH_REG: 78,
1334 | XOR_REGADDRESS_WITH_REG: 79,
1335 | XOR_ADDRESS_WITH_REG: 80,
1336 | XOR_NUMBER_WITH_REG: 81,
1337 | NOT_REG: 82,
1338 | SHL_REG_WITH_REG: 90,
1339 | SHL_REGADDRESS_WITH_REG: 91,
1340 | SHL_ADDRESS_WITH_REG: 92,
1341 | SHL_NUMBER_WITH_REG: 93,
1342 | SHR_REG_WITH_REG: 94,
1343 | SHR_REGADDRESS_WITH_REG: 95,
1344 | SHR_ADDRESS_WITH_REG: 96,
1345 | SHR_NUMBER_WITH_REG: 97
1346 | };
1347 |
1348 | return opcodes;
1349 | }]);
1350 | ;app.controller('Ctrl', ['$document', '$scope', '$timeout', 'cpu', 'memory', 'assembler', function ($document, $scope, $timeout, cpu, memory, assembler) {
1351 | $scope.memory = memory;
1352 | $scope.cpu = cpu;
1353 | $scope.error = '';
1354 | $scope.isRunning = false;
1355 | $scope.displayHex = true;
1356 | $scope.displayInstr = true;
1357 | $scope.displayA = false;
1358 | $scope.displayB = false;
1359 | $scope.displayC = false;
1360 | $scope.displayD = false;
1361 | $scope.speeds = [{speed: 1, desc: "1 HZ"},
1362 | {speed: 4, desc: "4 HZ"},
1363 | {speed: 8, desc: "8 HZ"},
1364 | {speed: 16, desc: "16 HZ"}];
1365 | $scope.speed = 4;
1366 | $scope.outputStartIndex = 232;
1367 |
1368 | $scope.code = "; Simple example\n; Writes Hello World to the output\n\n JMP start\nhello: DB \"Hello World!\" ; Variable\n DB 0 ; String terminator\n\nstart:\n MOV C, hello ; Point to var \n MOV D, 232 ; Point to output\n CALL print\n HLT ; Stop execution\n\nprint: ; print(C:*from, D:*to)\n PUSH A\n PUSH B\n MOV B, 0\n.loop:\n MOV A, [C] ; Get char from var\n MOV [D], A ; Write to output\n INC C\n INC D \n CMP B, [C] ; Check if end\n JNZ .loop ; jump if not\n\n POP B\n POP A\n RET";
1369 |
1370 | $scope.reset = function () {
1371 | cpu.reset();
1372 | memory.reset();
1373 | $scope.error = '';
1374 | $scope.selectedLine = -1;
1375 | };
1376 |
1377 | $scope.executeStep = function () {
1378 | if (!$scope.checkPrgrmLoaded()) {
1379 | $scope.assemble();
1380 | }
1381 |
1382 | try {
1383 | // Execute
1384 | var res = cpu.step();
1385 |
1386 | // Mark in code
1387 | if (cpu.ip in $scope.mapping) {
1388 | $scope.selectedLine = $scope.mapping[cpu.ip];
1389 | }
1390 |
1391 | return res;
1392 | } catch (e) {
1393 | $scope.error = e;
1394 | return false;
1395 | }
1396 | };
1397 |
1398 | var runner;
1399 | $scope.run = function () {
1400 | if (!$scope.checkPrgrmLoaded()) {
1401 | $scope.assemble();
1402 | }
1403 |
1404 | $scope.isRunning = true;
1405 | runner = $timeout(function () {
1406 | if ($scope.executeStep() === true) {
1407 | $scope.run();
1408 | } else {
1409 | $scope.isRunning = false;
1410 | }
1411 | }, 1000 / $scope.speed);
1412 | };
1413 |
1414 | $scope.stop = function () {
1415 | $timeout.cancel(runner);
1416 | $scope.isRunning = false;
1417 | };
1418 |
1419 | $scope.checkPrgrmLoaded = function () {
1420 | for (var i = 0, l = memory.data.length; i < l; i++) {
1421 | if (memory.data[i] !== 0) {
1422 | return true;
1423 | }
1424 | }
1425 |
1426 | return false;
1427 | };
1428 |
1429 | $scope.getChar = function (value) {
1430 | var text = String.fromCharCode(value);
1431 |
1432 | if (text.trim() === '') {
1433 | return '\u00A0\u00A0';
1434 | } else {
1435 | return text;
1436 | }
1437 | };
1438 |
1439 | $scope.assemble = function () {
1440 | try {
1441 | $scope.reset();
1442 |
1443 | var assembly = assembler.go($scope.code);
1444 | $scope.mapping = assembly.mapping;
1445 | var binary = assembly.code;
1446 | $scope.labels = assembly.labels;
1447 |
1448 | if (binary.length > memory.data.length)
1449 | throw "Binary code does not fit into the memory. Max " + memory.data.length + " bytes are allowed";
1450 |
1451 | for (var i = 0, l = binary.length; i < l; i++) {
1452 | memory.data[i] = binary[i];
1453 | }
1454 | } catch (e) {
1455 | if (e.line !== undefined) {
1456 | $scope.error = e.line + " | " + e.error;
1457 | $scope.selectedLine = e.line;
1458 | } else {
1459 | $scope.error = e.error;
1460 | }
1461 | }
1462 | };
1463 |
1464 | $scope.jumpToLine = function (index) {
1465 | $document[0].getElementById('sourceCode').scrollIntoView();
1466 | $scope.selectedLine = $scope.mapping[index];
1467 | };
1468 |
1469 |
1470 | $scope.isInstruction = function (index) {
1471 | return $scope.mapping !== undefined &&
1472 | $scope.mapping[index] !== undefined &&
1473 | $scope.displayInstr;
1474 | };
1475 |
1476 | $scope.getMemoryCellCss = function (index) {
1477 | if (index >= $scope.outputStartIndex) {
1478 | return 'output-bg';
1479 | } else if ($scope.isInstruction(index)) {
1480 | return 'instr-bg';
1481 | } else if (index > cpu.sp && index <= cpu.maxSP) {
1482 | return 'stack-bg';
1483 | } else {
1484 | return '';
1485 | }
1486 | };
1487 |
1488 | $scope.getMemoryInnerCellCss = function (index) {
1489 | if (index === cpu.ip) {
1490 | return 'marker marker-ip';
1491 | } else if (index === cpu.sp) {
1492 | return 'marker marker-sp';
1493 | } else if (index === cpu.gpr[0] && $scope.displayA) {
1494 | return 'marker marker-a';
1495 | } else if (index === cpu.gpr[1] && $scope.displayB) {
1496 | return 'marker marker-b';
1497 | } else if (index === cpu.gpr[2] && $scope.displayC) {
1498 | return 'marker marker-c';
1499 | } else if (index === cpu.gpr[3] && $scope.displayD) {
1500 | return 'marker marker-d';
1501 | } else {
1502 | return '';
1503 | }
1504 | };
1505 | }]);
1506 | ;app.filter('flag', function() {
1507 | return function(input) {
1508 | return input.toString().toUpperCase();
1509 | };
1510 | });
1511 | ;app.filter('number', function() {
1512 | return function(input, isHex) {
1513 | if (isHex) {
1514 | var hex = input.toString(16).toUpperCase();
1515 | return hex.length == 1 ? "0" + hex: hex;
1516 | } else {
1517 | return input.toString(10);
1518 | }
1519 | };
1520 | });
1521 | ;// Source: http://lostsource.com/2012/11/30/selecting-textarea-line.html
1522 | app.directive('selectLine', [function () {
1523 | return {
1524 | restrict: 'A',
1525 | link: function (scope, element, attrs, controller) {
1526 | scope.$watch('selectedLine', function () {
1527 | if (scope.selectedLine >= 0) {
1528 | var lines = element[0].value.split("\n");
1529 |
1530 | // Calculate start/end
1531 | var startPos = 0;
1532 | for (var x = 0; x < lines.length; x++) {
1533 | if (x == scope.selectedLine) {
1534 | break;
1535 | }
1536 | startPos += (lines[x].length + 1);
1537 | }
1538 |
1539 | var endPos = lines[scope.selectedLine].length + startPos;
1540 |
1541 | // Chrome / Firefox
1542 | if (typeof(element[0].selectionStart) != "undefined") {
1543 | element[0].focus();
1544 | element[0].selectionStart = startPos;
1545 | element[0].selectionEnd = endPos;
1546 | }
1547 |
1548 | // IE
1549 | if (document.selection && document.selection.createRange) {
1550 | element[0].focus();
1551 | element[0].select();
1552 | var range = document.selection.createRange();
1553 | range.collapse(true);
1554 | range.moveEnd("character", endPos);
1555 | range.moveStart("character", startPos);
1556 | range.select();
1557 | }
1558 | }
1559 | });
1560 | }
1561 | };
1562 | }]);
1563 | ;app.filter('startFrom', function() {
1564 | return function(input, start) {
1565 | start = +start; //parse to int
1566 | return input.slice(start);
1567 | };
1568 | });
1569 | ;app.directive('tabSupport', [function () {
1570 | return {
1571 | restrict: 'A',
1572 | link: function (scope, element, attrs, controller) {
1573 | element.bind("keydown", function (e) {
1574 | if (e.keyCode === 9) {
1575 | var val = this.value;
1576 | var start = this.selectionStart;
1577 | var end = this.selectionEnd;
1578 |
1579 | this.value = val.substring(0, start) + '\t' + val.substring(end);
1580 | this.selectionStart = this.selectionEnd = start + 1;
1581 |
1582 | e.preventDefault();
1583 | return false;
1584 | }
1585 | });
1586 | }
1587 | };
1588 | }]);
1589 |
--------------------------------------------------------------------------------