├── favicon.ico ├── Background.gif ├── breakpoint.png ├── assemblerTest.gif ├── nQueensPuzzle.jpg ├── seminar ├── PicoBlaze.doc ├── PicoBlaze.odp ├── PicoBlaze.odt ├── PicoBlaze.ppt ├── PicoBlaze.docx └── PicoBlaze.pptx ├── hexadecimal_counter.png ├── babel.config.js ├── index.php ├── schema.sql ├── .gitlab-ci.yml ├── __tests__ ├── Levenshtain_distance.test.js ├── tokenizer.test.js ├── parser.test.js ├── arithmetic_expressions.test.js ├── simulator.test.js └── assembler.test.js ├── sevenSegment.psm ├── deleteTheProgram.php ├── gray.psm ├── list_of_directives.js ├── .github └── workflows │ └── node.js.yml ├── package.json ├── LICENSE ├── db_helper.php ├── stop.svg ├── fastForward.svg ├── singleStep.svg ├── assemblerTest.psm ├── .gitignore ├── db.php ├── sharer.js ├── pause.svg ├── play.svg ├── preprocessor_test.psm ├── viewer.js ├── fibonacci.psm ├── examples.json ├── bin2dec.cpp ├── octal.psm ├── sierpinski.psm ├── bin2dec.psm ├── README.md ├── tokenizer.js ├── regbanks_flags_test.psm ├── jest.config.js ├── nQueensPuzzle.psm ├── dec2bin.psm ├── styles.css ├── permutations.psm ├── preprocessor.js ├── footerScript.js ├── TreeNode.js └── parser.js /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlatAssembler/PicoBlaze_Simulator_in_JS/HEAD/favicon.ico -------------------------------------------------------------------------------- /Background.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlatAssembler/PicoBlaze_Simulator_in_JS/HEAD/Background.gif -------------------------------------------------------------------------------- /breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlatAssembler/PicoBlaze_Simulator_in_JS/HEAD/breakpoint.png -------------------------------------------------------------------------------- /assemblerTest.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlatAssembler/PicoBlaze_Simulator_in_JS/HEAD/assemblerTest.gif -------------------------------------------------------------------------------- /nQueensPuzzle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlatAssembler/PicoBlaze_Simulator_in_JS/HEAD/nQueensPuzzle.jpg -------------------------------------------------------------------------------- /seminar/PicoBlaze.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlatAssembler/PicoBlaze_Simulator_in_JS/HEAD/seminar/PicoBlaze.doc -------------------------------------------------------------------------------- /seminar/PicoBlaze.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlatAssembler/PicoBlaze_Simulator_in_JS/HEAD/seminar/PicoBlaze.odp -------------------------------------------------------------------------------- /seminar/PicoBlaze.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlatAssembler/PicoBlaze_Simulator_in_JS/HEAD/seminar/PicoBlaze.odt -------------------------------------------------------------------------------- /seminar/PicoBlaze.ppt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlatAssembler/PicoBlaze_Simulator_in_JS/HEAD/seminar/PicoBlaze.ppt -------------------------------------------------------------------------------- /hexadecimal_counter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlatAssembler/PicoBlaze_Simulator_in_JS/HEAD/hexadecimal_counter.png -------------------------------------------------------------------------------- /seminar/PicoBlaze.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlatAssembler/PicoBlaze_Simulator_in_JS/HEAD/seminar/PicoBlaze.docx -------------------------------------------------------------------------------- /seminar/PicoBlaze.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlatAssembler/PicoBlaze_Simulator_in_JS/HEAD/seminar/PicoBlaze.pptx -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', {targets: {node: 'current'}}]], 3 | plugins: ['babel-plugin-rewire', 'export-toplevel'], 4 | }; -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 4 | Click here in case your browser does not automatically redirect you. 5 | -------------------------------------------------------------------------------- /schema.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE assembler_db; 2 | USE assembler_db; 3 | 4 | CREATE TABLE programs ( 5 | /* use UUID instead of INT AUTO_INCREMENT ? */ 6 | id INT AUTO_INCREMENT PRIMARY KEY, 7 | code TEXT NOT NULL, 8 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 9 | ); -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | javascript: 2 | image: node:latest 3 | stage: test 4 | before_script: 5 | - 'yarn global add jest' 6 | - 'yarn add --dev jest-junit' 7 | script: 8 | - 'jest --ci --reporters=default --reporters=jest-junit' 9 | artifacts: 10 | when: always 11 | reports: 12 | junit: 13 | - junit.xml 14 | -------------------------------------------------------------------------------- /__tests__/Levenshtain_distance.test.js: -------------------------------------------------------------------------------- 1 | const tree = require("../TreeNode.js"); 2 | global.LevenshtainDistance=tree.LevenshtainDistance; 3 | 4 | 5 | describe("Evaluation of Levenshtain Distance", () => { 6 | test("Two empty strings", () => { 7 | expect(LevenshtainDistance("","")).toEqual( 8 | 0, 9 | ); 10 | }); 11 | test("Empty string and a non-emtpy string", () => { 12 | expect(LevenshtainDistance("","ABC")).toEqual( 13 | 3, 14 | ); 15 | }); 16 | test("The \"kitten\"-\"sitting\" test", () => { // https://en.wikipedia.org/wiki/Levenshtein_distance#Example 17 | expect(LevenshtainDistance("kitten","sitting")).toEqual( 18 | 3, 19 | ); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /sevenSegment.psm: -------------------------------------------------------------------------------- 1 | ;Hexadecimal counter, 2 | ;useful for testing the seven-segment 3 | ;displays and the LEDs. 4 | ;Would run too fast on real PicoBlaze 5 | ;to be useful. 6 | address 000 7 | load s0,0 8 | load s1,0 9 | load s3,10100000'b 10 | load s4,0 11 | infiniteLoop: 12 | add s0,1 13 | jump nc,doNotIncreaseS1 14 | add s1,1 15 | doNotIncreaseS1: 16 | load s5,s4 17 | load s4,s0 18 | and s4,f0 19 | compare s4,s5 20 | jump z,theSecondDigitHasNotChanged 21 | rr s3 22 | theSecondDigitHasNotChanged: 23 | constant hex1_port,1 24 | constant hex2_port,2 25 | constant led_port,0 26 | output s0,hex2_port 27 | output s1,hex1_port 28 | output s3,led_port 29 | jump infiniteLoop 30 | -------------------------------------------------------------------------------- /deleteTheProgram.php: -------------------------------------------------------------------------------- 1 | getConnection(); 17 | $stmt = $conn->prepare("DELETE FROM programs WHERE id = :id"); 18 | $stmt->bindParam(':id', $_POST["id"]); 19 | $stmt->execute(); 20 | 21 | $result = $stmt->fetch(PDO::FETCH_ASSOC); 22 | echo $result; 23 | } else { 24 | http_response_code(400); 25 | die("You did not specify the id of the program which should be deleted!"); 26 | } 27 | -------------------------------------------------------------------------------- /gray.psm: -------------------------------------------------------------------------------- 1 | ;An example program which converts binary 2 | ;to Gray code and vice versa, maybe it 3 | ;comes useful to somebody. 4 | ;There is also a Reddit discussion about 5 | ;this program. 6 | address 0 7 | start: ;Infinite loop... 8 | ;Converting from binary to gray... 9 | constant binary_input,0 10 | constant gray_output,0 11 | input s0,binary_input 12 | load s1,s0 13 | sr0 s1 14 | xor s1,s0 15 | output s1,gray_output 16 | ;Converting from gray to binary... 17 | constant gray_input,1 18 | constant binary_output,1 19 | input s0,gray_input 20 | load s1,s0 21 | convert_to_binary_loop: 22 | sr0 s1 23 | xor s0,s1 24 | compare s1,0 25 | jump nz,convert_to_binary_loop 26 | output s0,binary_output 27 | jump start 28 | -------------------------------------------------------------------------------- /list_of_directives.js: -------------------------------------------------------------------------------- 1 | const mnemonics = [ 2 | "ADD", "ADDCY", "ADDC", "AND", "CALL", "CALL@", "COMPARE", 3 | "COMP", "DISABLE", "ENABLE", "FETCH", "INPUT", "IN", "JUMP", 4 | "JUMP@", "LOAD", "OR", "OUTPUT", "OUT", "RETURN", "RET", 5 | "RETURNI", "RETI", "RL", "RR", "SL0", "SL1", "SLA", 6 | "SLX", "SR0", "SR1", "SRA", "SRX", "STORE", "SUB", 7 | "SUBCY", "SUBC", "TEST", "XOR", "INST", "LOAD&RETURN", "HWBUILD", 8 | "STAR", "OUTPUTK", "REGBANK", "TESTCY", "TESTC", "COMPARECY", "COMPCY", 9 | ]; 10 | const preprocessor = [ 11 | "ADDRESS", 12 | "ORG", 13 | "VHDL", 14 | "EQU", 15 | "NAMEREG", 16 | "CONSTANT", 17 | "DISPLAY", 18 | "IF", 19 | "ELSE", 20 | "ENDIF", 21 | "WHILE", 22 | "ENDWHILE", 23 | "BASE_DECIMAL", 24 | "BASE_HEXADECIMAL", 25 | "PRINT_STRING", 26 | ]; 27 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [16.x, 18.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm test 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "picoblaze-simulator-js", 3 | "version": "6.0.1", 4 | "description": "A PicoBlaze simulator in JS", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "jest --verbose" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS.git" 12 | }, 13 | "keywords": [ 14 | "picoblaze", 15 | "githubpages" 16 | ], 17 | "author": "FlatAssembler", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues" 21 | }, 22 | "homepage": "https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS#readme", 23 | "engines": { 24 | "npm": ">=8.0.0", 25 | "node": ">=16.0.0" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.22.10", 29 | "@babel/preset-env": "^7.22.10", 30 | "babel-jest": "^29.6.3", 31 | "babel-plugin-export-toplevel": "^1.0.0", 32 | "babel-plugin-rewire": "^1.2.0", 33 | "jest": "^29.6.4", 34 | "jest-environment-jsdom": "^29.6.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Teo Samaržija 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /db_helper.php: -------------------------------------------------------------------------------- 1 | connection = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password); 24 | $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 25 | } catch (PDOException $e) { 26 | http_response_code(500); 27 | die("Connection to the database failed: " . $e->getMessage()); 28 | } 29 | } 30 | 31 | public static function getInstance() { 32 | if (!isset(self::$instance)) { 33 | self::$instance = new Database(); 34 | } 35 | return self::$instance; 36 | } 37 | 38 | public function getConnection() { 39 | return $this->connection; 40 | } 41 | } 42 | 43 | ?> 44 | -------------------------------------------------------------------------------- /stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 33 | 35 | 37 | 41 | 45 | 46 | 47 | 51 | 52 | -------------------------------------------------------------------------------- /fastForward.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /singleStep.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /assemblerTest.psm: -------------------------------------------------------------------------------- 1 | ;Examples of the assembly syntax: 2 | address 0 3 | ;Data directives... 4 | beginningOfDataDirectives: ;Label 5 | load s0, s1 6 | load s1, 14'd / 2 7 | star s2, s3 8 | star s3, 1 + 2 * 3 9 | namereg sf, stack_pointer ;Preprocessor 10 | store s4, (stack_pointer) 11 | store s5, FF 12 | fetch s6, (sa) 13 | fetch s7, A 14 | input s8, (sb) 15 | input s9, (1 + 2) * 3 16 | output sa, (sc) 17 | output sa, -1 + 15'd / 2 18 | constant eight, 15'd/2 ;Rounding 19 | outputk eight, a 20 | jump endOfDataDirectives 21 | regbank a 22 | regbank B 23 | endOfDataDirectives: hwbuild se 24 | jump beginningOfDataDirectives 25 | ;Conditional jumps... 26 | beginningOfConditionalJumps: 27 | jump z,beginningOfConditionalJumps 28 | jump nZ,beginningOfConditionalJumps 29 | jump C,beginningOfConditionalJumps 30 | jump Nc,beginningOfConditionalJumps 31 | ;Setting the program counter (PC)... 32 | jump@(sD,sE) 33 | ;The "call" instruction... 34 | call function 35 | call z,function 36 | call nz,function 37 | call c, function 38 | call nc, function 39 | call@ (sA,sb) 40 | ;Various "return" statements... 41 | function: 42 | return 43 | return z 44 | return nz 45 | return c 46 | return nc 47 | ;Arithmetic and logical operators... 48 | add s0,s1 49 | add s2,5+10'd/2 50 | addcy s3,s4 51 | addc s5,3+12'd/2+1 52 | sub s6,S7 53 | sub s8,ff 54 | subc s9,sa 55 | subcy sb,a+b 56 | and sc,sd 57 | and se,ff 58 | or sf,se 59 | or SE,00 60 | xor sf,se 61 | xor SE,00 62 | test sc,sd 63 | test se,ff 64 | testcy sc,sd 65 | testc se,ff 66 | compare s0,stack_pointer 67 | comp s1,eight 68 | comparecy s0,stack_pointer 69 | compcy s1,eight 70 | ;Bitwise operators... 71 | sl0 s0 72 | sl1 s1 73 | slx s2 74 | sla s3 75 | rl s4 76 | sr0 s5 77 | sr1 s6 78 | srx s7 79 | sra s8 80 | rr s9 81 | ;Interrupts... 82 | Disable interrupt 83 | Enable InterrupT 84 | ;Ternary conditional operator 85 | load s0, 1+2=3?5:4 86 | ;Bitwise operators in the preprocessor 87 | inst bitand(invertBits(a),f)=5?5:0 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Intellij ### 4 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 5 | 6 | *.iml 7 | 8 | ## Directory-based project format: 9 | .idea/ 10 | # if you remove the above rule, at least ignore the following: 11 | 12 | # User-specific stuff: 13 | # .idea/workspace.xml 14 | # .idea/tasks.xml 15 | # .idea/dictionaries 16 | 17 | # Sensitive or high-churn files: 18 | # .idea/dataSources.ids 19 | # .idea/dataSources.xml 20 | # .idea/sqlDataSources.xml 21 | # .idea/dynamic.xml 22 | # .idea/uiDesigner.xml 23 | 24 | # Gradle: 25 | # .idea/gradle.xml 26 | # .idea/libraries 27 | 28 | # Mongo Explorer plugin: 29 | # .idea/mongoSettings.xml 30 | 31 | ## File-based project format: 32 | *.ipr 33 | *.iws 34 | 35 | ## Plugin-specific files: 36 | 37 | # IntelliJ 38 | /out/ 39 | 40 | # mpeltonen/sbt-idea plugin 41 | .idea_modules/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Crashlytics plugin (for Android Studio and IntelliJ) 47 | com_crashlytics_export_strings.xml 48 | crashlytics.properties 49 | crashlytics-build.properties 50 | 51 | 52 | ### Node ### 53 | # Logs 54 | logs 55 | *.log 56 | 57 | # Runtime data 58 | pids 59 | *.pid 60 | *.seed 61 | 62 | # Directory for instrumented libs generated by jscoverage/JSCover 63 | lib-cov 64 | 65 | # Coverage directory used by tools like istanbul 66 | coverage 67 | 68 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 69 | .grunt 70 | 71 | # node-waf configuration 72 | .lock-wscript 73 | 74 | # Compiled binary addons (http://nodejs.org/api/addons.html) 75 | build/Release 76 | 77 | # Dependency directory 78 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 79 | node_modules 80 | 81 | #VisualStudio files 82 | .vs/* 83 | 84 | #The file containing the password. 85 | .env 86 | 87 | #The authentication files for SEO. 88 | BingSiteAuth.xml 89 | google2f3fce6b4fcc944c.html 90 | -------------------------------------------------------------------------------- /db.php: -------------------------------------------------------------------------------- 1 | getConnection(); 7 | 8 | // WARNING: The following piece of code is AI-generated, and I don't know enough PHP to tell whether it is correct. 9 | if (isset($_POST['code'])) { 10 | $code = $_POST['code']; 11 | 12 | // 1. Check if the code already exists in the database 13 | $stmt = $conn->prepare("SELECT id FROM programs WHERE code = :code"); 14 | $stmt->bindParam(':code', $code); 15 | $stmt->execute(); 16 | 17 | $result = $stmt->fetch(PDO::FETCH_ASSOC); 18 | 19 | if ($result) { 20 | // 2. If exists, return the existing id 21 | echo "?id=" . $result['id']; 22 | } else { 23 | // 3. If not, insert the new code and return its new id 24 | $stmt = $conn->prepare("INSERT INTO programs (code) VALUES (:code)"); 25 | $stmt->bindParam(':code', $code); 26 | 27 | try { 28 | $stmt->execute(); 29 | $lastInsertedId = $conn->lastInsertId(); 30 | echo "?id=" . $lastInsertedId; 31 | } catch (PDOException $e) { 32 | http_response_code(500); 33 | echo "Error: " . $e->getMessage(); 34 | } 35 | } 36 | } 37 | //End of the AI-generated code. 38 | 39 | if (isset($_GET['id'])) { 40 | $id = $_GET['id']; 41 | 42 | if ($id == "") { 43 | echo "NO"; 44 | return; 45 | } 46 | 47 | $stmt = $conn->prepare("SELECT code FROM programs WHERE id = :id"); 48 | $stmt->bindParam(':id', $id); 49 | $stmt->execute(); 50 | 51 | $result = $stmt->fetch(PDO::FETCH_ASSOC); 52 | if ($result) { 53 | $programCode = $result['code']; 54 | // mysql uses \r\n, the browser uses \n 55 | $programCode = str_replace("\r\n", "\n", $programCode); 56 | echo $programCode; 57 | } else { 58 | http_response_code(404); 59 | $dbname = $GLOBALS['dbname']; 60 | echo "Error 404:\nProgram with the ID \"$id\" not found\nin the database \"$dbname\"!"; 61 | } 62 | } 63 | ?> 64 | -------------------------------------------------------------------------------- /sharer.js: -------------------------------------------------------------------------------- 1 | const btn = document.getElementById("shareButton"); 2 | 3 | btn.addEventListener("click", (e) => { 4 | console.log("====\nSaving the program====\n"); 5 | saveAssemblyCode(); 6 | }); 7 | 8 | function saveAssemblyCode() { 9 | const assemblyCode = document.getElementById("assemblyCode").innerText; 10 | 11 | const formData = new FormData(); 12 | formData.append("code", assemblyCode); 13 | 14 | fetch("db.php", {method : "POST", body : formData, redirect : "error"}) 15 | .then((response) => { 16 | if (!response.ok) { 17 | throw new Error("The server responded with error: " + 18 | response.status); 19 | } 20 | 21 | return response.text(); 22 | }) 23 | 24 | .then((data) => { 25 | // data is ?id=int 26 | if (!/^\?id=\d+/.test( 27 | data)) { // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/36 28 | throw new Error( 29 | "The server responded with `200 OK`, but the data it sent is not formatted to be parsable by the front-end: " + 30 | data); 31 | } 32 | const shareURL = 33 | `${new URL(window.location.href).origin}/PicoBlaze.html${data}`; 34 | document.getElementById("shareURL").innerText = shareURL; 35 | document.getElementById("uploadSuccessfulMessage").style.display = 36 | "block"; 37 | }) 38 | 39 | .catch((error) => { alert(error); }); 40 | } 41 | 42 | function copyShareURLToClipboard() { 43 | const shareURL = document.getElementById("shareURL").innerText; 44 | if (!navigator.clipboard) { 45 | alert( 46 | "Your browser, for some reason, doesn't let JavaScript access the clipboard. You will need to copy the URL manually."); 47 | return; 48 | } 49 | navigator.clipboard.writeText(shareURL) 50 | .then(() => { alert("The URL is successfully copied!"); }) 51 | .catch( 52 | () => {alert( 53 | "Copying the URL to clipboard didn't succeed. You will need to copy the URL manually.")}); 54 | } 55 | -------------------------------------------------------------------------------- /pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 48 | 50 | 52 | 56 | 60 | 61 | 62 | 66 | 67 | -------------------------------------------------------------------------------- /play.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 48 | 50 | 55 | 59 | 63 | 64 | 65 | 69 | 70 | -------------------------------------------------------------------------------- /preprocessor_test.psm: -------------------------------------------------------------------------------- 1 | ;This is a program to test the 2 | ;new features of the preprocessor, 3 | ;the `display` directive, 4 | ;`if`-`else` branching and 5 | ;`while` loops, by 6 | ;printing the Fibonacci numbers 7 | ;smaller than 1000 on the terminal 8 | ;during the assembly time. 9 | ;This program compiles to no machine 10 | ;code at all, and it does nothing except 11 | ;causing an error message right after 12 | ;assembly (that the assembler written in 13 | ;JavaScript sent no machine code to the 14 | ;main program written in Java) in 15 | ;PicoBlaze Simulator for Android. 16 | ;I have started a 17 | ;forum thread about this program. 18 | 19 | ;WARNING: Please do not press 20 | ; "Highlight Assembly" 21 | ; before you assemble this program, 22 | ; as there is a bug in the syntax 23 | ; highlighter inserting a 24 | ; semi-colon after the less-than 25 | ; and greater-than characters, 26 | ; causing syntax errors in programs 27 | ; such as this one. I have opened 28 | ; a GitHub issue about that. 29 | 30 | base_decimal ;The PicoBlaze assembler by default considers numerical literals to be hexadecimal. 31 | 32 | display "We are about to display Fibonacci numbers smaller than 1000 in the UART terminal... " 33 | display a'x ;0xa=='\n', a new-line character. 34 | constant first, 0 35 | constant second, 1 36 | while first < 1000 37 | if first < 10 38 | display "0" + first 39 | display " " ;In case inserting a new-line character doesn't work... 40 | ;I have opened a GitHub issue about that problem. 41 | display a'x 42 | else 43 | constant remainder, first 44 | constant counter, 0 45 | while remainder > 10 - 1 46 | constant remainder, remainder - 10 47 | constant counter, counter + 1 48 | endwhile 49 | if counter > 10 - 1 ;If first is bigger than 99. 50 | constant hundreds, 0 51 | while counter > 10 - 1 52 | constant counter, counter - 10 53 | constant hundreds, hundreds + 1 54 | endwhile 55 | display hundreds + "0" 56 | endif 57 | display counter + "0" 58 | display remainder + "0" 59 | display " " 60 | display a'x 61 | endif 62 | constant second, first + second 63 | constant first, second - first 64 | endwhile 65 | -------------------------------------------------------------------------------- /viewer.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", function() { 2 | const urlParams = new URLSearchParams(window.location.search); 3 | 4 | if (urlParams.has("id")) { 5 | document.getElementById("assemblyCode").innerHTML = 6 | "Fetching the program from SourceForge..."; 7 | const id = urlParams.get("id"); 8 | const url = `db.php?id=${encodeURIComponent(id)}`; 9 | console.log(url); 10 | fetch(url, {redirect : "error"}) 11 | .then((response) => { 12 | if (!response.ok && response.status != 404) { 13 | throw new Error("The server responded with error " + 14 | response.status); 15 | } 16 | 17 | return response.text(); 18 | }) 19 | 20 | .then((data) => { 21 | const asm = document.getElementById("assemblyCode"); 22 | data = data.replace("\r\n", "\n"); 23 | document.getElementById("assemblyCode").innerText = data; 24 | console.log(asm.textContent); 25 | setUpLineNumbers(); 26 | document.getElementById("deleteTheProgram").style.display = "grid"; 27 | document.getElementById("place_in_the_button_for_id").innerText = 28 | urlParams.get("id"); 29 | setupLayout(); 30 | document.getElementById("deleteTheProgramButton").onclick = () => { 31 | const formData = 32 | new FormData(); // Doing it with `URLSearchParams` causes 33 | // Firefox 52 to send an empty POST request to 34 | // the server. 35 | formData.append("id", urlParams.get("id")); 36 | formData.append("password", 37 | document.getElementById("input_password").value); 38 | fetch("deleteTheProgram.php", 39 | {method : "POST", redirect : "error", body : formData}) 40 | .then((response) => { return response.text(); }) 41 | .then( 42 | (data) => { alert("The server responded with: " + data); }) 43 | .catch((error) => { alert(error.message); }); 44 | }; 45 | }) 46 | 47 | .catch((error) => { 48 | // Escape user-supplied ID to prevent XSS 49 | function escapeHtml(str) { 50 | return str.replace(/[&<>"'`]/g, function (s) { 51 | return ({ 52 | '&': '&', 53 | '<': '<', 54 | '>': '>', 55 | '"': '"', 56 | "'": ''', 57 | '`': '`' 58 | })[s]; 59 | }); 60 | } 61 | document.getElementById("assemblyCode").innerHTML = 62 | `;Unfortunately, fetching the example 63 | ;program "${escapeHtml(id)}" from SourceForge failed. 64 | ;No worries, you can still select one of 65 | ;the example programs to fetch from 66 | ;GitHub. 67 | `; 68 | setUpLineNumbers(); 69 | alert(error.message); 70 | }); 71 | } 72 | }); 73 | -------------------------------------------------------------------------------- /fibonacci.psm: -------------------------------------------------------------------------------- 1 | ;Count the number of ones in the binary 2 | ;representations of the numbers in the 3 | ;Fibonacci sequence. Output the indexes, 4 | ;the Fibonacci numbers and the number of 5 | ;ones in the binary representations as 6 | ;decimal (base 10) numbers. 7 | ;I have asked a MathOverflow question 8 | ;about the output of this program. 9 | Address 0 10 | ;On real PicoBlaze, disabling interrupts 11 | ;makes the program run slightly faster. 12 | ;So, let us do that here (although it has 13 | ;no effect in this simulator). 14 | DISABLE INTERRUPT 15 | ;The zeroth Fibonacci number is 0, and the 16 | ;first one is 1. Let us store them in the 17 | ;memory. 18 | Load s0,0 19 | load s1,1 20 | store s0,0 21 | store s1,1 22 | ;Output the results for 0th and 1st... 23 | Output s0,0 24 | output s0,1 25 | output s0,2 26 | output s1,3 27 | output s1,4 28 | output s1,5 29 | ;Load the pointers into registers... 30 | Load s5,6 31 | load s2,2 32 | ;Now goes the loop for calculating the 33 | ;Fibonacci numbers. We don't know the 34 | ;number of repetitions in advance, and 35 | ;we will stop when we can no longer store 36 | ;current Fibonacci number in a register. 37 | Infinite_loop: 38 | Regbank b 39 | ;Test if "flag_B_Z" works, 40 | ;because the rest of the program 41 | ;doesn't seem to trigger it. 42 | xor sf,ff 43 | Regbank a 44 | ;Fetch the previous 2 Fibonacci numbers 45 | ;from the RAM memory... 46 | sub s2, 2 47 | Fetch s0,(s2) 48 | add s2,1 49 | fetch s1,(s2) 50 | add s2,1 51 | ;Add them together... 52 | add s1,s0 53 | ;If the result does not fit in the 54 | ;register, go to the end of the program. 55 | jump c,overflow 56 | ;Store the just calculated Fibonacci 57 | ;number into RAM... 58 | store s1,(s2) 59 | ;Count the number of 1-s in its binary 60 | ;representation... 61 | load s4,0 62 | load s3,2^7 63 | count_the_ones_loop: ;Repeats 8 times. 64 | Test s1,s3 65 | jump z,it_is_zero 66 | add s4,1 67 | it_is_zero: 68 | sr0 s3 69 | jump nc,count_the_ones_loop 70 | ;Output the index as a decimal... 71 | load sa,s2 72 | call divideBy10 73 | call multiplyBy16 74 | add sa,sb 75 | output sa,(s5) 76 | add s5,1 77 | ;Output the Fibonacci number... 78 | Load sa,s1 79 | compare sa,100'd 80 | ;If it's less than 100... 81 | ;display as decimal. 82 | Jump nc,displayFibonacciAsHexadecimal 83 | call divideBy10 84 | call multiplyBy16 85 | add sa,sb 86 | displayFibonacciAsHexadecimal: 87 | output sa,(s5) 88 | add s5,1 89 | ;Output the number of ones... 90 | Output s4,(s5) ;Always less than 10. 91 | Add s5,1 92 | add s2,1 93 | jump Infinite_loop 94 | overflow: return 95 | 96 | divideBy10: 97 | star s0,sa 98 | regbank b 99 | load s1,0 100 | beginning_of_loop: 101 | compare s0,10'd 102 | jump c,end_of_loop 103 | sub s0,10'd 104 | add s1,1 105 | jump beginning_of_loop 106 | end_of_loop: 107 | star sa,s1 108 | star sb,s0 109 | regbank a 110 | return 111 | 112 | multiplyBy16: 113 | ;16=2^4 114 | sl0 sa 115 | sl0 sa 116 | sl0 sa 117 | sl0 sa 118 | return 119 | -------------------------------------------------------------------------------- /__tests__/tokenizer.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Hackish way to import TreeNode using a babel plugin to export all without modifying the actual code. 3 | Ideally the project should be using ES modules or a bundler like webpack. 4 | */ 5 | const tree = require("../TreeNode.js"); 6 | global.TreeNode = tree.TreeNode; //referenced by tokenizer 7 | 8 | const tokenizer = require("../tokenizer.js"); 9 | 10 | describe("PicoBlaze Tokenizer", () => { 11 | test("ignores comments", () => { 12 | const tokens = tokenizer.tokenize(` 13 | load s0, 123 ;this is a comment 14 | `); 15 | expect(tokens.map((t) => t.text)).toEqual([ 16 | "\n", 17 | "load", 18 | "s0", 19 | ",", 20 | "123", 21 | "\n", 22 | "\n", 23 | ]); 24 | }); 25 | 26 | test("includes binary literals", () => { 27 | const tokens = tokenizer.tokenize("load s0, 10100000'b"); 28 | expect(tokens.map((t) => t.text)).toEqual([ 29 | "load", 30 | "s0", 31 | ",", 32 | "10100000'b", 33 | "\n", 34 | ]); 35 | }); 36 | 37 | test("is whitespace insensitive", () => { 38 | const tokens = tokenizer.tokenize("load s0, 0"); 39 | expect(tokens.map((t) => t.text)).toEqual(["load", "s0", ",", "0", "\n"]); 40 | }); 41 | 42 | test("is newline sensitive", () => { 43 | const tokens = tokenizer.tokenize("addr\ness 0"); 44 | expect(tokens.map((t) => t.text)).toEqual(["addr", "\n", "ess", "0", "\n"]); 45 | }); 46 | 47 | test('" " is a single token', () => { 48 | const tokens = tokenizer.tokenize( 49 | 'load s9, " " ; https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/5', 50 | ); 51 | expect(tokens.map((t) => t.text)).toEqual(["load", "s9", ",", '" "', "\n"]); 52 | }); 53 | 54 | test("Labels are tokenized correctly 1", () => { 55 | const tokens = tokenizer.tokenize( 56 | `inst 2+2<5?1:0 57 | label: 58 | jump label`, 59 | ); 60 | expect(tokens.map((t) => t.text)).toEqual([ 61 | "inst", 62 | "2", 63 | "+", 64 | "2", 65 | "<", 66 | "5", 67 | "?", 68 | "1", 69 | ":", 70 | "0", 71 | "\n", 72 | "label:", 73 | "\n", 74 | "jump", 75 | "label", 76 | "\n", 77 | ]); 78 | }); 79 | test("Labels are tokenized correctly 2",() => { // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/31 80 | const tokens=tokenizer.tokenize( 81 | `address 0 82 | label1: 83 | load s0, s1 84 | label2: load s1, 1` 85 | ); 86 | expect(tokens.map((t) => t.text)).toEqual( 87 | [ 88 | "address", 89 | "0", 90 | "\n", 91 | "label1:", 92 | "\n", 93 | "load", 94 | "s0", 95 | ",", 96 | "s1", 97 | "\n", 98 | "label2:", 99 | "load", 100 | "s1", 101 | ",", 102 | "1", 103 | "\n", 104 | ] 105 | ) 106 | }) 107 | }); 108 | -------------------------------------------------------------------------------- /examples.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "0", 4 | "file_name": "bin2dec.psm", 5 | "image": "https://upload.wikimedia.org/wikipedia/commons/2/21/Arabic_numerals-en.svg", 6 | "image_alt": "Numeral Systems", 7 | "name": "Binary to Decimal" 8 | }, 9 | { 10 | "id": "1", 11 | "file_name": "dec2bin.psm", 12 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9f/DEC_VT100_terminal_transparent.png/220px-DEC_VT100_terminal_transparent.png", 13 | "image_alt": "UART Terminal", 14 | "name": "Decimal to Binary" 15 | }, 16 | { 17 | "id": "2", 18 | "file_name": "octal.psm", 19 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Evo8glyph.svg/330px-Evo8glyph.svg.png", 20 | "image_alt": "Number 8", 21 | "name": "Binary to Octal" 22 | }, 23 | { 24 | "id": "3", 25 | "file_name": "permutations.psm", 26 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Permutations_RGB.svg/180px-Permutations_RGB.svg.png", 27 | "image_alt": "Permutations", 28 | "name": "Permutations Algorithm" 29 | }, 30 | { 31 | "id": "4", 32 | "file_name": "fibonacci.psm", 33 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/7a/FibonacciRabbit.svg/220px-FibonacciRabbit.svg.png", 34 | "image_alt": "Rabbit Experiment", 35 | "name": "Fibonacci Sequence" 36 | }, 37 | { 38 | "id": "5", 39 | "file_name": "gray.psm", 40 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c2/Gray_code_tesseract.svg/220px-Gray_code_tesseract.svg.png", 41 | "image_alt": "Hamming Cube", 42 | "name": "Gray Code" 43 | }, 44 | { 45 | "id": "6", 46 | "file_name": "assemblerTest.psm", 47 | "image": "https://flatassembler.github.io/PicoBlaze/assemblerTest.gif", 48 | "image_alt": "Assembly Code", 49 | "name": "Assembler Test" 50 | }, 51 | { 52 | "id": "7", 53 | "file_name": "sevenSegment.psm", 54 | "image": "https://flatassembler.github.io/PicoBlaze/hexadecimal_counter.png", 55 | "image_alt": "Digital Clock", 56 | "name": "Hexadecimal Counter" 57 | }, 58 | { 59 | "id": "8", 60 | "file_name": "regbanks_flags_test.psm", 61 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dc/Intel_80486DX2_top.jpg/180px-Intel_80486DX2_top.jpg", 62 | "image_alt": "CPU", 63 | "name": "Regbanks-Flags Test" 64 | }, 65 | { 66 | "id": "9", 67 | "file_name": "preprocessor_test.psm", 68 | "image": "https://www.theflatearthsociety.org/forum/avr/avatar_1461509_1483220545.png", 69 | "image_alt": "Flat Assembler showcase", 70 | "name": "Preprocessor Test" 71 | }, 72 | { 73 | "id": "10", 74 | "file_name": "nQueensPuzzle.psm", 75 | "image": "https://flatassembler.github.io/PicoBlaze/nQueensPuzzle.jpg", 76 | "image_alt": "Eight Queens Puzzle", 77 | "name": "N-Queens Puzzle" 78 | }, 79 | { 80 | "id": "11", 81 | "file_name": "sierpinski.psm", 82 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/45/Sierpinski_triangle.svg/500px-Sierpinski_triangle.svg.png", 83 | "image_alt": "The Sierpinski Triangle", 84 | "name": "Sierpinski Triangle" 85 | } 86 | ] 87 | -------------------------------------------------------------------------------- /bin2dec.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Compile on 64-bit Linux or Solaris (I guess it will probably also work on 3 | * FreeBSD), like this: 4 | * g++ -o bin2dec bin2dec.cpp -std=c++11 -O3 5 | * Or like this: 6 | * clang++ -o bin2dec bin2dec.cpp -O3 7 | * Playing with inline assembly is a lot easier on UNIX-like systems than on 8 | * Windows. 9 | * */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | extern "C" { 17 | char binary_input[9]; // I think this does not need to be volatile because the 18 | // inline assembly isn't changing it. It's only reading 19 | // from it. 20 | volatile uint64_t first_digit, Gray_code, 21 | binary_coded_decimal; // We cannot simply put `int` here instead of 22 | // `uint64_t` because, on quite a few compilers, the 23 | // default `int` size even on 64-bit systems is not 64 24 | // bits. 25 | } 26 | 27 | int main() { 28 | std::cout << "Enter a binary number containing at most 8 digits: "; 29 | std::cin.width(9); 30 | std::cin >> binary_input; 31 | asm(R"assembly( 32 | .intel_syntax noprefix 33 | 34 | mov r8, 0 35 | mov r9, 0 36 | input_loop: 37 | cmp byte ptr [binary_input + r9], '0' 38 | jz input_is_zero 39 | cmp byte ptr [binary_input + r9], '1' 40 | jz input_is_one 41 | jmp end_of_the_input_loop # The current character is presumably '\0'. 42 | input_is_one: 43 | shl r8, 1 44 | inc r8 45 | jmp end_of_branching 46 | input_is_zero: 47 | shl r8, 1 48 | jmp end_of_branching 49 | end_of_branching: 50 | inc r9 51 | jmp input_loop 52 | end_of_the_input_loop: 53 | mov r10, r8 # Saved for the Gray Code. 54 | 55 | mov qword ptr [first_digit], 0 56 | cmp r8, 200 57 | jc less_than_200 58 | sub r8, 200 59 | mov qword ptr [first_digit], 2 60 | less_than_200: 61 | cmp r8, 100 62 | jc less_than_100 63 | sub r8, 100 64 | mov qword ptr [first_digit], 1 65 | less_than_100: 66 | 67 | mov r9, 0 68 | loop_for_dividing_r8_by_10: 69 | cmp r8, 10 70 | jc end_of_the_loop_for_dividing_r8_by_10 71 | sub r8, 10 72 | inc r9 73 | jmp loop_for_dividing_r8_by_10 74 | end_of_the_loop_for_dividing_r8_by_10: 75 | 76 | shl r9, 4 # Multiply r9 by 16. 77 | add r9, r8 78 | mov qword ptr [binary_coded_decimal], r9 79 | 80 | mov r11, r10 81 | shr r11, 1 82 | xor r10, r11 83 | mov qword ptr [Gray_code], r10 84 | 85 | .att_syntax 86 | )assembly"); 87 | std::cout << "The number converted to decimal is: " << std::hex 88 | << std::setfill('0') << std::setw(2) << first_digit 89 | << std::setfill('0') << std::setw(2) << binary_coded_decimal 90 | << std::endl; 91 | std::bitset<8> Gray_code_in_binary((unsigned char)Gray_code); 92 | std::cout << "The number converted to Gray code is: " << Gray_code_in_binary 93 | << std::endl; 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /octal.psm: -------------------------------------------------------------------------------- 1 | ;This is a program which is supposed to 2 | ;convert binary numbers entered using the 3 | ;switches to octal, and display the octal 4 | ;numbers using the 7-segment displays. 5 | ;For converting binary digits to octal, it 6 | ;is supposed to use the algorithm we were 7 | ;taught in our Digital Electronics classes, 8 | ;namely, grouping the binary digits into 9 | ;groups of three starting with the last 10 | ;digit, and then converting each group of 11 | ;the binary digits to octal separately. 12 | ;As far as I've tested this program, 13 | ;it works. I haven't tested it on 14 | ;real PicoBlaze, though. 15 | 16 | base_decimal 8 ;We are going to use a lot 17 | ;of octal numbers in this 18 | ;program, so it makes sense 19 | ;to switch the default base 20 | ;of numerical literals to 21 | ;8, so that we don't have 22 | ;to append `'o` every time 23 | ;we write an octal number. 24 | 25 | address 0 ;That's a message to the 26 | ;preprocessor of the assembler 27 | ;to start assembling from the 28 | ;memory address 0. Every 29 | ;PicoBlaze program starts like 30 | ;that. 31 | 32 | input s0, 0 ;The switches are at the 33 | ;input address 0. 34 | 35 | ;The octal numbers that fit in 8 bits 36 | ;can be up to 3 digits long. I think 37 | ;it's easier to deal with the first 38 | ;digit separately, storing it in the 39 | ;register sc. 40 | namereg sc, first_digit 41 | load first_digit, s0 42 | and first_digit, 300 43 | and s0, 077 44 | ;The idea to rotate the first digit 45 | ;left two times instead of shifting 46 | ;it to the right six times was given 47 | ;to me by a StackExchange user called 48 | ;RootTwo. 49 | rl first_digit 50 | rl first_digit 51 | 52 | ;So, here goes the core of the algorithm. 53 | ;It's supposed to take s0 as the input, 54 | ;the binary digits 00ABCDEF, and store 55 | ;into sa the result 0ABC0DEF. 56 | load sa, 0 57 | load s3, 0 58 | beginning_of_the_loop: 59 | compare s3, 2 * 3 + 1 ;Six binary digits 60 | ;plus one step with 61 | ;the special case. 62 | jump nc, end_of_the_loop 63 | compare s3, 3 64 | jump nz, not_the_special_case 65 | ;The fourth time, do nothing except 66 | ;shifting sa to the right and 67 | ;increasing the counter. 68 | sr0 sa 69 | add s3, 1 70 | jump beginning_of_the_loop 71 | not_the_special_case: 72 | sr0 s0 73 | sra sa 74 | add s3, 1 75 | jump beginning_of_the_loop 76 | end_of_the_loop: 77 | sr0 sa 78 | 79 | ;And now output the octal number you have 80 | ;got to the seven-segment displays. The 81 | ;first two digits are the output address 1, 82 | ;and the second two are the output 83 | ;address 2. 84 | output sa, 2 85 | output first_digit, 1 86 | 87 | ;Then run into an infinite loop... 88 | jump 0 ;You can add a breakpoint here. 89 | 90 | ;You also have another PicoBlaze assembly 91 | ;program converting binary numbers to 92 | ;octal, using consecutive division, here. 93 | 94 | ;I've also made a StackExchange thread 95 | ;about this program. -------------------------------------------------------------------------------- /sierpinski.psm: -------------------------------------------------------------------------------- 1 | address 0 2 | 3 | base_decimal 4 | 5 | constant width_of_the_triangle, 50 6 | 7 | print_string "We are about to print the Sierpinski Triangle into the UART terminal...", s9, UART_TX 8 | load s9, a'x 9 | call UART_TX 10 | 11 | load s0, 0 12 | setup_loop: 13 | compare s0, width_of_the_triangle 14 | jump nc, end_of_the_setup_loop 15 | compare s0, width_of_the_triangle / 2 16 | jump z, we_are_in_the_middle 17 | jump nz, we_are_not_in_the_middle 18 | we_are_in_the_middle: 19 | load s1, 1 20 | store s1, (s0) 21 | load s9, "*" 22 | call UART_TX 23 | jump end_of_the_branching_in_setup 24 | we_are_not_in_the_middle: 25 | load s1, 0 26 | store s1, (s0) 27 | load s9, " " 28 | call UART_TX 29 | jump end_of_the_branching_in_setup 30 | end_of_the_branching_in_setup: 31 | add s0, 1 32 | jump setup_loop 33 | end_of_the_setup_loop: 34 | load s9, a'x 35 | call UART_TX 36 | 37 | load s3, 1 38 | printing_loop: 39 | compare s3, width_of_the_triangle 40 | jump z, end_of_the_printing_loop 41 | 42 | load s9, " " 43 | call UART_TX 44 | 45 | load s0, 0 46 | load s1, 2 47 | load s2, width_of_the_triangle + 1 48 | next_iteration_loop: 49 | compare s2, 2 * width_of_the_triangle 50 | jump z, end_of_the_next_iteration_loop 51 | 52 | fetch s4, (s0) 53 | fetch s5, (s1) 54 | xor s4, s5 55 | test s4, s4 56 | jump z, we_are_printing_a_zero 57 | jump nz, we_are_printing_a_one 58 | we_are_printing_a_zero: 59 | store s4, (s2) 60 | load s9, " " 61 | call UART_TX 62 | jump end_of_the_branching_in_the_next_iteration_loop 63 | we_are_printing_a_one: 64 | store s4, (s2) 65 | load s9, "*" 66 | call UART_TX 67 | jump end_of_the_branching_in_the_next_iteration_loop 68 | end_of_the_branching_in_the_next_iteration_loop: 69 | 70 | add s2, 1 71 | add s1, 1 72 | add s0, 1 73 | jump next_iteration_loop 74 | end_of_the_next_iteration_loop: 75 | 76 | load s9, a'x 77 | call UART_TX 78 | 79 | load s0, 0 80 | load s1, width_of_the_triangle 81 | copying_loop: 82 | compare s0, width_of_the_triangle 83 | jump z, end_of_the_copying_loop 84 | 85 | fetch s2, (s1) 86 | store s2, (s0) 87 | 88 | add s0, 1 89 | add s1, 1 90 | jump copying_loop 91 | end_of_the_copying_loop: 92 | 93 | add s3, 1 94 | jump printing_loop 95 | end_of_the_printing_loop: 96 | 97 | print_string "The end.", s9, UART_TX 98 | load s9, a'x 99 | call UART_TX 100 | 101 | infinite_loop: jump infinite_loop 102 | 103 | base_hexadecimal 104 | 105 | CONSTANT LED_PORT, 00 106 | CONSTANT HEX1_PORT, 01 107 | CONSTANT HEX2_PORT, 02 108 | CONSTANT UART_TX_PORT, 03 109 | CONSTANT UART_RESET_PORT, 04 110 | CONSTANT SW_PORT, 00 111 | CONSTANT BTN_PORT, 01 112 | CONSTANT UART_STATUS_PORT, 02 113 | CONSTANT UART_RX_PORT, 03 114 | ; Tx data_present 115 | CONSTANT U_TX_D, 00000001'b 116 | ; Tx FIFO half_full 117 | CONSTANT U_TX_H, 00000010'b 118 | ; TxFIFO full 119 | CONSTANT U_TX_F, 00000100'b 120 | ; Rxdata_present 121 | CONSTANT U_RX_D, 00001000'b 122 | ; RxFIFO half_full 123 | CONSTANT U_RX_H, 00010000'b 124 | ; RxFIFO full 125 | CONSTANT U_RX_F, 00100000'b 126 | 127 | UART_RX: 128 | INPUT sA, UART_STATUS_PORT 129 | TEST sA, U_RX_D 130 | JUMP NZ, input_not_empty 131 | LOAD s0, s0 132 | JUMP UART_RX 133 | input_not_empty: 134 | INPUT s9, UART_RX_PORT 135 | RETURN 136 | 137 | UART_TX: 138 | INPUT sA, UART_STATUS_PORT 139 | TEST sA, U_TX_F 140 | JUMP NZ, UART_TX 141 | OUTPUT s9, UART_TX_PORT 142 | RETURN 143 | 144 | -------------------------------------------------------------------------------- /bin2dec.psm: -------------------------------------------------------------------------------- 1 | ;This program has been tested on real 2 | ;PicoBlaze and it worked there! While 3 | ;this program uses regbanks and flags, 4 | ;I would not take the fact that it 5 | ;works on PicoBlaze to mean that the 6 | ;emulation of flags in this simulator 7 | ;is realistic when the regbank changes. 8 | ;Namely, given what I know, it is 9 | ;possible that real PicoBlaze does not 10 | ;have separate flags for each regbank. 11 | ;The "Regbanks-Flags Test" is supposed 12 | ;to test for that (to succeed if and 13 | ;only if flags work the way I think they 14 | ;do when the regbank changes, and to 15 | ;fail if they do not). 16 | 17 | ;Converting binary numbers entered using 18 | ;the switches to decimal and displaying 19 | ;them on the 7-segment displays. 20 | ;Also, converting them to Gray code and 21 | ;displaying the Gray code using LEDs. 22 | 23 | base_decimal ;By default, the numerical 24 | ;literals in PicoBlaze 25 | ;assembly are treated as 26 | ;base 16 (hexadecimal), 27 | ;rather than base 10 28 | ;(decimal), as in almost 29 | ;all programming languages. 30 | ;I don't like that, so I 31 | ;added this preprocessor 32 | ;directive so that the 33 | ;users of my PicoBlaze 34 | ;assembler can change that 35 | ;behaviour. 36 | 37 | constant eight_switches_input , 0 38 | constant eight_LEDs_output , 0 39 | constant first_two_7s_displays , 1 40 | constant second_two_7s_displays, 2 41 | 42 | address 0 43 | infinite_loop: 44 | namereg sc , first_digit 45 | input s0 , eight_switches_input 46 | load sa , s0 47 | load first_digit, 0 48 | compare sa , 200 49 | jump c , lessThan200 50 | sub sa , 200 51 | load first_digit, 2 52 | lessThan200: 53 | compare sa, 100 54 | jump c , lessThan100 55 | sub sa , 100 56 | load first_digit, 1 57 | lessThan100: 58 | call divide_sa_by_10 59 | call multiply_sa_by_16 60 | add sa , sb ;sb contains the 61 | ;remainder of the 62 | ;division of sa by 63 | ;10. 64 | output first_digit, first_two_7s_displays 65 | output sa ,second_two_7s_displays 66 | ;I removed the space after `,` to make 67 | ;that line fit inside 43 columns, so that 68 | ;the user doesn't have to scroll it 69 | ;horizontally in Chrome (they didn't have 70 | ;to do that in Firefox even before that). 71 | 72 | ;Now goes the code for converting from 73 | ;binary to the Gray Code... 74 | load s1 , s0 75 | sr0 s1 76 | xor s1 , s0 77 | output s1 , eight_LEDs_output 78 | 79 | jump infinite_loop ;You can add a 80 | ;breakpoint here 81 | ;to make the 82 | ;program stop as 83 | ;soon as it 84 | ;prints out the 85 | ;result. 86 | 87 | divide_sa_by_10: 88 | star s0, sa 89 | regbank b 90 | load s1, 0 91 | beginning_of_loop: 92 | compare s0, 10 93 | jump c , end_of_loop 94 | sub s0, 10 95 | add s1, 1 96 | jump beginning_of_loop 97 | end_of_loop: 98 | star sa, s1 99 | star sb, s0 100 | regbank a 101 | return 102 | 103 | multiply_sa_by_16: 104 | ;16=2^4 105 | sl0 sa 106 | sl0 sa 107 | sl0 sa 108 | sl0 sa 109 | return 110 | 111 | ;You might also be interested in 112 | ;this program implemented in x86. 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PicoBlaze Assembler and Emulator in JavaScript 2 | 3 | ![Picture of PicoBlaze](Background.gif) 4 | 5 | This is my attempt to implement a [Xilinx PicoBlaze](https://en.wikipedia.org/wiki/PicoBlaze) assembler and emulator in JavaScript. My Computer Architecture professor Ivan Aleksi asked me to make it if physical laboratory exercises need to be canceled because of a pandemic, so that students can do the laboratory exercises in spite of not having access to a real PicoBlaze. Fortunately, thus far, that hasn't happened. 6 | 7 | It is available live on [SourceForge](https://picoblaze-simulator.sourceforge.io/). If that doesn't work (for instance, if I got a cross-site scripting attack), the front-end-only version is available on [GitHub Pages](https://flatassembler.github.io/PicoBlaze/PicoBlaze.html). As the assembler is written in JavaScript, rather than in PHP, the basic functionality is available even in the front-end-only version. The fork of this program maintained by @agustiza (Agustin Izaguirre) is available [live on his website](https://agustiza.github.io/PicoBlaze_Simulator_in_JS/PicoBlaze.html). 8 | 9 | Right now, this program has no back-end. Maybe I will add some back-end to enable users to share their own examples and comment on other users' examples later, but, for that, I will need to learn quite a bit of PHP, and it will work only on SourceForge because GitHub Pages supports no back-end scripting. Abidin Durdu (known as @abdrd on GitHub) has made a simple back-end allowing the users to upload their own PicoBlaze assembly programs. It uses MySQL and is running on the SourceForge servers. 10 | 11 | The documentation, in Croatian, is available in the `seminar` folder, in DOCX, DOC, ODT, PDF and [RTF](https://flatassembler.github.io/PicoBlaze/PicoBlaze.rtf) formats. (UPDATE: I received a message telling me that hosting the documentation on my website and my GitHub profile is a copyright infringement. So, the [documentation, in Croatian, is available on DABAR in the PDF format](https://repozitorij.etfos.hr/islandora/object/etfos:4489/datastream/PDF/download).) 12 | 13 | If you want to host this project yourself, you might want to edit the lines following the [17th line of the `PicoBlaze.html` file](https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/blob/6e28dd2b8ce3c8344bf223ced8983b5eb2fb2eb5/PicoBlaze.html#L17): 14 | ```html 15 | 21 | ``` 22 | and modify them to point to where you will host the examples. 23 | 24 | An example of a similar project is [FRISC JS](https://github.com/izuzak/FRISCjs). Here at the University of Osijek, we are using PicoBlaze as an example of a simple computer, but, at the University of Zagreb, they are using FRISC for that. And their students made an assembler and emulator for FRISC in JavaScript, though I guess it's not for the same reason I made PicoBlaze assembler and emulator in JavaScript (the FRISC JS project predates the pandemic by many years). 25 | 26 | **Note to the contributors**: While there are some JEST tests in this project (made mostly by @agustiza), the test coverage is pretty low, so don't rely solely on them. Please do some manual testing. And, if possible, do that manual testing in Firefox 52 (the last version of Firefox to work on Windows XP, and it's also the version of Firefox that comes with Solaris 11.4), as it is important to me not to break the compatibility with Firefox 52. Many computers at my university are running Windows XP and are using Firefox 52 as the browser, and it is important to me that this program works on those computers. 27 | 28 | **UPDATE** on 24/01/2021: I've started developing [a version of this app for Android](https://github.com/FlatAssembler/PicoBlaze_Simulator_for_Android). As I am not a skilled Android developer, any help will be appreciated. 29 | 30 | **UPDATE** on 18/07/2023: I have started a [forum.hr thread](https://www.forum.hr/showthread.php?t=1336407) where I listed non-trivial problems I currently have with my PicoBlaze Simulator. 31 | 32 | **UPDATE** on 05/07/2024: I've made [a YouTube video about this program](https://youtu.be/ckAvsglxTVc). 33 | 34 | **UPDATE** on 08/05/2025: The documentation is once again available in the *seminar* folder. 35 | 36 | [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/FlatAssembler/PicoBlaze_Simulator_in_JS.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/FlatAssembler/PicoBlaze_Simulator_in_JS/context:javascript) 37 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) 38 | -------------------------------------------------------------------------------- /tokenizer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function tokenize(input) { 3 | let tokenized = []; 4 | let areWeInAString = false; 5 | let areWeInAComment = false; 6 | let currentLine = 1; // Don't care about columns, lines in assembly language 7 | // are always short. 8 | let currentToken = ""; 9 | for (let i = 0; i < input.length; i++) { 10 | if (areWeInAComment && areWeInAString) { 11 | alert( 12 | "Tokenizer got into a forbidden state because of some bug in it! Line #" + 13 | currentLine); 14 | return []; 15 | } 16 | if (input[i] == ";" && !areWeInAString) { 17 | areWeInAComment = true; 18 | tokenized.push(new TreeNode(currentToken, currentLine)); 19 | tokenized.push(new TreeNode("\n", currentLine)); 20 | continue; 21 | } 22 | if (areWeInAComment && input[i] != "\n") 23 | continue; 24 | if (areWeInAComment && input[i] == "\n") { 25 | areWeInAComment = false; 26 | currentLine++; 27 | currentToken = ""; 28 | continue; 29 | } 30 | if (input[i] == '"' && !areWeInAString) { 31 | areWeInAString = true; 32 | tokenized.push(new TreeNode(currentToken, currentLine)); 33 | currentToken = '"'; 34 | continue; 35 | } 36 | if (input[i] == "\n" && areWeInAString) { 37 | alert("Unterminated string literal on line " + currentLine); 38 | return []; 39 | } 40 | if (input[i] == '"') { 41 | areWeInAString = false; 42 | currentToken += '"'; 43 | tokenized.push(new TreeNode(currentToken, currentLine)); 44 | currentToken = ""; 45 | continue; 46 | } 47 | if (input[i] == "\n") { 48 | tokenized.push(new TreeNode(currentToken, currentLine)); 49 | currentToken = ""; 50 | tokenized.push(new TreeNode( 51 | "\n", currentLine++)); // Because assembly language is a 52 | // whitespace-sensitive language, the new-line 53 | // characters are tokens visible to the parser. 54 | continue; 55 | } 56 | if ( 57 | (input[i] == " " || input[i] == "\t") && 58 | !areWeInAString // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/5 59 | ) { 60 | tokenized.push(new TreeNode(currentToken, currentLine)); 61 | currentToken = ""; 62 | continue; 63 | } 64 | if ((input[i] == "(" || input[i] == ")" || input[i] == "[" || 65 | input[i] == "]" || input[i] == "{" || input[i] == "}" || 66 | input[i] == "," || input[i] == "/" || input[i] == "*" || 67 | input[i] == "-" || input[i] == "+" || input[i] == "^" || 68 | input[i] == "<" || input[i] == ">" || input[i] == "=" || 69 | input[i] == "&" || input[i] == "|" || input[i] == "?" || 70 | input[i] == ':') && 71 | !areWeInAString) { 72 | tokenized.push(new TreeNode(currentToken, currentLine)); 73 | tokenized.push(new TreeNode(input[i], currentLine)); 74 | currentToken = ""; 75 | continue; 76 | } 77 | if (input[i] == ":" && !areWeInAString) { // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/39 78 | tokenized.push(new TreeNode(currentToken + ":", currentLine)); 79 | currentToken = ""; 80 | continue; 81 | } 82 | currentToken += input[i]; 83 | } 84 | if (currentToken.length) { 85 | tokenized.push(new TreeNode(currentToken, currentLine)); 86 | tokenized.push(new TreeNode("\n", currentLine)); 87 | } 88 | if (tokenized[tokenized.length - 1].text != "\n") 89 | tokenized.push(new TreeNode("\n", currentLine)); 90 | for (let i = 0; i < tokenized.length; i++) { 91 | if (!(tokenized[i] instanceof TreeNode)) { 92 | alert("Internal compiler error in tokenizer, the token #" + i + 93 | " is not of type TreeNode!"); 94 | return []; 95 | } 96 | if (tokenized[i].text == "") { 97 | tokenized.splice(i, 1); 98 | i--; 99 | } 100 | } 101 | 102 | // Labels are single tokens. 103 | for (let i = 0; i < tokenized.length; i++) 104 | if (tokenized[i].text == ':' && 105 | (tokenized[i + 1].text == '\n' || 106 | (i < 2 // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/32 107 | || 108 | tokenized[i - 2].text == 109 | '\n'))) { // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/31 110 | tokenized[i - 1].text += ':'; 111 | tokenized.splice(i, 1); 112 | i--; 113 | } 114 | 115 | // Functions in the preprocessor. 116 | for (let i = 0; i < tokenized.length - 1; i++) 117 | if ([ "invertbits", "bitand", "bitor", "mod" ].includes( 118 | tokenized[i].text.toLowerCase()) && 119 | tokenized[i + 1].text == '(') { 120 | tokenized[i].text += "("; 121 | tokenized.splice(i + 1, 1); 122 | } 123 | 124 | return tokenized; 125 | } 126 | -------------------------------------------------------------------------------- /__tests__/parser.test.js: -------------------------------------------------------------------------------- 1 | const tree = require("../TreeNode.js"); 2 | global.TreeNode = tree.TreeNode; // referenced by tokenizer 3 | 4 | const tokenizer = require("../tokenizer.js"); // Parser depends on the tokenizer to work... 5 | 6 | const list_of_directives = require("../list_of_directives.js"); //...as well as on the list of directives. 7 | global.mnemonics = list_of_directives.mnemonics; 8 | global.preprocessor = list_of_directives.preprocessor; 9 | 10 | const parser = require("../parser.js"); 11 | 12 | describe("PicoBlaze Parser", () => { 13 | test("Simple addition", () => { 14 | const AST = parser.parse(tokenizer.tokenize("5+5")); 15 | expect(AST.getLispExpression()).toEqual('("assembly" ("+" "5" "5"))'); 16 | }); 17 | test("Adding three numbers", () => { 18 | const AST = parser.parse(tokenizer.tokenize("1+2+3")); 19 | expect(AST.getLispExpression()).toEqual( 20 | '("assembly" ("+" ("+" "1" "2") "3"))', 21 | ); 22 | }); 23 | test("Operations with different priority", () => { 24 | const AST = parser.parse(tokenizer.tokenize("1+2*3")); 25 | expect(AST.getLispExpression()).toEqual( 26 | '("assembly" ("+" "1" ("*" "2" "3")))', 27 | ); 28 | }); 29 | test("Parentheses", () => { 30 | const AST = parser.parse(tokenizer.tokenize("(1+2)*3")); 31 | expect(AST.getLispExpression()).toEqual( 32 | '("assembly" ("*" ("()" ("+" "1" "2")) "3"))', 33 | ); 34 | }); 35 | test("Instruction with one operand", () => { 36 | const AST = parser.parse(tokenizer.tokenize("sra s0")); 37 | expect(AST.getLispExpression()).toEqual('("assembly" ("sra" "s0"))'); 38 | }); 39 | test("Instruction with two operands", () => { 40 | const AST = parser.parse(tokenizer.tokenize("load s0, s1")); 41 | expect(AST.getLispExpression()).toEqual( 42 | '("assembly" ("load" "s0" "," "s1"))', 43 | ); 44 | }); 45 | test("A simple if statement", () => { 46 | const AST = parser.parse( 47 | tokenizer.tokenize(` 48 | if 2+2=4 49 | display "Correct!" 50 | endif 51 | `), 52 | ); 53 | expect(AST.getLispExpression()).toEqual( 54 | '("assembly" ("if" ("=" ("+" "2" "2") "4") ("assembly" ("display" ""Correct!""))))', 55 | ); 56 | }); 57 | test("An if-else statement", () => { 58 | const AST = parser.parse( 59 | tokenizer.tokenize(` 60 | if 2+2=4 61 | display "Correct!" 62 | else 63 | display "Incorrect!" 64 | endif 65 | `), 66 | ); 67 | expect(AST.getLispExpression()).toEqual( 68 | '("assembly" ("if" ("=" ("+" "2" "2") "4") ("assembly" ("display" ""Correct!"")) ("assembly" ("display" ""Incorrect!""))))', 69 | ); 70 | }); 71 | test("An simple while statement", () => { 72 | const AST = parser.parse( 73 | tokenizer.tokenize(` 74 | while i < 10'd 75 | constant i, i + 1 76 | display "0"+i 77 | display a 78 | endwhile 79 | `), 80 | ); 81 | expect(AST.getLispExpression()).toEqual( 82 | '("assembly" ("while" ("<" "i" "10\'d") ("assembly" ("constant" "i" "," ("+" "i" "1")) ("display" ("+" ""0"" "i")) ("display" "a"))))', 83 | ); 84 | }); 85 | test("An if-else statement inside a while statement", () => { 86 | const AST = parser.parse( 87 | tokenizer.tokenize(` 88 | while b > 0 89 | if a > b 90 | constant a, a - b 91 | else 92 | constant b, b - a 93 | endif 94 | endwhile 95 | `), 96 | ); 97 | expect(AST.getLispExpression()).toEqual( 98 | '("assembly" ("while" (">" "b" "0") ("assembly" ("if" (">" "a" "b") ("assembly" ("constant" "a" "," ("-" "a" "b"))) ("assembly" ("constant" "b" "," ("-" "b" "a")))))))', 99 | ); 100 | }); 101 | test("An while statement inside an if statement", () => { 102 | const AST = parser.parse( 103 | tokenizer.tokenize(` 104 | if i > 0 & i < 10'd 105 | while i < 10'd 106 | constant i, i + 1 107 | display "0"+i 108 | display a 109 | endwhile 110 | endif 111 | `), 112 | ); 113 | expect(AST.getLispExpression()).toEqual( 114 | '("assembly" ("if" ("&" (">" "i" "0") ("<" "i" "10\'d")) ("assembly" ("while" ("<" "i" "10\'d") ("assembly" ("constant" "i" "," ("+" "i" "1")) ("display" ("+" ""0"" "i")) ("display" "a"))))))', 115 | ); 116 | }); 117 | test("Enabling and disabling interrupts", () => { 118 | const AST = parser.parse( 119 | tokenizer.tokenize(` 120 | enable interrupts 121 | returni enable 122 | `), 123 | ); 124 | expect(AST.getLispExpression()).toEqual( 125 | '("assembly" ("enable" "interrupts") ("returni" "enable"))', 126 | ); 127 | }); 128 | test("Unary operators", () => { 129 | const AST = parser.parse(tokenizer.tokenize("-1 + 2")); 130 | expect(AST.getLispExpression()).toEqual( 131 | '("assembly" ("+" ("-" "0" "1") "2"))', 132 | ); 133 | }); 134 | test("Second unary operators test", () => { 135 | const AST = parser.parse(tokenizer.tokenize("--5")); 136 | expect(AST.getLispExpression()).toEqual( 137 | '("assembly" ("-" "0" ("-" "0" "5")))', 138 | ); 139 | }); 140 | test("Ternary conditional operator", () => { 141 | const AST = parser.parse(tokenizer.tokenize("2+2<5?1:0")); 142 | expect(AST.getLispExpression()).toEqual( 143 | '("assembly" ("?:" ("<" ("+" "2" "2") "5") "1" "0"))', 144 | ); 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /regbanks_flags_test.psm: -------------------------------------------------------------------------------- 1 | ;This is a program I will run on 2 | ;actual PicoBlaze to test how 3 | ;flags behave when the regbank 4 | ;changes. Namely, it is possible, 5 | ;given what I know, that real 6 | ;PicoBlaze doesn't have separate 7 | ;flags for each regbank. In that 8 | ;case, this test will fail. 9 | ;I have started a 10 | ;StackOverflow question about this, 11 | ;thus far receiving no response. 12 | 13 | ;Now follows some boilerplate code 14 | ;we use in our Computer Architecture 15 | ;classes... 16 | CONSTANT LED_PORT, 00 17 | CONSTANT HEX1_PORT, 01 18 | CONSTANT HEX2_PORT, 02 19 | CONSTANT UART_TX_PORT, 03 20 | CONSTANT UART_RESET_PORT, 04 21 | CONSTANT SW_PORT, 00 22 | CONSTANT BTN_PORT, 01 23 | CONSTANT UART_STATUS_PORT, 02 24 | CONSTANT UART_RX_PORT, 03 25 | ; Tx data_present 26 | CONSTANT U_TX_D, 00000001'b 27 | ; Tx FIFO half_full 28 | CONSTANT U_TX_H, 00000010'b 29 | ; TxFIFO full 30 | CONSTANT U_TX_F, 00000100'b 31 | ; Rxdata_present 32 | CONSTANT U_RX_D, 00001000'b 33 | ; RxFIFO half_full 34 | CONSTANT U_RX_H, 00010000'b 35 | ; RxFIFO full 36 | CONSTANT U_RX_F, 00100000'b 37 | 38 | address 0 39 | load s9, "R" 40 | call UART_TX 41 | load s9, "u" 42 | call UART_TX 43 | load s9, "n" 44 | call UART_TX 45 | load s9, "n" 46 | call UART_TX 47 | load s9, "i" 48 | call UART_TX 49 | load s9, "n" 50 | call UART_TX 51 | load s9, "g" 52 | call UART_TX 53 | load s9, " " 54 | ;The last line tokenizes correctly 55 | ;only as of the version 56 | ;PicoBlaze_Simulator_in_JS v2.7, 57 | ;see this GitHub issue for more 58 | ;information about that. 59 | call UART_TX 60 | load s9, "t" 61 | call UART_TX 62 | load s9, "h" 63 | call UART_TX 64 | load s9, "e" 65 | call UART_TX 66 | load s9, " " 67 | call UART_TX 68 | load s9, "r" 69 | call UART_TX 70 | load s9, "e" 71 | call UART_TX 72 | load s9, "g" 73 | call UART_TX 74 | load s9, "b" 75 | call UART_TX 76 | load s9, "a" 77 | call UART_TX 78 | load s9, "n" 79 | call UART_TX 80 | load s9, "k" 81 | call UART_TX 82 | load s9, "s" 83 | call UART_TX 84 | load s9, "-" 85 | call UART_TX 86 | load s9, "f" 87 | call UART_TX 88 | load s9, "l" 89 | call UART_TX 90 | load s9, "a" 91 | call UART_TX 92 | load s9, "g" 93 | call UART_TX 94 | load s9, "s" 95 | call UART_TX 96 | load s9, " " 97 | call UART_TX 98 | load s9, "t" 99 | call UART_TX 100 | load s9, "e" 101 | call UART_TX 102 | load s9, "s" 103 | call UART_TX 104 | load s9, "t" 105 | call UART_TX 106 | load s9, "." 107 | call UART_TX 108 | load s9, "." 109 | call UART_TX 110 | load s9, "." 111 | call UART_TX 112 | load s9, " " 113 | call UART_TX 114 | ;Now follows the core of the test... 115 | regbank a 116 | load s0, 0 117 | sub s0, 0 118 | regbank b 119 | load s0, 1 120 | sub s0, 0 121 | regbank a 122 | ;Now, if flags on PicoBlaze work 123 | ;the way I think they do, the zero 124 | ;flag will be set. 125 | jump z , success 126 | jump nz, failure 127 | ;This point should not be reached... 128 | load s9, "I" 129 | call UART_TX 130 | load s9, "n" 131 | call UART_TX 132 | load s9, "d" 133 | call UART_TX 134 | load s9, "e" 135 | call UART_TX 136 | load s9, "t" 137 | call UART_TX 138 | load s9, "e" 139 | call UART_TX 140 | load s9, "r" 141 | call UART_TX 142 | load s9, "m" 143 | call UART_TX 144 | load s9, "i" 145 | call UART_TX 146 | load s9, "n" 147 | call UART_TX 148 | load s9, "a" 149 | call UART_TX 150 | load s9, "t" 151 | call UART_TX 152 | load s9, "e" 153 | call UART_TX 154 | load s9, "!" 155 | call UART_TX 156 | load s9, a ;New-line character. 157 | ;The last line is being highlighted 158 | ;incorrectly by the syntax 159 | ;highlighter, see this GitHub issue for 160 | ;more information about that. 161 | ;While I don't know how to solve that, 162 | ;there does appear to be a simple 163 | ;work-around: simply, when `a` 164 | ;is a hexadecimal constant, type `0a` 165 | ;instead, and it will be highlighted 166 | ;correctly. 167 | call UART_TX 168 | load s0, ff 169 | output s0, LED_PORT 170 | jump infinite_loop 171 | success: 172 | load s9, "S" 173 | call UART_TX 174 | load s9, "u" 175 | call UART_TX 176 | load s9, "c" 177 | call UART_TX 178 | load s9, "c" 179 | call UART_TX 180 | load s9, "e" 181 | call UART_TX 182 | load s9, "s" 183 | call UART_TX 184 | load s9, "s" 185 | call UART_TX 186 | load s9, "!" 187 | call UART_TX 188 | load s9, a ;New-line character. 189 | call UART_TX 190 | load s0, aa 191 | output s0, LED_PORT 192 | jump infinite_loop 193 | failure: 194 | load s9, "F" 195 | call UART_TX 196 | load s9, "a" 197 | call UART_TX 198 | load s9, "i" 199 | call UART_TX 200 | load s9, "l" 201 | call UART_TX 202 | load s9, "u" 203 | call UART_TX 204 | load s9, "r" 205 | call UART_TX 206 | load s9, "e" 207 | call UART_TX 208 | load s9, "!" 209 | call UART_TX 210 | load s9, a ;New-line character. 211 | call UART_TX 212 | load s0, 55 213 | output s0, LED_PORT 214 | infinite_loop: 215 | jump infinite_loop 216 | 217 | ;Once again, some boilerplate code 218 | ;we use in our Computer Architecture 219 | ;classes... 220 | UART_RX: 221 | INPUT sA, UART_STATUS_PORT 222 | TEST sA, U_RX_D 223 | JUMP Z , UART_RX 224 | INPUT s9, UART_RX_PORT 225 | RETURN 226 | 227 | UART_TX: 228 | INPUT sA, UART_STATUS_PORT 229 | TEST sA, U_TX_F 230 | JUMP NZ, UART_TX 231 | OUTPUT s9, UART_TX_PORT 232 | RETURN 233 | -------------------------------------------------------------------------------- /__tests__/arithmetic_expressions.test.js: -------------------------------------------------------------------------------- 1 | const tree = require("../TreeNode.js"); 2 | global.TreeNode = tree.TreeNode; // referenced by tokenizer 3 | 4 | const tokenizer = require("../tokenizer.js"); // Parser depends on the tokenizer to work... 5 | 6 | const list_of_directives = require("../list_of_directives.js"); //...as well as on the list of directives. 7 | global.mnemonics = list_of_directives.mnemonics; 8 | global.preprocessor = list_of_directives.preprocessor; 9 | 10 | const parser = require("../parser.js"); 11 | 12 | describe("Evaluation of Arithmetic Expressions", () => { 13 | test("Simple addition", () => { 14 | const AST = parser.parse(tokenizer.tokenize("5+5")); 15 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 16 | 5 + 5, 17 | ); 18 | }); 19 | test("Addition of three numbers", () => { 20 | const AST = parser.parse(tokenizer.tokenize("1+2+3")); 21 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 22 | 1 + 2 + 3, 23 | ); 24 | }); 25 | test("Subtraction", () => { 26 | const AST = parser.parse(tokenizer.tokenize("5-2")); 27 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 28 | 5 - 2, 29 | ); 30 | }); 31 | test("Parentheses", () => { 32 | const AST = parser.parse(tokenizer.tokenize("5-(5-2)")); 33 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 34 | 5 - (5 - 2), 35 | ); 36 | }); 37 | test("Multiplication", () => { 38 | const AST = parser.parse(tokenizer.tokenize("5*5")); 39 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 40 | 5 * 5, 41 | ); 42 | }); 43 | test("Division", () => { 44 | const AST = parser.parse(tokenizer.tokenize("6/2")); 45 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 46 | 6 / 2, 47 | ); 48 | }); 49 | test("Exponentiation", () => { 50 | const AST = parser.parse(tokenizer.tokenize("5^2")); 51 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 52 | 5 ** 2, 53 | ); 54 | }); 55 | test("Logical operators", () => { 56 | const AST = parser.parse(tokenizer.tokenize("1 & 1 | 0")); 57 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 58 | (1 && 1) || 0, 59 | ); 60 | }); 61 | test("Decimal numbers", () => { 62 | const AST = parser.parse(tokenizer.tokenize("10'd / 2")); 63 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 64 | 10 / 2, 65 | ); 66 | }); 67 | test("Binary numbers", () => { 68 | const AST = parser.parse(tokenizer.tokenize("1010'b / 2")); 69 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 70 | 0b1010 / 2, 71 | ); 72 | }); 73 | test("Octal numbers", () => { 74 | const AST = parser.parse(tokenizer.tokenize("252'o")); 75 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 76 | 0o252, 77 | ); 78 | }); 79 | test("Hexadecimal numbers", () => { 80 | const AST = parser.parse(tokenizer.tokenize("a / 2")); 81 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 82 | 0xa / 2, 83 | ); 84 | }); 85 | test("Hexadecimal numbers composed entirely of decimal digits", () => { 86 | const AST = parser.parse(tokenizer.tokenize("10 / 2")); 87 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 88 | 0x10 / 2, 89 | ); 90 | }); 91 | test("ASCII", () => { 92 | const AST = parser.parse(tokenizer.tokenize('"A"')); 93 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 94 | "A".charCodeAt(0), 95 | ); 96 | }); 97 | test("Unary operators", () => { 98 | const AST = parser.parse(tokenizer.tokenize("5 + + 1")); 99 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 100 | 5 + +1, 101 | ); 102 | }); 103 | test("Fake unary operator", () => { 104 | const AST = parser.parse(tokenizer.tokenize("(1 + 2) + 3")); 105 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 106 | 1 + 2 + 3, 107 | ); 108 | }); 109 | test("Unary minus", () => { 110 | const AST = parser.parse(tokenizer.tokenize("5*-1")); 111 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 112 | 5 * -1, 113 | ); 114 | }); 115 | test("Ternary conditional operator 1", () => { 116 | const AST = parser.parse( 117 | tokenizer.tokenize( 118 | '(2+2>5?3+3<7?1:-2:2+2-4<1?0:2+2<4?-1:-3)+("A"+2="C"?0:-1)', 119 | ), 120 | ); 121 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 122 | 0, 123 | ); 124 | }); 125 | test("Ternary conditional operator 2", () => { 126 | const AST = parser.parse(tokenizer.tokenize("1 ? 2 ? 3 : 4 : 5")); 127 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 128 | 3, 129 | ); 130 | }); 131 | test("The modulo operator", () => { 132 | const AST = parser.parse(tokenizer.tokenize("mod(5, 2)")); 133 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 134 | 5 % 2, 135 | ); 136 | }); 137 | test("The bitwise operators", () => { 138 | const AST = parser.parse(tokenizer.tokenize("bitand(invertBits(a),f)")); 139 | expect(AST.children[0].interpretAsArithmeticExpression(new Map())).toEqual( 140 | ~0xa & 0xf, 141 | ); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /__tests__/simulator.test.js: -------------------------------------------------------------------------------- 1 | global.formatAsAddress = require("../headerScript.js").formatAsAddress; //referenced by simulator 2 | 3 | const simulator = require("../simulator.js"); 4 | 5 | const clearGlobals = () => { 6 | global.registers = [new Uint8Array(16), new Uint8Array(16)]; 7 | global.PC = 0; 8 | global.machineCode = [new Array(4096).fill({ hex: "00000", line: 0 })]; 9 | global.regbank = 0; 10 | global.flagZ = [0, 0]; 11 | global.flagC = [0, 0]; 12 | 13 | global.breakpoints = []; 14 | global.playing = false; 15 | global.displayRegistersAndFlags = jest.fn(); 16 | global.alert = (...args) => console.log(args); 17 | 18 | for (let i = 0; i < 10; i++) { 19 | const td = document.createElement("td"); 20 | td.setAttribute("id", "PC_label_" + formatAsAddress(i)); 21 | document.body.appendChild(td); 22 | } 23 | 24 | /* UART setup*/ 25 | global.is_UART_enabled = false; 26 | global.currentlyReadCharacterInUART = 0; 27 | const uartInputEl = document.createElement("textarea"); 28 | uartInputEl.setAttribute("id", "UART_INPUT"); 29 | 30 | const uartOutputEl = document.createElement("pre"); 31 | uartOutputEl.setAttribute("id", "UART_OUTPUT"); 32 | uartOutputEl.innerText = ""; 33 | document.body.appendChild(uartOutputEl); 34 | document.body.appendChild(uartInputEl); 35 | }; 36 | 37 | describe("PicoBlaze MachineCode Simulator", () => { 38 | beforeEach(clearGlobals); 39 | 40 | test("add 4 + 5 equals 9", () => { 41 | global.machineCode = [ 42 | { hex: "01005", line: 3 }, //load s0, 5 43 | { hex: "11004", line: 4 }, //add s0, 4 44 | ]; 45 | 46 | simulator.simulateOneInstruction(); //load s0, 5 47 | simulator.simulateOneInstruction(); //add s0, 4 48 | expect(registers[0][0]).toBe(9); 49 | }); 50 | 51 | test("SR1 shifts right adding 1", () => { 52 | global.machineCode = [ 53 | { hex: "01005", line: 3 }, //load s0, 00000101´b 54 | { hex: "1400f", line: 4 }, //sr1 s0 55 | ]; 56 | 57 | simulator.simulateOneInstruction(); //load s0, 00000101´b 58 | simulator.simulateOneInstruction(); //sr1 s0 59 | expect(registers[0][0].toString(2)).toBe("10000010"); 60 | }); 61 | 62 | test("sub 5 - 4 equals 1", () => { 63 | global.machineCode = [ 64 | { hex: "01005", line: 3 }, 65 | { hex: "19004", line: 4 }, 66 | ]; 67 | console.time("test"); // @agustiza, what does this do? 68 | simulator.simulateOneInstruction(); //load s0, 5 69 | simulator.simulateOneInstruction(); //sub s0, 4 70 | 71 | expect(registers[0][0]).toBe(1); 72 | }); 73 | 74 | test("sub 0 - 1 equals 0xff", () => { 75 | // This once crashed the simulator: https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/pull/15#issuecomment-1696714640 76 | global.machineCode = [ 77 | { hex: "01000", line: 3 }, 78 | { hex: "19001", line: 4 }, 79 | ]; 80 | simulator.simulateOneInstruction(); //load s0, 0 81 | simulator.simulateOneInstruction(); //sub s0, 1 82 | 83 | expect(registers[0][0]).toBe(0xff); 84 | }); 85 | 86 | test("sra works properly when the c flag is set", () => { 87 | // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/9 88 | global.machineCode = [ 89 | { hex: "01041", line: 2 }, 90 | { hex: "01101", line: 3 }, 91 | { hex: "1410e", line: 4 }, 92 | { hex: "14008", line: 5 }, 93 | ]; 94 | simulator.simulateOneInstruction(); // load s0, 41 95 | simulator.simulateOneInstruction(); // load s1, 1 96 | simulator.simulateOneInstruction(); // sr0 s1 97 | simulator.simulateOneInstruction(); // sra s0 98 | expect(registers[0][0]).toBe(0b10100000); 99 | }); 100 | 101 | test("jump nz + labels work", () => { 102 | global.machineCode = [ 103 | { hex: "01000", line: 4 }, 104 | { hex: "011ff", line: 5 }, 105 | { hex: "11001", line: 7 }, 106 | { hex: "19101", line: 8 }, 107 | { hex: "36002", line: 9 }, 108 | ]; 109 | simulator.simulateOneInstruction(); //load s0, 0 110 | simulator.simulateOneInstruction(); //load s1, 255'd 111 | 112 | /* 113 | label: 114 | add s0, 1 115 | sub s1, 1 116 | jump nz, label 117 | */ 118 | for (let i = 0; i < 255 * 3; i++) { 119 | //we do it a few times since it's fast :). took less than 100ms on my machine 120 | simulator.simulateOneInstruction(); 121 | } 122 | expect(registers[0][0]).toBe(255); 123 | }); 124 | 125 | test("Input UART to register", () => { 126 | global.is_UART_enabled = true; 127 | 128 | const str = "Hello"; 129 | const el = document.getElementById("UART_INPUT"); 130 | el.value = str; 131 | 132 | global.machineCode = str.split("").map((c, i) => 133 | //Input to Hello to s0, s1, ...sN 134 | ({ hex: "09" + i + "03", line: i + 1 }), 135 | ); 136 | 137 | global.machineCode.forEach(() => { 138 | simulator.simulateOneInstruction(); 139 | }); 140 | 141 | //Reconstruct string from registers 142 | const actual = Array.from(registers[0]) 143 | .slice(0, str.length) //s0 to s5 144 | .map((charcode) => String.fromCharCode(charcode)) 145 | .join(""); 146 | 147 | expect(actual).toBe("Hello"); 148 | expect(global.currentlyReadCharacterInUART).toBe(str.length); 149 | }); 150 | 151 | test("Output register to UART terminal", () => { 152 | global.is_UART_enabled = true; 153 | 154 | const machineCode = "Hello" 155 | .split("") 156 | .map((c, i) => [ 157 | //Convert every char to its codepoint and load s0 158 | { hex: "010" + c.charCodeAt(0).toString(16), line: i * 2 + 1 }, 159 | //Write to port 3 160 | { hex: "2d003", line: i * 2 + 2 }, 161 | ]) 162 | .flat(); 163 | 164 | global.machineCode = machineCode; 165 | machineCode.forEach(() => { 166 | simulator.simulateOneInstruction(); 167 | }); 168 | 169 | expect(document.getElementById("UART_OUTPUT").innerText).toBe("Hello"); 170 | }); 171 | 172 | test("Function pointers work", () =>{ 173 | const machineCode = [ 174 | {hex: "01005", line: "2"}, //load s0, 5 175 | {hex: "01100", line: "3"}, //load s1, 0 176 | {hex: "26100", line: "4"}, //jump@(s1,s0) 177 | ]; 178 | global.machineCode=machineCode; 179 | 180 | simulator.simulateOneInstruction(); //load s0, 5 181 | simulator.simulateOneInstruction(); //load s1, 0 182 | simulator.simulateOneInstruction(); //jump@(s1,s0) 183 | 184 | expect(global.PC).toBe(5); //jump to address 5 185 | }) 186 | }); 187 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * For a detailed explanation regarding each configuration property, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | /** @type {import('jest').Config} */ 7 | const config = { 8 | // All imported modules in your tests should be mocked automatically 9 | // automock: false, 10 | 11 | // Stop running tests after `n` failures 12 | // bail: 0, 13 | 14 | // The directory where Jest should store its cached dependency information 15 | // cacheDirectory: "/tmp/jest_rs", 16 | 17 | // Automatically clear mock calls, instances, contexts and results before every test 18 | clearMocks: true, 19 | 20 | // Indicates whether the coverage information should be collected while executing the test 21 | collectCoverage: true, 22 | 23 | // An array of glob patterns indicating a set of files for which coverage information should be collected 24 | // collectCoverageFrom: undefined, 25 | 26 | // The directory where Jest should output its coverage files 27 | coverageDirectory: "coverage", 28 | 29 | // An array of regexp pattern strings used to skip coverage collection 30 | // coveragePathIgnorePatterns: [ 31 | // "/node_modules/" 32 | // ], 33 | 34 | // Indicates which provider should be used to instrument code for coverage 35 | coverageProvider: "v8", 36 | 37 | // A list of reporter names that Jest uses when writing coverage reports 38 | // coverageReporters: [ 39 | // "json", 40 | // "text", 41 | // "lcov", 42 | // "clover" 43 | // ], 44 | 45 | // An object that configures minimum threshold enforcement for coverage results 46 | // coverageThreshold: undefined, 47 | 48 | // A path to a custom dependency extractor 49 | // dependencyExtractor: undefined, 50 | 51 | // Make calling deprecated APIs throw helpful error messages 52 | // errorOnDeprecated: false, 53 | 54 | // The default configuration for fake timers 55 | // fakeTimers: { 56 | // "enableGlobally": false 57 | // }, 58 | 59 | // Force coverage collection from ignored files using an array of glob patterns 60 | // forceCoverageMatch: [], 61 | 62 | // A path to a module which exports an async function that is triggered once before all test suites 63 | // globalSetup: undefined, 64 | 65 | // A path to a module which exports an async function that is triggered once after all test suites 66 | // globalTeardown: undefined, 67 | 68 | // A set of global variables that need to be available in all test environments 69 | // globals: {}, 70 | 71 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 72 | // maxWorkers: "50%", 73 | 74 | // An array of directory names to be searched recursively up from the requiring module's location 75 | // moduleDirectories: [ 76 | // "node_modules" 77 | // ], 78 | 79 | // An array of file extensions your modules use 80 | // moduleFileExtensions: [ 81 | // "js", 82 | // "mjs", 83 | // "cjs", 84 | // "jsx", 85 | // "ts", 86 | // "tsx", 87 | // "json", 88 | // "node" 89 | // ], 90 | 91 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 92 | // moduleNameMapper: {}, 93 | 94 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 95 | // modulePathIgnorePatterns: [], 96 | 97 | // Activates notifications for test results 98 | // notify: false, 99 | 100 | // An enum that specifies notification mode. Requires { notify: true } 101 | // notifyMode: "failure-change", 102 | 103 | // A preset that is used as a base for Jest's configuration 104 | // preset: undefined, 105 | 106 | // Run tests from one or more projects 107 | // projects: undefined, 108 | 109 | // Use this configuration option to add custom reporters to Jest 110 | // reporters: undefined, 111 | 112 | // Automatically reset mock state before every test 113 | // resetMocks: false, 114 | 115 | // Reset the module registry before running each individual test 116 | // resetModules: false, 117 | 118 | // A path to a custom resolver 119 | // resolver: undefined, 120 | 121 | // Automatically restore mock state and implementation before every test 122 | // restoreMocks: false, 123 | 124 | // The root directory that Jest should scan for tests and modules within 125 | // rootDir: undefined, 126 | 127 | // A list of paths to directories that Jest should use to search for files in 128 | // roots: [ 129 | // "" 130 | // ], 131 | 132 | // Allows you to use a custom runner instead of Jest's default test runner 133 | // runner: "jest-runner", 134 | 135 | // The paths to modules that run some code to configure or set up the testing environment before each test 136 | // setupFiles: [], 137 | 138 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 139 | // setupFilesAfterEnv: [], 140 | 141 | // The number of seconds after which a test is considered as slow and reported as such in the results. 142 | // slowTestThreshold: 5, 143 | 144 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 145 | // snapshotSerializers: [], 146 | 147 | // The test environment that will be used for testing 148 | testEnvironment: "jsdom", 149 | 150 | // Options that will be passed to the testEnvironment 151 | // testEnvironmentOptions: {}, 152 | 153 | // Adds a location field to test results 154 | // testLocationInResults: false, 155 | 156 | // The glob patterns Jest uses to detect test files 157 | // testMatch: [ 158 | // "**/__tests__/**/*.[jt]s?(x)", 159 | // "**/?(*.)+(spec|test).[tj]s?(x)" 160 | // ], 161 | 162 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 163 | // testPathIgnorePatterns: [ 164 | // "/node_modules/" 165 | // ], 166 | 167 | // The regexp pattern or array of patterns that Jest uses to detect test files 168 | // testRegex: [], 169 | 170 | // This option allows the use of a custom results processor 171 | // testResultsProcessor: undefined, 172 | 173 | // This option allows use of a custom test runner 174 | // testRunner: "jest-circus/runner", 175 | 176 | // A map from regular expressions to paths to transformers 177 | // transform: undefined, 178 | 179 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 180 | // transformIgnorePatterns: [ 181 | // "/node_modules/", 182 | // "\\.pnp\\.[^\\/]+$" 183 | // ], 184 | 185 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 186 | // unmockedModulePathPatterns: undefined, 187 | 188 | // Indicates whether each individual test should be reported during the run 189 | // verbose: undefined, 190 | 191 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 192 | // watchPathIgnorePatterns: [], 193 | 194 | // Whether to use watchman for file crawling 195 | // watchman: true, 196 | }; 197 | 198 | module.exports = config; 199 | -------------------------------------------------------------------------------- /__tests__/assembler.test.js: -------------------------------------------------------------------------------- 1 | const tree = require("../TreeNode.js"); 2 | global.TreeNode = tree.TreeNode; // referenced by tokenizer 3 | 4 | const tokenizer = 5 | require("../tokenizer.js"); // Parser depends on the tokenizer to work... 6 | 7 | const list_of_directives = require( 8 | "../list_of_directives.js"); //...as well as on the list of directives. 9 | global.mnemonics = list_of_directives.mnemonics; 10 | global.preprocessor = list_of_directives.preprocessor; 11 | 12 | const parser = require("../parser.js"); 13 | 14 | const preprocessor = require("../preprocessor.js"); 15 | 16 | const headerScript = require("../headerScript.js"); 17 | global.machineCode = headerScript.machineCode; 18 | global.formatAsAddress = 19 | headerScript.formatAsAddress; // Or else labels won't work. 20 | 21 | global.default_base_of_literals_in_assembly = 16; 22 | 23 | const assembler = require("../assembler.js"); 24 | 25 | describe("Assembler tests", () => { 26 | test("`inst` works", () => { 27 | const assembly = ` 28 | address 0 29 | inst 1 + 2 * 3 30 | `; 31 | const abstract_syntax_tree = parser.parse(tokenizer.tokenize(assembly)); 32 | const compilation_context = 33 | preprocessor.makeCompilationContext(abstract_syntax_tree); 34 | assembler.assemble(abstract_syntax_tree, compilation_context); 35 | expect(machineCode[0].hex).toBe("00007"); 36 | }); 37 | 38 | test("Loading a constant into a register works", () => { 39 | const assembly = ` 40 | address 0 41 | load s1, (1 + 2) * 3 42 | `; 43 | const abstract_syntax_tree = parser.parse(tokenizer.tokenize(assembly)); 44 | const compilation_context = 45 | preprocessor.makeCompilationContext(abstract_syntax_tree); 46 | assembler.assemble(abstract_syntax_tree, compilation_context); 47 | expect(machineCode[0].hex).toBe("01109"); 48 | }); 49 | 50 | test("Moving values between registers works", () => { 51 | const assembly = ` 52 | address 0 53 | load s0, s1 54 | `; 55 | const abstract_syntax_tree = parser.parse(tokenizer.tokenize(assembly)); 56 | const compilation_context = 57 | preprocessor.makeCompilationContext(abstract_syntax_tree); 58 | assembler.assemble(abstract_syntax_tree, compilation_context); 59 | expect(machineCode[0].hex).toBe("00010"); 60 | }); 61 | 62 | test("Labels work", () => { 63 | const assembly = ` 64 | address 0 65 | jump label 66 | load s0, 1 67 | label: 68 | add s0, s0 69 | `; 70 | const abstract_syntax_tree = parser.parse(tokenizer.tokenize(assembly)); 71 | const compilation_context = 72 | preprocessor.makeCompilationContext(abstract_syntax_tree); 73 | assembler.assemble(abstract_syntax_tree, compilation_context); 74 | expect(machineCode[0].hex).toBe("22002"); 75 | }); 76 | 77 | test("Pointers and namereg work", () => { 78 | const assembly = ` 79 | address 0 80 | namereg sf, pointer 81 | store s0, (pointer) 82 | `; 83 | const abstract_syntax_tree = parser.parse(tokenizer.tokenize(assembly)); 84 | const compilation_context = 85 | preprocessor.makeCompilationContext(abstract_syntax_tree); 86 | assembler.assemble(abstract_syntax_tree, compilation_context); 87 | expect(machineCode[0].hex).toBe("2e0f0"); 88 | }); 89 | 90 | test("Conditional jumps work", () => { 91 | const assembly = ` 92 | address 0 93 | input s0, 0 94 | compare s0, 200'd 95 | jump c, less_than_200 96 | sub s0, 200'd 97 | load s1, 2 98 | less_than_200: 99 | compare s0, 100'd 100 | `; 101 | const abstract_syntax_tree = parser.parse(tokenizer.tokenize(assembly)); 102 | const compilation_context = 103 | preprocessor.makeCompilationContext(abstract_syntax_tree); 104 | assembler.assemble(abstract_syntax_tree, compilation_context); 105 | expect(machineCode[2].hex).toBe("3a005"); 106 | }); 107 | 108 | test("Changing the bases of the constant literals works", () => { 109 | const assembly = ` 110 | address 0 111 | inst 10 112 | base_decimal 113 | inst 10 114 | base_hexadecimal 115 | inst 10 116 | `; 117 | const abstract_syntax_tree = parser.parse(tokenizer.tokenize(assembly)); 118 | const compilation_context = 119 | preprocessor.makeCompilationContext(abstract_syntax_tree); 120 | assembler.assemble(abstract_syntax_tree, compilation_context); 121 | expect(machineCode[0].hex).toBe("00010"); 122 | expect(machineCode[1].hex).toBe("0000a"); 123 | expect(machineCode[2].hex).toBe("00010"); 124 | }); 125 | 126 | test("Function pointers are assembled correctly", () => { 127 | const assembly = ` 128 | address 0 129 | call@(s1, s2) 130 | `; 131 | const abstract_syntax_tree = parser.parse(tokenizer.tokenize(assembly)); 132 | const compilation_context = 133 | preprocessor.makeCompilationContext(abstract_syntax_tree); 134 | assembler.assemble(abstract_syntax_tree, compilation_context); 135 | expect(machineCode[0].hex).toBe("24120"); 136 | }); 137 | 138 | test("The ternary conditional operator inside JUMP works correctly", () => { // This was once crashing the assembler: https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/38 139 | const assembly = ` 140 | ;This is an example program demonstrating 141 | ;how the \`?:\` operator in jumps, supposedly 142 | ;enabled in v5.2.1, doesn't really work as 143 | ;intended. 144 | 145 | address 0 146 | jump PicoBlaze_Simulator_in_JS ? code_that_should_run_in_browser : code_that_should_run_on_mobile 147 | load s0, 0 148 | code_that_should_run_on_mobile: 149 | load s0, 1 150 | jump end_of_branching 151 | code_that_should_run_in_browser: 152 | load s0, 2 153 | jump end_of_branching 154 | end_of_branching: 155 | 156 | ;The 7th line doesn't assemble. The assembler 157 | ;asks the user \`Instead of 158 | ;"code_that_should_run_in_browser", in the 159 | ;line #7, did you perhaps mean 160 | ;"PicoBlaze_Simulator_in_JS"?\`. It has to do 161 | ;with the way the assembler is structured 162 | ;internally. Namely, when the core of the 163 | ;assembler sees the "jump" instruction, it 164 | ;invokes the "getLabelAddress" method of the 165 | ;"TreeNode" class. However, when that method 166 | ;sees that it's being invoked on an \`?:\` 167 | ;operator, it wrongly assumes that all of its 168 | ;operands are arithmetic expressions, so 169 | ;it invokes the 170 | ;"interpretAsArithmeticExpression" method. 171 | ;That method takes as the only argument the 172 | ;"constants" argument, and it has no access 173 | ;to the labels. There doesn't seem to be 174 | ;a simple solution. 175 | `; 176 | const abstract_syntax_tree = parser.parse(tokenizer.tokenize(assembly)); 177 | const compilation_context = 178 | preprocessor.makeCompilationContext(abstract_syntax_tree); 179 | assembler.assemble(abstract_syntax_tree, compilation_context); 180 | expect(machineCode[0].hex).toBe("22004"); 181 | }); 182 | 183 | test("Strings containing the colon tokenize correctly", () => { // This was once crashing the assembler: https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/39 184 | const assembly = ` 185 | ;This is an example program showing how 186 | ;":" doesn't tokenize correctly. 187 | 188 | address 0 189 | load s9, ":" 190 | `; 191 | const abstract_syntax_tree = parser.parse(tokenizer.tokenize(assembly)); 192 | const compilation_context = 193 | preprocessor.makeCompilationContext(abstract_syntax_tree); 194 | assembler.assemble(abstract_syntax_tree, compilation_context); 195 | expect(machineCode[0].hex).toBe("0193a"); 196 | }); 197 | 198 | test("The \"print_string\" pseudo-mnemonic works", () => { 199 | const assembly = ` 200 | address 0 201 | print_string "Hello world!", s9, UART_TX 202 | load s9, a 203 | call UART_TX 204 | infinite_loop: jump infinite_loop 205 | 206 | ;Now follows some boilerplate code 207 | ;we use in our Computer Architecture 208 | ;classes... 209 | CONSTANT LED_PORT, 00 210 | CONSTANT HEX1_PORT, 01 211 | CONSTANT HEX2_PORT, 02 212 | CONSTANT UART_TX_PORT, 03 213 | CONSTANT UART_RESET_PORT, 04 214 | CONSTANT SW_PORT, 00 215 | CONSTANT BTN_PORT, 01 216 | CONSTANT UART_STATUS_PORT, 02 217 | CONSTANT UART_RX_PORT, 03 218 | ; Tx data_present 219 | CONSTANT U_TX_D, 00000001'b 220 | ; Tx FIFO half_full 221 | CONSTANT U_TX_H, 00000010'b 222 | ; TxFIFO full 223 | CONSTANT U_TX_F, 00000100'b 224 | ; Rxdata_present 225 | CONSTANT U_RX_D, 00001000'b 226 | ; RxFIFO half_full 227 | CONSTANT U_RX_H, 00010000'b 228 | ; RxFIFO full 229 | CONSTANT U_RX_F, 00100000'b 230 | 231 | UART_RX: 232 | INPUT sA, UART_STATUS_PORT 233 | TEST sA, U_RX_D 234 | JUMP NZ, input_not_empty 235 | LOAD s0, s0 236 | JUMP UART_RX 237 | input_not_empty: 238 | INPUT s9, UART_RX_PORT 239 | RETURN 240 | 241 | UART_TX: 242 | INPUT sA, UART_STATUS_PORT 243 | TEST sA, U_TX_F 244 | JUMP NZ, UART_TX 245 | OUTPUT s9, UART_TX_PORT 246 | RETURN 247 | `; 248 | const abstract_syntax_tree = parser.parse(tokenizer.tokenize(assembly)); 249 | const compilation_context = 250 | preprocessor.makeCompilationContext(abstract_syntax_tree); 251 | const call_UART_TX="20"+formatAsAddress(compilation_context.labels.get("UART_TX")); 252 | assembler.assemble(abstract_syntax_tree, compilation_context); 253 | expect(machineCode[0].hex).toBe("01948"); // H 254 | expect(machineCode[1].hex).toBe(call_UART_TX); 255 | expect(machineCode[2].hex).toBe("01965"); // e 256 | expect(machineCode[3].hex).toBe(call_UART_TX); 257 | expect(machineCode[4].hex).toBe("0196c"); // l 258 | expect(machineCode[5].hex).toBe(call_UART_TX); 259 | expect(machineCode[6].hex).toBe("0196c"); // l 260 | expect(machineCode[7].hex).toBe(call_UART_TX); 261 | expect(machineCode[8].hex).toBe("0196f"); // o 262 | expect(machineCode[9].hex).toBe(call_UART_TX); 263 | expect(machineCode[10].hex).toBe("01920"); // (space) 264 | expect(machineCode[11].hex).toBe(call_UART_TX); 265 | expect(machineCode[12].hex).toBe("01977"); // w 266 | expect(machineCode[13].hex).toBe(call_UART_TX); 267 | expect(machineCode[14].hex).toBe("0196f"); // o 268 | expect(machineCode[15].hex).toBe(call_UART_TX); 269 | expect(machineCode[16].hex).toBe("01972"); // r 270 | expect(machineCode[17].hex).toBe(call_UART_TX); 271 | expect(machineCode[18].hex).toBe("0196c"); // l 272 | expect(machineCode[19].hex).toBe(call_UART_TX); 273 | expect(machineCode[20].hex).toBe("01964"); // d 274 | expect(machineCode[21].hex).toBe(call_UART_TX); 275 | expect(machineCode[22].hex).toBe("01921"); // ! 276 | expect(machineCode[23].hex).toBe(call_UART_TX); 277 | expect(machineCode[24].hex).toBe("0190a"); // new-line character 278 | expect(machineCode[25].hex).toBe(call_UART_TX); 279 | }); 280 | }); 281 | -------------------------------------------------------------------------------- /nQueensPuzzle.psm: -------------------------------------------------------------------------------- 1 | ;This is my attempt to solve the Eight queens puzzle in PicoBlaze assembly. 2 | ;This is also the Leetcode problem #51. 3 | ;It finds all the 92 solutions, it's just very slow. 4 | ;In case you are interested, I've also asked a Mathematics StackExchange question about the number of solutions to this puzzle. 5 | 6 | base_decimal 7 | 8 | address 0 9 | 10 | constant NDEBUG, 1 11 | constant should_we_print_chessboards, 1 12 | constant size_of_the_chessboard, 8 13 | constant address_of_the_current_attempt, 0 14 | constant digits_of_the_ordinal_number, 10 15 | constant bottom_of_the_stack, 16 16 | 17 | 18 | print_string "Searching for solutions...", s9, UART_TX 19 | load s9, a'x 20 | call UART_TX 21 | 22 | ;Let's set all the digits of the ordinal number of solutions to "0" 23 | regbank b 24 | load s0, digits_of_the_ordinal_number 25 | load s2, digits_of_the_ordinal_number ;End of the digits of the ordinal number. 26 | reset_ordinal_numbers_loop: 27 | compare s0, bottom_of_the_stack 28 | jump nc, end_of_the_reset_ordinal_numbers_loop 29 | load s1, "0" 30 | store s1, (s0) 31 | add s0, 1 32 | jump reset_ordinal_numbers_loop 33 | end_of_the_reset_ordinal_numbers_loop: 34 | regbank a 35 | 36 | namereg sf, top_of_the_stack 37 | 38 | load top_of_the_stack, bottom_of_the_stack 39 | load s0, 0 40 | store s0, (top_of_the_stack) 41 | add top_of_the_stack, size_of_the_chessboard + 1 42 | 43 | main_loop: 44 | compare top_of_the_stack, bottom_of_the_stack 45 | jump z, end_of_the_main_loop 46 | sub top_of_the_stack, size_of_the_chessboard + 1 47 | 48 | namereg se, length_of_the_current_attempt 49 | fetch length_of_the_current_attempt, (top_of_the_stack) 50 | load s0, address_of_the_current_attempt 51 | store length_of_the_current_attempt, (s0) 52 | load s1, top_of_the_stack 53 | load s2, 0 54 | 55 | copying_the_current_attempt_from_the_stack_loop: 56 | compare s2, length_of_the_current_attempt 57 | jump z, end_of_copying_the_current_attempt_from_the_stack_loop 58 | add s2, 1 59 | add s0, 1 60 | add s1, 1 61 | fetch s3, (s1) 62 | store s3, (s0) 63 | jump copying_the_current_attempt_from_the_stack_loop 64 | end_of_copying_the_current_attempt_from_the_stack_loop: 65 | 66 | load s0, NDEBUG 67 | test s0, s0 68 | jump nz, dont_print_the_current_attempt 69 | 70 | print_string "The current attempt is: ", s9, UART_TX 71 | call print_the_current_attempt 72 | dont_print_the_current_attempt: 73 | 74 | compare length_of_the_current_attempt, size_of_the_chessboard 75 | jump nz, not_a_solution 76 | print_string "Found a solution: ", s9, UART_TX 77 | call print_the_current_attempt 78 | 79 | jump should_we_print_chessboards ? print_the_chessboard : dont_print_the_chessboard 80 | print_the_chessboard: 81 | load s6, size_of_the_chessboard - 1 82 | outer_loop_for_printing_the_chessboard: 83 | load s7, address_of_the_current_attempt + 1 84 | inner_loop_for_printing_the_chessboard: 85 | load s9, "Q" 86 | fetch s8, (s7) 87 | compare s8, s6 88 | jump z, queen_is_on_the_field 89 | load s9, "." 90 | queen_is_on_the_field: 91 | call UART_TX 92 | add s7, 1 93 | compare s7, address_of_the_current_attempt + 1 + size_of_the_chessboard 94 | jump nz, inner_loop_for_printing_the_chessboard 95 | end_of_inner_loop_for_printing_the_chessboard: 96 | load s9, a'x 97 | call UART_TX 98 | sub s6, 1 99 | jump nc, outer_loop_for_printing_the_chessboard 100 | end_of_outer_loop_for_printing_the_chessboard: 101 | dont_print_the_chessboard: 102 | 103 | regbank b 104 | 105 | print_string "That's the solution #", s9, UART_TX 106 | load s1, digits_of_the_ordinal_number 107 | increasing_the_ordinal_number_loop: 108 | fetch s0, (s1) 109 | add s0, 1 110 | store s0, (s1) 111 | compare s0, "9" + 1 112 | jump nz, end_of_increasing_the_ordinal_number_loop 113 | load s0, "0" 114 | store s0, (s1) 115 | add s1, 1 116 | jump increasing_the_ordinal_number_loop 117 | end_of_increasing_the_ordinal_number_loop: 118 | 119 | compare s1, s2 120 | jump c, not_a_new_digit 121 | load s2, s1 122 | not_a_new_digit: 123 | 124 | load s1, s2 125 | printing_the_ordinal_number: 126 | fetch s9, (s1) 127 | call UART_TX 128 | sub s1, 1 129 | compare s1, digits_of_the_ordinal_number 130 | jump nc, printing_the_ordinal_number 131 | end_of_printing_the_ordinal_number: 132 | load s9, a'x 133 | call UART_TX 134 | 135 | regbank a 136 | 137 | jump end_of_branching 138 | 139 | not_a_solution: 140 | 141 | namereg sd, row_of_the_queen_we_are_trying_to_add 142 | load row_of_the_queen_we_are_trying_to_add, size_of_the_chessboard - 1 143 | 144 | adding_a_new_queen_loop: 145 | jump NDEBUG ? dont_print_the_new_queen: print_the_new_queen 146 | print_the_new_queen: 147 | 148 | print_string "We will try to add a queen at the field: ", s9, UART_TX 149 | load s9, length_of_the_current_attempt 150 | add s9, "A" 151 | call UART_TX 152 | load s9, row_of_the_queen_we_are_trying_to_add 153 | add s9, "1" 154 | call UART_TX 155 | load s9, a'x 156 | call UART_TX 157 | 158 | dont_print_the_new_queen: 159 | 160 | load s0, address_of_the_current_attempt + 1 161 | load s1, 0 162 | 163 | ;s2 will be the diagonal of the current attempt. 164 | load s2, row_of_the_queen_we_are_trying_to_add 165 | add s2, length_of_the_current_attempt 166 | 167 | ;s3 will be the anti-diagonal of the current attempt. 168 | load s3, row_of_the_queen_we_are_trying_to_add 169 | sub s3, length_of_the_current_attempt 170 | 171 | looping_through_current_attempt: 172 | compare s1, length_of_the_current_attempt 173 | jump z, end_of_looping_through_current_attempt 174 | 175 | fetch s4, (s0) 176 | compare s4, row_of_the_queen_we_are_trying_to_add 177 | jump z, queen_is_in_the_same_row 178 | 179 | load s5, s4 180 | add s5, s1 181 | compare s5, s2 182 | jump z, queen_is_on_the_same_diagonal 183 | 184 | load s6, s4 185 | sub s6, s1 186 | compare s6, s3 187 | jump z, queen_is_on_the_same_antidiagonal 188 | 189 | add s0, 1 190 | add s1, 1 191 | jump looping_through_current_attempt 192 | end_of_looping_through_current_attempt: 193 | 194 | jump add_the_new_queen 195 | 196 | queen_is_in_the_same_row: 197 | jump NDEBUG ? dont_add_the_new_queen : print_the_first_debug_message 198 | print_the_first_debug_message: 199 | print_string "There is a queen in the same row, aborting!", s9, UART_TX 200 | load s9, a'x 201 | call UART_TX 202 | jump dont_add_the_new_queen 203 | 204 | queen_is_on_the_same_diagonal: 205 | jump NDEBUG ? dont_add_the_new_queen : print_the_second_debug_message 206 | print_the_second_debug_message: 207 | print_string "There is a queen on the same diagonal, aborting!", s9, UART_TX 208 | load s9, a'x 209 | call UART_TX 210 | jump dont_add_the_new_queen 211 | 212 | queen_is_on_the_same_antidiagonal: 213 | jump NDEBUG ? dont_add_the_new_queen : print_the_third_debug_message 214 | print_the_third_debug_message: 215 | print_string "There is a queen on the same anti-diagonal, aborting!", s9, UART_TX 216 | load s9, a'x 217 | call UART_TX 218 | jump dont_add_the_new_queen 219 | 220 | add_the_new_queen: 221 | 222 | jump NDEBUG ? dont_print_the_fourth_debug_message : print_the_fourth_debug_message 223 | print_the_fourth_debug_message: 224 | print_string "Nothing seems to prevent that queen from being added!", s9, UART_TX 225 | load s9, a'x 226 | call UART_TX 227 | 228 | dont_print_the_fourth_debug_message: 229 | load s0, top_of_the_stack 230 | load s1, length_of_the_current_attempt 231 | add s1, 1 232 | store s1, (s0) 233 | add s0, 1 234 | load s1, 0 235 | copying_the_current_attempt_onto_stack: 236 | compare s1, length_of_the_current_attempt 237 | jump z, end_of_copying_the_current_attempt_onto_stack 238 | load s2, address_of_the_current_attempt + 1 239 | add s2, s1 240 | fetch s3, (s2) 241 | store s3, (s0) 242 | add s0, 1 243 | add s1, 1 244 | jump copying_the_current_attempt_onto_stack 245 | end_of_copying_the_current_attempt_onto_stack: 246 | store row_of_the_queen_we_are_trying_to_add, (s0) 247 | add top_of_the_stack, size_of_the_chessboard + 1 248 | 249 | dont_add_the_new_queen: 250 | sub row_of_the_queen_we_are_trying_to_add, 1 251 | jump nc, adding_a_new_queen_loop 252 | end_of_adding_a_new_queen_loop: 253 | 254 | jump NDEBUG ? end_of_branching : adding_a_new_queen_exited_message 255 | adding_a_new_queen_exited_message: 256 | print_string "The `adding_a_new_queen_loop` loop exited!", s9, UART_TX 257 | load s9, a'x 258 | call UART_TX 259 | 260 | end_of_branching: 261 | 262 | jump main_loop 263 | end_of_the_main_loop: 264 | 265 | print_string "The end!", s9, UART_TX 266 | load s9, a'x 267 | call UART_TX 268 | 269 | infinite_loop: jump infinite_loop 270 | 271 | print_the_current_attempt: 272 | load s0, address_of_the_current_attempt + 1 273 | load s1, 0 274 | compare length_of_the_current_attempt, 0 275 | jump nz, printing_the_current_attempt_loop 276 | print_string "Empty", s9, UART_TX 277 | jump end_of_printing_the_current_attempt_loop 278 | printing_the_current_attempt_loop: 279 | compare s1, length_of_the_current_attempt 280 | jump z, end_of_printing_the_current_attempt_loop 281 | load s9, s1 282 | add s9, "A" 283 | call UART_TX 284 | fetch s9, (s0) 285 | add s9, "1" 286 | call UART_TX 287 | load sb, length_of_the_current_attempt 288 | sub sb, 1 289 | compare s1, sb 290 | jump z, dont_print_the_trailing_space 291 | load s9, " " 292 | call UART_TX 293 | dont_print_the_trailing_space: 294 | add s0, 1 295 | add s1, 1 296 | jump printing_the_current_attempt_loop 297 | end_of_printing_the_current_attempt_loop: 298 | load s9, a'x 299 | call UART_TX 300 | return 301 | 302 | base_hexadecimal 303 | ;Now follows some boilerplate code 304 | ;we use in our Computer Architecture 305 | ;classes... 306 | CONSTANT LED_PORT, 00 307 | CONSTANT HEX1_PORT, 01 308 | CONSTANT HEX2_PORT, 02 309 | CONSTANT UART_TX_PORT, 03 310 | CONSTANT UART_RESET_PORT, 04 311 | CONSTANT SW_PORT, 00 312 | CONSTANT BTN_PORT, 01 313 | CONSTANT UART_STATUS_PORT, 02 314 | CONSTANT UART_RX_PORT, 03 315 | ; Tx data_present 316 | CONSTANT U_TX_D, 00000001'b 317 | ; Tx FIFO half_full 318 | CONSTANT U_TX_H, 00000010'b 319 | ; TxFIFO full 320 | CONSTANT U_TX_F, 00000100'b 321 | ; Rxdata_present 322 | CONSTANT U_RX_D, 00001000'b 323 | ; RxFIFO half_full 324 | CONSTANT U_RX_H, 00010000'b 325 | ; RxFIFO full 326 | CONSTANT U_RX_F, 00100000'b 327 | 328 | UART_RX: 329 | INPUT sA, UART_STATUS_PORT 330 | TEST sA, U_RX_D 331 | JUMP NZ, input_not_empty 332 | LOAD s0, s0 333 | JUMP UART_RX 334 | input_not_empty: 335 | INPUT s9, UART_RX_PORT 336 | RETURN 337 | 338 | UART_TX: 339 | INPUT sA, UART_STATUS_PORT 340 | TEST sA, U_TX_F 341 | JUMP NZ, UART_TX 342 | OUTPUT s9, UART_TX_PORT 343 | RETURN 344 | 345 | ;I've opened a StackExchange thread about this program, in case you are interested. 346 | ;Quite a while ago, I implemented a similar program in WebAssembly. 347 | 348 | ;I've made MEM file of this program compatible with OpbSim, a part of OpbAsm. 349 | ;Just, when running it, make sure you set the scratchpad memory is set to 256 bytes, rather than to the default 64 bytes. 350 | -------------------------------------------------------------------------------- /dec2bin.psm: -------------------------------------------------------------------------------- 1 | ;WARNING: I have tried to run this on 2 | ; real PicoBlaze, but failed. 3 | ; Now, I did not have time to 4 | ; test whether a simpler 5 | ; example program using UART 6 | ; would work, to see if it 7 | ; is a problem with this 8 | ; program, or if it is maybe 9 | ; a problem with the way I 10 | ; set up PicoBlaze. I have 11 | ; opened a 12 | ; StackOverflow question 13 | ; about it, but, thus far, I 14 | ; received no response. 15 | 16 | 17 | ;This is an example program that uses 18 | ;UART, the interface that PicoBlaze uses 19 | ;for connecting to terminals (a DOS-like 20 | ;user interface, with a keyboard and a 21 | ;screen capable of displaying text). 22 | ;It loads base-10 integer numbers from 23 | ;the terminal, converts them into binary, 24 | ;and then prints the binary 25 | ;representations back onto the terminal. 26 | ;Because all registers in PicoBlaze are 27 | ;8-bit, the numbers must be smaller than 28 | ;2**8=256. Dealing with bigger numbers 29 | ;would make the program much more 30 | ;complicated. 31 | ;Example input would be: 32 | ;1 33 | ;2 34 | ;4 35 | ;8 36 | ;15 37 | ;127 38 | ;255 39 | ; 40 | ;And the expected output is: 41 | ;1_(10)=1_(2) 42 | ;2_(10)=10_(2) 43 | ;4_(10)=100_(2) 44 | ;8_(10)=1000_(2) 45 | ;15_(10)=1111_(2) 46 | ;127_(10)=1111111_(2) 47 | ;255_(10)=11111111_(2) 48 | ; 49 | ;Note that you need to click the 50 | ;"Enable UART" button in order to use it. 51 | ;Also, the trailing empty line in the 52 | ;input is necessary for the result to be 53 | ;printed. 54 | 55 | ;Now follows some boilerplate code 56 | ;we use in our Computer Architecture 57 | ;classes... 58 | CONSTANT LED_PORT, 00 59 | CONSTANT HEX1_PORT, 01 60 | CONSTANT HEX2_PORT, 02 61 | CONSTANT UART_TX_PORT, 03 62 | CONSTANT UART_RESET_PORT, 04 63 | CONSTANT SW_PORT, 00 64 | CONSTANT BTN_PORT, 01 65 | CONSTANT UART_STATUS_PORT, 02 66 | CONSTANT UART_RX_PORT, 03 67 | ; Tx data_present 68 | CONSTANT U_TX_D, 00000001'b 69 | ; Tx FIFO half_full 70 | CONSTANT U_TX_H, 00000010'b 71 | ; TxFIFO full 72 | CONSTANT U_TX_F, 00000100'b 73 | ; Rxdata_present 74 | CONSTANT U_RX_D, 00001000'b 75 | ; RxFIFO half_full 76 | CONSTANT U_RX_H, 00010000'b 77 | ; RxFIFO full 78 | CONSTANT U_RX_F, 00100000'b 79 | 80 | ;The following code will not 81 | ;assemble in PicoBlaze_Simulator_in_JS 82 | ;version older than v2.8. 83 | if PicoBlaze_Simulator_in_JS 84 | display "Before starting this " 85 | display "program, enter decimal " 86 | display "integers separated by " 87 | display "new-line characters in " 88 | display "the text-area above, and " 89 | display "also insert a new-line " 90 | display "character after the last " 91 | display "number. " 92 | display a ;A new-line character, see 93 | ;below for more details. 94 | display "The numbers must be " 95 | display "smaller than 256. " 96 | display a 97 | display "It is recommended that, " 98 | display "before starting this " 99 | display "program, you uncheck the " 100 | display "checkbox for updating " 101 | display "the tables with " 102 | display "registers and flags on " 103 | display "every step. " 104 | display a 105 | display "That way, " 106 | display "this program will run " 107 | display "significantly faster in " 108 | display "all browsers except for " 109 | display "WebPositive. " 110 | display a 111 | display "Namely, in " 112 | display "the current version of " 113 | display "this simulator, in most " 114 | display "browsers, updating the " 115 | display "tables with registers " 116 | display "and flags causes " 117 | display "layout trashing. " 118 | display a 119 | display "The authors think that, " 120 | display "in some future version " 121 | display "of this simulator, that " 122 | display "problem will be solved " 123 | display "using advanced CSS (by " 124 | display "properly setting up the " 125 | display "'contain' property on " 126 | display "various elements), but, " 127 | display "for the time being, " 128 | display "there is a checkbox to " 129 | display "reduce layout trashing. " 130 | display a 131 | endif 132 | 133 | ;The following code is a no-operation 134 | ;in the current version of 135 | ;PicoBlaze Simulator for Android, 136 | ;but I hope it will be useful in some 137 | ;future version (when that program is 138 | ;finished by somebody who knows Java 139 | ;and Android well). 140 | if PicoBlaze_Simulator_for_Android 141 | display "After starting this " 142 | display "program, type decimal " 143 | display "integers here and " 144 | display "press Enter after " 145 | display "each. Right after " 146 | display "you press Enter, " 147 | display "this program will " 148 | display "convert the decimal " 149 | display "integer you just entered " 150 | display "to binary." 151 | display a 152 | display "The numbers must be " 153 | display "smaller than 256." 154 | display a 155 | endif 156 | 157 | 158 | ADDRESS 0 159 | START: 160 | ;At the beginning, the number is 0. 161 | load s0, 0 162 | ;And we are storing its string 163 | ;representation at the beginning 164 | ;of RAM. 165 | namereg s3, pointer 166 | load pointer, 0 167 | ;Now follows a loop to load 168 | ;the digits of the number. 169 | loading_the_number: 170 | ;Load a character from the UART 171 | ;terminal. 172 | call UART_RX 173 | ;Check whether the character is a digit. 174 | compare s9, "0" 175 | ;If it is not a digit, jump to the 176 | ;part of the program for printing 177 | ;the number you have got. 178 | jump c , print_the_number 179 | compare s9, "9" + 1 180 | ;Comparing with "9" + 1 instead of "9" 181 | ;in order to reduce the number of 182 | ;instructions is a suggestion by a 183 | ;StackExchange user called Sep Roland. 184 | jump nc, print_the_number 185 | ;If it is a digit, store it into RAM. 186 | store s9, (pointer) 187 | add pointer, 1 188 | ;Multiply the number you have got by 10. 189 | load sf, s0 190 | call multiply_by_10 191 | load s0, se 192 | ;Then, convert the digit from ASCII 193 | ;into binary. 194 | sub s9, "0" 195 | ;And then add it to the number you 196 | ;have got. 197 | add s0, s9 198 | call c, number_too_big 199 | jump loading_the_number ;Repeat until a 200 | ;non-digit is 201 | ;loaded. 202 | print_the_number: 203 | ;If there are no digits to be printed, 204 | ;do not print anything. 205 | sub pointer, 0 206 | jump z, START 207 | print_the_decimal: 208 | load s4, pointer 209 | load pointer, 0 210 | printing_the_decimal_loop: 211 | compare pointer, s4 212 | jump nc, end_of_printing_the_decimal 213 | fetch s9, (pointer) 214 | ;Do some basic sanity check: Is the 215 | ;character you are printing indeed 216 | ;a decimal digit? 217 | compare s9, "0" 218 | call c , abort 219 | compare s9, "9" + 1 220 | call nc, abort 221 | ;If it is indeed a decimal digit, 222 | ;print it. 223 | call UART_TX 224 | add pointer, 1 225 | jump printing_the_decimal_loop 226 | end_of_printing_the_decimal: 227 | ;The following line will only assemble 228 | ;using v5.3.0 or newer 229 | print_string "_(10)=", s9, UART_TX 230 | ;If the number to be printed is 231 | ;equal to zero, print 0. 232 | sub s0, 0 233 | jump nz, print_the_binary 234 | load s9, "0" 235 | call UART_TX 236 | jump end_of_printing_loop 237 | print_the_binary: 238 | ;Make the pointer point to the 239 | ;beginning of RAM. 240 | load pointer, 0 241 | ;Now goes a loop which stores the binary 242 | ;representation of the number we have 243 | ;got into RAM, but reversed. 244 | beginning_of_converting_to_binary: 245 | sub s0, 0 246 | jump z , end_of_converting_to_binary 247 | load s9, "0" 248 | sr0 s0 249 | jump nc, store_digit_to_memory 250 | add s9, 1 251 | store_digit_to_memory: 252 | store s9, (pointer) 253 | add pointer, 1 254 | jump beginning_of_converting_to_binary 255 | end_of_converting_to_binary: 256 | ;Do some basic sanity check, such as that 257 | ;the pointer does not point to zero. 258 | compare pointer, 0 259 | call z, abort ;Something went wrong 260 | ;so end the program. 261 | ;Check whether there are more than 8 bits. 262 | compare pointer, 8 + 1 263 | call nc , abort 264 | ;Now goes a loop which will print 265 | ;the binary number in RAM, with digits 266 | ;in the correct order. The pointer now 267 | ;points at a memory location right after 268 | ;the binary number (not at the last digit, 269 | ;but right after it). 270 | beginning_of_printing_loop: 271 | sub pointer, 1 272 | jump c , end_of_printing_loop 273 | fetch s9 , (pointer) 274 | ;Do some basic sanity check: 275 | ;Is the character the pointer points to 276 | ;indeed a binary digit? 277 | compare s9, "0" 278 | jump z , memory_is_fine 279 | compare s9, "1" 280 | jump z , memory_is_fine 281 | call abort ;Something went wrong, 282 | ;so end the program. 283 | memory_is_fine: 284 | ;If everything is fine, print that 285 | ;digit. 286 | call UART_TX 287 | ;Repeat until you have printed all 288 | ;digits of the binary number 289 | ;stored in RAM. 290 | jump beginning_of_printing_loop 291 | end_of_printing_loop: 292 | print_string "_(2)", s9, UART_TX 293 | load s9, a ;newline character, 0xa=='\n'. 294 | ;The last line is being highlighted 295 | ;incorrectly by the syntax 296 | ;highlighter, see this GitHub issue for 297 | ;more information about that. 298 | ;While I don't know how to solve that, 299 | ;there does appear to be a simple 300 | ;work-around: simply, when `a` 301 | ;is a hexadecimal constant, type `0a` 302 | ;instead, and it will be highlighted 303 | ;correctly. 304 | call UART_TX 305 | ;The program runs in an infinite loop... 306 | JUMP START 307 | 308 | multiply_by_10: 309 | load se, sf 310 | add se, se 311 | call c , number_too_big 312 | add se, se 313 | call c , number_too_big 314 | add se, sf 315 | call c , number_too_big 316 | add se, se 317 | call c , number_too_big 318 | return 319 | 320 | number_too_big: 321 | print_string "The entered number is bigger than 255!", s9, UART_TX 322 | load s9, a ;newline 323 | call UART_TX 324 | jump infinite_loop 325 | return 326 | 327 | abort: 328 | load s9, "E" 329 | call UART_TX 330 | load s9, "R" 331 | call UART_TX 332 | load s9, "R" 333 | call UART_TX 334 | load s9, "O" 335 | call UART_TX 336 | load s9, "R" 337 | call UART_TX 338 | load s9, "!" 339 | call UART_TX 340 | load s9, a ;newline 341 | call UART_TX 342 | infinite_loop: 343 | jump infinite_loop 344 | return 345 | 346 | ;Now follows some boilerplate code 347 | ;we use in our Computer Architecture 348 | ;classes... 349 | UART_RX: 350 | INPUT sA, UART_STATUS_PORT 351 | TEST sA, U_RX_D 352 | JUMP NZ, input_not_empty 353 | LOAD s0, s0 ;Add a breakpoint here 354 | ;if you want the program 355 | ;to stop when it has 356 | ;read to the end of the 357 | ;input (rather than running 358 | ;into an infinite loop 359 | ;then). 360 | JUMP UART_RX 361 | input_not_empty: 362 | INPUT s9, UART_RX_PORT 363 | RETURN 364 | 365 | UART_TX: 366 | INPUT sA, UART_STATUS_PORT 367 | TEST sA, U_TX_F 368 | JUMP NZ, UART_TX 369 | OUTPUT s9, UART_TX_PORT 370 | RETURN 371 | 372 | ;By the way, you might also be interested 373 | ;in seeing the 374 | ;decimal-to-IEEE754-binary converter 375 | ;I wrote in x86 assembly back when 376 | ;I was 15. I am sorry that the comments 377 | ;and the variable names are in Croatian, 378 | ;but I didn't know English well back 379 | ;then. 380 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fff7ff; 3 | } 4 | @media (min-width: 500px) { 5 | body { 6 | background-image: url("Background.gif"); 7 | background-repeat: no-repeat; 8 | background-attachment: fixed; 9 | } 10 | } 11 | #ribbon { 12 | display: none; 13 | } 14 | @media (min-width: 700px) { 15 | #ribbon { 16 | display: inline-block; 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | transform: rotate(-45deg) translate(-65px, -10px) scale(0.9); 21 | padding: 5px 50px; 22 | color: white; 23 | background: black; 24 | } 25 | #ribbon a { 26 | color: white; 27 | text-decoration: none; 28 | } 29 | body { 30 | background-position: top right; 31 | } 32 | } 33 | main { 34 | width: 100%; 35 | max-width: 500px; 36 | margin-left: auto; 37 | margin-right: auto; 38 | position: absolute; 39 | top: 80px; 40 | --lineNumbersWidth: 60px; 41 | } 42 | header, 43 | footer { 44 | width: 100%; 45 | text-align: center; 46 | font-family: Arial; 47 | line-height: 100%; 48 | } 49 | #authors { 50 | font-size: 0.8em; 51 | } 52 | #authors a { 53 | color: blue; 54 | font-weight: bold; 55 | } 56 | footer { 57 | width: calc(100% - 2 * 5px); 58 | max-width: calc(500px - 2 * 5px); 59 | position: absolute; 60 | top: 1390px; 61 | left: 50%; 62 | transform: translate(-50%, 0); 63 | padding-bottom: 20px; 64 | margin-left: 5px; 65 | margin-right: 5px; 66 | } 67 | h1 { 68 | margin-bottom: 5px; 69 | } 70 | #lineNumbers { 71 | width: 60px; /*Fallback for browsers which do not support "var".*/ 72 | width: var(--lineNumbersWidth); 73 | margin-right: 0; 74 | font-family: 75 | Courier New, 76 | monospace; 77 | display: inline-block; 78 | text-align: right; 79 | background: #aaaaaa; 80 | height: 100vh; 81 | max-height: 400px; 82 | position: absolute; 83 | overflow: hidden; 84 | font-size: 1em; 85 | border-top-left-radius: 5px; 86 | border-bottom-left-radius: 5px; 87 | } 88 | #assemblyCode { 89 | margin: 0; 90 | width: calc( 91 | 100% - 70px 92 | ); /* I am not sure breaking compatibility 93 | * with Firefox 52 (probably the most 94 | * advanced browser which can run on 95 | * Windows XP, but it does not support 96 | * CSS "var" function) is acceptable today. 97 | */ 98 | width: calc(100% - var(--lineNumbersWidth) - 10px); 99 | padding-left: 5px; 100 | font-family: 101 | Courier New, 102 | monospace; 103 | text-align: left; 104 | display: inline-block; 105 | background: #eeeeee; 106 | height: 100vh; 107 | max-height: 400px; 108 | position: absolute; 109 | left: 60px; 110 | left: var(--lineNumbersWidth); 111 | overflow: scroll; 112 | white-space: pre; 113 | font-size: 1em; /* Microsoft Edge seems to have some weird ideas 114 | * about how font size should be smaller in
115 |                    * than in 
. 116 | */ 117 | border-top-right-radius: 5px; 118 | border-bottom-right-radius: 5px; 119 | } 120 | .comment { 121 | color: #333333; 122 | font-style: italic; 123 | } 124 | .mnemonic { 125 | color: #000077; 126 | font-weight: bold; 127 | } 128 | .register { 129 | color: #773300; 130 | font-weight: bold; 131 | } 132 | #buttons { 133 | top: 400px; 134 | position: absolute; 135 | width: 100%; 136 | text-align: center; 137 | } 138 | .flag { 139 | color: #007700; 140 | font-weight: bold; 141 | } 142 | .label { 143 | color: #770077; 144 | } 145 | label { 146 | display: block; 147 | font-family: Arial, Helvetica, sans-serif; 148 | margin-left: 8px; 149 | margin-top: 3px; 150 | } 151 | .string { 152 | color: #770000; 153 | } 154 | .number { 155 | color: #007777; 156 | } 157 | .directive { 158 | color: #770077; 159 | font-weight: bold; 160 | } 161 | .parenthesis { 162 | font-weight: bold; 163 | } 164 | #highlightButton { 165 | position: absolute; 166 | left: 0; 167 | text-align: left; 168 | margin-left: 0; 169 | float: left; 170 | } 171 | #assembleButton { 172 | position: absolute; 173 | right: 5px; 174 | text-align: right; 175 | margin-right: 0; 176 | float: right; 177 | } 178 | @keyframes assembleButtonAnimation { 179 | begin { 180 | background: white; 181 | } 182 | 50% { 183 | background: lightblue; 184 | } 185 | end { 186 | background: white; 187 | } 188 | } 189 | #assembleButton:target { 190 | animation: assembleButtonAnimation 2s infinite; 191 | } 192 | #whyClickAssemble { 193 | position: absolute; 194 | top: 450px; 195 | width: calc(100% - 10px); 196 | text-align: justify; 197 | font-family: sans-serif; 198 | left: 50%; 199 | transform: translateX(-50%); 200 | } 201 | #divWithMachineCode { 202 | position: absolute; 203 | width: calc(100% - 5px); 204 | background: white; 205 | height: 100vh; 206 | top: 450px; 207 | overflow: scroll; 208 | max-height: 400px; 209 | margin-bottom: 10px; 210 | } 211 | #warningAboutJavaScript { 212 | display: block; 213 | justify-content: center; 214 | text-align: center; 215 | align-items: center; 216 | height: 100%; 217 | font-weight: bold; 218 | font-family: Arial; 219 | margin-top: auto; 220 | margin-bottom: auto; 221 | margin-right: 25px; 222 | margin-left: 10px; 223 | max-width: 450px; 224 | } 225 | table, 226 | th, 227 | td { 228 | border: 1px solid black; 229 | border-collapse: collapse; 230 | } 231 | th { 232 | font-family: Arial, Sans-Serif; 233 | } 234 | td { 235 | font-family: 236 | Courier New, 237 | monospace; 238 | text-align: center; 239 | } 240 | td:nth-child(1) { 241 | text-align: right; 242 | font-weight: bold; 243 | } 244 | #machineCode { 245 | width: calc(100% - 10px); 246 | margin: 5px; 247 | } 248 | table { 249 | margin-left: auto; 250 | margin-right: auto; 251 | margin-top: 5px; 252 | margin-bottom: 5px; 253 | } 254 | #simulationButtons { 255 | top: 855px; 256 | position: absolute; 257 | width: 100%; 258 | height: 50px; 259 | text-align: center; 260 | } 261 | #simulationResults { 262 | top: 910px; 263 | position: absolute; 264 | width: calc(100% - 8px); 265 | height: 100vh; 266 | max-height: 400px; 267 | overflow: scroll; 268 | background-color: white; 269 | margin-bottom: 15px; 270 | } 271 | input { 272 | width: 30px; 273 | } 274 | .regbank_b { 275 | font-style: italic; 276 | } 277 | .active { 278 | background: white; 279 | color: black; 280 | } 281 | .inactive { 282 | background: black; 283 | color: white; 284 | border-color: white; 285 | } 286 | tr:last-child td.inactive { 287 | border-bottom-color: black; 288 | } 289 | .changed, 290 | .turning_off { 291 | background: lightGreen; 292 | color: black; 293 | font-weight: bold; 294 | } 295 | .turning_off { 296 | background: red; 297 | } 298 | #interrupt_flag { 299 | font-style: normal; 300 | } 301 | .tooltip { 302 | display: none; 303 | } 304 | button:hover .tooltip { 305 | display: block; 306 | position: absolute; 307 | background: #ffffaa; 308 | z-index: 10; 309 | padding: 2px; 310 | font-style: italic; 311 | font-family: Times; 312 | } 313 | .exampleCodeLink { 314 | margin: 5px; 315 | width: 120px; 316 | flex-shrink: 0; 317 | align-items: center; 318 | justify-content: center; 319 | height: 150px; 320 | background: white; 321 | text-align: center; 322 | border-radius: 10px; 323 | box-shadow: 3px 5px 5px black; 324 | } 325 | .exampleCodeLink:hover { 326 | background: lightGray; 327 | transition-delay: 250ms; 328 | transition-duration: 250ms; 329 | } 330 | .exampleCodeLink:last-child { 331 | margin-right: 7.5px; 332 | } 333 | .exampleCodeLink:first-child { 334 | margin-left: 7.5px; 335 | } 336 | .exampleCodeLink:last-child::after { 337 | /* 338 | Firefox 52, apparently, ignores margin-right on the last element of the 339 | flexbox. So, let's solve it somewhow differently. 340 | */ 341 | content: " "; 342 | display: block; 343 | width: 1px; 344 | flex-shrink: 0; 345 | white-space: pre; 346 | } 347 | #divWithExamples { 348 | background: #ffffee; 349 | font-family: Arial; 350 | width: calc(100% - 7px); 351 | max-width: calc( 352 | 500px - 7px 353 | ); /* To make the warning about 354 | * lack of support for modern 355 | * JavaScript legible in 356 | * Internet Explorer 11. 357 | */ 358 | position: absolute; 359 | top: 405px; 360 | padding-left: 2px; 361 | border-radius: 10px; 362 | } 363 | .exampleCodeLink img { 364 | width: 100px; 365 | height: 100px; 366 | margin-left: auto; 367 | margin-top: 5px; 368 | margin-right: auto; 369 | background: white; 370 | } 371 | #examplesSpan { 372 | display: block; 373 | text-align: center; 374 | font-weight: normal; 375 | font-size: 1em; 376 | margin-bottom: 5px; 377 | margin-top: 5px; 378 | } 379 | #examples { 380 | overflow-x: scroll; 381 | overflow-y: hidden; 382 | margin: 5px; 383 | display: flex; 384 | justify-content: center; 385 | align-items: center; 386 | background: gray; 387 | height: 173px; 388 | border-radius: 10px; 389 | } 390 | .callForMoreExamples { 391 | margin: 5px; 392 | } 393 | #graphicalResults { 394 | overflow: scroll; 395 | position: absolute; 396 | top: 910px; 397 | height: 200px; 398 | width: calc(100% - 5px); 399 | background: white; 400 | } 401 | .sevenSegmentDisplay { 402 | background: black; 403 | width: 50px; 404 | height: 100px; 405 | margin: 5px; 406 | } 407 | .breakpoint_icon { 408 | display: none; 409 | width: calc(1em - 3px); 410 | height: calc(1em - 3px); 411 | } 412 | #register_PC { 413 | font-style: normal; 414 | /*Otherwise it will turn italic when Regbank B 415 | is being used, even though it stays active.*/ 416 | } 417 | button { 418 | position: relative; 419 | /* 420 | https://stackoverflow.com/questions/64995585/tooltips-work-in-firefox-but-not-in-chrome 421 | */ 422 | background: #cccccc; /*WebPositive apparently shows 423 | no background on buttons by 424 | default.*/ 425 | padding-top: 3px; 426 | padding-bottom: 1px; 427 | } 428 | #downloadHex { 429 | margin-top: 5px; 430 | margin-bottom: 5px; 431 | margin-left: auto; 432 | margin-right: auto; 433 | display: block; 434 | } 435 | textarea { 436 | resize: none; 437 | overflow: scroll; 438 | width: calc(100% - 3 * 8px - 2px); 439 | margin-left: calc(2 * 8px); 440 | height: 95px; 441 | background-color: black; 442 | color: aquamarine; 443 | } 444 | #UART_IO { 445 | height: 250px; 446 | font-size: 1em; 447 | position: absolute; 448 | width: calc(100% - 5px); 449 | overflow: hidden; 450 | background-color: azure; 451 | display: none; 452 | } 453 | #UART_OUTPUT { 454 | height: 95px; 455 | width: calc(100% - 3 * 8px); 456 | margin-left: calc(2 * 8px); 457 | overflow: scroll; 458 | background-color: black; 459 | color: aquamarine; 460 | margin-top: auto; 461 | border-style: solid; 462 | border-width: 2px; 463 | border-right-color: lightgray; 464 | border-bottom-color: lightgray; 465 | border-top-color: darkgray; 466 | border-left-color: darkgray; 467 | } 468 | #UART_enable_button { 469 | position: absolute; 470 | width: calc(100% - 5px); 471 | font-size: 24px; 472 | line-height: 36px; 473 | } 474 | #fetchingExamples { 475 | background: white; 476 | padding: 10px; 477 | margin: 10px; 478 | } 479 | #MIT_licence { 480 | background: #eeeeff; 481 | border-top-left-radius: 5px; 482 | border-top-right-radius: 5px; 483 | position: fixed; 484 | bottom: 0px; 485 | left: 10px; 486 | width: calc(100% - 20px); 487 | font-family: Arial; 488 | } 489 | #licence_message { 490 | float: left; 491 | padding-left: 10px; 492 | padding-top: 5px; 493 | padding-bottom: 5px; 494 | padding-right: 5px; 495 | text-align: center; 496 | width: calc( 497 | 100% - 75px /* 75px is the width of the "Close" button. */ - 2 * 498 | (10px + 5px) 499 | ); 500 | } 501 | #closing_the_licence { 502 | float: right; 503 | position: absolute; 504 | top: 50%; 505 | transform: translate(0, -50%); 506 | margin-right: 10px; 507 | line-height: 2em; 508 | width: 75px; 509 | background: #eeffee; 510 | } 511 | #warningAboutDownloadingHexadecimal { 512 | text-align: justify; 513 | font-family: Arial; 514 | margin-left: 5px; 515 | margin-right: 5px; 516 | } 517 | #uploadSuccessfulMessage { 518 | display: none; 519 | border-top-left-radius: 10px; 520 | border-top-right-radius: 10px; 521 | overflow: hidden; 522 | border-bottom-style: ridge; 523 | border-left-style: ridge; 524 | border-right-style: ridge; 525 | position: fixed; 526 | left: 50%; 527 | top: 50%; 528 | transform: translate(-50%, -50%); 529 | max-width: calc(100% - 10px); 530 | background: #ffffee; 531 | z-index: 100; 532 | box-shadow: 5px 5px 2.5px gray; 533 | } 534 | #messageAccompanyingTheURL { 535 | margin-left: 5px; 536 | margin-right: 5px; 537 | } 538 | #titleBar { 539 | background: linear-gradient(to right, darkBlue, #00ffff); 540 | color: white; 541 | padding-left: 10px; 542 | padding-right: 10px; 543 | padding-bottom: 5px; 544 | padding-top: 5px; 545 | font-family: sans-serif; 546 | } 547 | #closeButton { 548 | float: right; 549 | margin-right: 5px; 550 | margin-bottom: 5px; 551 | } 552 | #copyButton { 553 | float: left; 554 | margin-left: 5px; 555 | margin-bottom: 5px; 556 | } 557 | #shareURL { 558 | background: white; 559 | font-family: monospace; 560 | margin: 3px; 561 | border-style: solid; 562 | border-width: 1px; 563 | padding: 2px; 564 | } 565 | .sticky-header { 566 | position: sticky; 567 | top: 0; 568 | background-color: white; 569 | } 570 | #deleteTheProgram { 571 | display: none; 572 | grid-template-areas: 573 | "label label label" 574 | "password password password" 575 | ". . button"; 576 | background-color: white; 577 | padding: 5px; 578 | } 579 | #label_for_input_password { 580 | grid-area: label; 581 | } 582 | #input_password { 583 | grid-area: password; 584 | width: calc(100% - 10px); 585 | margin-bottom: 5px; 586 | } 587 | #deleteTheProgramButton { 588 | grid-area: button; 589 | float: right; 590 | display: block; 591 | } 592 | -------------------------------------------------------------------------------- /permutations.psm: -------------------------------------------------------------------------------- 1 | ;A very advanced example: Implementing the permutations algorithm in PicoBlaze assembly. 2 | ;For sorting, we will use the Bubble Sort algorithm. And we will use stack instead of recursion. 3 | 4 | base_decimal 5 | 6 | constant NDEBUG, 1 7 | constant address_of_the_current_attempt, 8 8 | constant digits_of_the_ordinal_number, 16 9 | constant bottom_of_the_stack, 24 10 | 11 | address 0 12 | 13 | namereg sf, length_of_the_input 14 | 15 | regbank a 16 | call print_the_introduction_message 17 | load length_of_the_input, 0 18 | beginning_of_the_input_loop: 19 | call UART_RX 20 | compare s9, a'x ;The new-line character. 21 | jump z, end_of_the_input_loop 22 | store s9, (length_of_the_input) 23 | add length_of_the_input, 1 24 | jump beginning_of_the_input_loop 25 | end_of_the_input_loop: 26 | compare length_of_the_input, 0 27 | jump z, 0 28 | 29 | ;An improved version of BubbleSort written by Sep Roland... 30 | beginning_of_the_bubble_sort: 31 | load s5, length_of_the_input 32 | outer_bubble_sort_loop: 33 | sub s5, 1 34 | jump z, end_of_the_bubble_sort 35 | load s4, 0 ; Indicates swap(s) performed. 36 | load s1, 0 37 | inner_bubble_sort_loop: 38 | compare s1, s5 39 | jump nc, end_of_the_inner_bubble_sort_loop 40 | load s0, s1 41 | add s1, 1 42 | fetch s2, (s0) 43 | fetch s3, (s1) 44 | compare s3, s2 45 | jump nc, inner_bubble_sort_loop 46 | store s3, (s0) 47 | store s2, (s1) 48 | load s4, 1 49 | jump inner_bubble_sort_loop 50 | end_of_the_inner_bubble_sort_loop: 51 | test s4, s4 52 | jump nz, outer_bubble_sort_loop 53 | end_of_the_bubble_sort: 54 | 55 | jump NDEBUG ? the_permutations_algorithm : printing_the_sorted_array 56 | printing_the_sorted_array: 57 | call print_the_sorted_array_message 58 | load s0, 0 59 | printing_the_sorted_array_loop: 60 | compare s0, length_of_the_input 61 | jump nc, end_of_the_printing_the_sorted_array_loop 62 | fetch s9, (s0) 63 | call UART_TX 64 | add s0, 1 65 | jump printing_the_sorted_array_loop 66 | end_of_the_printing_the_sorted_array_loop: 67 | load s9, a'x 68 | call UART_TX 69 | 70 | the_permutations_algorithm: 71 | 72 | ;Let's set all the digits of the ordinal number of permutations to "0" 73 | regbank b 74 | load s0, digits_of_the_ordinal_number 75 | load s2, digits_of_the_ordinal_number ;End of the digits of the ordinal number. 76 | reset_ordinal_numbers_loop: 77 | compare s0, bottom_of_the_stack 78 | jump nc, end_of_the_reset_ordinal_numbers_loop 79 | load s1, "0" 80 | store s1, (s0) 81 | add s0, 1 82 | jump reset_ordinal_numbers_loop 83 | end_of_the_reset_ordinal_numbers_loop: 84 | regbank a 85 | 86 | namereg se, top_of_the_stack 87 | load top_of_the_stack, bottom_of_the_stack 88 | load s0, 0 89 | store s0, (top_of_the_stack) 90 | add top_of_the_stack, length_of_the_input 91 | add top_of_the_stack, 1 92 | beginning_of_the_permutations_loop: 93 | compare top_of_the_stack, bottom_of_the_stack 94 | jump z, end_of_the_permutations_loop 95 | sub top_of_the_stack, length_of_the_input 96 | sub top_of_the_stack, 1 97 | namereg sd, length_of_the_current_attempt 98 | fetch length_of_the_current_attempt, (top_of_the_stack) 99 | load s0, address_of_the_current_attempt 100 | store length_of_the_current_attempt, (s0) 101 | load s1, 0 102 | copying_the_current_attempt_from_the_stack_loop: 103 | compare s1, length_of_the_current_attempt 104 | jump nc, end_of_copying 105 | load s0, address_of_the_current_attempt 106 | add s0, s1 107 | add s0, 1 108 | load s3, top_of_the_stack 109 | add s3, s1 110 | add s3, 1 111 | fetch s4, (s3) 112 | store s4, (s0) 113 | add s1, 1 114 | jump copying_the_current_attempt_from_the_stack_loop 115 | end_of_copying: 116 | jump NDEBUG ? dont_print_the_current_attempt : print_the_current_attempt 117 | print_the_current_attempt: 118 | call print_the_length_of_the_current_attempt_message 119 | load s9, length_of_the_current_attempt 120 | add s9, "0" 121 | call UART_TX 122 | load s9, a'x 123 | call UART_TX 124 | call print_the_current_attempt_message 125 | load s0, address_of_the_current_attempt + 1 126 | printing_the_current_attempt_loop: 127 | load s1, address_of_the_current_attempt + 1 128 | add s1, length_of_the_current_attempt 129 | compare s0, s1 130 | jump nc, end_of_the_printing_the_current_attempt_loop 131 | fetch s9, (s0) 132 | call UART_TX 133 | add s0, 1 134 | jump printing_the_current_attempt_loop 135 | end_of_the_printing_the_current_attempt_loop: 136 | load s9, a'x 137 | call UART_TX 138 | dont_print_the_current_attempt: 139 | compare length_of_the_current_attempt, length_of_the_input 140 | jump c, current_attempt_is_not_a_solution 141 | call print_found_a_solution_message 142 | load s0, address_of_the_current_attempt + 1 143 | printing_the_solution_loop: 144 | load s1, address_of_the_current_attempt + 1 145 | add s1, length_of_the_current_attempt 146 | compare s0, s1 147 | jump nc, end_of_the_printing_the_solution_loop 148 | fetch s9, (s0) 149 | call UART_TX 150 | add s0, 1 151 | jump printing_the_solution_loop 152 | end_of_the_printing_the_solution_loop: 153 | load s9, a'x 154 | call UART_TX 155 | regbank b 156 | call print_the_ordinal_number_message 157 | load s1, digits_of_the_ordinal_number 158 | increasing_the_ordinal_number_loop: 159 | fetch s0, (s1) 160 | add s0, 1 161 | store s0, (s1) 162 | compare s0, "9" + 1 163 | jump nz, end_of_increasing_the_ordinal_number_loop 164 | load s0, "0" 165 | store s0, (s1) 166 | add s1, 1 167 | jump increasing_the_ordinal_number_loop 168 | end_of_increasing_the_ordinal_number_loop: 169 | compare s1, s2 170 | jump c, not_a_new_digit 171 | load s2, s1 172 | not_a_new_digit: 173 | load s1, s2 174 | printing_the_ordinal_number: 175 | fetch s9, (s1) 176 | call UART_TX 177 | sub s1, 1 178 | compare s1, digits_of_the_ordinal_number 179 | jump nc, printing_the_ordinal_number 180 | end_of_printing_the_ordinal_number: 181 | load s9, a'x 182 | call UART_TX 183 | regbank a 184 | jump end_of_the_branching 185 | current_attempt_is_not_a_solution: 186 | load s0, length_of_the_input 187 | sub s0, 1 188 | add_a_new_character_loop: 189 | ;No check at the beginning of this loop, for this is a do-while-loop, 190 | ;rather than a while-loop. Checking whether an overflow has occurred 191 | ;when decreasing the pointer stored in s0 by 1 is done at the end of 192 | ;this loop. 193 | namereg sc, character_we_try_to_add 194 | fetch character_we_try_to_add, (s0) 195 | load s7, s0 196 | add s7, 1 197 | load s8, 0 ;Whether we already tried adding that character. 198 | check_if_we_already_tried_that_character_loop: 199 | compare s7, length_of_the_input 200 | jump nc, end_of_the_check_if_we_already_tried_that_character_loop 201 | fetch s6, (s7) 202 | compare s6, character_we_try_to_add 203 | jump nz, third_characters_are_not_equal_label 204 | load s8, 1 205 | third_characters_are_not_equal_label: 206 | add s7, 1 207 | jump check_if_we_already_tried_that_character_loop 208 | end_of_the_check_if_we_already_tried_that_character_loop: 209 | test s8, s8 210 | jump nz, dont_add_the_new_character 211 | jump NDEBUG ? dont_print_the_character_we_are_trying_to_add : print_the_character_we_are_trying_to_add 212 | print_the_character_we_are_trying_to_add: 213 | call print_we_are_trying_to_add_message 214 | load s9, character_we_try_to_add 215 | call UART_TX 216 | load s9, a'x 217 | call UART_TX 218 | dont_print_the_character_we_are_trying_to_add: 219 | load s2, 0 ; How many of the chosen character are present in the current attempt. 220 | load s1, address_of_the_current_attempt + 1 221 | count_in_the_current_attempt_loop: 222 | load s4, address_of_the_current_attempt + 1 223 | add s4, length_of_the_current_attempt 224 | compare s1, s4 225 | jump z, end_of_the_count_in_the_current_attempt_loop 226 | fetch s4, (s1) 227 | compare s4, character_we_try_to_add 228 | jump nz, first_the_characters_are_not_equal_label 229 | add s2, 1 230 | first_the_characters_are_not_equal_label: 231 | add s1, 1 232 | jump count_in_the_current_attempt_loop 233 | end_of_the_count_in_the_current_attempt_loop: 234 | jump NDEBUG ? dont_print_how_many_in_the_current_attempt : print_how_many_in_the_current_attempt 235 | print_how_many_in_the_current_attempt: 236 | call print_the_current_attempt_count_message 237 | load s9, s2 238 | add s9, "0" 239 | call UART_TX 240 | load s9, a'x 241 | call UART_TX 242 | dont_print_how_many_in_the_current_attempt: 243 | load s3, 0 ; How many of the chosen character are present in the input. 244 | load s1, 0 245 | count_in_the_input_loop: 246 | compare s1, length_of_the_input 247 | jump z, end_of_the_count_in_the_input_loop 248 | fetch s4, (s1) 249 | compare s4, character_we_try_to_add 250 | jump nz, second_the_characters_are_not_equal_label 251 | add s3, 1 252 | second_the_characters_are_not_equal_label: 253 | add s1, 1 254 | jump count_in_the_input_loop 255 | end_of_the_count_in_the_input_loop: 256 | jump NDEBUG ? dont_print_how_many_in_the_input : print_how_many_in_the_input 257 | print_how_many_in_the_input: 258 | call print_count_in_the_input_message 259 | load s9, s3 260 | add s9, "0" 261 | call UART_TX 262 | load s9, a'x 263 | call UART_TX 264 | dont_print_how_many_in_the_input: 265 | compare s2, s3 266 | jump nc, dont_add_the_new_character 267 | load s1, NDEBUG 268 | test s1, s1 269 | call z, print_the_we_are_adding_the_new_character_message 270 | load s1, top_of_the_stack 271 | load s2, length_of_the_current_attempt 272 | add s2, 1 273 | store s2, (s1) 274 | add s1, 1 275 | load s3, address_of_the_current_attempt + 1 276 | copying_the_new_attempt_loop: 277 | load s5, address_of_the_current_attempt + 1 278 | add s5, length_of_the_current_attempt 279 | compare s3, s5 280 | jump z, end_of_the_copying_the_new_attempt_loop 281 | fetch s4, (s3) 282 | store s4, (s1) 283 | add s3, 1 284 | add s1, 1 285 | jump copying_the_new_attempt_loop 286 | end_of_the_copying_the_new_attempt_loop: 287 | ;s1 now points to the location right after the copied attempt. 288 | store character_we_try_to_add, (s1) 289 | add top_of_the_stack, length_of_the_input 290 | add top_of_the_stack, 1 291 | dont_add_the_new_character: 292 | sub s0, 1 293 | jump nc, add_a_new_character_loop 294 | end_of_the_add_a_new_character_loop: 295 | load sb, NDEBUG 296 | compare sb, 0 297 | call z, print_the_exited_the_loop_message 298 | end_of_the_branching: 299 | jump beginning_of_the_permutations_loop 300 | end_of_the_permutations_loop: 301 | call print_the_end_message 302 | jump 0 303 | 304 | print_the_introduction_message: 305 | print_string "Enter a short string and press enter.", s9, UART_TX 306 | load s9, a'x 307 | call UART_TX 308 | return 309 | 310 | print_the_sorted_array_message: 311 | print_string "After the Bubble Sort algorithm, the input string looks like this: ", s9, UART_TX 312 | return 313 | 314 | print_the_current_attempt_message: 315 | print_string "The current attempt is: ", s9, UART_TX 316 | return 317 | 318 | print_we_are_trying_to_add_message: 319 | print_string "We are trying to add the character: ", s9, UART_TX 320 | return 321 | 322 | print_the_current_attempt_count_message: 323 | print_string "The count of that character in the current attempt is: ", s9, UART_TX 324 | return 325 | 326 | print_count_in_the_input_message: 327 | print_string "The count of that character in the input string is: ", s9, UART_TX 328 | return 329 | 330 | print_the_we_are_adding_the_new_character_message: 331 | print_string "We will try to add that character.", s9, UART_TX 332 | load s9, a'x 333 | call UART_TX 334 | return 335 | 336 | print_found_a_solution_message: 337 | print_string "Found a permutation: ", s9, UART_TX 338 | return 339 | 340 | print_the_end_message: 341 | print_string "The end!", s9, UART_TX 342 | load s9, a'x 343 | call UART_TX 344 | return 345 | 346 | print_the_length_of_the_current_attempt_message: 347 | print_string "The length of the current attempt is: ", s9, UART_TX 348 | return 349 | 350 | print_the_ordinal_number_message: 351 | print_string "That's the permutation #", s9, UART_TX 352 | return 353 | 354 | print_the_exited_the_loop_message: 355 | print_string "The 'add_a_new_character_loop' loop has exited!", s9, UART_TX 356 | load s9, a'x 357 | call UART_TX 358 | return 359 | 360 | base_hexadecimal 361 | ;Now follows some boilerplate code 362 | ;we use in our Computer Architecture 363 | ;classes... 364 | CONSTANT LED_PORT, 00 365 | CONSTANT HEX1_PORT, 01 366 | CONSTANT HEX2_PORT, 02 367 | CONSTANT UART_TX_PORT, 03 368 | CONSTANT UART_RESET_PORT, 04 369 | CONSTANT SW_PORT, 00 370 | CONSTANT BTN_PORT, 01 371 | CONSTANT UART_STATUS_PORT, 02 372 | CONSTANT UART_RX_PORT, 03 373 | ; Tx data_present 374 | CONSTANT U_TX_D, 00000001'b 375 | ; Tx FIFO half_full 376 | CONSTANT U_TX_H, 00000010'b 377 | ; TxFIFO full 378 | CONSTANT U_TX_F, 00000100'b 379 | ; Rxdata_present 380 | CONSTANT U_RX_D, 00001000'b 381 | ; RxFIFO half_full 382 | CONSTANT U_RX_H, 00010000'b 383 | ; RxFIFO full 384 | CONSTANT U_RX_F, 00100000'b 385 | 386 | UART_RX: 387 | INPUT sA, UART_STATUS_PORT 388 | TEST sA, U_RX_D 389 | JUMP NZ, input_not_empty 390 | LOAD s0, s0 391 | JUMP UART_RX 392 | input_not_empty: 393 | INPUT s9, UART_RX_PORT 394 | RETURN 395 | 396 | UART_TX: 397 | INPUT sA, UART_STATUS_PORT 398 | TEST sA, U_TX_F 399 | JUMP NZ, UART_TX 400 | OUTPUT s9, UART_TX_PORT 401 | RETURN 402 | 403 | ;You may also be interested in my implementation of the same algorithm in WebAssembly. 404 | ;I've also opened a StackExchange thread about this program. 405 | 406 | -------------------------------------------------------------------------------- /preprocessor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function isMnemonic(str) { 3 | if (typeof str !== "string") { 4 | alert( 5 | 'Internal compiler error: The first argument of the "isMnemonic" function is not a string!'); 6 | return false; 7 | } 8 | for (const mnemonic of mnemonics) 9 | if (RegExp("^" + mnemonic + "$", "i").test(str)) 10 | return true; 11 | return false; 12 | } 13 | function makeCompilationContext(root_of_the_abstract_syntax_tree, 14 | oldCompilationContext) { 15 | let context; 16 | if (typeof oldCompilationContext == "undefined") 17 | default_base_of_literals_in_assembly = 16; 18 | if (typeof oldCompilationContext != "undefined") 19 | context = oldCompilationContext; 20 | else 21 | context = { 22 | constants : new Map(), 23 | namedRegisters : new Map(), 24 | labels : new Map(), 25 | }; 26 | if (typeof PicoBlaze === "object") { 27 | context.constants.set("PicoBlaze_Simulator_for_Android", 1); 28 | context.constants.set("PicoBlaze_Simulator_in_JS", 0); 29 | } else { 30 | context.constants.set("PicoBlaze_Simulator_in_JS", 1); 31 | context.constants.set("PicoBlaze_Simulator_for_Android", 0); 32 | } 33 | if (!(root_of_the_abstract_syntax_tree instanceof TreeNode) || 34 | root_of_the_abstract_syntax_tree.text != "assembly") { 35 | // Such an error would be impossible in C++, but there is nothing preventing 36 | // it in JavaScript. 37 | alert( 38 | "Internal compiler error: The input to the preprocessor doesn't appear to be the output of the parser!"); 39 | return context; 40 | } 41 | let address; 42 | for (const node_of_depth_1 of root_of_the_abstract_syntax_tree.children) { 43 | if (typeof oldCompilationContext != "undefined" && 44 | !isDirective(node_of_depth_1.text)) { 45 | alert( 46 | "Line #" + node_of_depth_1.lineNumber + ": \"" + 47 | node_of_depth_1.text + 48 | "\" appears in an if-branching or a while-loop, but it is not a preprocessor directive. Only preprocessor directives can appear in if-branching and while-loops."); 49 | return context; 50 | } 51 | if (isMnemonic(node_of_depth_1.text)) { 52 | if (typeof oldCompilationContext != "undefined") { 53 | alert( 54 | "Line #" + node_of_depth_1.lineNumber + ': A mnemonic "' + 55 | node_of_depth_1.text + 56 | '" appears in an if-branching or a while-loop, where only preprocessor directives can appear!'); 57 | return context; 58 | } 59 | if (typeof address === "undefined") { 60 | alert("Line " + node_of_depth_1.lineNumber + ': The mnemonic "' + 61 | node_of_depth_1.text + 62 | '" appears before any address has been set.'); 63 | return context; 64 | } 65 | address++; // This won't work for most assembly language dialects, but it 66 | // works for PicoBlaze (where all directives have the same 67 | // size: 18 bits). 68 | } 69 | if (/^BASE_HEXADECIMAL$/i.test(node_of_depth_1.text)) { 70 | default_base_of_literals_in_assembly = 16; 71 | if (node_of_depth_1.children.length == 1) { 72 | default_base_of_literals_in_assembly = 73 | node_of_depth_1.children[0].interpretAsArithmeticExpression( 74 | context.constants); 75 | } else if ( 76 | node_of_depth_1.children.length != 77 | 0) { // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/35 78 | alert( 79 | "Line " + node_of_depth_1.lineNumber + 80 | ': The "BASE_HEXADECIMAL" pseudo-mnemonic should have 0 or 1 arguments.'); 81 | return context; 82 | } 83 | } 84 | if (/^BASE_DECIMAL$/i.test(node_of_depth_1.text)) { 85 | default_base_of_literals_in_assembly = 10; 86 | if (node_of_depth_1.children.length == 1) { 87 | default_base_of_literals_in_assembly = 88 | node_of_depth_1.children[0].interpretAsArithmeticExpression( 89 | context.constants); 90 | } else if (node_of_depth_1.children.length != 0) { 91 | alert( 92 | "Line " + node_of_depth_1.lineNumber + 93 | ': The "BASE_DECIMAL" pseudo-mnemonic should have 0 or 1 arguments.'); 94 | return context; 95 | } 96 | } 97 | if (/:$/.test(node_of_depth_1.text)) { 98 | console.log( 99 | "DEBUG: Dealing with a label, point #1..."); // Eh, those JavaScript 100 | // debuggers are worse 101 | // than useless, I think 102 | // now. Logging debug 103 | // messages is so much 104 | // easier than trying to 105 | // use a debugger. 106 | if (typeof address === "undefined") { 107 | alert("Line " + node_of_depth_1.lineNumber + ': The label "' + 108 | node_of_depth_1.text + 109 | '" appears before any address has been set.'); 110 | return context; 111 | } 112 | if (!/^(_|[a-z])\w*:$/i.test(node_of_depth_1.text)) { 113 | alert("Line " + node_of_depth_1.lineNumber + ': "' + 114 | node_of_depth_1.text + '" is not an allowed label name.'); 115 | return context; 116 | } 117 | console.log("DEBUG: Dealing with a label, point #2..."); 118 | context.labels.set( 119 | node_of_depth_1.text.substring( 120 | 0, 121 | node_of_depth_1.text.length - 122 | 1), // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30 123 | address); 124 | console.log("DEBUG: Dealing with a label, point #3..."); 125 | } 126 | if (/^address$/i.test(node_of_depth_1.text) || 127 | /^org$/i.test(node_of_depth_1.text)) { 128 | if (typeof oldCompilationContext != "undefined") { 129 | alert( 130 | "Line #" + node_of_depth_1.lineNumber + 131 | ': The pseudo-mnemonic "address" appears in an if-branching or a while-loop, where only preprocessor directives can appear!'); 132 | return context; 133 | } 134 | console.log("DEBUG: Setting the address, point #1..."); 135 | if (node_of_depth_1.children.length != 1) { 136 | alert( 137 | "Line " + node_of_depth_1.lineNumber + 138 | ': The "address" pseudo-mnemonic doesn\'t have exactly one argument.'); 139 | return context; 140 | } 141 | console.log("DEBUG: Setting the address, point #2..."); 142 | address = node_of_depth_1.children[0].interpretAsArithmeticExpression( 143 | context.constants); 144 | console.log("DEBUG: Setting the address, point #3..."); 145 | } 146 | if (/^PRINT_STRING$/i.test(node_of_depth_1.text)) { 147 | if (node_of_depth_1.children.length != 5) { 148 | alert( 149 | "Line " + node_of_depth_1.lineNumber + 150 | ": The \"print_string\" pseudo-mnemonic should have exactly five arguments (a ',' token also counts as an argument)!"); 151 | return context; 152 | } 153 | if (node_of_depth_1.children[0].text[0] != '\"') { 154 | alert( 155 | "Line " + node_of_depth_1.lineNumber + 156 | ": The first argument to the \"print_string\" pseudo-mnemonic should be a string!"); 157 | return context; 158 | } 159 | address += (node_of_depth_1.children[0].text.length - 2) * 2; 160 | } 161 | if (/^constant$/i.test(node_of_depth_1.text) || 162 | /^equ$/i.test(node_of_depth_1.text)) { 163 | console.log("DEBUG: Setting a constant, point #1..."); 164 | if (node_of_depth_1.children.length != 3) { 165 | alert( 166 | "Line " + node_of_depth_1.lineNumber + 167 | ': The AST node "constant" should have exactly three child nodes (the comma is also an AST node).'); 168 | return context; 169 | } 170 | if (!/^(_|[a-z])\w*$/i.test(node_of_depth_1.children[0].text)) { 171 | alert("Line " + node_of_depth_1.lineNumber + ': "' + 172 | node_of_depth_1.children[0].text + 173 | '" is not an allowed constant name.'); 174 | return context; 175 | } 176 | if (node_of_depth_1.children[1].text != ",") { 177 | alert("Line " + node_of_depth_1.lineNumber + 178 | ': The second child of the "' + node_of_depth_1.text + 179 | '" node is "' + node_of_depth_1.children[1].text + 180 | '" instead of a comma.'); 181 | return context; 182 | } 183 | console.log("DEBUG: Setting a constant, point #2..."); 184 | context.constants.set( 185 | node_of_depth_1.children[0].text, 186 | node_of_depth_1.children[2].interpretAsArithmeticExpression( 187 | context.constants)); 188 | console.log("DEBUG: Setting a constant, point #3..."); 189 | } 190 | if (/^namereg$/i.test(node_of_depth_1.text)) { 191 | console.log("DEBUG: Naming a register, point #1..."); 192 | if (node_of_depth_1.children.length != 3) { 193 | alert( 194 | "Line " + node_of_depth_1.lineNumber + 195 | ': The AST node "namereg" should have exactly three child nodes (the comma is also an AST node).'); 196 | return context; 197 | } 198 | if (context.namedRegisters.has(node_of_depth_1.children[2].text)) { 199 | alert("Line " + node_of_depth_1.lineNumber + ': Variable named "' + 200 | node_of_depth_1.children[2].text + 201 | '" has already been declared!'); 202 | return context; 203 | } 204 | if (!/s([0-9]|[a-f])/i.test(node_of_depth_1.children[0].text)) { 205 | alert( 206 | "Line " + node_of_depth_1.lineNumber + ': "' + 207 | node_of_depth_1.children[0].text + 208 | "\" is supposed to be a register name, but it doesn't match the regular expression for registers."); 209 | return context; 210 | } 211 | if (node_of_depth_1.children[1].text != ",") { 212 | alert("Line " + node_of_depth_1.lineNumber + 213 | ': The second child of the "' + node_of_depth_1.text + 214 | '" node is "' + node_of_depth_1.children[1].text + 215 | '" instead of a comma.'); 216 | return context; 217 | } 218 | console.log("DEBUG: Naming a register, point #2..."); 219 | context.namedRegisters.set(node_of_depth_1.children[2].text, 220 | node_of_depth_1.children[0].text); 221 | console.log("DEBUG: Naming a register, point #3..."); 222 | } 223 | if (/^display$/i.test(node_of_depth_1.text) && 224 | ( 225 | typeof PicoBlaze !== 226 | "object" // Because UART_OUTPUT is not declared in PicoBlaze 227 | // Simulator for Android, which we detect by `PicoBlaze` 228 | // being declared. 229 | )) { 230 | if (node_of_depth_1.children[0].text[0] == '"') 231 | document.getElementById("UART_OUTPUT").innerText += 232 | node_of_depth_1.children[0] 233 | .text 234 | .substring( // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30 235 | 1, node_of_depth_1.children[0].text.length - 2 + 1); 236 | else { 237 | const ASCIIValue = 238 | node_of_depth_1.children[0].interpretAsArithmeticExpression( 239 | context.constants); 240 | if (ASCIIValue != '\n'.charCodeAt(0)) 241 | document.getElementById("UART_OUTPUT") 242 | .appendChild( 243 | document.createTextNode(String.fromCharCode(ASCIIValue))); 244 | else 245 | document.getElementById("UART_OUTPUT") 246 | .appendChild(document.createElement( 247 | "br")); // This doesn't appear to work in Firefox if UART is 248 | // disabled while assembling, and I have opened a 249 | // GitHub issue about that: 250 | // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/8 251 | } 252 | } else if (/^display$/i.test(node_of_depth_1.text)) { 253 | if (node_of_depth_1.children[0].text[0] == '"') { 254 | for (let i = 0; i < node_of_depth_1.children[0].text.length; i++) 255 | if (node_of_depth_1.children[0].text[i] != '"') 256 | PicoBlaze.displayCharacterOnTerminal( 257 | node_of_depth_1.children[0].text.charCodeAt( 258 | i)); // Right now, `displayCharacterOnTerminal` is a 259 | // no-operation in PicoBlaze_Simulator_for_Android. 260 | } else { 261 | PicoBlaze.displayCharacterOnTerminal( 262 | node_of_depth_1.children[0].interpretAsArithmeticExpression( 263 | context.constants)); 264 | } 265 | } 266 | if (/^if$/i.test(node_of_depth_1.text) && 267 | node_of_depth_1.children.length == 2) { 268 | //"if" without "else" 269 | if (node_of_depth_1.children[0].interpretAsArithmeticExpression( 270 | context.constants)) { 271 | context = makeCompilationContext(node_of_depth_1.children[1], context); 272 | } 273 | } else if (/^if$/i.test(node_of_depth_1.text) && 274 | node_of_depth_1.children.length == 3) { 275 | // if-else 276 | if (node_of_depth_1.children[0].interpretAsArithmeticExpression( 277 | context.constants)) 278 | context = makeCompilationContext(node_of_depth_1.children[1], context); 279 | else 280 | context = makeCompilationContext(node_of_depth_1.children[2], context); 281 | } else if (/^if$/i.test(node_of_depth_1.text)) { 282 | alert( 283 | "Line #" + node_of_depth_1.lineNumber + 284 | ': The "if" node should have either 2 or 3 child nodes. This one has ' + 285 | node_of_depth_1.children.length + " child nodes!"); 286 | return context; 287 | } 288 | if (/^while$/i.test(node_of_depth_1.text) && 289 | node_of_depth_1.children.length == 2) { 290 | while (node_of_depth_1.children[0].interpretAsArithmeticExpression( 291 | context.constants)) { 292 | context = makeCompilationContext(node_of_depth_1.children[1], context); 293 | } 294 | } else if (/^while$/i.test(node_of_depth_1.text)) { 295 | alert("Line #" + node_of_depth_1.lineNumber + 296 | ': The "while" node should have 2 nodes. This one has ' + 297 | node_of_depth_1.children.length + " child nodes!"); 298 | } 299 | } 300 | return context; 301 | } 302 | -------------------------------------------------------------------------------- /footerScript.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // For now, attempting to highlight code as the user is typing is worse 3 | // than useless, because it moves the cursor to the beginning. 4 | /* 5 | document.getElementById("assemblyCode"). 6 | oninput=syntaxHighlighter; 7 | */ 8 | setUpLineNumbers(); 9 | let hasTheCodeBeenModifiedSinceLastSuccessfulAssembly = false; 10 | let areWeCurrentlyAssembling = false; 11 | document.getElementById("assemblyCode").oninput = () => { 12 | setUpLineNumbers(); 13 | hasTheCodeBeenModifiedSinceLastSuccessfulAssembly = true; 14 | }; 15 | document.getElementById("assemblyCode").onscroll = () => { 16 | document.getElementById("lineNumbers") 17 | .scroll(0, document.getElementById("assemblyCode").scrollTop); 18 | }; 19 | document.getElementById("highlightButton").onclick = syntaxHighlighter; 20 | let hasTheCodeBeenAssembled = false; 21 | document.getElementById("assembleButton").onclick = () => { 22 | const assembly = document.getElementById("assemblyCode").innerText; 23 | 24 | let tokenized; 25 | try { 26 | tokenized = tokenize(assembly); 27 | } catch (error) { 28 | alert("Internal compiler error in the tokenizer: " + error.message); 29 | return; 30 | } 31 | let resultOfTokenizing = "["; 32 | for (let i = 0; i < tokenized.length; i++) { 33 | const token = tokenized[i]; 34 | if (token.text === "\n") 35 | resultOfTokenizing += '"\\n"'; 36 | else 37 | resultOfTokenizing += '"' + token.text + '"'; 38 | if (i !== tokenized.length - 1) 39 | resultOfTokenizing += ","; 40 | } 41 | resultOfTokenizing += "]"; 42 | console.log("Result of tokenizing: ", resultOfTokenizing); 43 | let parsed; 44 | try { 45 | parsed = parse(tokenized); 46 | } catch (error) { 47 | alert("Internal compiler error in the parser: " + error.message); 48 | } 49 | console.log("Result of parsing: ", parsed.getLispExpression()); 50 | let context; 51 | try { 52 | context = makeCompilationContext(parsed); 53 | } catch (error) { 54 | alert("Internal compiler error in the preprocessor: " + error.message); 55 | } 56 | console.log("Result of preprocessing: ", context); 57 | try { 58 | assemble(parsed, context); 59 | } catch (error) { 60 | alert("Internal assembler error: " + error.message); 61 | } 62 | drawTable(); 63 | areWeCurrentlyAssembling = true; 64 | stopSimulation(); 65 | areWeCurrentlyAssembling = false; 66 | hasTheCodeBeenAssembled = true; 67 | hasTheCodeBeenModifiedSinceLastSuccessfulAssembly = false; 68 | }; 69 | function stopSimulation() { 70 | if (!playing && !areWeCurrentlyAssembling) { 71 | alert("You are not supposed to press the stop button unless the simulation is currently playing, and it is not right now!"); 72 | return; 73 | } 74 | document.getElementById("fastForwardButton").disabled = false; 75 | document.getElementById("singleStepButton").disabled = false; 76 | document.getElementById("UART_INPUT").disabled = false; 77 | if (playing) 78 | clearInterval(simulationThread); 79 | document.getElementById("PC_label_" + formatAsAddress(PC)).innerHTML = ""; 80 | PC = 0; 81 | document.getElementById("PC_label_000").innerHTML = 82 | "\"->\""; 83 | playing = false; 84 | document.getElementById("playImage").style.display = "inline"; 85 | document.getElementById("pauseImage").style.display = "none"; 86 | for (let i = 0; i < 256; i++) 87 | output[i] = 0; 88 | for (let i = 0; i < 16; i++) 89 | registers[0][i] = registers[1][i] = 0; 90 | flagZ = [ 0, 0 ]; 91 | flagC = [ 0, 0 ]; 92 | callStack = []; 93 | regbank = 0; 94 | flagIE = 1; 95 | displayRegistersAndFlags(); 96 | displayOutput(); 97 | currentlyReadCharacterInUART = 0; 98 | } 99 | setupLayout(); 100 | window.onresize = setupLayout; 101 | drawTable(); 102 | displayRegistersAndFlags(); 103 | window.onscroll = () => { 104 | if (window.innerWidth >= 700) { 105 | if (window.scrollY > document.getElementById("assemblyCode").clientHeight) { 106 | document.body.style.backgroundPosition = "top left"; 107 | } else { 108 | document.body.style.backgroundPosition = "top right"; 109 | } 110 | } else { 111 | document.body.style.backgroundPosition = "top left"; 112 | } 113 | }; 114 | function onPlayPauseButton() { 115 | playing = !playing; 116 | if (!playing) { 117 | clearInterval(simulationThread); 118 | document.getElementById("fastForwardButton").disabled = false; 119 | document.getElementById("singleStepButton").disabled = false; 120 | document.getElementById("UART_INPUT").disabled = false; 121 | document.getElementById("playImage").style.display = "inline"; 122 | document.getElementById("pauseImage").style.display = "none"; 123 | } else { 124 | if (!hasTheCodeBeenAssembled) { 125 | if (!confirm( 126 | "The code has not been assembled. Do you really want to proceed starting the emulation?")) 127 | return; 128 | } else if ( 129 | hasTheCodeBeenModifiedSinceLastSuccessfulAssembly) // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/28 130 | { 131 | if (!confirm( 132 | "The code has been modified since the last successful assembling. Are you sure you want to proceed starting the emulation?")) 133 | return; 134 | } 135 | if (!document.getElementById("shouldWeUpdateRegisters") 136 | .checked) // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/20 137 | document.getElementById("PC_label_" + formatAsAddress(PC)).innerHTML = 138 | " "; 139 | simulationThread = setInterval(simulateOneInstruction, 500); 140 | document.getElementById("fastForwardButton").disabled = true; 141 | document.getElementById("singleStepButton").disabled = true; 142 | document.getElementById("UART_INPUT").disabled = true; 143 | document.getElementById("playImage").style.display = "none"; 144 | document.getElementById("pauseImage").style.display = "inline"; 145 | } 146 | } 147 | function onSingleStepButton() { 148 | if (playing) 149 | return; 150 | simulateOneInstruction(); 151 | } 152 | function fastForward() { 153 | if (!hasTheCodeBeenAssembled) { 154 | if (!confirm( 155 | "The code has not been assembled. Do you really want to proceed starting the emulation?")) 156 | return; 157 | } else if ( 158 | hasTheCodeBeenModifiedSinceLastSuccessfulAssembly) // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/28 159 | { 160 | if (!confirm( 161 | "The code has been modified since the last successful assembling. Are you sure you want to proceed starting the emulation?")) 162 | return; 163 | } 164 | playing = true; 165 | if (!document.getElementById("shouldWeUpdateRegisters") 166 | .checked) // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/20 167 | document.getElementById("PC_label_" + formatAsAddress(PC)).innerHTML = " "; 168 | document.getElementById("fastForwardButton").disabled = true; 169 | document.getElementById("singleStepButton").disabled = true; 170 | document.getElementById("UART_INPUT").disabled = true; 171 | simulationThread = setInterval(simulateOneInstruction, 0); 172 | document.getElementById("playImage").style.display = "none"; 173 | document.getElementById("pauseImage").style.display = "inline"; 174 | } 175 | document.getElementById("playPauseButton").onclick = onPlayPauseButton; 176 | document.getElementById("stopButton").onclick = stopSimulation; 177 | document.getElementById("singleStepButton").onclick = onSingleStepButton; 178 | document.getElementById("fastForwardButton").onclick = fastForward; 179 | const svgNS = document.getElementById("emptySVG").namespaceURI; 180 | let sevenSegmentDisplays = document.createElement("div"); 181 | sevenSegmentDisplays.style.width = 4 * 60 + "px"; 182 | sevenSegmentDisplays.style.marginLeft = "auto"; 183 | sevenSegmentDisplays.style.marginRight = "auto"; 184 | for (let i = 0; i < 4; i++) { 185 | let sevenSegmentDisplay = document.createElementNS(svgNS, "svg"); 186 | sevenSegmentDisplay.classList.add("sevenSegmentDisplay"); 187 | sevenSegmentDisplay.id = "sevenSegmentDisplay" + i; 188 | const polygons = [ 189 | "15,5 35, 5 40,10 35,15 15,15 10,10", // Segment A (top) 190 | "40,10 45,15 45,45 40,50 35,45 35,15", // Segment B (top-right) 191 | "40,50 45,55 45,85 40,90 35,85 35,55", // Segment C (bottom-right) 192 | "40,90 35,95 15,95 10,90 15,85 35,85", // Segment D (bottom) 193 | "10,90 5,85 5,55 10,50 15,55 15,85", // Segment E (bottom-left) 194 | "10,50 5,45 5,15 10,10 15,15 15,45", // Segment F (top-left) 195 | "10,50 15,45 35,45 40,50 35,55 15,55", // Segment G (hyphen in the middle) 196 | ]; 197 | for (const polygonPoints of polygons) { 198 | let polygon = document.createElementNS(svgNS, "polygon"); 199 | if (polygons.indexOf(polygonPoints) === 6) 200 | polygon.setAttribute("fill", "#ffaaaa"); 201 | else 202 | polygon.setAttribute("fill", "#333333"); 203 | polygon.setAttribute("points", polygonPoints); 204 | polygon.setAttribute("stroke", "black"); 205 | sevenSegmentDisplay.appendChild(polygon); 206 | } 207 | sevenSegmentDisplays.appendChild(sevenSegmentDisplay); 208 | } 209 | document.getElementById("graphicalResults").appendChild(sevenSegmentDisplays); 210 | let LEDs = document.createElementNS(svgNS, "svg"); 211 | LEDs.setAttribute("width", 400); 212 | LEDs.setAttribute("height", 60); 213 | LEDs.style.background = "darkGreen"; //"fill" does not work here. 214 | LEDs.style.marginLeft = "auto"; // No idea why this is necessary. 215 | LEDs.style.marginRight = "auto"; 216 | LEDs.style.display = "block"; 217 | for (let i = 0; i < 8; i++) { 218 | let LED = document.createElementNS(svgNS, "circle"); 219 | LED.setAttribute("fill", "#333333"); 220 | LED.setAttribute("cx", 25 + i * 50); 221 | LED.setAttribute("cy", 15); 222 | LED.setAttribute("r", 5); 223 | LED.id = "LED" + (7 - i); 224 | LEDs.appendChild(LED); 225 | let switchHolder = document.createElementNS(svgNS, "rect"); 226 | switchHolder.setAttribute("fill", "black"); 227 | switchHolder.setAttribute("width", 5); 228 | switchHolder.setAttribute("height", 30); 229 | switchHolder.setAttribute("y", 25); 230 | switchHolder.setAttribute("x", 23 + i * 50); 231 | LEDs.appendChild(switchHolder); 232 | let button = document.createElementNS(svgNS, "rect"); 233 | button.setAttribute("fill", "gray"); 234 | button.setAttribute("width", 15); 235 | button.setAttribute("height", 15); 236 | button.setAttribute("x", 18 + i * 50); 237 | button.setAttribute("y", 25 + 30 - 15); 238 | button.id = "switch" + i; 239 | button.setAttribute("data-buttonValue", 1 << (7 - i)); 240 | button.onclick = onSwitchPressed; 241 | LEDs.appendChild(button); 242 | } 243 | document.getElementById("graphicalResults").appendChild(LEDs); 244 | function onSwitchPressed(event) { 245 | let valueOfTheFirstInput = 246 | parseInt(document.getElementById("input_00").value, 16); 247 | valueOfTheFirstInput ^= event.target.getAttribute("data-buttonValue"); 248 | // https://discord.com/channels/530598289813536771/847014270922391563/1434254492014219315 249 | const isValid = 250 | valueOfTheFirstInput & event.target.getAttribute("data-buttonValue"); 251 | if (isValid) 252 | event.target.setAttribute("y", 25); 253 | else 254 | event.target.setAttribute("y", 25 + 30 - 15); 255 | document.getElementById("input_00").value = 256 | formatAsByte(valueOfTheFirstInput); 257 | } 258 | document.getElementById("input_00").oninput = () => { 259 | const value = 260 | Math.abs(parseInt(document.getElementById("input_00").value, 16) | 0) % 261 | 256; 262 | let formatedAsBinary = value.toString(2); 263 | while (formatedAsBinary.length < 8) 264 | formatedAsBinary = "0" + formatedAsBinary; 265 | for (let i = 0; i < 8; i++) 266 | document.getElementById("switch" + i) 267 | .setAttribute("y", 40 - formatedAsBinary[i] * 15); 268 | }; 269 | document.getElementById("UART_enable_button").onclick = () => { 270 | is_UART_enabled = !is_UART_enabled; 271 | document.getElementById("input_02").disabled = 272 | document.getElementById("input_03").disabled = is_UART_enabled; 273 | document.getElementById("UART_IO").style.display = 274 | is_UART_enabled ? "block" : "none"; 275 | document.getElementById("enable_or_disable_UART").innerHTML = 276 | is_UART_enabled ? "Disable" : "Enable"; 277 | if (is_UART_enabled) 278 | document.getElementById("shouldWeUpdateRegisters").checked = false; 279 | window.onresize(); 280 | }; 281 | fetch(URL_of_JSON_with_examples) 282 | .then((response) => { 283 | if (!response.ok) 284 | throw new Error(response.status); 285 | else 286 | return response.json(); 287 | }) 288 | .then((examplesArray) => { 289 | let examplesHTML = examplesArray 290 | .map((example) => ` 291 | 302 | `).join("") + ` 303 | 309 | 318 | `; 319 | document.getElementById("examples").style.justifyContent = "initial"; 320 | document.getElementById("examples").style.alignItems = "initial"; 321 | document.getElementById("examples").innerHTML = examplesHTML; 322 | }) 323 | .catch((error) => { 324 | document.getElementById("fetchingExamples").innerHTML = 325 | "Failed to fetch the examples JSON from GitHub: " + error; 326 | }); 327 | -------------------------------------------------------------------------------- /TreeNode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function LevenshtainDistance(A, B) { 4 | // Will be used to find the closest label to the one that the user has. 5 | // Adapted from: 6 | // https://github.com/royalpranjal/Interview-Bit/blob/master/DynamicProgramming/EditDistance.cpp 7 | 8 | const row = A.length; 9 | const col = B.length; 10 | 11 | const temp = []; 12 | for (let i = 0; i < row + 1; i++) { 13 | let tmp = []; 14 | for (let j = 0; j < col + 1; j++) { 15 | tmp.push(0); 16 | } 17 | temp.push(tmp); 18 | } 19 | 20 | const min = (a, b) => { 21 | if (a < b) 22 | return a; 23 | else 24 | return b; 25 | }; // I am not sure whether there should be a semi-colon here or not. The code 26 | // seems to somehow compile either way in both Firefox and Chrome. 27 | 28 | for (let i = 0; i < temp.length; i++) { 29 | for (let j = 0; j < temp[0].length; j++) { 30 | if (j == 0) { 31 | temp[i][j] = i; 32 | } else if (i == 0) { 33 | temp[i][j] = j; 34 | } else if (A[i - 1] == B[j - 1]) { 35 | temp[i][j] = temp[i - 1][j - 1]; 36 | } else { 37 | temp[i][j] = min(temp[i - 1][j - 1], temp[i - 1][j]); 38 | temp[i][j] = min(temp[i][j - 1], temp[i][j]); 39 | temp[i][j] = temp[i][j] + 1; 40 | } 41 | } 42 | } 43 | 44 | return temp[row][col]; 45 | } 46 | 47 | class TreeNode { 48 | constructor(text, lineNumber) { 49 | this.text = text; 50 | this.lineNumber = lineNumber; 51 | this.children = []; 52 | } 53 | getLispExpression() { 54 | if (!this.children.length) 55 | return '"' + (this.text == "\n" ? "\\n" : this.text) + '"'; 56 | let ret = '("' + this.text + '" '; 57 | for (let i = 0; i < this.children.length; i++) 58 | if (i < this.children.length - 1) 59 | ret += this.children[i].getLispExpression() + " "; 60 | else 61 | ret += this.children[i].getLispExpression() + ")"; 62 | return ret; 63 | } 64 | interpretAsArithmeticExpression(constants, labels) { 65 | if (!(constants instanceof Map)) { 66 | alert( 67 | 'Internal compiler error: The "constants" argument is not of the type "Map"!'); 68 | } 69 | if (labels && !(labels instanceof Map)) { 70 | alert( 71 | 'Internal compiler error: The "labels" argument is not of the type "Map"!'); 72 | } 73 | if (labels && labels.has(this.text)) { 74 | return labels.get(this.text); 75 | } 76 | if (constants.has(this.text)) 77 | return constants.get(this.text); 78 | if (this.children.length != 2 && 79 | (this.text == "*" || this.text == "/" || this.text == "+" || 80 | this.text == "-" || this.text == "<" || this.text == "=" || 81 | this.text == ">" || this.text == "&" || this.text == "|")) { 82 | alert("Line #" + this.lineNumber + ": The binary operator " + this.text + 83 | " has less than two operands."); 84 | return NaN; 85 | } 86 | if (this.text == "^") 87 | return (this.children[0].interpretAsArithmeticExpression(constants) ** 88 | this.children[1].interpretAsArithmeticExpression(constants)); 89 | if (this.text == "*") 90 | return (this.children[0].interpretAsArithmeticExpression(constants) * 91 | this.children[1].interpretAsArithmeticExpression(constants)); 92 | if (this.text == "/") 93 | return (this.children[0].interpretAsArithmeticExpression(constants) / 94 | this.children[1].interpretAsArithmeticExpression(constants)); 95 | if (this.text == "+") 96 | return (this.children[0].interpretAsArithmeticExpression(constants) + 97 | this.children[1].interpretAsArithmeticExpression(constants)); 98 | if (this.text == "-") 99 | return (this.children[0].interpretAsArithmeticExpression(constants) - 100 | this.children[1].interpretAsArithmeticExpression(constants)); 101 | if (this.text == "<") 102 | return (this.children[0].interpretAsArithmeticExpression(constants) < 103 | this.children[1].interpretAsArithmeticExpression(constants)); 104 | if (this.text == ">") 105 | return (this.children[0].interpretAsArithmeticExpression(constants) > 106 | this.children[1].interpretAsArithmeticExpression(constants)); 107 | if (this.text == "=") 108 | return (this.children[0].interpretAsArithmeticExpression(constants) == 109 | this.children[1].interpretAsArithmeticExpression(constants)); 110 | if (this.text == "&") 111 | return (this.children[0].interpretAsArithmeticExpression(constants) && 112 | this.children[1].interpretAsArithmeticExpression(constants)); 113 | if (this.text == "|") 114 | return (this.children[0].interpretAsArithmeticExpression(constants) || 115 | this.children[1].interpretAsArithmeticExpression(constants)); 116 | if (this.text == "?:") 117 | return ( 118 | this.children[0].interpretAsArithmeticExpression(constants) 119 | ? this.children[1].interpretAsArithmeticExpression(constants, 120 | labels) 121 | : this.children[2].interpretAsArithmeticExpression( 122 | constants, 123 | labels)); // We are passing "labels" in an attempt to mend 124 | // this bug: 125 | // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/38 126 | if (this.text == "()") { 127 | if (this.children.length != 1) { 128 | alert("Line #" + this.lineNumber + 129 | ": The node '()' doesn't have exactly 1 child."); 130 | return NaN; 131 | } 132 | return this.children[0].interpretAsArithmeticExpression(constants); 133 | } 134 | if (this.text == "invertBits()") { 135 | if (this.children.length != 1) { 136 | alert("Line #" + this.lineNumber + 137 | ": The node 'invertBits()' doesn't have exactly 1 child."); 138 | return NaN; 139 | } 140 | return ~this.children[0].interpretAsArithmeticExpression(constants); 141 | } 142 | if (this.text == "bitand()") { 143 | if (this.children.length != 3 || this.children[1].text != ',') { 144 | alert( 145 | "Line #" + this.lineNumber + 146 | ": The node 'bitand()' doesn't have exactly 3 children (a comma is also a child node)."); 147 | return NaN; 148 | } 149 | return this.children[0].interpretAsArithmeticExpression(constants) & 150 | this.children[2].interpretAsArithmeticExpression(constants); 151 | } 152 | if (this.text == "bitor()") { 153 | if (this.children.length != 3 || this.children[1].text != ',') { 154 | alert( 155 | "Line #" + this.lineNumber + 156 | ": The node 'bitor()' doesn't have exactly 3 children (a comma is also a child node)."); 157 | return NaN; 158 | } 159 | return this.children[0].interpretAsArithmeticExpression(constants) | 160 | this.children[2].interpretAsArithmeticExpression(constants); 161 | } 162 | if (this.text == "mod()") { 163 | if (this.children.length != 3 || this.children[1].text != ',') { 164 | alert( 165 | "Line #" + this.lineNumber + 166 | ": The node 'mod()' doesn't have exactly 3 children (a comma is also a child node)."); 167 | return NaN; 168 | } 169 | return this.children[0].interpretAsArithmeticExpression(constants) % 170 | this.children[2].interpretAsArithmeticExpression(constants); 171 | } 172 | if (/\'d$/.test(this.text)) { 173 | for (let i = 0; i < this.text.length - 2; i++) 174 | if (this.text.charCodeAt(i) < '0'.charCodeAt(0) || 175 | this.text.charCodeAt(i) > '9'.charCodeAt(0)) { 176 | alert( 177 | "Line #" + this.lineNumber + ": `" + this.text + 178 | "` is not a valid decimal constant!"); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/27 179 | return NaN; 180 | } 181 | return parseInt(this.text.substring( 182 | 0, 183 | this.text.length - 184 | 2)); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30 185 | } 186 | if (/\'o$/.test(this.text)) { 187 | for (let i = 0; i < this.text.length - 2; i++) 188 | if (this.text.charCodeAt(i) < '0'.charCodeAt(0) || 189 | this.text.charCodeAt(i) > '7'.charCodeAt(0)) { 190 | alert( 191 | "Line #" + this.lineNumber + ": `" + this.text + 192 | "` is not a valid octal constant!"); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/27 193 | return NaN; 194 | } 195 | return parseInt( 196 | this.text.substring(0, this.text.length - 2), 197 | 8); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30 198 | } 199 | if (/\'b$/.test(this.text)) { 200 | for (let i = 0; i < this.text.length - 2; i++) 201 | if (!(this.text[i] == '0' || this.text[i] == '1')) { 202 | alert("Line #" + this.lineNumber + ": `" + this.text + 203 | "` is not a valid binary constant!"); 204 | return NaN; 205 | } 206 | return parseInt( 207 | this.text.substring(0, this.text.length - 2), 208 | 2); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30 209 | } 210 | if (/\'x$/.test(this.text)) { 211 | if (!(/^([a-f]|[0-9])*\'x$/i.test(this.text))) { 212 | alert("Line #" + this.lineNumber + ": `" + this.text + 213 | "` is not a valid hexadecimal constant!"); 214 | return NaN; 215 | } 216 | return parseInt( 217 | this.text.substring(0, this.text.length - 2), 218 | 16); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30 219 | } 220 | if (/^([a-f]|[0-9])*$/i.test(this.text)) 221 | return parseInt( 222 | this.text, 223 | ((typeof default_base_of_literals_in_assembly) == "undefined") 224 | ? 16 225 | : default_base_of_literals_in_assembly); 226 | if (this.text[0] === '"' && this.text.length === 3) 227 | return this.text.charCodeAt(1); 228 | let keys = []; 229 | constants.forEach((value, key) => { keys.push(key); }); 230 | let smallest_Levenshtain_distance = keys[0]; 231 | for (const key of keys) { 232 | if (LevenshtainDistance(key, this.text) < 233 | LevenshtainDistance(smallest_Levenshtain_distance, this.text)) { 234 | smallest_Levenshtain_distance = key; 235 | } 236 | } 237 | if (confirm("Instead of \"" + this.text + "\", in the line #" + 238 | this.lineNumber + ", did you perhaps mean \"" + 239 | smallest_Levenshtain_distance + "\"?")) { 240 | if (constants.has(smallest_Levenshtain_distance)) { 241 | constants.set(this.text, constants.get(smallest_Levenshtain_distance)); 242 | return constants.get(this.text); 243 | } 244 | } 245 | alert('Some part of the assembler tried to interpret the token "' + 246 | this.text + '" in the line #' + this.lineNumber + 247 | " as a part of an arithmetic expression, which makes no sense."); 248 | return NaN; 249 | } 250 | checkTypes() { 251 | // Let's check for inconsistencies that would be impossible in C++, but are 252 | // possible in JavaScript. 253 | if (typeof this.text !== "string") { 254 | alert("Internal compiler error: For some token in the line #" + 255 | this.lineNumber + ', the "text" property is not of type "string"'); 256 | return false; 257 | } 258 | if (!(this.children instanceof Array)) { 259 | alert('Internal compiler error: The "children" property of the "' + 260 | this.text + '" token in the line #' + this.lineNumber + 261 | " is not an array!"); 262 | return false; 263 | } 264 | for (const child of this.children) 265 | if (!(child instanceof TreeNode)) { 266 | alert('Internal compiler error: The node "this.text" in the line #' + 267 | this.lineNumber + 268 | " has a child that is not an instance of TreeNode!"); 269 | return false; 270 | } 271 | return true; 272 | } 273 | getRegisterNumber(registers) { 274 | if (registers.has(this.text)) 275 | return registers.get(this.text) 276 | .substring(1) 277 | .toLowerCase(); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30 278 | if (/^s(\d|[a-f])$/i.test(this.text)) 279 | return this.text.substring(1) 280 | .toLowerCase(); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30 281 | // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/37#issuecomment-2778246629 282 | let register_name_with_Levenshtain_distance_of_one; 283 | registers.forEach((value, key) => { 284 | if (LevenshtainDistance(key, this.text) == 1) 285 | register_name_with_Levenshtain_distance_of_one = key; 286 | }); 287 | if (register_name_with_Levenshtain_distance_of_one) { 288 | if (confirm( 289 | "Instead of \"" + this.text + "\", in the line #" + 290 | this.lineNumber + ", did you perhaps mean \"" + 291 | register_name_with_Levenshtain_distance_of_one + 292 | "\", an alternate name for the register \"" + 293 | registers.get(register_name_with_Levenshtain_distance_of_one) + 294 | "\"?")) { 295 | registers.set( 296 | this.text, 297 | registers.get(register_name_with_Levenshtain_distance_of_one)); 298 | return registers.get(this.text).substring(1).toLowerCase(); 299 | } 300 | } 301 | return "none"; 302 | } 303 | getLabelAddress(labels, constants) { 304 | if (labels.has(this.text)) 305 | return formatAsAddress(labels.get(this.text)); 306 | if (constants.has(this.text)) 307 | return formatAsAddress(constants.get(this.text)); 308 | if (/^(\d|[a-f])*$/i.test(this.text) || 309 | [ "+", "-", "*", "/", "^", "?:" ].includes(this.text)) 310 | return formatAsAddress(this.interpretAsArithmeticExpression( 311 | constants, 312 | labels)); // We are passing the `labels` argument to resolve this bug: 313 | // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/38 314 | if (this.text == "()") 315 | return "none"; // Must not detect "()" as a label. 316 | let keys = []; 317 | labels.forEach((value, key) => { keys.push(key); }); 318 | constants.forEach((value, key) => { keys.push(key); }); 319 | let smallest_Levenshtain_distance = keys[0]; 320 | for (const key of keys) { 321 | if (LevenshtainDistance(key, this.text) < 322 | LevenshtainDistance(smallest_Levenshtain_distance, this.text)) { 323 | smallest_Levenshtain_distance = key; 324 | } 325 | } 326 | if (confirm("Instead of \"" + this.text + "\", in the line #" + 327 | this.lineNumber + ", did you perhaps mean \"" + 328 | smallest_Levenshtain_distance + "\"?")) { 329 | if (labels.has(smallest_Levenshtain_distance)) { 330 | labels.set(this.text, labels.get(smallest_Levenshtain_distance)); 331 | return formatAsAddress(labels.get(smallest_Levenshtain_distance)); 332 | } 333 | if (constants.has(smallest_Levenshtain_distance)) { 334 | constants.set(this.text, constants.get(smallest_Levenshtain_distance)); 335 | return formatAsAddress(constants.get(smallest_Levenshtain_distance)); 336 | } 337 | } 338 | return "none"; 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /parser.js: -------------------------------------------------------------------------------- 1 | // I have hand-written this parser partly because I don't know of any BISON-like 2 | // tool that supports JavaScript and partly because I am not even sure how would 3 | // I make a parser for PicoBlaze Assembly in BISON. The keywords "enable" and 4 | // "disable" are problematic (they can be both mnemonics and, let's say so, 5 | // "adverbs"). I have opened a StackExchange question about that: 6 | // https://langdev.stackexchange.com/q/1679/330 7 | "use strict"; 8 | 9 | /* 10 | * In most assemblers, the parser returns a two-dimensional array of trees, 11 | * many of those trees containing only a single node (and only arithmetic 12 | * expressions being represented with a multiple-node tree). The parser of 13 | * this assembler works differently, more like a parser for higher-level 14 | * programming languages. The parser of this assembler returns one big 15 | * tree, with the root being a node containing the text "assembly". Labels, 16 | * preprocessor directives and mnemonics are nodes of depth equal to 1, and 17 | * their operands are their children. 18 | */ 19 | 20 | function parse(tokenized) { 21 | 22 | // This function is recursive, so we are going to print the argument to 23 | // make it easier to debug it. 24 | let report = "["; 25 | for (let i = 0; i < tokenized.length; i++) 26 | if (i < tokenized.length - 1) 27 | report += tokenized[i].getLispExpression() + ","; 28 | else 29 | report += tokenized[i].getLispExpression(); 30 | report += "]"; 31 | console.log("Parsing the expression: " + report); 32 | 33 | let root_of_abstract_syntax_tree = new TreeNode( 34 | "assembly", 0); // Value which will be returned from the parser. 35 | 36 | for ( 37 | let i = 0; i < tokenized.length; 38 | i++ // First, let's deal with if-branching and while-loops... 39 | ) { 40 | if (/^if$/i.test(tokenized[i].text)) { 41 | let pointerToTheNextNewline = i + 1, condition = []; 42 | while (tokenized[pointerToTheNextNewline].text != "\n") { 43 | condition.push(tokenized[pointerToTheNextNewline]); 44 | if (pointerToTheNextNewline >= tokenized.length) { 45 | alert( 46 | "Line #" + tokenized[i].lineNumber + 47 | ': The condition after "if" doesn\'t end in a new-line character!'); 48 | return root_of_abstract_syntax_tree; 49 | } 50 | pointerToTheNextNewline++; 51 | } 52 | tokenized[i].children.push(parse(condition).children[0]); 53 | tokenized.splice(i + 1, pointerToTheNextNewline - i); 54 | let pointerToTheEndIfOrElse = i + 1, counter = 1, thenClause = []; 55 | while (true) { 56 | if (pointerToTheEndIfOrElse >= tokenized.length) { 57 | alert("Line #" + tokenized[i].lineNumber + 58 | ': The "if" directive here isn\'t closed by an "endif"!'); 59 | return root_of_abstract_syntax_tree; 60 | } 61 | if (/^if$/i.test(tokenized[pointerToTheEndIfOrElse].text)) 62 | counter++; 63 | if (/^endif$/i.test(tokenized[pointerToTheEndIfOrElse].text)) 64 | counter--; 65 | if (!counter || 66 | (/^else$/i.test(tokenized[pointerToTheEndIfOrElse].text) && 67 | counter == 1)) 68 | break; 69 | thenClause.push(tokenized[pointerToTheEndIfOrElse]); 70 | pointerToTheEndIfOrElse++; 71 | } 72 | let lineNumberOfElseOrEndIf = 73 | tokenized[pointerToTheEndIfOrElse].lineNumber; 74 | tokenized.splice(i + 1, pointerToTheEndIfOrElse - i); 75 | tokenized[i].children.push(parse(thenClause)); 76 | if (counter) { 77 | // If there is an "else"-clause 78 | let pointerToEndIf = i + 1, elseClause = []; 79 | while (counter) { 80 | if (pointerToEndIf >= tokenized.length) { 81 | alert("Line #" + lineNumberOfElseOrEndIf + 82 | ': The "else" here is not followed by an "endif"!'); 83 | return root_of_abstract_syntax_tree; 84 | } 85 | if (/^if$/i.test(tokenized[pointerToEndIf].text)) 86 | counter++; 87 | if (/^endif$/i.test(tokenized[pointerToEndIf].text)) 88 | counter--; 89 | if (/^else$/i.test(tokenized[pointerToEndIf].text) && counter == 1) { 90 | alert("Line #" + tokenized[pointerToEndIf].lineNumber + 91 | ': Found "else" when expecting "endif"!'); 92 | return root_of_abstract_syntax_tree; 93 | } 94 | elseClause.push(tokenized[pointerToEndIf]); 95 | pointerToEndIf++; 96 | } 97 | elseClause.splice(elseClause.length - 1, 1); 98 | tokenized.splice(i + 1, pointerToEndIf - i); 99 | tokenized[i].children.push(parse(elseClause)); 100 | } 101 | } else if (/^endif$/i.test(tokenized[i].text) || 102 | /^else$/i.test(tokenized[i].text)) { 103 | alert("Line #" + tokenized[i].lineNumber + 104 | ': The preprocessor directive "' + tokenized[i].text + 105 | '" found without the corresponding "if" directive!'); 106 | return root_of_abstract_syntax_tree; 107 | } else if (/^while$/i.test(tokenized[i].text)) { 108 | let pointerToTheNextNewline = i + 1, condition = []; 109 | while (tokenized[pointerToTheNextNewline].text != "\n") { 110 | condition.push(tokenized[pointerToTheNextNewline]); 111 | if (pointerToTheNextNewline >= tokenized.length) { 112 | alert( 113 | "Line #" + tokenized[i].lineNumber + 114 | ': The condition after "while" doesn\'t end in a new-line character!'); 115 | return root_of_abstract_syntax_tree; 116 | } 117 | pointerToTheNextNewline++; 118 | } 119 | tokenized[i].children.push(parse(condition).children[0]); 120 | tokenized.splice(i + 1, pointerToTheNextNewline - i); 121 | let pointerToEndWhile = i + 1, counter = 1, loopClause = []; 122 | while (counter) { 123 | if (pointerToEndWhile >= tokenized.length) { 124 | alert("Line #" + tokenized[i].lineNumber + 125 | ': The "while" here isn\'t being closed by an "endwhile"!'); 126 | return root_of_abstract_syntax_tree; 127 | } 128 | if (/^while$/i.test(tokenized[pointerToEndWhile].text)) 129 | counter++; 130 | if (/^endwhile$/i.test(tokenized[pointerToEndWhile].text)) 131 | counter--; 132 | loopClause.push(tokenized[pointerToEndWhile]); 133 | pointerToEndWhile++; 134 | } 135 | loopClause.splice(loopClause.length - 1, 1); 136 | tokenized[i].children.push(parse(loopClause)); 137 | tokenized.splice(i + 1, pointerToEndWhile - i); 138 | } else if (/^endwhile$/i.test(tokenized[i].text)) { 139 | alert( 140 | "Line #" + tokenized[i].lineNumber + 141 | ': The preprocessor directive "endwhile" found without the corresponding "while" directive!'); 142 | return root_of_abstract_syntax_tree; 143 | } 144 | } 145 | 146 | for ( 147 | let i = 0; i < tokenized.length; 148 | i++ // Then, let's deal with the parentheses. 149 | ) { 150 | if (/\($/.test(tokenized[i].text)) { 151 | // As far as I know, PicoBlaze Assembly uses only this type of 152 | // parentheses. 153 | let counter = 1; 154 | let j = i + 1; 155 | while (counter) { 156 | if (j >= tokenized.length) { 157 | alert("The parenthesis on line " + tokenized[i].lineNumber + 158 | " isn't closed!"); 159 | return root_of_abstract_syntax_tree; 160 | } 161 | if (/\($/.test(tokenized[j].text)) 162 | counter++; 163 | if (tokenized[j].text == ")") 164 | counter--; 165 | j++; 166 | } 167 | let newArray = []; 168 | for (let k = i + 1; k < j - 1; k++) 169 | newArray.push(tokenized[k]); 170 | tokenized.splice(i + 1, j - i - 1); 171 | tokenized[i].text += ")"; 172 | tokenized[i].children = parse(newArray).children; 173 | } 174 | } 175 | 176 | // Dealing with mnemonics and preprocessor directives... 177 | for (let i = 0; i < tokenized.length; i++) { 178 | if (tokenized[i].text == "\n") { 179 | // Delete the new-line characters when you pass over them. 180 | tokenized.splice(i, 1); 181 | i--; 182 | continue; 183 | } 184 | // Check if the current token is a mnemonic or a preprocessor directive... 185 | let isMnemonicOrPreprocessorDirective = false; 186 | for (const mnemonic of mnemonics) 187 | if (RegExp("^" + mnemonic + "$", "i").test(tokenized[i].text)) 188 | isMnemonicOrPreprocessorDirective = true; 189 | for (const directive of preprocessor) 190 | if (RegExp("^" + directive + "$", "i").test(tokenized[i].text) && 191 | !/^while$/i.test(tokenized[i].text) && 192 | !/^if$/i.test(tokenized[i].text)) 193 | isMnemonicOrPreprocessorDirective = true; 194 | if (!isMnemonicOrPreprocessorDirective || 195 | (tokenized.length === 1 && (/^enable$/i.test(tokenized[0].text) || 196 | /^disable$/i.test(tokenized[0].text)))) 197 | continue; 198 | // If the current token is a mnemonic or a preprocessor directive, seek for 199 | // the next new-line character. Unfortunately, we can't use the C++ find_if 200 | // here... 201 | let j = i; 202 | while (true) { 203 | if (j >= tokenized.length) { 204 | alert( 205 | "Internal compiler error: The assembly-lanaguage expression in line " + 206 | tokenized[i].lineNumber + " doesn't end with a new-line token!\n" + 207 | "Did you try writing something like `load (load s0, s1), s2`? That's invalid assembly code, assembly language doesn't support linguistic recursion. You need to write this:\n" + 208 | "load s1, s2\n" + 209 | "load s0, s1\n" + 210 | "instead."); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/17 211 | return root_of_abstract_syntax_tree; 212 | } 213 | if (tokenized[j].text == "\n") 214 | break; 215 | j++; 216 | } 217 | let newArray = []; 218 | for (let k = i + 1; k < j; k++) 219 | newArray.push(tokenized[k]); 220 | tokenized[i].children = parse(newArray).children; 221 | tokenized.splice(i + 1, j - i - 1); 222 | } 223 | 224 | // Parsing arithmetic expressions... 225 | for (let i = tokenized.length - 1; i >= 0; 226 | i--) // We need to iterate backward rather than forward in order to parse 227 | // tthe expressions such as "inst --5" correctly. 228 | if ((tokenized[i].text == "+" || tokenized[i].text == "-") && 229 | (i == 0 || tokenized[i - 1].text == "," || 230 | tokenized[i - 1].text.substring(tokenized[i - 1].length - 1) == "(" || 231 | tokenized[i - 1].text == "\n" || 232 | ([ 233 | "+", "-", "*", "/", "^", "&", "|", "=", "<", ">", "?", ":" 234 | ].includes(tokenized[i - 1].text) && 235 | !tokenized[i - 1].children.length)) && 236 | !tokenized[i].children.length) { 237 | // Unary operators 238 | if (tokenized.length == 1 || 239 | i >= 240 | tokenized.length - 241 | 1 || // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/42 242 | tokenized[i + 1].text == "," || 243 | tokenized[i + 1].text == "\n") { 244 | alert("Line #" + tokenized[i].lineNumber + ": The unary operator '" + 245 | tokenized[i].text + "' has zero operands!"); 246 | return root_of_abstract_syntax_tree; 247 | } 248 | tokenized[i].children = [ 249 | new TreeNode("0", tokenized[i].lineNumber), 250 | tokenized[i + 1], 251 | ]; 252 | tokenized.splice(i + 1, 1); 253 | } 254 | 255 | /* 256 | * To better understand how the following code (for parsing arithmetic 257 | * expressions) works, I'd suggest you to study the task "Izraz" from Infokup 258 | * 2013: https://informatika.azoo.hr/natjecanje/dogadjaj/235/rezultati 259 | */ 260 | 261 | const parseBinaryOperators = (operators) => { 262 | for (let i = 0; i < tokenized.length; i++) 263 | if (operators.includes(tokenized[i].text) && 264 | tokenized[i].children.length == 0) { 265 | if (i == 0 || tokenized[i - 1].text == "," || 266 | tokenized[i - 1].text == "\n" || i == tokenized.length - 1 || 267 | tokenized[i + 1].text == "," || tokenized[i + 1].text == "\n") { 268 | alert("Line #" + tokenized[i].lineNumber + ": The binary operator '" + 269 | tokenized[i].text + "' has less than two operands!"); 270 | return false; 271 | } 272 | tokenized[i].children = [ tokenized[i - 1], tokenized[i + 1] ]; 273 | tokenized.splice(i - 1, 1); 274 | tokenized.splice(i, 1); 275 | i--; 276 | continue; 277 | } 278 | return true; 279 | }; 280 | 281 | const binaryOperators = [ 282 | [ "^" ], // Exponentiation (has the highest priority). 283 | [ 284 | "*", "/" 285 | ], // Multiplication and division have the same priority, that's why they 286 | // are in the same row in the 2-dimensional array. 287 | [ "+", "-" ], // So do addition and subtraction have the same priority... 288 | [ "<", ">", "=" ], [ "&" ], 289 | [ "|" ] // Logical "or" (has the lowest priority). 290 | ]; 291 | for (const operators of binaryOperators) 292 | if (!parseBinaryOperators(operators)) 293 | return root_of_abstract_syntax_tree; 294 | 295 | // Ternary conditional operator... 296 | /* 297 | * What is the best way of parsing right-associative operators, such as the 298 | * ternary conditional `?:` operator? In both my AEC-to-WebAssembly compiler 299 | * and the following code in my PicoBlaze assembler, I was using the "scan 300 | * backwards" method. However, I received some comments that it is considered 301 | * to be an anti-pattern. So, I opened a StackExchange question about that: 302 | * https://langdev.stackexchange.com/q/4071/330 303 | */ 304 | let lastColon = tokenized.length - 2; 305 | if (lastColon > 0) 306 | while (lastColon) { 307 | if (tokenized[lastColon].text == ':' && 308 | tokenized[lastColon + 1].text != '\n') { 309 | let questionMarkCorrespondingToTheLastColon = lastColon, counter = 1; 310 | console.log( 311 | "DEBUG: Parsing the ternary conditional operator. The colon is at the index: " + 312 | lastColon); 313 | while (counter) { 314 | questionMarkCorrespondingToTheLastColon--; 315 | if (!questionMarkCorrespondingToTheLastColon || 316 | questionMarkCorrespondingToTheLastColon < 0) { 317 | alert( 318 | "Line #" + tokenized[lastColon].lineNumber + 319 | ": There is a colon without a matching question mark before it!"); 320 | return root_of_abstract_syntax_tree; 321 | } 322 | if (tokenized[questionMarkCorrespondingToTheLastColon].text == '?') 323 | counter--; 324 | else if (tokenized[questionMarkCorrespondingToTheLastColon].text == 325 | ':') 326 | counter++; 327 | } 328 | console.log("DEBUG: The corresponding question mark is at the index: " + 329 | questionMarkCorrespondingToTheLastColon); 330 | let nodesThatRecursionDealsWith = []; 331 | for (let i = questionMarkCorrespondingToTheLastColon + 1; i < lastColon; 332 | i++) 333 | nodesThatRecursionDealsWith.push(tokenized[i]); 334 | tokenized[questionMarkCorrespondingToTheLastColon].text = "?:"; 335 | tokenized[questionMarkCorrespondingToTheLastColon].children.push( 336 | tokenized[questionMarkCorrespondingToTheLastColon - 1]); 337 | const whatTheRecursionReturned = parse(nodesThatRecursionDealsWith); 338 | if (whatTheRecursionReturned.children.length > 1) { 339 | alert("Line #" + whatTheRecursionReturned.children[1].lineNumber + 340 | ": Unexpected token `" + 341 | whatTheRecursionReturned.children[1].text + "`!"); 342 | return root_of_abstract_syntax_tree; 343 | } 344 | tokenized[questionMarkCorrespondingToTheLastColon].children.push( 345 | whatTheRecursionReturned.children[0]); 346 | tokenized[questionMarkCorrespondingToTheLastColon].children.push( 347 | tokenized[lastColon + 1]); 348 | console.log( 349 | "DEBUG: The ternary conditional operator converted to LISP is: " + 350 | tokenized[questionMarkCorrespondingToTheLastColon] 351 | .getLispExpression()); 352 | tokenized.splice(questionMarkCorrespondingToTheLastColon + 1, 353 | lastColon - questionMarkCorrespondingToTheLastColon + 354 | 1); 355 | tokenized.splice(questionMarkCorrespondingToTheLastColon - 1, 1); 356 | lastColon = questionMarkCorrespondingToTheLastColon; 357 | } 358 | lastColon--; 359 | } 360 | 361 | root_of_abstract_syntax_tree.children = tokenized; 362 | if (root_of_abstract_syntax_tree.checkTypes()) 363 | return root_of_abstract_syntax_tree; 364 | 365 | return new TreeNode("assembly", 0); 366 | } 367 | --------------------------------------------------------------------------------