├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── release.yml │ └── update-release-branch.yml ├── .gitignore ├── .vscode ├── extensions.json └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── _redirects ├── astro.config.mjs ├── package.json ├── pnpm-lock.yaml ├── public ├── FaSolidUser.svg ├── favicon-dark.svg ├── favicon-with-background.svg ├── favicon.ico ├── favicon.svg ├── images │ └── screen.png ├── logo-with-green-background.svg └── tips │ ├── Comments.html │ ├── Labels.html │ └── Number.html ├── pwa-assets.config.js ├── release.config.mjs ├── scripts └── build-emulator.sh ├── src ├── components │ ├── ActionButton.jsx │ ├── Actions.jsx │ ├── ActionsLoader.jsx │ ├── App.jsx │ ├── Assembled.jsx │ ├── Assembled.module.css │ ├── Card.astro │ ├── CodeFormatter.jsx │ ├── CodeFormatterLoader.jsx │ ├── CodingArea.jsx │ ├── CopyComponent.jsx │ ├── DocumentationFooter.astro │ ├── DocumentationHead.astro │ ├── DonationPlea.jsx │ ├── FAQs.astro │ ├── FileActions.jsx │ ├── FileNode.jsx │ ├── Flags.jsx │ ├── FolderNode.jsx │ ├── Footer.astro │ ├── Header.astro │ ├── IOPorts.jsx │ ├── InterruptState.jsx │ ├── KeyboardShortcuts.jsx │ ├── KeyboardShortcuts.module.css │ ├── LEDArray.jsx │ ├── LambdaClassesPoster.jsx │ ├── LeftPanel.css │ ├── LeftPanel.jsx │ ├── MemoryGrid.jsx │ ├── MemoryList.jsx │ ├── NatoryAd.jsx │ ├── PlusDialog.jsx │ ├── Registers.jsx │ ├── RightPanel.jsx │ ├── Settings.jsx │ ├── StoreContext.js │ ├── Terminal.jsx │ ├── TextToolip.module.css │ ├── TextTooltip.jsx │ ├── ThemeSwitcher.astro │ ├── Tips.astro │ ├── Toolbox.jsx │ ├── Workspace.jsx │ ├── WorkspaceTree.jsx │ ├── analytics │ │ ├── CookieBanner.astro │ │ ├── PostHogInit.astro │ │ ├── PostHogInitialize.astro │ │ ├── identify.js │ │ └── tracker.js │ ├── codemirror │ │ ├── 8085.js │ │ ├── CodeMirror.css │ │ └── CodeMirror.jsx │ ├── generic │ │ ├── DelayedComponent.jsx │ │ ├── Dialog.css │ │ ├── Dialog.jsx │ │ ├── DropdownMenu.css │ │ ├── DropdownMenu.jsx │ │ ├── Select.css │ │ ├── Select.jsx │ │ ├── Switch.css │ │ ├── Switch.jsx │ │ ├── Tabs.css │ │ ├── Tabs.jsx │ │ ├── Toast.css │ │ ├── Toast.jsx │ │ ├── Tooltip.css │ │ ├── Tooltip.jsx │ │ └── wrapWithClass.js │ ├── pwa.js │ ├── styles.css │ └── toaster.jsx ├── content │ ├── changelog │ │ ├── 2024-11-11.md │ │ ├── 2024-11-16.md │ │ ├── 2025-04-24.md │ │ ├── 2025-05-03.md │ │ ├── 2025-05-05.md │ │ ├── 2025-05-06.md │ │ ├── 2025-05-07.md │ │ ├── 2025-05-08.md │ │ ├── 2025-05-14.md │ │ └── images │ │ │ ├── android-sim8085-install-2024-11-16.jpeg │ │ │ ├── doc-section-2024-11-11.png │ │ │ ├── full-memory-search-top-2025-05-03.png │ │ │ ├── instruction-timing-setting-2025-05-03.png │ │ │ ├── interrupt-controls-2025-05-08.png │ │ │ ├── interrupt-panel-2025-05-08.png │ │ │ ├── ios-sim8085-install-2024-11-16.jpeg │ │ │ ├── keyboard-shortcuts-2024-11-11.png │ │ │ ├── led-array-2025-05-05.png │ │ │ ├── sim8085-mobile-layout-2024-11-16.jpeg │ │ │ └── toolbox-panel-2025-05-05.png │ ├── config.js │ └── docs │ │ └── docs │ │ ├── en │ │ ├── 404.md │ │ ├── 8085.mdx │ │ ├── app-install.mdx │ │ ├── assembly.mdx │ │ ├── directives │ │ │ └── end.md │ │ ├── guides │ │ │ └── example.md │ │ ├── index.mdx │ │ ├── infinite-loop-reasons.mdx │ │ ├── instructions │ │ │ ├── aci.md │ │ │ └── adc.md │ │ ├── interrupts.mdx │ │ ├── reference │ │ │ ├── ascii.md │ │ │ └── instruction-summary.md │ │ └── unsupported.mdx │ │ └── images │ │ ├── install-using-chrome.png │ │ ├── install-using-edge.png │ │ ├── interrupt-controls-2025-05-08.png │ │ └── interrupt-panel-2025-05-08.png ├── core │ ├── 8085.c │ ├── 8085.js │ ├── 8085.pegjs │ ├── 8085.wasm │ ├── cpuState.js │ ├── parserTests │ │ └── label.test.js │ ├── sdk85-monitor.asm │ ├── sdk85-monitor.lst │ ├── simulator-library.js │ ├── simulator.js │ └── simulator.test.js ├── env.d.ts ├── icons │ ├── bmc.svg │ ├── computer-desktop.svg │ ├── github.svg │ ├── hand-holding-heart.svg │ ├── kofi.svg │ ├── lock.svg │ ├── logo-with-green-background.svg │ ├── logo.svg │ ├── menu.svg │ ├── moon.svg │ ├── paypal.svg │ ├── razorpay.svg │ ├── refresh.svg │ ├── regular-dock-left.svg │ ├── regular-dock-right.svg │ ├── solid-dock-left.svg │ ├── solid-dock-right.svg │ ├── sponsor.svg │ ├── sun.svg │ ├── triangle-info.svg │ └── youtube.svg ├── images │ ├── lambda-classes-poster.png │ ├── natory-light.png │ ├── natory.png │ ├── pc-start-value-textbox.png │ └── project-track-poster.jpg ├── layouts │ └── Layout.astro ├── lib │ └── supabase.js ├── pages │ ├── changelog.astro │ ├── donate.astro │ ├── index.astro │ ├── login.astro │ ├── privacy-policy.astro │ └── tos.astro ├── pwa.js ├── store │ └── store.js ├── tailwind.docs.config.mjs ├── tailwind.docs.css ├── tailwind.main.config.mjs ├── tailwind.main.css ├── tests │ ├── aci.test.js │ ├── adi.test.js │ ├── ani.test.js │ ├── call.test.js │ ├── cma.test.js │ ├── cmc.test.js │ ├── cpi.test.js │ ├── daa.test.js │ ├── dad.test.js │ ├── dcr.test.js │ ├── dcx.test.js │ ├── inr.test.js │ ├── inx.test.js │ ├── jump.test.js │ ├── lda.test.js │ ├── ldax.test.js │ ├── lhld.test.js │ ├── lxi.test.js │ ├── mov.test.js │ ├── mvi.test.js │ ├── ori.test.js │ ├── pchl.test.js │ ├── ral.test.js │ ├── rar.test.js │ ├── rc.test.js │ ├── return.test.js │ ├── rlc.test.js │ ├── rnc.test.js │ ├── rnz.test.js │ ├── rp.test.js │ ├── rrc.test.js │ ├── rz.test.js │ ├── sbb.test.js │ ├── sbi.test.js │ ├── shld.test.js │ ├── sphl.test.js │ ├── sta.test.js │ ├── stax.test.js │ ├── stc.test.js │ ├── sui.test.js │ ├── test-utils.js │ ├── xchg.test.js │ ├── xri.test.js │ └── xthl.test.js ├── themes │ ├── dark.css │ └── light.css └── utils │ ├── NumberFormat.js │ ├── deepMerge.js │ └── lens.js ├── tailwind.config.mjs ├── tsconfig.json └── vitest.config.mjs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | max_line_length = 120 10 | tab_width = 4 11 | 12 | [*.yml] 13 | indent_size = 2 14 | tab_width = 2 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: debjitbis08 2 | buy_me_a_coffee: debjit.biswas 3 | ko_fi: debjitbiswas 4 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master # or main 6 | 7 | permissions: 8 | contents: write # for checkout 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write # to be able to publish a GitHub release 16 | issues: write # to be able to comment on released issues 17 | pull-requests: write # to be able to comment on released pull requests 18 | id-token: write # to enable use of OIDC for npm provenance 19 | strategy: 20 | matrix: 21 | node-version: [20] # Use the desired Node.js version 22 | steps: 23 | - uses: actions/checkout@v4 # Checkout the repository 24 | - name: Install pnpm 25 | uses: pnpm/action-setup@v4 # Use pnpm's official GitHub Action 26 | with: 27 | version: 9 # Specify the pnpm version to use 28 | - name: Use Node.js ${{ matrix.node-version }} 29 | uses: actions/setup-node@v4 30 | with: 31 | node-version: ${{ matrix.node-version }} 32 | cache: "pnpm" # Enable pnpm cache 33 | - name: Install dependencies 34 | run: pnpm install --frozen-lockfile 35 | # - name: Verify the integrity of provenance attestations and registry signatures for installed dependencies 36 | # run: pnpm audit 37 | - name: Release 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.PAT }} 40 | run: pnpm semantic-release 41 | -------------------------------------------------------------------------------- /.github/workflows/update-release-branch.yml: -------------------------------------------------------------------------------- 1 | name: Update Release Branch 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | update-release-branch: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout the repository 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 # Fetch all history for tags and branches 17 | 18 | - name: Update release branch 19 | run: | 20 | # Get the latest tag 21 | LATEST_TAG=$(git describe --tags --abbrev=0) 22 | echo "Latest Tag: $LATEST_TAG" 23 | 24 | # Update the release branch to point to the latest tag 25 | git checkout release 26 | git reset --hard "$LATEST_TAG" 27 | 28 | # Push the updated release branch 29 | git push origin release --force 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | # jetbrains setting folder 24 | .idea/ 25 | 26 | sim8085_ed25519* -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-present, Debjit Biswas 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /_redirects: -------------------------------------------------------------------------------- 1 | https://v2.sim8085.com/* https://www.sim8085.com/:splat 301! 2 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | import solidJs from "@astrojs/solid-js"; 3 | import tailwind from "@astrojs/tailwind"; 4 | import peggy from "vite-plugin-peggy-loader"; 5 | import AstroPWA from "@vite-pwa/astro"; 6 | import starlight from "@astrojs/starlight"; 7 | import alpinejs from "@astrojs/alpinejs"; 8 | 9 | // https://astro.build/config 10 | export default defineConfig({ 11 | trailingSlash: "always", 12 | integrations: [ 13 | starlight({ 14 | title: "Sim8085 Docs", 15 | defaultLocale: "en", 16 | locales: { 17 | en: { 18 | label: "English", 19 | }, 20 | }, 21 | customCss: ["./src/tailwind.docs.css"], 22 | components: { 23 | Head: "./src/components/DocumentationHead.astro", 24 | // Footer: './src/components/DocumentationFooter.astro' 25 | }, 26 | social: { 27 | github: "https://github.com/debjitbis08/sim8085", 28 | }, 29 | disable404Route: true, 30 | sidebar: [ 31 | { slug: "docs/en" }, 32 | { slug: "docs/en/assembly" }, 33 | { slug: "docs/en/unsupported" }, 34 | { slug: "docs/en/interrupts" }, 35 | { slug: "docs/en/infinite-loop-reasons" }, 36 | { slug: "docs/en/app-install" }, 37 | /* 38 | { 39 | label: 'Instructions', 40 | items: [ 41 | 'docs/en/instructions/aci', 42 | 'docs/en/instructions/adc', 43 | ] 44 | }, 45 | */ 46 | { 47 | label: "References", 48 | items: ["docs/en/reference/ascii", "docs/en/reference/instruction-summary"], 49 | }, 50 | ], 51 | }), 52 | solidJs({ 53 | devtools: true, 54 | }), 55 | tailwind({ 56 | applyBaseStyles: false, 57 | }), 58 | alpinejs(), 59 | AstroPWA({ 60 | registerType: "autoUpdate", 61 | manifest: { 62 | name: "Sim8085", 63 | short_name: "Sim8085", 64 | theme_color: "#ffffff", 65 | }, 66 | includeAssets: ["favicon.svg", "favicon-dark.svg", "favicon.ico", "favicon-with-background.svg"], 67 | pwaAssets: { 68 | config: true, 69 | }, 70 | workbox: { 71 | globIgnores: [ 72 | "tips/**", // Exclude all files in the `tips` folder 73 | ], 74 | }, 75 | }), 76 | ], 77 | output: "static", 78 | vite: { 79 | plugins: [peggy()], 80 | ssr: { 81 | noExternal: ["nanoid"], 82 | }, 83 | }, 84 | }); 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sim8085", 3 | "description": "Fast, accurate, and offline-capable Intel 8085 simulator with assembler and debugger. Works on desktop and mobile, no installation needed.", 4 | "type": "module", 5 | "version": "2.15.4", 6 | "private": true, 7 | "license": "BSD-3-Clause", 8 | "author": "Debjit Biswas", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/debjitbis08/sim8085" 12 | }, 13 | "scripts": { 14 | "dev": "astro dev", 15 | "test:dev": "vitest", 16 | "test": "vitest run", 17 | "start": "astro dev", 18 | "build": "astro build && cp _redirects dist/", 19 | "preview": "astro preview", 20 | "astro": "astro", 21 | "build-emulator": "./scripts/build-emulator.sh" 22 | }, 23 | "dependencies": { 24 | "@astrojs/alpinejs": "^0.4.0", 25 | "@astrojs/solid-js": "^4.4.1", 26 | "@astrojs/tailwind": "^5.1.0", 27 | "@codemirror/commands": "^6.6.2", 28 | "@codemirror/language": "^6.10.3", 29 | "@codemirror/state": "^6.4.1", 30 | "@codemirror/view": "^6.33.0", 31 | "@kobalte/core": "^0.13.6", 32 | "@kobalte/tailwindcss": "^0.9.0", 33 | "@lezer/highlight": "^1.2.1", 34 | "@solid-primitives/keyboard": "^1.3.0", 35 | "@supabase/supabase-js": "^2.46.1", 36 | "@tanstack/solid-virtual": "^3.10.8", 37 | "@thumbmarkjs/thumbmarkjs": "^0.20.2", 38 | "alpinejs": "^3.14.1", 39 | "codemirror": "^6.0.1", 40 | "debounce": "^2.2.0", 41 | "posthog-js": "^1.239.1", 42 | "solid-devtools": "^0.30.1", 43 | "solid-icons": "^1.1.0", 44 | "solid-js": "^1.9.5", 45 | "tailwindcss": "^3.4.11", 46 | "uuid": "^11.0.1" 47 | }, 48 | "devDependencies": { 49 | "@astrojs/starlight": "^0.28.3", 50 | "@astrojs/starlight-tailwind": "^2.0.3", 51 | "@semantic-release/changelog": "^6.0.3", 52 | "@semantic-release/git": "^10.0.1", 53 | "@tailwindcss/typography": "^0.5.16", 54 | "@types/alpinejs": "^3.13.10", 55 | "@vite-pwa/assets-generator": "^0.2.6", 56 | "@vite-pwa/astro": "^0.4.3", 57 | "astro": "^4.16.6", 58 | "fast-check": "^3.22.0", 59 | "nanoid": "^5.0.7", 60 | "peggy": "^4.0.3", 61 | "semantic-release": "^24.2.0", 62 | "sharp": "^0.33.5", 63 | "vite-plugin-peggy-loader": "^2.0.1", 64 | "vitest": "^2.1.1", 65 | "workbox-window": "^7.1.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /public/FaSolidUser.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/favicon-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/favicon-with-background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 35 | 38 | 39 | 41 | 46 | 54 | 55 | 59 | 63 | 71 | 74 | 77 | 80 | 83 | 86 | 89 | 92 | 95 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/images/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/public/images/screen.png -------------------------------------------------------------------------------- /public/tips/Comments.html: -------------------------------------------------------------------------------- 1 |

Comment Syntax

2 | 3 |
4 |

5 | Comments always start with the semicolon character 6 | ;. Everything after this character on the same line 7 | is ignored by the assembler. 8 |

9 |
LABEL: MVI A, 0AH; LOAD 0AH INTO REGISTER A
12 |
13 | -------------------------------------------------------------------------------- /public/tips/Labels.html: -------------------------------------------------------------------------------- 1 |

Label Syntax

2 | 3 |
4 |

5 | Labels must consist of one to six alphanumeric characters, with the first character being either alphabetic or 6 | one of the special characters ? or 7 | @. Each label must be followed by a colon 8 | : to indicate that it is a label. There should be 9 | no space between the word and the 10 | : character. Additionally, a label can only be 11 | defined once in the program, and it must be unique. 12 |

13 |
LABEL: MVI A, 0AH; LOAD 0AH INTO REGISTER A
16 |
17 | -------------------------------------------------------------------------------- /public/tips/Number.html: -------------------------------------------------------------------------------- 1 |

Number Syntax

2 | 3 |
4 | 5 |
6 |

7 | Each hexadecimal number must begin with a numeric digit (0 through 9) and must be followed by the letter 8 | H. 9 |

10 |
HERE: MVI C, 0BAH   ; LOAD REG C WITH HEX BA
13 |
14 | 15 | 16 |
17 |

18 | Each decimal number may be identified by the letter 19 | D immediately after its last digit or may 20 | stand alone. Any number not specifically identified as hexadecimal, octal, or binary is assumed to be 21 | decimal. 22 |

23 |
ABC: MVI E, 15D   ; LOAD E WITH 15 IN DECIMAL
26 |
27 | 28 | 29 |
30 |

31 | Each octal number must be followed by the letter 32 | O or the letter 33 | Q. 34 |

35 |
LABEL: MVI A, 72Q   ; LOAD OCTAL 72 INTO ACCUM
38 |
39 | 40 | 41 |
42 |

43 | Each binary number must be followed by the letter 44 | B. 45 |

46 |
NOW: MVI D, 1111011OB   ; LOAD REGISTER D WITH 0F6H
49 |
50 |
51 | -------------------------------------------------------------------------------- /pwa-assets.config.js: -------------------------------------------------------------------------------- 1 | import { 2 | createAppleSplashScreens, 3 | defineConfig, 4 | minimal2023Preset as preset, 5 | } from '@vite-pwa/assets-generator/config'; 6 | 7 | export default defineConfig({ 8 | preset, 9 | images: [ 10 | 'public/favicon-with-background.svg', 11 | ] 12 | }); 13 | -------------------------------------------------------------------------------- /release.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('semantic-release').GlobalConfig} 3 | */ 4 | export default { 5 | branches: ["master"], 6 | plugins: [ 7 | "@semantic-release/commit-analyzer", 8 | "@semantic-release/release-notes-generator", 9 | [ 10 | "@semantic-release/changelog", 11 | { 12 | changelogFile: "CHANGELOG.md", 13 | }, 14 | ], 15 | "@semantic-release/npm", // Updates package.json version 16 | [ 17 | "@semantic-release/git", 18 | { 19 | assets: ["package.json", "package-lock.json", "CHANGELOG.md"], 20 | message: "release: Release ${nextRelease.version} [skip actions]\n\n${nextRelease.notes}", 21 | }, 22 | ], 23 | "@semantic-release/github", // Creates GitHub release 24 | ], 25 | }; -------------------------------------------------------------------------------- /scripts/build-emulator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set +x 4 | 5 | emcc \ 6 | src/core/8085.c \ 7 | -o src/core/8085.js \ 8 | -Oz --closure 0 \ 9 | -s EXPORTED_FUNCTIONS="['_malloc', '_free','_Init8085', '_getMemory', '_getIO', '_ExecuteProgram', '_ExecuteProgramSlice', '_Emulate8085Op', '_LoadProgram', '_UnloadProgram', '_ExecuteProgramUntil', '_set_timing_enabled', '_InterruptToHalt', '_triggerInterrupt']" \ 10 | -s EXPORTED_RUNTIME_METHODS="['ccall', 'cwrap', 'getValue', 'setValue']" \ 11 | -s NO_EXIT_RUNTIME=1 \ 12 | -s NO_FILESYSTEM=1 \ 13 | -s MODULARIZE=1 \ 14 | -s WASM=0 \ 15 | -s EXPORT_ES6=1 \ 16 | -s ENVIRONMENT="web,worker" \ 17 | --js-library src/core/simulator-library.js 18 | 19 | # --memory-init-file 0 \ 20 | -------------------------------------------------------------------------------- /src/components/ActionButton.jsx: -------------------------------------------------------------------------------- 1 | import { Tooltip } from "./generic/Tooltip.jsx"; 2 | 3 | export default function ActionButton(props) { 4 | return ( 5 | 6 | 11 |
{props.icon}
12 |
13 | 14 | 15 | 16 |
17 |

{props.title}

18 | {props.shortcut ? ( 19 | {props.shortcut} 20 | ) : null} 21 |
22 |
23 |
24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/components/ActionsLoader.jsx: -------------------------------------------------------------------------------- 1 | import { VsLoading } from "solid-icons/vs"; 2 | import { createSignal, lazy, Suspense, onMount } from "solid-js"; 3 | import DelayedComponent from "./generic/DelayedComponent.jsx"; 4 | 5 | const Actions = lazy(() => import("./Actions.jsx")); 6 | 7 | export default function ActionsLoader() { 8 | return ( 9 | import("./Actions.jsx")} 12 | fallback={ 13 |
14 | 15 | Loading Action Buttons... 16 |
17 | } 18 | /> 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/components/App.jsx: -------------------------------------------------------------------------------- 1 | import { Registers } from "./Registers"; 2 | import { createStore } from "solid-js/store"; 3 | import { StoreContext } from "./StoreContext"; 4 | import { Flags } from "./Flags"; 5 | import { MemoryGrid } from "./MemoryGrid"; 6 | import { CodeMirror } from "./codemirror/CodeMirror"; 7 | import { HiOutlineCpuChip } from "solid-icons/hi"; 8 | import { BsMemory } from "solid-icons/bs"; 9 | import Actions from "./Actions"; 10 | import MemoryList from "./MemoryList"; 11 | import { LeftPanel } from "./LeftPanel"; 12 | import "./styles.css"; 13 | import { createEffect } from "solid-js"; 14 | 15 | const INITIAL_CODE = ` 16 | ; 17 | 18 | jmp start 19 | 20 | ;data 21 | 22 | ;code 23 | start: nop 24 | 25 | 26 | hlt 27 | `; 28 | 29 | export function App() { 30 | return ( 31 | 32 |
33 |
34 | 35 |
36 |
37 |
38 |
39 |
40 |
41 | ASM 42 | main.asm 43 |
44 |
45 |
46 | 47 |
48 | 49 |
50 |
51 |
52 |
53 | $> 54 |
58 |
59 |
Coming Soon!
60 |
61 |
62 |
63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/components/Assembled.module.css: -------------------------------------------------------------------------------- 1 | .radiatingBorder { 2 | } 3 | 4 | .radiatingBorder::after { 5 | content: ''; 6 | position: absolute; 7 | top: 0; 8 | left: 0; 9 | width: 100%; 10 | height: 100%; 11 | border-radius: 50%; /* Creates the circular border */ 12 | border: 2px solid rgb(var(--sm-primary)); /* Replace with your terminal color */ 13 | animation: radiate 1.5s infinite; 14 | opacity: 0; 15 | } 16 | 17 | /* Keyframes for radiating effect */ 18 | @keyframes radiate { 19 | 0% { 20 | transform: scale(1); 21 | opacity: 1; 22 | } 23 | 100% { 24 | transform: scale(3); /* Expands outward */ 25 | opacity: 0; /* Fades out */ 26 | } 27 | } 28 | 29 | .machineCodeTable tr:nth-child(odd) { 30 | background-color: rgb(var(--sm-main-background)); 31 | } 32 | 33 | .machineCodeTable tr:nth-child(even) { 34 | background-color: rgb(var(--sm-secondary-background)); 35 | } 36 | 37 | .machineCodeTable thead tr { 38 | background-color: rgb(var(--sm-secondary-background)) !important; 39 | } 40 | 41 | .machineCodeTable tr:hover { 42 | background-color: rgb(var(--sm-active-background)); 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Card.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | title: string; 4 | body: string; 5 | href: string; 6 | } 7 | 8 | const { href, title, body } = Astro.props; 9 | --- 10 | 11 | 22 | 62 | -------------------------------------------------------------------------------- /src/components/CodeFormatterLoader.jsx: -------------------------------------------------------------------------------- 1 | import { VsLoading } from "solid-icons/vs"; 2 | import DelayedComponent from "./generic/DelayedComponent.jsx"; 3 | 4 | export default function CodeFormatterLoader() { 5 | return ( 6 | import("./CodeFormatter.jsx")} 9 | fallback={ 10 |
11 | 12 | Loading Code Formatter... 13 |
14 | } 15 | /> 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/CodingArea.jsx: -------------------------------------------------------------------------------- 1 | import { onMount } from "solid-js"; 2 | import { INITIAL_CODE, setStore, store } from "../store/store.js"; 3 | import { CodeMirror } from "./codemirror/CodeMirror.jsx"; 4 | 5 | export function CodingArea() { 6 | onMount(() => { 7 | const savedFileStr = localStorage.getItem("activeFile"); 8 | 9 | if (savedFileStr) { 10 | const savedFile = JSON.parse(savedFileStr); 11 | setStore("activeFile", savedFile); 12 | } else { 13 | const mainAsmCode = localStorage.getItem("main.asm"); 14 | setStore("activeFile", { 15 | name: "untitled-1.asm", 16 | workspaceItemId: null, 17 | content: mainAsmCode || INITIAL_CODE, 18 | }); 19 | localStorage.setItem("activeFile", JSON.stringify(store.activeFile)); 20 | if (mainAsmCode) { 21 | localStorage.removeItem("main.asm"); 22 | } 23 | } 24 | }); 25 | 26 | const handleContentChange = (newContent) => { 27 | setStore("activeFile", "content", newContent); 28 | if (store.assembled.length) { 29 | setStore("assembled", []); 30 | } 31 | localStorage.setItem("activeFile", JSON.stringify(store.activeFile)); 32 | }; 33 | 34 | return ( 35 |
36 | 37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/components/CopyComponent.jsx: -------------------------------------------------------------------------------- 1 | import { createSignal } from "solid-js"; 2 | import { VsCopy } from "solid-icons/vs"; 3 | import { FaSolidCheck } from "solid-icons/fa"; 4 | import { HiSolidCheckCircle } from "solid-icons/hi"; 5 | 6 | function CopyComponent(props) { 7 | const [copied, setCopied] = createSignal(false); 8 | 9 | const copyOutputAsText = () => { 10 | navigator.clipboard.writeText(props.getTextToCopy()) 11 | .then(() => { 12 | setCopied(true); // Set copied to true 13 | setTimeout(() => setCopied(false), 2000); 14 | }) 15 | .catch(err => console.error("Copy failed: ", err)); 16 | }; 17 | 18 | return ( 19 | 29 | ); 30 | } 31 | 32 | export default CopyComponent; 33 | -------------------------------------------------------------------------------- /src/components/DocumentationFooter.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { Props } from '@astrojs/starlight/props' 3 | import Default from '@astrojs/starlight/components/Footer.astro' 4 | import CookieBanner from './analytics/CookieBanner.astro' 5 | --- 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/DocumentationHead.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { Props } from '@astrojs/starlight/props'; 3 | import Default from '@astrojs/starlight/components/Head.astro'; 4 | import PostHogInit from './analytics/PostHogInit.astro'; 5 | --- 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/FAQs.astro: -------------------------------------------------------------------------------- 1 |
2 | FAQs 7 | 8 | 73 |
74 | -------------------------------------------------------------------------------- /src/components/Flags.jsx: -------------------------------------------------------------------------------- 1 | // import { useContext } from "solid-js"; 2 | // import { StoreContext } from "./StoreContext"; 3 | import { AiOutlineClear } from "solid-icons/ai"; 4 | import { produce } from "solid-js/store"; 5 | import { store, setStore } from "../store/store"; 6 | // import { setFlags } from "../core/simulator"; 7 | 8 | export function Flags() { 9 | let lazySetFlags; 10 | 11 | const callSetFlags = async () => { 12 | if (!lazySetFlags) { 13 | const module = await import("../core/simulator"); 14 | lazySetFlags = module.setFlags; 15 | } 16 | lazySetFlags(store); 17 | }; 18 | 19 | const updateFlag = (flagId, isChecked) => { 20 | setStore("flags", flagId, isChecked); 21 | callSetFlags(store); 22 | }; 23 | 24 | const clearFlags = () => { 25 | setStore( 26 | "flags", 27 | produce((flags) => { 28 | Object.keys(flags).forEach((flagId) => (flags[flagId] = false)); 29 | }), 30 | ); 31 | callSetFlags(store); 32 | }; 33 | 34 | return ( 35 |
36 |
37 |

Flags

38 | 41 |
42 |
43 | {Object.keys(store.flags).map((flagId) => ( 44 | updateFlag(flagId, isChecked)} 49 | /> 50 | ))} 51 |
52 |
53 | ); 54 | } 55 | 56 | function Flag(props) { 57 | const id = `flag-${props.id}`; 58 | return ( 59 |
60 | 75 |
76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /src/components/FolderNode.jsx: -------------------------------------------------------------------------------- 1 | import {createSignal} from "solid-js"; 2 | import {supabase} from "../lib/supabase.js"; 3 | import {FileNode} from "./FileNode"; 4 | import {FaRegularFolder, FaRegularFolderClosed, FaRegularFolderOpen} from "solid-icons/fa"; 5 | import {WorkspaceTree} from "./WorkspaceTree.jsx"; 6 | import {store} from "../store/store.js"; 7 | 8 | export function FolderNode(props) { 9 | const [children, setChildren] = createSignal([]); 10 | const [expanded, setExpanded] = createSignal(false); 11 | const [loading, setLoading] = createSignal(false); 12 | 13 | async function fetchFolderContents(folderId) { 14 | try { 15 | setLoading(true); 16 | 17 | const {data, error} = await supabase 18 | .from("workspace_items") 19 | .select("id, name, parent_folder_id, status_id, created_at, updated_at") 20 | .eq("parent_folder_id", folderId); 21 | 22 | if (error) throw error; 23 | 24 | setChildren(data); 25 | } catch (err) { 26 | console.error("Error fetching folder contents:", err.message); 27 | } finally { 28 | setLoading(false); 29 | } 30 | } 31 | 32 | function toggleExpand() { 33 | if (!expanded()) { 34 | fetchFolderContents(props.folder.id); 35 | } 36 | setExpanded(!expanded()); 37 | } 38 | 39 | return ( 40 |
41 |
43 | 44 | {expanded() ? ( 45 | 46 | ) : ( 47 | 48 | )} 49 | 50 | {props.folder.name} 51 |
52 | {expanded() && ( 53 |
54 | {loading() &&

Loading...

} 55 | 56 |
57 | )} 58 |
59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /src/components/InterruptState.jsx: -------------------------------------------------------------------------------- 1 | import { store } from "../store/store"; 2 | import { FaSolidCircle } from "solid-icons/fa"; 3 | 4 | export default function InterruptState() { 5 | const statusColor = () => 6 | store.interruptsEnabled 7 | ? "text-green-foreground bg-terminal-100 border-terminal-300" 8 | : "text-red-foreground bg-red-100 border-red-300"; 9 | 10 | return ( 11 |
12 |
13 |

Interrupts

14 | 15 |
16 | 17 |
18 |
19 |
20 | 21 | 26 | 31 | 36 |
37 |
38 | ); 39 | } 40 | 41 | function Item(props) { 42 | const statusText = () => (props.status ? "Enabled" : "Disabled"); 43 | const statusColor = () => 44 | props.status 45 | ? "text-green-foreground bg-terminal-100 border-terminal-300" 46 | : "text-red-foreground bg-red-100 border-red-300"; 47 | 48 | return ( 49 |
50 |
{props.name}
51 |
52 | {props.pending && ( 53 | Pending 54 | )} 55 | 56 |
57 |
58 | ); 59 | } 60 | 61 | function Indicator(props) { 62 | return ( 63 | 64 | 65 | 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /src/components/KeyboardShortcuts.module.css: -------------------------------------------------------------------------------- 1 | .sectionHeader { 2 | @apply text-lg font-bold mt-6 mb-3 pb-1 border-b border-gray-300; 3 | @apply text-gray-500; 4 | @apply dark:border-gray-700 dark:text-gray-500; 5 | } 6 | 7 | .shortcutsGrid { 8 | @apply grid grid-cols-2 gap-y-3 text-sm; 9 | } 10 | 11 | .keyCombination { 12 | @apply text-right space-x-1 text-gray-900 dark:text-gray-200; 13 | } 14 | 15 | .key { 16 | @apply inline-block px-2 py-1 rounded bg-gray-200 text-gray-900 dark:bg-gray-600 dark:text-gray-200; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/LambdaClassesPoster.jsx: -------------------------------------------------------------------------------- 1 | import LambdaClassesPoster from "../images/lambda-classes-poster.png"; 2 | import ProjectTrackPoster from "../images/project-track-poster.jpg"; 3 | import { createEffect, createSignal, onMount } from "solid-js"; 4 | 5 | export default function lambdaClassesPoster(props) { 6 | let lambdaClassesPoster = null; 7 | let [isHidden, setIsHidden] = createSignal(true); 8 | 9 | const hidePoster = () => { 10 | localStorage.setItem("lambda-classes-poster-hide", "true"); 11 | setIsHidden(true); 12 | if (window.posthog) { 13 | posthog.capture("lc dismissed"); 14 | } 15 | }; 16 | 17 | const onClickthrough = () => { 18 | localStorage.setItem("lambda-classes-poster-hide", "true"); 19 | setIsHidden(true); 20 | if (window.posthog) { 21 | posthog.capture("lc clickthrough"); 22 | } 23 | }; 24 | 25 | onMount(() => { 26 | const value = localStorage.getItem("lambda-classes-poster-hide"); 27 | if (!value || value === "false") { 28 | console.log("Showing poster"); 29 | setIsHidden(false); 30 | } 31 | }); 32 | 33 | createEffect(() => { 34 | const value = localStorage.getItem("lambda-classes-poster-hide"); 35 | if (props.isHidden || value === "true") { 36 | console.log("Hiding Poster"); 37 | setIsHidden(true); 38 | } else { 39 | setIsHidden(false); 40 | } 41 | }); 42 | 43 | return ( 44 |
(lambdaClassesPoster = el)}> 45 | 46 | 47 | 48 |
53 | × 54 |
55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/components/LeftPanel.css: -------------------------------------------------------------------------------- 1 | .scroll-shadow { 2 | --bg-color: rgb(32, 35, 39); 3 | --bg-color-transparent: rgba(32, 35, 39, 1); 4 | --shadow-color: rgba(0, 0, 0, 0.6); 5 | --shadow-fade: rgba(0, 0, 0, 0); 6 | 7 | background: 8 | /* Shadow Cover TOP */ 9 | linear-gradient(var(--bg-color) 30%, var(--bg-color-transparent)) center top, 10 | /* Shadow Cover BOTTOM */ linear-gradient(var(--bg-color-transparent), var(--bg-color) 70%) center bottom, 11 | /* Shadow TOP */ radial-gradient(farthest-side at 50% 0, var(--shadow-color), var(--shadow-fade)) center top, 12 | /* Shadow BOTTOM */ radial-gradient(farthest-side at 50% 100%, var(--shadow-color), var(--shadow-fade)) center 13 | bottom rgb(var(--sm-secondary-background)); 14 | 15 | background-repeat: no-repeat; 16 | background-size: 17 | 100% 40px, 18 | 100% 40px, 19 | 100% 14px, 20 | 100% 14px; 21 | background-attachment: local, local, scroll, scroll; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/StoreContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "solid-js" 2 | 3 | export const StoreContext = createContext(); 4 | -------------------------------------------------------------------------------- /src/components/Terminal.jsx: -------------------------------------------------------------------------------- 1 | export function Terminal() { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/components/TextToolip.module.css: -------------------------------------------------------------------------------- 1 | .tooltipContent { 2 | @apply border; 3 | @apply rounded-sm; 4 | @apply border-gray-400; 5 | @apply dark:border-gray-600; 6 | @apply p-2; 7 | @apply bg-gray-400; 8 | @apply dark:bg-gray-800; 9 | @apply dark:text-gray-200; 10 | @apply text-sm; 11 | @apply z-50; 12 | max-width: min(calc(100vw - 16px), 380px); 13 | box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); 14 | animation: contentHide 150ms ease-in forwards; 15 | } 16 | 17 | .tooltipContent[data-expanded] { 18 | animation: contentShow 150ms ease-out; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/TextTooltip.jsx: -------------------------------------------------------------------------------- 1 | import { Tooltip } from "@kobalte/core/tooltip"; 2 | import styles from "./TextToolip.module.css"; 3 | 4 | export function TextTooltip(props) { 5 | return ( 6 | 7 | {props.children} 8 | 9 | 10 | 11 |

{props.message}

12 |
13 |
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/analytics/CookieBanner.astro: -------------------------------------------------------------------------------- 1 | 18 | 19 | 49 | 57 | -------------------------------------------------------------------------------- /src/components/analytics/PostHogInit.astro: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /src/components/analytics/PostHogInitialize.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { useTracking, apiKey } = Astro.props; 3 | --- 4 | {useTracking && ( 5 | 14 | )} 15 | -------------------------------------------------------------------------------- /src/components/analytics/identify.js: -------------------------------------------------------------------------------- 1 | import { getSession } from "../../lib/supabase.js"; 2 | 3 | export async function identifyUser() { 4 | const cookieConsent = localStorage.getItem("cookie_consent"); 5 | 6 | console.log("cookieConsent", cookieConsent); 7 | 8 | const { session, error } = await getSession(); 9 | 10 | if (!error && session?.user) { 11 | const name = session.user?.user_metadata?.name || ""; 12 | 13 | posthog.identify(session.user.id, { 14 | email: session.user.email, 15 | name, 16 | }); 17 | } else if (cookieConsent === "no") { 18 | const { getFingerprint } = await import("@thumbmarkjs/thumbmarkjs"); 19 | const fingerprint = await getFingerprint(); 20 | 21 | posthog.identify(fingerprint, { 22 | anon: true, 23 | }); 24 | } else if (cookieConsent === "yes") { 25 | let id = localStorage.getItem("anon_id"); 26 | 27 | if (!id) { 28 | const bytes = new Uint8Array(16); 29 | crypto.getRandomValues(bytes); 30 | id = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join(""); 31 | localStorage.setItem("anon_id", id); 32 | } 33 | 34 | posthog.identify(id, { 35 | anon: true, 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/analytics/tracker.js: -------------------------------------------------------------------------------- 1 | import posthog from 'posthog-js'; 2 | 3 | export function trackEvent(eventName, data) { 4 | posthog.capture(eventName, data); 5 | } 6 | -------------------------------------------------------------------------------- /src/components/codemirror/CodeMirror.css: -------------------------------------------------------------------------------- 1 | .cm-editor { 2 | height: 100%; 3 | } 4 | 5 | .cm-line.cm-activeLine { 6 | background-color: rgba(var(--sm-editor-active-line), 0.3); 7 | } 8 | 9 | .cm-debug-line-highlight, 10 | .cm-line.cm-activeLine.cm-debug-line-highlight { 11 | background-color: rgb(var(--sm-editor-debug-line)); 12 | } 13 | 14 | .cm-editor .cm-gutters { 15 | @apply border-0; 16 | background-color: rgb(var(--sm-main-background)); 17 | } 18 | 19 | .cm-editor .cm-activeLineGutter { 20 | background-color: rgb(var(--sm-secondary-background)); 21 | } 22 | 23 | .cm-editor .cm-cursor { 24 | border-left: 1px solid rgb(var(--sm-editor-cursor)); 25 | } 26 | 27 | .cm-editor .cm-content { 28 | font-size: 0.85rem; 29 | } 30 | 31 | .cm-editor > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, 32 | .cm-editor.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground { 33 | background-color: rgb(var(--sm-editor-selection-background)); 34 | } 35 | 36 | .cm-editor.cm-focused { 37 | outline: none; 38 | } 39 | -------------------------------------------------------------------------------- /src/components/generic/DelayedComponent.jsx: -------------------------------------------------------------------------------- 1 | import { VsLoading } from "solid-icons/vs"; 2 | import { createSignal, lazy, Suspense, onMount } from "solid-js"; 3 | 4 | export default function DelayedComponent(props) { 5 | const [isReady, setIsReady] = createSignal(false); 6 | 7 | const Comp = lazy(props.fn); 8 | 9 | onMount(() => { 10 | setTimeout(() => { 11 | setIsReady(true); 12 | }, props.delayInMs); 13 | }); 14 | 15 | return ( 16 |
17 | {isReady() ? ( 18 | 19 | 20 | 21 | ) : ( 22 | props.fallback 23 | )} 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/components/generic/Dialog.css: -------------------------------------------------------------------------------- 1 | .dialog__overlay { 2 | @apply fixed; 3 | @apply inset-0; 4 | @apply z-50; 5 | @apply bg-gray-600; 6 | @apply bg-opacity-20; 7 | @apply backdrop-blur-sm; 8 | animation: overlayHide 250ms ease 100ms forwards; 9 | } 10 | 11 | .dialog__overlay[data-expanded] { 12 | animation: overlayShow 250ms ease; 13 | } 14 | 15 | .dialog__positioner { 16 | @apply fixed; 17 | @apply inset-0; 18 | @apply z-50; 19 | @apply flex; 20 | @apply items-center; 21 | @apply justify-center; 22 | } 23 | 24 | .dialog__content { 25 | @apply z-50; 26 | @apply min-w-full md:min-w-[500px] min-h-svh md:min-h-max; 27 | background-color: rgb(var(--sm-secondary-background)); 28 | border: 1px solid hsl(240 5% 84%); 29 | border-radius: 6px; 30 | padding: 16px; 31 | box-shadow: 32 | 0 10px 15px -3px rgb(0 0 0 / 0.1), 33 | 0 4px 6px -4px rgb(0 0 0 / 0.1); 34 | animation: contentHide 300ms ease-in forwards; 35 | } 36 | 37 | .dialog__content[data-expanded] { 38 | animation: contentShow 300ms ease-out; 39 | } 40 | 41 | .dialog__header { 42 | @apply flex; 43 | @apply items-baseline; 44 | @apply justify-between; 45 | @apply mb-4; 46 | } 47 | 48 | .dialog__close-button { 49 | height: 16px; 50 | width: 16px; 51 | } 52 | 53 | .dialog__title { 54 | @apply text-lg; 55 | @apply font-semibold; 56 | color: rgb(var(--sm-secondary-foreground)); 57 | } 58 | 59 | .dialog__description { 60 | } 61 | 62 | @keyframes overlayShow { 63 | from { 64 | opacity: 0; 65 | } 66 | to { 67 | opacity: 1; 68 | } 69 | } 70 | @keyframes overlayHide { 71 | from { 72 | opacity: 1; 73 | } 74 | to { 75 | opacity: 0; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/components/generic/Dialog.jsx: -------------------------------------------------------------------------------- 1 | export { Dialog } from "@kobalte/core/dialog"; 2 | import "./Dialog.css"; 3 | -------------------------------------------------------------------------------- /src/components/generic/DropdownMenu.jsx: -------------------------------------------------------------------------------- 1 | export { DropdownMenu } from "@kobalte/core/dropdown-menu"; 2 | import "./DropdownMenu.css"; 3 | -------------------------------------------------------------------------------- /src/components/generic/Select.css: -------------------------------------------------------------------------------- 1 | .select__trigger { 2 | transition: 3 | border-color 250ms, 4 | color 250ms; 5 | color: rgb(var(--sm-active-foreground)); 6 | background-color: rgb(var(--sm-page-background)); 7 | border-color: rgb(var(--sm-active-border)); 8 | @apply flex items-center gap-2 justify-between rounded py-3 px-2 text-sm h-5 outline-none border; 9 | } 10 | .select__trigger:hover { 11 | border-color: hsl(240 5% 65%); 12 | } 13 | .select__trigger:focus-visible { 14 | outline: 2px solid hsl(200 98% 39%); 15 | outline-offset: 2px; 16 | } 17 | .select__trigger[data-invalid] { 18 | border-color: hsl(0 72% 51%); 19 | color: hsl(0 72% 51%); 20 | } 21 | .select__value { 22 | text-overflow: ellipsis; 23 | white-space: nowrap; 24 | overflow: hidden; 25 | } 26 | .select__value[data-placeholder-shown] { 27 | color: hsl(240 4% 46%); 28 | } 29 | 30 | .select__icon { 31 | } 32 | 33 | .select__description { 34 | margin-top: 8px; 35 | color: hsl(240 5% 26%); 36 | font-size: 12px; 37 | user-select: none; 38 | } 39 | .select__error-message { 40 | margin-top: 8px; 41 | color: hsl(0 72% 51%); 42 | font-size: 12px; 43 | user-select: none; 44 | } 45 | .select__content { 46 | border-radius: 6px; 47 | border: 1px solid hsl(240 6% 90%); 48 | box-shadow: 49 | 0 4px 6px -1px rgb(0 0 0 / 0.1), 50 | 0 2px 4px -2px rgb(0 0 0 / 0.1); 51 | transform-origin: var(--kb-select-content-transform-origin); 52 | animation: contentHide 250ms ease-in forwards; 53 | } 54 | .select__content[data-expanded] { 55 | animation: contentShow 250ms ease-out; 56 | } 57 | .select__listbox { 58 | overflow-y: auto; 59 | max-height: 360px; 60 | padding: 8px; 61 | } 62 | .select__item { 63 | font-size: 16px; 64 | line-height: 1; 65 | border-radius: 4px; 66 | display: flex; 67 | align-items: center; 68 | justify-content: space-between; 69 | height: 32px; 70 | padding: 0 8px; 71 | position: relative; 72 | user-select: none; 73 | outline: none; 74 | } 75 | .select__item[data-disabled] { 76 | color: hsl(240 5% 65%); 77 | opacity: 0.5; 78 | pointer-events: none; 79 | } 80 | .select__item[data-highlighted] { 81 | outline: none; 82 | background-color: hsl(200 98% 39%); 83 | color: white; 84 | } 85 | .select__section { 86 | padding: 8px 0 0 8px; 87 | font-size: 14px; 88 | line-height: 32px; 89 | color: hsl(240 4% 46%); 90 | } 91 | .select__item-indicator { 92 | height: 20px; 93 | width: 20px; 94 | display: inline-flex; 95 | align-items: center; 96 | justify-content: center; 97 | } 98 | -------------------------------------------------------------------------------- /src/components/generic/Select.jsx: -------------------------------------------------------------------------------- 1 | export { Select } from "@kobalte/core/select"; 2 | import "./Select.css"; 3 | -------------------------------------------------------------------------------- /src/components/generic/Switch.css: -------------------------------------------------------------------------------- 1 | .switch { 2 | display: inline-flex; 3 | align-items: center; 4 | } 5 | 6 | .switch__control { 7 | display: inline-flex; 8 | align-items: center; 9 | height: 24px; 10 | width: 44px; 11 | border: 1px solid rgb(var(--sm-gray-200)); 12 | border-radius: 12px; 13 | padding: 0 2px; 14 | background-color: rgb(var(--sm-gray-300)); 15 | transition: 250ms background-color; 16 | @apply cursor-pointer; 17 | } 18 | 19 | .switch__input:focus-visible + .switch__control { 20 | outline: 2px solid hsl(200 98% 39%); 21 | outline-offset: 2px; 22 | } 23 | 24 | .switch__control[data-checked] { 25 | @apply border; 26 | border-color: rgb(var(--sm-primary)); 27 | background-color: rgb(var(--sm-primary)); 28 | } 29 | 30 | .switch__thumb { 31 | height: 20px; 32 | width: 20px; 33 | border-radius: 10px; 34 | background-color: white; 35 | transition: 250ms transform; 36 | box-shadow: 0 2px 5px rgb(var(--sm-gray-600)); 37 | } 38 | 39 | .switch__thumb[data-checked] { 40 | transform: translateX(calc(100% - 1px)); 41 | } 42 | 43 | .switch__label { 44 | margin-right: 6px; 45 | font-size: 14px; 46 | user-select: none; 47 | } 48 | -------------------------------------------------------------------------------- /src/components/generic/Switch.jsx: -------------------------------------------------------------------------------- 1 | export { Switch } from "@kobalte/core/switch"; 2 | import "./Switch.css"; 3 | -------------------------------------------------------------------------------- /src/components/generic/Tabs.css: -------------------------------------------------------------------------------- 1 | .tabs { 2 | width: 100%; 3 | } 4 | .tabs[data-orientation="vertical"] { 5 | display: flex; 6 | } 7 | .tabs__list { 8 | position: relative; 9 | display: flex; 10 | } 11 | .tabs__list[data-orientation="horizontal"] { 12 | align-items: center; 13 | border-bottom-color: rgb(var(--sm-inactive-border)); 14 | @apply border-b; 15 | } 16 | .tabs__list[data-orientation="vertical"] { 17 | flex-direction: column; 18 | align-items: stretch; 19 | border-right-color: rgb(var(--sm-inactive-border)); 20 | @apply border-r; 21 | } 22 | .tabs__indicator { 23 | transition: all 250ms; 24 | background-color: rgb(var(--sm-primary)); 25 | @apply absolute; 26 | } 27 | .tabs__indicator[data-orientation="horizontal"] { 28 | bottom: -1px; 29 | height: 2px; 30 | } 31 | .tabs__indicator[data-orientation="vertical"] { 32 | right: -1px; 33 | width: 2px; 34 | } 35 | .tabs__trigger { 36 | display: inline-block; 37 | padding: 8px 16px; 38 | outline: none; 39 | } 40 | .tabs__trigger:hover { 41 | color: rgb(var(--sm-active-foreground)); 42 | background-color: rgb(var(--sm-active-background)); 43 | } 44 | .tabs__trigger:focus-visible { 45 | background-color: hsl(240 5% 96%); 46 | } 47 | .tabs__trigger[data-disabled], 48 | .tabs__trigger[data-disabled]:hover { 49 | opacity: 0.5; 50 | background-color: transparent; 51 | } 52 | -------------------------------------------------------------------------------- /src/components/generic/Tabs.jsx: -------------------------------------------------------------------------------- 1 | export { Tabs } from "@kobalte/core/tabs"; 2 | import "./Tabs.css"; 3 | -------------------------------------------------------------------------------- /src/components/generic/Toast.css: -------------------------------------------------------------------------------- 1 | .toast__list { 2 | --viewport-padding: 16px; 3 | position: fixed; 4 | bottom: 0; 5 | right: 0; 6 | display: flex; 7 | flex-direction: column; 8 | padding: var(--viewport-padding); 9 | gap: 8px; 10 | width: 400px; 11 | max-width: 100vw; 12 | margin: 0; 13 | list-style: none; 14 | z-index: 9999; 15 | outline: none; 16 | } 17 | 18 | .toast { 19 | box-shadow: 20 | 0 10px 15px -3px rgb(0 0 0 / 0.1), 21 | 0 4px 6px -4px rgb(0 0 0 / 0.1); 22 | } 23 | 24 | .toast[data-opened] { 25 | animation: slideIn 150ms cubic-bezier(0.16, 1, 0.3, 1); 26 | } 27 | 28 | .toast[data-closed] { 29 | animation: hide 100ms ease-in; 30 | } 31 | 32 | .toast[data-swipe="move"] { 33 | transform: translateX(var(--kb-toast-swipe-move-x)); 34 | } 35 | 36 | .toast[data-swipe="cancel"] { 37 | transform: translateX(0); 38 | transition: transform 200ms ease-out; 39 | } 40 | 41 | .toast[data-swipe="end"] { 42 | animation: swipeOut 100ms ease-out; 43 | } 44 | 45 | .toast__progress-fill { 46 | width: var(--kb-toast-progress-fill-width); 47 | transition: width 250ms linear; 48 | } 49 | 50 | @keyframes hide { 51 | from { 52 | opacity: 1; 53 | } 54 | to { 55 | opacity: 0; 56 | } 57 | } 58 | 59 | @keyframes slideIn { 60 | from { 61 | transform: translateX(calc(100% + var(--viewport-padding))); 62 | } 63 | to { 64 | transform: translateX(0); 65 | } 66 | } 67 | 68 | @keyframes swipeOut { 69 | from { 70 | transform: translateX(var(--kb-toast-swipe-end-x)); 71 | } 72 | to { 73 | transform: translateX(calc(100% + var(--viewport-padding))); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/components/generic/Toast.jsx: -------------------------------------------------------------------------------- 1 | export { Toast, toaster } from "@kobalte/core/toast"; 2 | import "./Toast.css"; 3 | -------------------------------------------------------------------------------- /src/components/generic/Tooltip.css: -------------------------------------------------------------------------------- 1 | @keyframes contentShow { 2 | from { 3 | opacity: 0; 4 | transform: scale(0.96); 5 | } 6 | to { 7 | opacity: 1; 8 | transform: scale(1); 9 | } 10 | } 11 | @keyframes contentHide { 12 | from { 13 | opacity: 1; 14 | transform: scale(1); 15 | } 16 | to { 17 | opacity: 0; 18 | transform: scale(0.96); 19 | } 20 | } 21 | 22 | .tooltip__content { 23 | @apply border; 24 | @apply rounded-sm; 25 | @apply p-2; 26 | @apply text-sm; 27 | @apply z-50; 28 | background-color: rgb(var(--sm-main-background)); 29 | border-color: rgb(var(--sm-main-border)); 30 | max-width: min(calc(100vw - 16px), 380px); 31 | box-shadow: 32 | 0 10px 15px -3px rgb(0 0 0 / 0.1), 33 | 0 4px 6px -4px rgb(0 0 0 / 0.1); 34 | animation: contentHide 150ms ease-in forwards; 35 | } 36 | 37 | .tooltip__content[data-expanded] { 38 | animation: contentShow 150ms ease-out; 39 | } 40 | -------------------------------------------------------------------------------- /src/components/generic/Tooltip.jsx: -------------------------------------------------------------------------------- 1 | export { Tooltip } from "@kobalte/core/tooltip"; 2 | import "./Tooltip.css"; 3 | -------------------------------------------------------------------------------- /src/components/generic/wrapWithClass.js: -------------------------------------------------------------------------------- 1 | export function withDefaultClass(Component, defaultClass) { 2 | return (props) => ; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/pwa.js: -------------------------------------------------------------------------------- 1 | import { registerSW } from 'virtual:pwa-register' 2 | 3 | window.addEventListener('load', () => { 4 | const pwaToast = document.querySelector('#pwa-toast') 5 | const pwaToastMessage = pwaToast.querySelector('.message #toast-message') 6 | const pwaCloseBtn = pwaToast.querySelector('#pwa-close') 7 | const pwaRefreshBtn = pwaToast.querySelector('#pwa-refresh') 8 | 9 | let refreshSW; // : ((reloadPage?: boolean) => Promise) | undefined 10 | 11 | const refreshCallback = () => refreshSW?.(true) 12 | 13 | const hidePwaToast = (raf = false) => { 14 | if (raf) { 15 | requestAnimationFrame(() => hidePwaToast(false)) 16 | return 17 | } 18 | if (pwaToast.classList.contains('refresh')) 19 | pwaRefreshBtn.removeEventListener('click', refreshCallback) 20 | 21 | pwaToast.classList.remove('show', 'refresh') 22 | } 23 | const showPwaToast = (offline) => { 24 | if (!offline) 25 | pwaRefreshBtn.addEventListener('click', refreshCallback) 26 | requestAnimationFrame(() => { 27 | hidePwaToast(false) 28 | if (!offline) 29 | pwaToast.classList.add('refresh') 30 | pwaToast.classList.add('show') 31 | }) 32 | } 33 | 34 | pwaCloseBtn.addEventListener('click', () => hidePwaToast(true)) 35 | 36 | refreshSW = registerSW({ 37 | immediate: true, 38 | onOfflineReady() { 39 | pwaToastMessage.innerHTML = 'App ready to work offline' 40 | showPwaToast(true) 41 | }, 42 | onNeedRefresh() { 43 | pwaToastMessage.innerHTML = 'New content available, click on reload button to update' 44 | showPwaToast(false) 45 | }, 46 | onRegisteredSW(swScriptUrl) { 47 | console.log('SW registered: ', swScriptUrl) 48 | } 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /src/components/styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/components/styles.css -------------------------------------------------------------------------------- /src/components/toaster.jsx: -------------------------------------------------------------------------------- 1 | import { Toast, toaster } from "./generic/Toast.jsx"; 2 | import { VsClose } from "solid-icons/vs"; 3 | import { FaSolidCircleCheck, FaSolidCircleInfo, FaSolidCircleXmark } from "solid-icons/fa"; 4 | 5 | export function showToaster(type, title, message) { 6 | toaster.show((props) => { 7 | const color = type === "info" ? "blue" : type === "success" ? "green" : type === "error" ? "red" : "blue"; 8 | return ( 9 | 13 |
14 |
15 | 16 | {type === "success" ? ( 17 | 18 | ) : type === "info" ? ( 19 | 20 | ) : type === "error" ? ( 21 | 22 | ) : ( 23 | 24 | )} 25 | 26 | {title} 27 | 28 | 29 | 30 |
31 | {message} 32 | 33 | 44 | 45 |
46 |
47 | ); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /src/content/changelog/2024-11-11.md: -------------------------------------------------------------------------------- 1 | --- 2 | versions: 3 | - "2.1.0" 4 | date: "2024-11-11" 5 | --- 6 | 7 | ### Keyboard shortcuts are here! 8 | 9 | ![Keyboard Shortcuts](./images/keyboard-shortcuts-2024-11-11.png) 10 | 11 | Use your keyboard to assemble, run, and control other parts of the application. 12 | 13 | ### Add documentation page 14 | 15 | ![Documentation Section](./images/doc-section-2024-11-11.png) 16 | 17 | Sim8085 now has a [documentation section](/docs/en). Documents about Sim8085 and 18 | 8085 microprocessor in general will be available in this section. 19 | 20 | ### Support for END Directive 21 | 22 | The `END` directive is used for marking the end of assembling process 23 | and to set the starting address to start execution. [Read the docs](/docs/en/directives/end). 24 | -------------------------------------------------------------------------------- /src/content/changelog/2024-11-16.md: -------------------------------------------------------------------------------- 1 | --- 2 | versions: 3 | - "2.2.0" 4 | - "2.2.1" 5 | - "2.3.0" 6 | - "2.3.1" 7 | - "2.3.2" 8 | - "2.3.3" 9 | date: "2024-11-16" 10 | --- 11 | 12 | ### Mobile & Offline Support Has Arrived! 13 | 14 | Sim8085 is now more accessible than ever! Use it seamlessly on your mobile device or desktop, whether you're online or offline. 15 | 16 | ![Mobile Layout](./images/sim8085-mobile-layout-2024-11-16.jpeg) 17 | 18 | ### What’s New? 19 | 20 | - **Mobile-Friendly**: Run Sim8085 on your phone to quickly try out programs, learn, and experiment with 8085 assembly programming—anytime, anywhere. 21 | - **Offline Access**: Enjoy Sim8085 even without an internet connection. Yes, you could technically debug an assembly program on a plane (not that you’d want to… or would you?). 22 | - **Installable as an App**: Add Sim8085 to your home screen or desktop and use it like a native app for a smoother experience. 23 | 24 | ### How to Install 25 | 26 | - **On iOS**: 27 | 1. Open [https://www.sim8085.com](https://www.sim8085.com) in Safari. 28 | 2. Tap the **Share** icon and select **Add to Home Screen**. 29 | 30 | ![Install on iOS](./images/ios-sim8085-install-2024-11-16.jpeg) 31 | 32 | - **On Android**: 33 | 1. Open [https://www.sim8085.com](https://www.sim8085.com) in your browser. 34 | 2. Tap the menu (three dots) and select **Add to Home Screen** or **Install App**. 35 | 36 | ![Install on Android](./images/android-sim8085-install-2024-11-16.jpeg) 37 | 38 | - **On Desktop**: 39 | 1. Open [https://www.sim8085.com](https://www.sim8085.com). 40 | 2. Look for the **Install** option in your browser's URL bar. 41 | 42 | For detailed instructions, check out Mozilla’s [Installing and Uninstalling Web Apps](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Installing) guide. 43 | -------------------------------------------------------------------------------- /src/content/changelog/2025-04-24.md: -------------------------------------------------------------------------------- 1 | --- 2 | versions: 3 | - "2.8.0" 4 | date: "2025-04-24" 5 | --- 6 | 7 | ### Added support for multiple labels 8 | 9 | Programs such as the one below failed to assemble earlier, 10 | 11 | ```asm 12 | 13 | ; Program with multiple labels 14 | ; for single statement 15 | 16 | JMP INIT 17 | 18 | ;data 19 | STR: DB 'HELLO' 20 | 21 | INIT: 22 | START: 23 | MVI A, STR 24 | 25 | HLT 26 | ``` 27 | 28 | These programs are now supported by the assembler. 29 | 30 | Note that you **cannot** add multiple labels to data definitions such as 31 | `DB`, `EQU`, `SET` and others. 32 | 33 | ``` 34 | 35 | ``` 36 | -------------------------------------------------------------------------------- /src/content/changelog/2025-05-03.md: -------------------------------------------------------------------------------- 1 | --- 2 | versions: 3 | - "2.9.0" 4 | date: "2025-05-03" 5 | --- 6 | 7 | ### ✨ Added Support for Long-Running Programs 8 | 9 | Sim8085 can now handle programs with infinite loops, such as those that generate square waves: 10 | 11 | ```asm 12 | ORG 00H 13 | 14 | WHILE: 15 | MVI A, 00H 16 | OUT 02H 17 | MVI D, 0FFH 18 | MVI E, 0FFH 19 | CALL DELAY 20 | MVI A, 01H 21 | OUT 02H 22 | MVI D, 0FFH 23 | MVI E, 0FFH 24 | CALL DELAY 25 | JMP WHILE 26 | HLT 27 | 28 | DELAY: 29 | DCX D 30 | MOV A,D 31 | ORA E 32 | JNZ DELAY 33 | RET 34 | ``` 35 | 36 | Previously, such programs could freeze the browser due to tight loops. This update introduces **Instruction Timing Mode**, a new simulator setting that emulates instruction execution delays more realistically. 37 | 38 | ![Instruction Timing Setting](./images/instruction-timing-setting-2025-05-03.png) 39 | 40 | When enabled, the simulator inserts delays based on actual 8085 timing, allowing the host browser to remain responsive — even for infinite or long-running loops. 41 | 42 | > **Note**: Instruction Timing Mode is **off by default**. You must enable it before running time-sensitive or infinite loop programs. 43 | 44 | This lays the groundwork for exciting features like: 45 | 46 | - Interactive programs (e.g., waiting for keyboard input) 47 | - Monitor programs that rely on polling loops 48 | - Realistic peripheral emulation in future updates 49 | 50 | --- 51 | 52 | ### 🛠️ Minor Fixes 53 | 54 | - When using the **Full Memory View**, searched rows now appear at the **top** of the view instead of the bottom — making them easier to find. 55 | 56 | ![Row on top after search in full memory view](./images/full-memory-search-top-2025-05-03.png) 57 | -------------------------------------------------------------------------------- /src/content/changelog/2025-05-05.md: -------------------------------------------------------------------------------- 1 | --- 2 | versions: 3 | - "2.11.2" 4 | - "2.11.1" 5 | - "2.11.0" 6 | - "2.10.3" 7 | - "2.10.2" 8 | - "2.10.1" 9 | - "2.10.0" 10 | date: "2025-05-05" 11 | --- 12 | 13 | ### 💡 LED Array Connected to J3 14 | 15 | Sim8085 now includes a **virtual LED Array** connected to **port J3**, letting you simulate LED output visually: 16 | 17 | ```asm 18 | MVI A, 55H 19 | OUT 03H ; lights up alternating LEDs 20 | HLT 21 | ``` 22 | 23 | This is perfect for writing I/O programs that display patterns or signals, such as binary counters or blinking sequences. 24 | 25 | ![LED Array](./images/led-array-2025-05-05.png) 26 | 27 | --- 28 | 29 | ### 🧰 New Toolbox Panel 30 | 31 | Introducing the **Toolbox Panel** — a handy sidebar with utilities to help you convert and inspect values while programming. 32 | 33 | It currently includes: 34 | 35 | - **Hex ⇄ Decimal Converter** 36 | - **Binary ⇄ Decimal Converter** 37 | - **SIM ⇄ Hex Helper** to visualize SIM instruction bits 38 | 39 | ![Toolbox Panel](./images/toolbox-panel-2025-05-05.png) 40 | 41 | > This is just the beginning — more tools are planned for upcoming releases. 42 | 43 | --- 44 | 45 | ### ⚡ Performance Improvements 46 | 47 | - Improved loading performance with smarter asset loading 48 | - Removed unused Google Fonts to reduce page size 49 | 50 | --- 51 | 52 | ### 🐞 Bug Fixes 53 | 54 | #### 📱 Mobile Layouts 55 | 56 | - Fixed layout of the **Actions** panel on small screens 57 | - Reordered icons in the Actions list for better clarity 58 | - Fixed login page logo distortion on **iPhone Safari** 59 | - Improved **changelog layout** on mobile screens 60 | 61 | #### 🧱 General UI 62 | 63 | - Fixed CSS build issues 64 | - Corrected documentation path in sidebar 65 | - Fixed Tips section styling for consistency 66 | 67 | --- 68 | 69 | Let me know if you'd like to split these into separate posts for each major feature or keep them combined. 70 | -------------------------------------------------------------------------------- /src/content/changelog/2025-05-06.md: -------------------------------------------------------------------------------- 1 | --- 2 | versions: 3 | - "2.12.1" 4 | - "2.12.0" 5 | - "2.11.3" 6 | date: "2025-05-06" 7 | --- 8 | 9 | ### 🧠 RIM & SIM Instruction Support 10 | 11 | Sim8085 now supports the **RIM** (Read Interrupt Mask) and **SIM** (Set Interrupt Mask) instructions — enabling simulation of advanced interrupt behavior and control flags. 12 | 13 | - You can now write programs that **read and modify interrupt masks** using `RIM` and `SIM`. 14 | - Useful for implementing software-based interrupt control and serial I/O logic. 15 | 16 | ```asm 17 | SIM ; Configure interrupt masks or SOD 18 | RIM ; Read interrupt masks and pending RSTs 19 | ``` 20 | 21 | --- 22 | 23 | ### 🚨 RST Instruction and Interrupts 24 | 25 | Support for `RST n` instructions (software interrupts) is now included! 26 | 27 | You can now write monitor-style programs and test interrupt service routines: 28 | 29 | ```asm 30 | RST 7 ; Jump to 0038H 31 | ``` 32 | 33 | This opens the door to more **realistic system-level programming** and educational use cases. 34 | 35 | --- 36 | 37 | ### 🧰 SIM/RIM ↔ Number Converter Tool 38 | 39 | A new section has been added to the **Toolbox Panel** to decode and encode values for SIM and RIM instructions. 40 | 41 | - Visualize the meaning of each bit in SIM/RIM values 42 | - Easily toggle flags and copy the result into your program 43 | 44 | > This is especially helpful when setting up SOD, interrupt masks, and checking pending interrupts. 45 | 46 | --- 47 | 48 | ### 🐞 Bug Fixes 49 | 50 | - Fixed a bugs related to extra scrollbars on some screens. 51 | -------------------------------------------------------------------------------- /src/content/changelog/2025-05-07.md: -------------------------------------------------------------------------------- 1 | --- 2 | versions: 3 | - "2.12.3" 4 | - "2.12.2" 5 | date: "2025-05-07" 6 | --- 7 | 8 | ### ⚙️ Improved Memory and I/O Access Reliability 9 | 10 | This update ensures that **memory and I/O access remain stable and consistent**, even as internal optimizations are made. 11 | 12 | You can now rely on your programs working as expected — without surprises caused by behind-the-scenes changes. 13 | 14 | --- 15 | 16 | ### ⚡ Performance Improvements 17 | 18 | - Simulator is now **lazy-loaded**, meaning it's only fetched when needed — improving initial load times significantly. 19 | - Additional load time optimizations applied to reduce startup delays, especially on slower connections. 20 | 21 | --- 22 | 23 | ### 🎨 UI & Styling Fixes 24 | 25 | - Fixed styling issues in the **left and right panels**, including spacing, overflow, and alignment. 26 | - Fixed layout problems caused by **large footers**, especially in documentation or tutorial pages. 27 | - Polished the **changelog page styling** for better readability and consistency. 28 | -------------------------------------------------------------------------------- /src/content/changelog/2025-05-08.md: -------------------------------------------------------------------------------- 1 | --- 2 | versions: 3 | - "2.13.0" 4 | date: "2025-05-08" 5 | --- 6 | 7 | ### 🆕 Interrupt Controls & Indicators 8 | 9 | Sim8085 now supports **realistic interrupt simulation** — not just the CPU behavior, but also the **hardware lines and pending states**: 10 | 11 | - 🟡 **Interrupt Control Panel** 12 | You can now toggle **TRAP**, **RST 5.5**, **RST 6.5**, and **RST 7.5** lines from the new lightning bolt (⚡) menu — just like pressing hardware buttons. Each toggle represents the state of the actual interrupt line. 13 | 14 | ![Interrupt Controls](./images/interrupt-controls-2025-05-08.png) 15 | 16 | - 🟢 **Live Interrupt Status Panel** 17 | A new sidebar section displays the **enabled/masked state** of each interrupt, as well as any **pending interrupts**. 18 | TRAP is always enabled (as it is in real 8085), and RST 7.5 now includes proper **latch behavior**. 19 | 20 | ![Interrupt Panel](./images/interrupt-panel-2025-05-08.png) 21 | 22 | These improvements make it easier to understand and debug interrupt-driven programs — and bring Sim8085 even closer to how real 8085 hardware behaves. 23 | -------------------------------------------------------------------------------- /src/content/changelog/2025-05-14.md: -------------------------------------------------------------------------------- 1 | --- 2 | versions: 3 | - "2.15.0" 4 | date: "2025-05-14" 5 | --- 6 | 7 | ### 🧹 Code Formatter 8 | 9 | Sim8085 now includes a built-in **code formatting tool** to clean up and align your 8085 assembly code — making it more readable and consistent with traditional 3-column style: 10 | 11 | - 🧠 **Smart Label-Aware Formatting** 12 | Labels are aligned to the left, and if a label is too long, the instruction is moved to the next line automatically. 13 | 14 | - 🧱 **Mnemonic and Operand Alignment** 15 | Instructions and operands are neatly spaced into consistent columns, making the structure of your code easier to follow. 16 | 17 | - ✍️ **Comment Preservation** 18 | Comments are retained exactly where you typed them — whether inline or on standalone lines. 19 | 20 | - ✨ **Blank Line Preservation** 21 | Your original blank lines are preserved, so logical sections of your program remain visually distinct. 22 | 23 | 🧪 **Note:** This feature is still in testing. If you notice any formatting issues or edge cases, please [report them on GitHub](https://github.com/debjitbis08/sim8085/issues). 24 | -------------------------------------------------------------------------------- /src/content/changelog/images/android-sim8085-install-2024-11-16.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/changelog/images/android-sim8085-install-2024-11-16.jpeg -------------------------------------------------------------------------------- /src/content/changelog/images/doc-section-2024-11-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/changelog/images/doc-section-2024-11-11.png -------------------------------------------------------------------------------- /src/content/changelog/images/full-memory-search-top-2025-05-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/changelog/images/full-memory-search-top-2025-05-03.png -------------------------------------------------------------------------------- /src/content/changelog/images/instruction-timing-setting-2025-05-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/changelog/images/instruction-timing-setting-2025-05-03.png -------------------------------------------------------------------------------- /src/content/changelog/images/interrupt-controls-2025-05-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/changelog/images/interrupt-controls-2025-05-08.png -------------------------------------------------------------------------------- /src/content/changelog/images/interrupt-panel-2025-05-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/changelog/images/interrupt-panel-2025-05-08.png -------------------------------------------------------------------------------- /src/content/changelog/images/ios-sim8085-install-2024-11-16.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/changelog/images/ios-sim8085-install-2024-11-16.jpeg -------------------------------------------------------------------------------- /src/content/changelog/images/keyboard-shortcuts-2024-11-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/changelog/images/keyboard-shortcuts-2024-11-11.png -------------------------------------------------------------------------------- /src/content/changelog/images/led-array-2025-05-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/changelog/images/led-array-2025-05-05.png -------------------------------------------------------------------------------- /src/content/changelog/images/sim8085-mobile-layout-2024-11-16.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/changelog/images/sim8085-mobile-layout-2024-11-16.jpeg -------------------------------------------------------------------------------- /src/content/changelog/images/toolbox-panel-2025-05-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/changelog/images/toolbox-panel-2025-05-05.png -------------------------------------------------------------------------------- /src/content/config.js: -------------------------------------------------------------------------------- 1 | import { defineCollection, z } from 'astro:content'; 2 | import { docsSchema } from '@astrojs/starlight/schema'; 3 | 4 | export const collections = { 5 | docs: defineCollection({ schema: docsSchema() }), 6 | changes: defineCollection({ 7 | versions: z.array(z.string()), 8 | date: z.string() 9 | }) 10 | }; 11 | -------------------------------------------------------------------------------- /src/content/docs/docs/en/404.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Not found 3 | template: splash 4 | editUrl: false 5 | hero: 6 | title: '404' 7 | tagline: Houston, we have a problem. We couldn’t find that page.
Check the URL or try using the search bar. 8 | actions: 9 | - text: Go home 10 | icon: right-arrow 11 | link: / 12 | variant: primary 13 | --- 14 | -------------------------------------------------------------------------------- /src/content/docs/docs/en/8085.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 8085 Microprocessor 3 | description: Sim8085 CPU features. 4 | --- 5 | 6 | The processor in Sim8085 is obviously the Intel 8085A microprocessor. You can read about it in detail 7 | in the [8085 datasheet](https://ia801807.us.archive.org/3/items/intel-8085-datasheet/8085_datasheet.pdf). 8 | 9 | -------------------------------------------------------------------------------- /src/content/docs/docs/en/app-install.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installing Sim8085 3 | description: Sim8085 can be installed as a desktop or mobile app and can be used offline. 4 | --- 5 | 6 | Sim8085 is a **Progressive Web App (PWA)**, which means you can install it like a native desktop 7 | application directly from your Chrome browser on Windows. Once installed, it behaves like a 8 | standalone app — with offline support, a cleaner UI, and faster load times. 9 | 10 | * 🪟 A standalone window (no address bar or tabs) 11 | * 🛫 Full offline access after installation 12 | * 📌 Launch from Start Menu or taskbar 13 | * ⚡ Faster access and cleaner experience 14 | 15 | --- 16 | 17 | ## 🧭 Option 1: Google Chrome (Windows) 18 | 19 | ### ✅ Steps 20 | 21 | 1. Open **Chrome** and go to [https://www.sim8085.com](https://www.sim8085.com). 22 | 2. Click the **three-dot menu** in the top-right corner of the browser. 23 | 3. Hover over **"Cast, save, and share"**. 24 | 4. Click **"Install page as app…"** 25 | *(as shown in the screenshot below)* 26 | 27 | ![Install using Chrome](../images/install-using-chrome.png) 28 | 29 | 5. Confirm the prompt by clicking **Install**. 30 | 6. Sim8085 will launch in its own window, and you can pin it to the **taskbar**, **desktop**, or **Start menu**. 31 | 32 | --- 33 | 34 | ## 🧭 Option 2: Microsoft Edge (Windows) 35 | 36 | ### ✅ Steps 37 | 38 | 1. Open **Edge** and navigate to [https://www.sim8085.com](https://www.sim8085.com). 39 | 2. Click the **three-dot menu** in the top-right corner. 40 | 3. Go to **"Apps"** → **"Install this site as an app"**. 41 | 42 | ![Install using Edge](../images/install-using-edge.png) 43 | 44 | 4. Click **Install** in the confirmation dialog. 45 | 5. Sim8085 will open in a new window. You’ll also find it listed in the Start Menu, and you can pin it to your taskbar or desktop. 46 | 47 | --- 48 | 49 | ## 📡 Offline Support 50 | 51 | Once installed, Sim8085 will work **offline**. You can: 52 | 53 | * Open and use it without internet. 54 | * Load programs, edit, and simulate 8085 code offline. 55 | * Updates will automatically apply when you're online next. 56 | 57 | --- 58 | 59 | ## 🧽 How to Uninstall 60 | 61 | 1. Right-click the **Sim8085** icon in your Start menu or taskbar. 62 | 2. Select **Uninstall** or **Remove from this device**. 63 | 3. Alternatively, open Chrome, go to `chrome://apps`, right-click Sim8085, and choose **Remove from Chrome**. 64 | 65 | --- 66 | 67 | ## 🔍 Need More Help? 68 | 69 | See [Chrome's official PWA install guide](https://support.google.com/chrome/answer/9658361). 70 | 71 | -------------------------------------------------------------------------------- /src/content/docs/docs/en/directives/end.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: END 3 | description: END Directive 4 | --- 5 | 6 | The END directive marks the conclusion of the source program and signals the assembler to terminate each pass. 7 | 8 | Format: 9 | 10 | | Label | Opcode | Operand (Optional) | 11 | |-----------|--------|--------------------| 12 | | Optional: | `END` | `expression` | 13 | 14 | * Only one END statement is allowed in a source program, and it must be the final statement. 15 | * If an optional expression is provided, its value determines the starting address for program execution. 16 | * If no expression is specified, the assembler defaults the starting address to zero. 17 | -------------------------------------------------------------------------------- /src/content/docs/docs/en/guides/example.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Example Guide 3 | description: A guide in my new Starlight docs site. 4 | --- 5 | 6 | Guides lead a user through a specific task they want to accomplish, often with a sequence of steps. 7 | Writing a good guide requires thinking about what your users are trying to do. 8 | 9 | ## Further reading 10 | 11 | - Read [about how-to guides](https://diataxis.fr/how-to-guides/) in the Diátaxis framework 12 | -------------------------------------------------------------------------------- /src/content/docs/docs/en/instructions/aci.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ACI - Add Immediate with Carry 3 | description: Add Immediate with Carry 4 | --- 5 | 6 | **Add Immediate with Carry** 7 | 8 | The ACI instruction adds the immediate operand (data) and the current carry bit to the accumulator. The result is stored back in the accumulator. The carry flag is added if it's set. 9 | 10 | ## Instruction Format 11 | 12 | | Opcode | Operand | 13 | |--------|---------| 14 | | `ACI` | `data` | 15 | 16 | ## Operand Details 17 | 18 | The operand specifies the actual data to be added to the accumulator, except for the carry bit. The data may be in one of the following forms: 19 | - A number 20 | - An ASCII constant 21 | - The label of a previously defined value 22 | - An expression 23 | 24 | The data must not exceed **one byte**. 25 | 26 | ## Operation 27 | 28 | The operation performed by `ACI` is: 29 | 30 | ``` 31 | Accumulator = Accumulator + Immediate Data + Carry 32 | ``` 33 | 34 | - **Operand**: The immediate data to be added to the accumulator. It must be a single byte (8 bits) and can be in the form of a number, an ASCII constant, or an expression. 35 | - **Carry Bit**: This instruction includes the carry bit in its addition. 36 | 37 | 38 | ## Flags Affected 39 | 40 | - **Z (Zero)**: Set if the result is zero. 41 | - **S (Sign)**: Set if the result is negative. 42 | - **P (Parity)**: Set if the result has even parity. 43 | - **CY (Carry)**: Set if there is a carry out. 44 | - **AC (Auxiliary Carry)**: Set if there is a carry out from the lower nibble. 45 | 46 | 47 | ## Execution Details 48 | 49 | | **Cycles** | **States** | **Addressing Mode** | **Flags** | 50 | |------------|------------|---------------------|---------------------| 51 | | 2 | 7 | Immediate | Z, S, P, CY, AC | 52 | 53 | ## Example 54 | 55 | **Before Execution**: 56 | - **Accumulator**: `24H` (00100100) 57 | - **Immediate Data**: `36H` (00110110) 58 | - **Carry Bit**: `1` 59 | 60 | **After Execution**: 61 | - **Accumulator**: `(24H + 36H + 1)` = `5BH` (01011011) 62 | 63 | ### Breakdown of Addition (Binary) 64 | 65 | ``` 66 | Accumulator = 00100100 (24H) 67 | Immediate = 00110110 (36H) 68 | Carry = 00000001 69 | -------- 70 | Result = 01011011 (5BH) 71 | ``` 72 | -------------------------------------------------------------------------------- /src/content/docs/docs/en/instructions/adc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ADC — Add with Carry 3 | --- 4 | 5 | The `ADC` instruction adds the contents of a register or memory location, along 6 | with the carry flag, to the accumulator. The result is stored back in the 7 | accumulator. This instruction also updates the carry flag and other condition 8 | flags based on the result of the operation. 9 | 10 | The `ADC` instruction enables multi-byte addition by utilizing the carry flag, 11 | allowing you to chain multiple additions together. 12 | 13 | ## Instruction Format 14 | 15 | | Opcode | Operand | 16 | |--------|-------------| 17 | | `ADC` | `register` | 18 | | `ADC` | `M` | 19 | 20 | **Operand**: The operand can be one of the registers `A` through `E`, `H`, or `L`, or the 21 | memory location (`M`) addressed by the contents of the `H` and `L` registers. 22 | 23 | ## Operation 24 | 25 | ``` 26 | Accumulator = Accumulator + Register/Memory + Carry 27 | ``` 28 | 29 | **Carry Bit**: If the carry bit is set (CY=1), it is added to the result along with 30 | the contents of the specified register or memory. 31 | 32 | ## Flags Affected 33 | 34 | - **Z (Zero)**: Set if the result is zero. 35 | - **S (Sign)**: Set if the result is negative (most significant bit is 1). 36 | - **P (Parity)**: Set if the result has even parity. 37 | - **CY (Carry)**: Set if there is a carry out of the most significant bit. 38 | - **AC (Auxiliary Carry)**: Set if there is a carry from bit 3 to bit 4. 39 | 40 | ## Execution Details 41 | 42 | ### Add Register to Accumulator with Carry 43 | 44 | This adds the contents of the specified register and the carry bit to the 45 | accumulator, and stores the result in the accumulator. 46 | 47 | | **Cycles** | **States** | **Addressing Mode** | **Flags** | 48 | |------------|------------|---------------------|---------------------| 49 | | 1 | 4 | Register | Z, S, P, CY, AC | 50 | 51 | ### Add Memory to Accumulator with Carry 52 | 53 | This adds the contents of the memory location addressed by the `H` and `L` 54 | registers, along with the carry bit, to the accumulator. `M` refers to the memory 55 | location (`H` and `L` registers). 56 | 57 | | **Cycles** | **States** | **Addressing Mode** | **Flags** | 58 | |------------|------------|---------------------|---------------------| 59 | | 2 | 7 | Register Indirect | Z, S, P, CY, AC | 60 | 61 | ## Example 1: (Carry = 0) 62 | 63 | **Initial State** 64 | - Register C: 3DH (00111101) 65 | - Accumulator: 42H (01000010) 66 | - Carry Bit: 0 67 | 68 | **After Execution (ADC C)** 69 | 70 | Result: 71 | 72 | ``` 73 | Accumulator = 42H + 3DH + 0 = 7FH (01111111) 74 | ``` 75 | 76 | The condition flags are set as follows: 77 | 78 | - Carry: `0` 79 | - Sign: `0` 80 | - Zero: `0` 81 | - Parity: `0` (odd parity) 82 | - Auxiliary Carry: `0` 83 | 84 | ## Example 2: (Carry = 1) 85 | 86 | **Initial State** 87 | - Register C: 3DH (00111101) 88 | - Accumulator: 42H (01000010) 89 | - Carry Bit: 1 90 | 91 | **After Execution (ADC C)** 92 | 93 | Result: 94 | 95 | ``` 96 | Accumulator = 42H + 3DH + 1 = 80H (10000000) 97 | ``` 98 | 99 | The condition flags are set as follows: 100 | 101 | - Carry: `0` 102 | - Sign: `1` (most significant bit is 1) 103 | - Zero: `0` 104 | - Parity: `0` (odd parity) 105 | - Auxiliary Carry: `1` (carry from bit 3 to bit 4) 106 | -------------------------------------------------------------------------------- /src/content/docs/docs/en/unsupported.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Unsupported Features 3 | description: Sim8085 does not support some instructions and advanced assembler features yet. 4 | --- 5 | 6 | We have tried our best to implement as many features specified in the original 7 | 8080/85 Assembly Language Programming Manual but due to time constraints and 8 | not been able to implement all of them. We want the application to be useful 9 | to users as soon as possible and thus we have released the application with 10 | these missing features. 11 | 12 | We will be implementing all of these in due time. 13 | 14 | If you believe any of these are essential, please raise a request through the 15 | GitHub [discussions](https://github.com/debjitbis08/sim8085/discussions/categories/ideas) page. 16 | 17 | ### Unsupported Directives 18 | 19 | The application does not support the following, 20 | 21 | - `DS` 22 | - `IF`, `ELSE` & `ENDIF` 23 | - `EOT` 24 | - Some of the relocation directives, `CSEG`, `DSEG`, `PUBLIC`, `EXTRN`, `NAME`, `STKLN` & the `STACK` and `MEMORY` 25 | reserved words. 26 | - None of the macro directives, `MACRO`, `ENDM`, `LOCAL`, `REPT`, `IRP`, `IRPC`, `EXITM` 27 | 28 | ### Unsupported Expressions 29 | 30 | Apart of the arithmetic expression, no other kind of expression are supported right now. 31 | -------------------------------------------------------------------------------- /src/content/docs/docs/images/install-using-chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/docs/docs/images/install-using-chrome.png -------------------------------------------------------------------------------- /src/content/docs/docs/images/install-using-edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/docs/docs/images/install-using-edge.png -------------------------------------------------------------------------------- /src/content/docs/docs/images/interrupt-controls-2025-05-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/docs/docs/images/interrupt-controls-2025-05-08.png -------------------------------------------------------------------------------- /src/content/docs/docs/images/interrupt-panel-2025-05-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/content/docs/docs/images/interrupt-panel-2025-05-08.png -------------------------------------------------------------------------------- /src/core/8085.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/core/8085.wasm -------------------------------------------------------------------------------- /src/core/parserTests/label.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from 'vitest'; 2 | 3 | describe('8085 Simulator Functions', () => { 4 | test('label with colon and op should be parsed', () => { 5 | expect(1).toBe(1); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/core/simulator-library.js: -------------------------------------------------------------------------------- 1 | addToLibrary({ 2 | io_write: function (address, value) { 3 | io_write(address, value); 4 | }, 5 | }); 6 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | interface Window { 5 | posthog: object; 6 | } 7 | -------------------------------------------------------------------------------- /src/icons/bmc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/icons/computer-desktop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/icons/hand-holding-heart.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/kofi.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/lock.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | -------------------------------------------------------------------------------- /src/icons/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/menu.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/icons/moon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/paypal.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/razorpay.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/regular-dock-left.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/regular-dock-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/solid-dock-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/solid-dock-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/sponsor.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/sun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/triangle-info.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/images/lambda-classes-poster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/images/lambda-classes-poster.png -------------------------------------------------------------------------------- /src/images/natory-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/images/natory-light.png -------------------------------------------------------------------------------- /src/images/natory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/images/natory.png -------------------------------------------------------------------------------- /src/images/pc-start-value-textbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/images/pc-start-value-textbox.png -------------------------------------------------------------------------------- /src/images/project-track-poster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debjitbis08/sim8085/fed030ab927293272254c0c5e81b079f4f8c29a3/src/images/project-track-poster.jpg -------------------------------------------------------------------------------- /src/lib/supabase.js: -------------------------------------------------------------------------------- 1 | import { createClient } from "@supabase/supabase-js"; 2 | 3 | const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL; 4 | const supabaseAnonKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY; 5 | 6 | export let supabase = null; 7 | 8 | if (supabaseUrl && supabaseAnonKey) { 9 | supabase = createClient(supabaseUrl, supabaseAnonKey); 10 | } 11 | 12 | export const getUser = async () => { 13 | if (!supabase) return { user: null, error: new Error("Supabse is not initialized") }; 14 | 15 | const { 16 | data: { user }, 17 | error, 18 | } = await supabase.auth.getUser(); 19 | 20 | return { user, error }; 21 | }; 22 | 23 | export const getSession = async () => { 24 | if (!supabase) return { session: null, error: new Error("Supabse is not initialized") }; 25 | 26 | const { 27 | data: { session }, 28 | error, 29 | } = await supabase.auth.getSession(); 30 | 31 | return { session, error }; 32 | }; 33 | 34 | export const getUserFromSession = async () => { 35 | if (!supabase) return { user: null, error: new Error("Supabse is not initialized") }; 36 | 37 | const { 38 | data: { session }, 39 | error, 40 | } = await supabase.auth.getSession(); 41 | 42 | return { user: session?.user, error }; 43 | }; 44 | 45 | export const signOut = async () => { 46 | if (supabase) { 47 | await supabase.auth.signOut(); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/pages/changelog.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '../layouts/Layout.astro'; 3 | import { getCollection, getEntries } from 'astro:content'; 4 | 5 | const dateFormatter = new Intl.DateTimeFormat('en-US', { 6 | year: 'numeric', 7 | month: 'long', 8 | day: 'numeric', 9 | }) 10 | 11 | const entries = await Promise.all( 12 | (await getCollection("changelog")) 13 | .sort((entryA, entryB) => 14 | Number(new Date(entryB.data.date)) - Number(new Date(entryA.data.date)) 15 | ) 16 | .map((entry) => { 17 | return entry.render().then(({ Content }) => ({ 18 | ...entry, 19 | Content, 20 | date: dateFormatter.format(new Date(entry.data.date)), 21 | })).catch((err) => console.error(err)) 22 | }) 23 | ); 24 | --- 25 | 26 | 27 |
28 |
29 |
30 | 33 |
34 |

Changelog

35 |

New updates and improvements for Sim8085

36 |
37 | Detailed Changelog 38 |
39 |
40 |
41 |
42 | {entries.map((entry, index) => ( 43 |
44 |
45 |

46 | {entry.date} 47 |

48 |
49 |
50 |
51 | 52 |
53 |
54 |

Detailed Changelogs

55 |
    56 | {entry.data.versions.map((version) => ( 57 |
  • 58 | v{version} 59 |
  • 60 | ))} 61 |
62 |
63 |
64 |
65 | ))} 66 |
67 |
68 |
69 |
70 | 71 | -------------------------------------------------------------------------------- /src/pwa.js: -------------------------------------------------------------------------------- 1 | import { registerSW } from 'virtual:pwa-register'; 2 | 3 | registerSW({ 4 | immediate: true, 5 | onRegisteredSW(swScriptUrl) { 6 | console.log('SW registered: ', swScriptUrl) 7 | }, 8 | onOfflineReady() { 9 | console.log('PWA application ready to work offline') 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | import { createStore } from "solid-js/store"; 2 | 3 | export const INITIAL_CODE = `; 4 | 5 | JMP START 6 | 7 | ;data 8 | 9 | ;code 10 | START: NOP 11 | 12 | ;Start writing your code here 13 | 14 | HLT 15 | `; 16 | 17 | export const REGISTER_KEYS = ["bc", "de", "hl"]; 18 | 19 | const initialRegisterState = () => { 20 | return REGISTER_KEYS.reduce( 21 | (o, registerId) => ({ 22 | ...o, 23 | [registerId]: { high: 0, low: 0, isEditing: false }, 24 | }), 25 | {}, 26 | ); 27 | }; 28 | 29 | export const DEFAULT_SETTINGS = { 30 | run: { 31 | enableTiming: false, 32 | clockFrequency: "3072000", 33 | }, 34 | beforeRun: { 35 | clearFlags: true, 36 | clearRegisters: true, 37 | clearAllMemoryLocations: false, 38 | }, 39 | alert: { 40 | afterSuccessfulRun: true, 41 | afterClearAll: true, 42 | afterDebugStop: true, 43 | }, 44 | editor: { 45 | fontSize: 16, 46 | }, 47 | }; 48 | 49 | export const [store, setStore] = createStore({ 50 | code: INITIAL_CODE, 51 | codeWithError: "", 52 | programState: "Idle", // Idle, Loaded, Running, Paused 53 | assembled: [], 54 | loadAddress: 0, 55 | accumulator: 0, 56 | isEditingAccumulator: false, 57 | registers: initialRegisterState(), 58 | stackPointer: 0xffff, 59 | programCounter: 0, 60 | pcStartValue: 0, 61 | statePointer: null, 62 | flags: { 63 | s: false, 64 | z: false, 65 | ac: false, 66 | p: false, 67 | c: false, 68 | }, 69 | memory: Array(65536).fill(0), 70 | io: Array(256).fill(0), 71 | interruptsEnabled: false, 72 | interruptMasks: { 73 | rst55: false, 74 | rst65: false, 75 | rst75: false, 76 | }, 77 | pendingInterrupts: { 78 | trap: false, 79 | rst55: false, 80 | rst65: false, 81 | rst75: false, 82 | }, 83 | breakpoints: [], 84 | errors: [], 85 | activeFile: { 86 | name: "untitled-1.asm", 87 | workspaceItemId: null, 88 | currentVersionId: null, 89 | content: INITIAL_CODE, 90 | }, 91 | homeFolderId: null, 92 | settings: structuredClone(DEFAULT_SETTINGS), 93 | }); 94 | -------------------------------------------------------------------------------- /src/tailwind.docs.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | 3 | import starlightPlugin from "@astrojs/starlight-tailwind"; 4 | 5 | export default { 6 | content: { 7 | relative: true, 8 | files: ["./content/docs/**/*.{md,mdx}"], 9 | }, 10 | darkMode: ["class", '[data-theme="dark"]'], 11 | theme: { 12 | extend: {}, 13 | }, 14 | plugins: [starlightPlugin()], 15 | }; 16 | -------------------------------------------------------------------------------- /src/tailwind.docs.css: -------------------------------------------------------------------------------- 1 | @config "./tailwind.docs.config.mjs"; 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | -------------------------------------------------------------------------------- /src/tailwind.main.css: -------------------------------------------------------------------------------- 1 | @config './tailwind.main.config.mjs'; 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | @layer base { 8 | :root { 9 | --sm-primary-50: 238 251 243; 10 | --sm-primary-100: 214 245 225; 11 | --sm-primary-200: 177 233 200; 12 | --sm-primary-300: 126 215 169; 13 | --sm-primary-400: 73 190 132; 14 | --sm-primary-500: 38 162 105; 15 | --sm-primary-600: 24 131 84; 16 | --sm-primary-700: 19 105 69; 17 | --sm-primary-800: 18 83 57; 18 | --sm-primary-900: 15 69 48; 19 | --sm-primary-950: 8 38 27; 20 | 21 | --sm-primary: var(--sm-primary-500); 22 | 23 | --sm-gray-50: 251 251 251; /* #fbfbfb */ 24 | --sm-gray-100: 239 239 239; /* #efefef */ 25 | --sm-gray-200: 220 220 220; /* #dcdcdc */ 26 | --sm-gray-300: 189 189 189; /* #bdbdbd */ 27 | --sm-gray-400: 152 152 152; /* #989898 */ 28 | --sm-gray-500: 124 124 124; /* #7c7c7c */ 29 | --sm-gray-600: 101 101 101; /* #656565 */ 30 | --sm-gray-700: 82 82 82; /* #525252 */ 31 | --sm-gray-800: 70 70 70; /* #464646 */ 32 | --sm-gray-900: 61 61 61; /* #3d3d3d */ 33 | --sm-gray-950: 41 41 41; /* #292929 */ 34 | 35 | --sm-page-background: 232 232 232; /* #e8e8e8 */ 36 | --sm-main-background: var(--sm-gray-50); 37 | --sm-secondary-background: var(--sm-gray-100); 38 | --sm-main-border: var(--sm-gray-300); 39 | 40 | --sm-primary-foreground: var(--sm-gray-700); 41 | --sm-primary-border: var(--sm-gray-600); 42 | 43 | --sm-secondary-foreground: var(--sm-gray-500); 44 | --sm-secondary-border: var(--sm-gray-600); 45 | 46 | --sm-active-foreground: var(--sm-gray-700); 47 | --sm-active-background: var(--sm-gray-200); 48 | --sm-active-border: var(--sm-gray-300); 49 | 50 | --sm-inactive-foreground: var(--sm-gray-400); 51 | --sm-inactive-border: var(--sm-gray-400); 52 | 53 | --sm-red-foreground: 185 28 28; /* #b91c1c */ 54 | --sm-green-foreground: 21 128 61; /* #15803d */ 55 | --sm-yellow-foreground: 234 179 8; /* #eab308 */ 56 | --sm-blue-foreground: 37 99 235; /* #2563eb */ 57 | --sm-orange-foreground: 234 88 12; /* #ea580c */ 58 | 59 | /* Editor colors */ 60 | --sm-editor-active-line: 191 219 254; /* #bfdbfe */ 61 | --sm-editor-debug-line: 96 165 250; /* #60a5fa */ 62 | --sm-editor-active-line-gutter: 255 255 255; 63 | --sm-editor-cursor: 75 85 99; /* #4b5563 */ 64 | --sm-editor-selection-background: 191 219 254; /* #bfdbfe */ 65 | 66 | --sm-editor-opcode: 28 128 61; 67 | --sm-editor-directive: 29 78 216; 68 | --sm-editor-label: 168 91 7; 69 | --sm-editor-comment: 107 114 128; 70 | --sm-editor-register: 185 28 28; 71 | --sm-editor-number: 194 65 12; 72 | --sm-editor-string: 234 88 12; 73 | --sm-editor-operator: 190 24 ; 74 | --sm-editor-punctuation: 156 163 175; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/tests/adi.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import { runTest } from "./test-utils"; 3 | 4 | describe("ADI Instruction Tests", () => { 5 | test("ADI: Sets AC flag", async () => { 6 | const code = ` 7 | mvi a, 8h 8 | adi 8h 9 | hlt 10 | `; 11 | const expectedCpuState = { 12 | accumulator: 0x10, 13 | flags: { 14 | z: false, 15 | s: false, 16 | p: false, 17 | c: false, 18 | ac: true, 19 | }, 20 | programCounter: 0x0005, 21 | }; 22 | await runTest(code, {}, expectedCpuState); 23 | }); 24 | 25 | test("ADI: Resets AC flag", async () => { 26 | const code = ` 27 | mvi a, 14h 28 | adi 42h 29 | hlt 30 | `; 31 | const expectedCpuState = { 32 | accumulator: 0x56, 33 | flags: { 34 | z: false, 35 | s: false, 36 | p: true, 37 | c: false, 38 | ac: false, 39 | }, 40 | programCounter: 0x0005, 41 | }; 42 | await runTest(code, {}, expectedCpuState); 43 | }); 44 | }); -------------------------------------------------------------------------------- /src/tests/ani.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import * as fc from "fast-check"; 3 | import { runTest } from "./test-utils"; 4 | 5 | describe("ANI Instruction Tests", () => { 6 | test("ANI: Performs logical AND between accumulator and immediate data, resets carry flag", async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit accumulator value 10 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit immediate value 11 | fc.record({ 12 | z: fc.boolean(), 13 | s: fc.boolean(), 14 | p: fc.boolean(), 15 | c: fc.boolean(), 16 | ac: fc.boolean(), 17 | }), // Random initial flag states 18 | async (accumulator, immediateValue, flags) => { 19 | const result = accumulator & immediateValue; 20 | 21 | // Calculate expected flags 22 | const zeroFlag = result === 0; 23 | const signFlag = (result & 0x80) !== 0; 24 | const parityFlag = 25 | result 26 | .toString(2) 27 | .split("") 28 | .filter((bit) => bit === "1").length % 29 | 2 === 30 | 0; 31 | 32 | const code = ` 33 | org 0x0000 34 | ani ${immediateValue} 35 | hlt 36 | `; 37 | 38 | // Define the initial state of the CPU 39 | const initialCpuState = { 40 | accumulator, 41 | registers: { 42 | bc: { high: 0x00, low: 0x00 }, 43 | de: { high: 0x00, low: 0x00 }, 44 | hl: { high: 0x00, low: 0x00 }, 45 | }, 46 | flags, 47 | }; 48 | 49 | // Define the expected state of the CPU after execution 50 | const expectedCpuState = { 51 | ...initialCpuState, 52 | accumulator: result, // Result of the AND operation 53 | flags: { 54 | z: zeroFlag, 55 | s: signFlag, 56 | p: parityFlag, 57 | c: false, // Carry flag is always reset to 0 by ANI 58 | ac: false, // Auxiliary carry is always reset to 0 by ANI 59 | }, 60 | programCounter: 0x0003, 61 | }; 62 | 63 | await runTest(code, initialCpuState, expectedCpuState); 64 | }, 65 | ), 66 | { verbose: true, numRuns: 100 }, // Run 100 variations for ANI instruction 67 | ); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /src/tests/call.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import * as fc from "fast-check"; 3 | import { runTest } from "./test-utils"; 4 | 5 | // Define the condition functions for each CALL instruction 6 | const callConditions = { 7 | CALL: () => true, // Always call 8 | CC: (flags) => flags.c, // Call if carry flag set 9 | CNC: (flags) => !flags.c, // Call if carry flag not set 10 | CZ: (flags) => flags.z, // Call if zero flag set 11 | CNZ: (flags) => !flags.z, // Call if zero flag not set 12 | CM: (flags) => flags.s, // Call if minus (sign flag set) 13 | CP: (flags) => !flags.s, // Call if positive (sign flag not set) 14 | CPE: (flags) => flags.p, // Call if parity even 15 | CPO: (flags) => !flags.p, // Call if parity odd 16 | }; 17 | 18 | describe("CALL and RET Instructions with Dynamically Generated Labels Tests", () => { 19 | Object.entries(callConditions).forEach(([instruction, condition]) => { 20 | test(`${instruction}: Conditional call and return with dynamically generated labels`, async () => { 21 | await fc.assert( 22 | fc.asyncProperty( 23 | fc.record({ 24 | z: fc.boolean(), 25 | s: fc.boolean(), 26 | p: fc.boolean(), 27 | c: fc.boolean(), 28 | ac: fc.boolean(), 29 | }), // Random flag states 30 | fc.stringMatching(/^[a-zA-Z?@][a-zA-Z0-9]{0,5}$/), 31 | async (flags, labelName) => { 32 | const code = ` 33 | org 0x0000 34 | ${instruction} ${labelName} 35 | hlt 36 | 37 | org 0x1000 38 | ${labelName}: 39 | pop b 40 | dcx sp 41 | dcx sp 42 | ret 43 | `; 44 | 45 | const initialCpuState = { 46 | accumulator: 0x00, 47 | registers: { 48 | bc: { high: 0x00, low: 0x00 }, 49 | de: { high: 0x00, low: 0x00 }, 50 | }, 51 | flags, // Randomized flags 52 | programCounter: 0x0000, // Initial PC 53 | }; 54 | 55 | const shouldCall = condition(flags); 56 | 57 | await runTest(code, initialCpuState, { 58 | registers: { 59 | bc: shouldCall 60 | ? { 61 | high: 0, 62 | low: 3, 63 | } 64 | : { high: 0, low: 0 }, 65 | }, 66 | programCounter: 4, 67 | }); 68 | }, 69 | ), 70 | { verbose: true, numRuns: 100 }, // Run 100 variations for each instruction 71 | ); 72 | }); 73 | }); 74 | }); -------------------------------------------------------------------------------- /src/tests/cma.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import * as fc from 'fast-check'; 3 | import { runTest } from './test-utils'; 4 | 5 | describe('CMA Instruction Property-Based Tests', () => { 6 | test('CMA: Complements the accumulator without modifying flags', async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | // Generate an initial accumulator value and flags 10 | fc.integer({ min: 0x00, max: 0xFF }), // Accumulator can hold 8-bit values 11 | fc.record({ 12 | z: fc.boolean(), 13 | s: fc.boolean(), 14 | p: fc.boolean(), 15 | c: fc.boolean(), 16 | ac: fc.boolean(), 17 | }), 18 | async (initialAccumulator, initialFlags) => { 19 | const code = ` 20 | cma 21 | hlt 22 | `; 23 | 24 | const initialCpuState = { 25 | accumulator: initialAccumulator, 26 | registers: { 27 | bc: { high: 0x00, low: 0x00 }, 28 | de: { high: 0x00, low: 0x00 }, 29 | hl: { high: 0x00, low: 0x00 }, 30 | }, 31 | flags: initialFlags, 32 | programCounter: 0x0000, 33 | memory: {}, 34 | }; 35 | 36 | const expectedCpuState = { 37 | ...initialCpuState, 38 | accumulator: ~initialAccumulator & 0xFF, // Compute one's complement (ensure 8-bit wrap) 39 | programCounter: 0x0002, 40 | }; 41 | 42 | await runTest(code, initialCpuState, expectedCpuState); 43 | } 44 | ), 45 | { 46 | verbose: true, 47 | numRuns: 100, // Test with 100 random variations 48 | } 49 | ); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /src/tests/cmc.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import { runTest } from './test-utils'; 3 | 4 | describe('CMC Instruction Tests', () => { 5 | test('CMC: Toggles carry flag from 0 to 1', async () => { 6 | const code = ` 7 | cmc 8 | hlt 9 | `; 10 | 11 | const initialCpuState = { 12 | accumulator: 0x00, 13 | registers: { 14 | bc: { high: 0x00, low: 0x00 }, 15 | de: { high: 0x00, low: 0x00 }, 16 | hl: { high: 0x00, low: 0x00 }, 17 | }, 18 | flags: { 19 | z: false, 20 | s: false, 21 | p: false, 22 | c: false, // Carry flag starts at 0 23 | ac: false, 24 | }, 25 | }; 26 | 27 | const expectedCpuState = { 28 | ...initialCpuState, 29 | flags: { 30 | ...initialCpuState.flags, 31 | c: true, // Carry flag toggled to 1 32 | }, 33 | programCounter: 0x0002, 34 | }; 35 | 36 | await runTest(code, initialCpuState, expectedCpuState); 37 | }); 38 | 39 | test('CMC: Toggles carry flag from 1 to 0', async () => { 40 | const code = ` 41 | cmc 42 | hlt 43 | `; 44 | 45 | const initialCpuState = { 46 | accumulator: 0x00, 47 | registers: { 48 | bc: { high: 0x00, low: 0x00 }, 49 | de: { high: 0x00, low: 0x00 }, 50 | hl: { high: 0x00, low: 0x00 }, 51 | }, 52 | flags: { 53 | z: false, 54 | s: false, 55 | p: false, 56 | c: true, // Carry flag starts at 1 57 | ac: false, 58 | }, 59 | }; 60 | 61 | const expectedCpuState = { 62 | ...initialCpuState, 63 | flags: { 64 | ...initialCpuState.flags, 65 | c: false, // Carry flag toggled to 0 66 | }, 67 | programCounter: 0x0002, 68 | }; 69 | 70 | await runTest(code, initialCpuState, expectedCpuState); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /src/tests/cpi.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import * as fc from "fast-check"; 3 | import { runTest } from "./test-utils"; 4 | 5 | describe("CPI Instruction Tests", () => { 6 | test("CPI: Compares immediate data with accumulator and sets zero and carry flags", async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit accumulator value 10 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit immediate value 11 | fc.record({ 12 | z: fc.boolean(), 13 | s: fc.boolean(), 14 | p: fc.boolean(), 15 | c: fc.boolean(), 16 | ac: fc.boolean(), 17 | }), // Random initial flag states 18 | async (accumulator, immediateValue, flags) => { 19 | const result = accumulator - immediateValue; 20 | 21 | // Calculate expected flags 22 | const zeroFlag = result === 0; 23 | const carryFlag = accumulator < immediateValue; 24 | 25 | const code = ` 26 | org 0x0000 27 | cpi ${immediateValue} 28 | hlt 29 | `; 30 | 31 | // Define the initial state of the CPU 32 | const initialCpuState = { 33 | accumulator, 34 | registers: { 35 | bc: { high: 0x00, low: 0x00 }, 36 | de: { high: 0x00, low: 0x00 }, 37 | hl: { high: 0x00, low: 0x00 }, 38 | }, 39 | flags, // Randomized flags from fast-check 40 | }; 41 | 42 | // Define the expected state of the CPU after execution 43 | const expectedCpuState = { 44 | ...initialCpuState, 45 | flags: { 46 | z: zeroFlag, 47 | s: (result & 0x80) !== 0, // Sign flag based on the most significant bit of the result 48 | p: 49 | (result & 0xff) 50 | .toString(2) 51 | .split("") 52 | .filter((bit) => bit === "1").length % 53 | 2 === 54 | 0, // Parity flag based on even/odd 1 bits 55 | c: carryFlag, // Carry flag based on the comparison 56 | ac: false, // Auxiliary carry flag is reset 57 | }, 58 | programCounter: 0x0003, // PC should increment by 2 after CPI (2-byte instruction) 59 | }; 60 | 61 | await runTest(code, initialCpuState, expectedCpuState); 62 | }, 63 | ), 64 | { verbose: true, numRuns: 100 }, // Run 100 variations for CPI instruction 65 | ); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/tests/lda.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import * as fc from "fast-check"; 3 | import { runTest } from "./test-utils"; 4 | 5 | describe("LDA Instruction Tests", () => { 6 | test("LDA: Loads value from specified memory location into accumulator", async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit memory value 10 | fc.integer({ min: 0x0005, max: 0xffff }), // Random 16-bit memory address 11 | fc.record({ 12 | z: fc.boolean(), 13 | s: fc.boolean(), 14 | p: fc.boolean(), 15 | c: fc.boolean(), 16 | ac: fc.boolean(), 17 | }), // Random initial flag states 18 | async (memoryValue, memoryAddress, flags) => { 19 | const code = ` 20 | org 0x0000 21 | lda ${memoryAddress} 22 | hlt 23 | `; 24 | 25 | // Define the initial state of the CPU 26 | const initialCpuState = { 27 | accumulator: 0x00, // Initial accumulator value 28 | memory: { 29 | [memoryAddress]: memoryValue, // Memory contains the value at the specified address 30 | }, 31 | registers: { 32 | bc: { high: 0x00, low: 0x00 }, 33 | de: { high: 0x00, low: 0x00 }, 34 | hl: { high: 0x00, low: 0x00 }, 35 | }, 36 | flags, // Randomized flags from fast-check 37 | }; 38 | 39 | // Define the expected state of the CPU after execution 40 | const expectedCpuState = { 41 | ...initialCpuState, 42 | accumulator: memoryValue, // Accumulator is updated with the value from memory 43 | memory: { 44 | [memoryAddress]: memoryValue, 45 | }, 46 | programCounter: 0x0004, 47 | }; 48 | 49 | await runTest(code, initialCpuState, expectedCpuState); 50 | }, 51 | ), 52 | { verbose: true, numRuns: 100 }, // Run 100 variations for LDA instruction 53 | ); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /src/tests/lhld.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import * as fc from "fast-check"; 3 | import { runTest } from "./test-utils"; 4 | 5 | describe("LHLD Instruction Tests", () => { 6 | test("LHLD: Loads L and H register values from specified memory locations", async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | fc.integer({ min: 0x0005, max: 0xfffe }), // Random 16-bit memory address 10 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit memory value for L 11 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit memory value for H 12 | fc.record({ 13 | z: fc.boolean(), 14 | s: fc.boolean(), 15 | p: fc.boolean(), 16 | c: fc.boolean(), 17 | ac: fc.boolean(), 18 | }), // Random initial flag states 19 | async (memoryAddress, lValue, hValue, flags) => { 20 | const code = ` 21 | org 0x0000 22 | lhld ${memoryAddress} 23 | hlt 24 | `; 25 | 26 | // Define the initial state of the CPU 27 | const initialCpuState = { 28 | accumulator: 0x00, // Initial accumulator value 29 | memory: { 30 | [memoryAddress]: lValue, // L value stored at specified memory address 31 | [memoryAddress + 1]: hValue, // H value stored at the next memory address 32 | }, 33 | registers: { 34 | bc: { high: 0x00, low: 0x00 }, 35 | de: { high: 0x00, low: 0x00 }, 36 | hl: { high: 0x00, low: 0x00 }, // HL initially zero 37 | }, 38 | flags, // Randomized flags from fast-check 39 | }; 40 | 41 | // Define the expected state of the CPU after execution 42 | const expectedCpuState = { 43 | ...initialCpuState, 44 | registers: { 45 | ...initialCpuState.registers, 46 | hl: { high: hValue, low: lValue }, // HL registers updated with memory values 47 | }, 48 | programCounter: 0x0004, // PC should increment by 3 after LHLD (3-byte instruction) 49 | }; 50 | 51 | await runTest(code, initialCpuState, expectedCpuState); 52 | }, 53 | ), 54 | { verbose: true, numRuns: 100 }, // Run 100 variations for LHLD instruction 55 | ); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/tests/lxi.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import * as fc from "fast-check"; 3 | import { runTest } from "./test-utils"; 4 | 5 | describe("LXI Instruction Tests", () => { 6 | const registerPairs = ["B", "D", "H", "SP"]; 7 | 8 | registerPairs.forEach((pair) => { 9 | test(`LXI ${pair}: Load immediate data into ${pair} register pair`, async () => { 10 | await fc.assert( 11 | fc.asyncProperty( 12 | fc.integer({ min: 0x0000, max: 0xffff }), // Random 16-bit immediate data 13 | async (immediateValue) => { 14 | const lowByte = immediateValue & 0xff; 15 | const highByte = (immediateValue >> 8) & 0xff; 16 | 17 | const code = ` 18 | org 0x0000 19 | lxi ${pair.toLowerCase()}, 0x${immediateValue.toString(16).padStart(4, "0")} 20 | hlt 21 | `; 22 | 23 | // Define the initial state of the CPU 24 | const initialCpuState = { 25 | accumulator: 0x00, 26 | registers: { 27 | bc: { high: 0x00, low: 0x00 }, 28 | de: { high: 0x00, low: 0x00 }, 29 | hl: { high: 0x00, low: 0x00 }, 30 | }, 31 | stackPointer: 0xffff, 32 | flags: { 33 | z: false, 34 | s: false, 35 | p: false, 36 | c: false, 37 | ac: false, 38 | }, 39 | programCounter: 0x0000, // Initial PC 40 | }; 41 | 42 | // Define the expected state of the CPU after execution 43 | const expectedCpuState = { 44 | ...initialCpuState, 45 | registers: { 46 | ...initialCpuState.registers, 47 | ...(pair === "SP" 48 | ? {} 49 | : { 50 | [registerPairNames[pair.toLowerCase()]]: { high: highByte, low: lowByte }, 51 | }), 52 | }, 53 | stackPointer: pair === "SP" ? immediateValue : initialCpuState.stackPointer, 54 | programCounter: 0x0004, // PC should increment by 3 after LXI (3-byte instruction) 55 | }; 56 | 57 | await runTest(code, initialCpuState, expectedCpuState); 58 | }, 59 | ), 60 | { verbose: true, numRuns: 100 }, // Run 100 variations for each register pair 61 | ); 62 | }); 63 | }); 64 | }); 65 | 66 | const registerPairNames = { 67 | b: "bc", 68 | d: "de", 69 | h: "hl", 70 | }; -------------------------------------------------------------------------------- /src/tests/mvi.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import { runTest } from './test-utils'; 3 | 4 | describe('MVI Instruction Tests', () => { 5 | 6 | // Test 1: MVI A with a specific value 7 | test('MVI A, 5Ah', async () => { 8 | const code = ` 9 | MVI A, 5Ah 10 | HLT 11 | `; 12 | const expectedCpuState = { 13 | accumulator: 0x5A, // Verifying the accumulator value 14 | programCounter: 0x0003, // After HLT, PC should increment by 3 15 | }; 16 | await runTest(code, {}, expectedCpuState); 17 | }); 18 | 19 | // Test 2: MVI B with a specific value 20 | test('MVI B, 1Eh', async () => { 21 | const code = ` 22 | MVI B, 1Eh 23 | HLT 24 | `; 25 | const expectedCpuState = { 26 | registers: { 27 | bc: { high: 0x1E } // Verifying the B register (high part of BC) 28 | }, 29 | programCounter: 0x0003, // After HLT, PC should increment by 3 30 | }; 31 | await runTest(code, {}, expectedCpuState); 32 | }); 33 | 34 | // Test 3: MVI C with a specific value 35 | test('MVI C, 5Ah', async () => { 36 | const code = ` 37 | MVI C, 5Ah 38 | HLT 39 | `; 40 | const expectedCpuState = { 41 | registers: { 42 | bc: { low: 0x5A } // Verifying the C register (low part of BC) 43 | }, 44 | programCounter: 0x0003, // After HLT, PC should increment by 3 45 | }; 46 | await runTest(code, {}, expectedCpuState); 47 | }); 48 | 49 | // Test 4: MVI D with a specific value 50 | test('MVI D, 20h', async () => { 51 | const code = ` 52 | MVI D, 20h 53 | HLT 54 | `; 55 | const expectedCpuState = { 56 | registers: { 57 | de: { high: 0x20 } // Verifying the D register (high part of DE) 58 | }, 59 | programCounter: 0x0003, // After HLT, PC should increment by 3 60 | }; 61 | await runTest(code, {}, expectedCpuState); 62 | }); 63 | 64 | // Test 5: MVI E with a specific value 65 | test('MVI E, 3Ch', async () => { 66 | const code = ` 67 | MVI E, 3Ch 68 | HLT 69 | `; 70 | const expectedCpuState = { 71 | registers: { 72 | de: { low: 0x3C } // Verifying the E register (low part of DE) 73 | }, 74 | programCounter: 0x0003, // After HLT, PC should increment by 3 75 | }; 76 | await runTest(code, {}, expectedCpuState); 77 | }); 78 | 79 | // Test 6: MVI H with a specific value 80 | test('MVI H, 4Ah', async () => { 81 | const code = ` 82 | MVI H, 4Ah 83 | HLT 84 | `; 85 | const expectedCpuState = { 86 | registers: { 87 | hl: { high: 0x4A } // Verifying the H register (high part of HL) 88 | }, 89 | programCounter: 0x0003, // After HLT, PC should increment by 3 90 | }; 91 | await runTest(code, {}, expectedCpuState); 92 | }); 93 | 94 | // Test 7: MVI L with a specific value 95 | test('MVI L, 2Bh', async () => { 96 | const code = ` 97 | MVI L, 2Bh 98 | HLT 99 | `; 100 | const expectedCpuState = { 101 | registers: { 102 | hl: { low: 0x2B } // Verifying the L register (low part of HL) 103 | }, 104 | programCounter: 0x0003, // After HLT, PC should increment by 3 105 | }; 106 | await runTest(code, {}, expectedCpuState); 107 | }); 108 | 109 | }); -------------------------------------------------------------------------------- /src/tests/ori.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import * as fc from "fast-check"; 3 | import { runTest } from "./test-utils"; 4 | 5 | describe("ORI Instruction Tests", () => { 6 | test("ORI: Performs logical OR between accumulator and immediate data, resets carry and auxiliary carry flags", async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit accumulator value 10 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit immediate value 11 | fc.record({ 12 | z: fc.boolean(), 13 | s: fc.boolean(), 14 | p: fc.boolean(), 15 | c: fc.boolean(), 16 | ac: fc.boolean(), 17 | }), // Random initial flag states 18 | async (accumulator, immediateValue, flags) => { 19 | const result = accumulator | immediateValue; 20 | 21 | // Calculate expected flags 22 | const zeroFlag = result === 0; 23 | const signFlag = (result & 0x80) !== 0; 24 | const parityFlag = 25 | result 26 | .toString(2) 27 | .split("") 28 | .filter((bit) => bit === "1").length % 29 | 2 === 30 | 0; 31 | 32 | const code = ` 33 | org 0x0000 34 | ori ${immediateValue} 35 | hlt 36 | `; 37 | 38 | // Define the initial state of the CPU 39 | const initialCpuState = { 40 | accumulator, 41 | registers: { 42 | bc: { high: 0x00, low: 0x00 }, 43 | de: { high: 0x00, low: 0x00 }, 44 | hl: { high: 0x00, low: 0x00 }, 45 | }, 46 | flags, // Randomized flags from fast-check 47 | }; 48 | 49 | // Define the expected state of the CPU after execution 50 | const expectedCpuState = { 51 | ...initialCpuState, 52 | accumulator: result, // Result of the OR operation 53 | flags: { 54 | z: zeroFlag, 55 | s: signFlag, 56 | p: parityFlag, 57 | c: false, // Carry flag is always reset to 0 by ORI 58 | ac: false, // Auxiliary carry flag is always reset to 0 by ORI 59 | }, 60 | programCounter: 0x0003, 61 | }; 62 | 63 | await runTest(code, initialCpuState, expectedCpuState); 64 | }, 65 | ), 66 | { verbose: true, numRuns: 100 }, // Run 100 variations for ORI instruction 67 | ); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /src/tests/pchl.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import * as fc from 'fast-check'; 3 | import { runTest } from './test-utils'; 4 | 5 | describe('PCHL Instruction Property-Based Tests', () => { 6 | test('PCHL: Sets PC to the value in HL', async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | // Generate values for H and L registers 10 | fc.integer({ min: 0x01, max: 0xFE }), 11 | fc.integer({ min: 0x01, max: 0xFE }), 12 | async (high, low) => { 13 | const code = ` 14 | pchl 15 | org ${(high<<8)|low} 16 | hlt 17 | `; 18 | 19 | const initialCpuState = { 20 | accumulator: 0x00, 21 | registers: { 22 | bc: { high: 0x00, low: 0x00 }, 23 | de: { high: 0x00, low: 0x00 }, 24 | hl: { high, low }, // Random values for H and L 25 | }, 26 | flags: { 27 | z: false, 28 | s: false, 29 | p: false, 30 | c: false, 31 | ac: false, 32 | }, 33 | programCounter: 0x0000, // Initial PC is 0 34 | memory: {}, 35 | }; 36 | 37 | const expectedCpuState = { 38 | ...initialCpuState, 39 | programCounter: ((high << 8) | low) + 1, // PC = (H << 8) | L 40 | }; 41 | 42 | await runTest(code, initialCpuState, expectedCpuState); 43 | } 44 | ), 45 | { 46 | verbose: true, 47 | numRuns: 100, // Test with 100 random combinations of H and L 48 | } 49 | ); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /src/tests/ral.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import { runTest } from "./test-utils"; 3 | 4 | describe("RAL Instruction Tests", () => { 5 | test("RAL: Rotate accumulator left with carry flag 0", async () => { 6 | const code = ` 7 | mvi a, 0aah ; A = 10101010 8 | ral 9 | hlt 10 | `; 11 | const expectedCpuState = { 12 | accumulator: 0x54, // A = 01010100 after rotation 13 | flags: { 14 | z: false, 15 | s: false, 16 | p: false, 17 | c: true, // Carry flag set because of the bit shifted out from A 18 | }, 19 | programCounter: 0x0004, 20 | }; 21 | await runTest(code, {}, expectedCpuState); 22 | }); 23 | 24 | test("RAL: Rotate accumulator left with carry flag 1", async () => { 25 | const code = ` 26 | mvi a, 0aah ; A = 10101010 27 | stc ; Set carry flag to 1 28 | ral 29 | hlt 30 | `; 31 | const expectedCpuState = { 32 | accumulator: 0x55, // A = 01010101 after rotation 33 | flags: { 34 | z: false, 35 | s: false, 36 | p: false, 37 | c: true, 38 | }, 39 | programCounter: 0x0005, 40 | }; 41 | await runTest(code, {}, expectedCpuState); 42 | }); 43 | }); -------------------------------------------------------------------------------- /src/tests/rar.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import { runTest } from "./test-utils"; 3 | 4 | describe("RAR Instruction Tests", () => { 5 | test("RAR: Rotate accumulator left with carry flag 0", async () => { 6 | const code = ` 7 | mvi a, 0aah ; A = 10101010 8 | rar 9 | hlt 10 | `; 11 | const expectedCpuState = { 12 | accumulator: 0x55, // A = 01010101 after rotation 13 | flags: { 14 | z: false, 15 | s: false, 16 | p: false, 17 | c: false, 18 | }, 19 | programCounter: 0x0004, 20 | }; 21 | await runTest(code, {}, expectedCpuState); 22 | }); 23 | 24 | test("RAR: Rotate accumulator left with carry flag 1", async () => { 25 | const code = ` 26 | mvi a, 0aah ; A = 10101010 27 | stc ; Set carry flag to 1 28 | rar 29 | hlt 30 | `; 31 | const expectedCpuState = { 32 | accumulator: 0xd5, // A = 11010101 after rotation 33 | flags: { 34 | z: false, 35 | s: false, 36 | p: false, 37 | c: false, 38 | }, 39 | programCounter: 0x0005, 40 | }; 41 | await runTest(code, {}, expectedCpuState); 42 | }); 43 | }); -------------------------------------------------------------------------------- /src/tests/rc.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import * as fc from 'fast-check'; 3 | import { runTest } from './test-utils'; 4 | 5 | describe('RC Instruction Property-Based Tests', () => { 6 | test('RC: Pops two bytes and updates program counter only if carry flag is set', async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | // Max has to be 2 less than 0xffff because there cannot be an RC without a call 10 | fc.integer({ min: 0x1000, max: 0xFFFD }), // Stack pointer range (valid memory locations) 11 | fc.integer({ min: 0x0005, max: 0xFFFE }), // Random return address 12 | fc.boolean(), // Random carry flag state 13 | async (stackPointer, returnAddress, carryFlag) => { 14 | const lowByte = returnAddress & 0xFF; 15 | const highByte = (returnAddress >> 8) & 0xFF; 16 | 17 | const code = ` 18 | org 0x0000 19 | lxi sp, ${stackPointer} 20 | rc 21 | hlt 22 | org ${returnAddress} 23 | hlt 24 | `; 25 | 26 | const initialCpuState = { 27 | accumulator: 0x00, 28 | registers: { 29 | bc: { high: 0x00, low: 0x00 }, 30 | de: { high: 0x00, low: 0x00 }, 31 | }, 32 | flags: { 33 | z: false, 34 | s: false, 35 | p: false, 36 | c: carryFlag, // Randomized carry flag state 37 | ac: false, 38 | }, 39 | programCounter: 0x0000, // Initial PC 40 | memory: { 41 | [stackPointer]: lowByte, // Low byte of return address 42 | [stackPointer + 1]: highByte, // High byte of return address 43 | }, 44 | }; 45 | 46 | const expectedCpuState = { 47 | ...initialCpuState, 48 | stackPointer: carryFlag ? stackPointer + 2 : stackPointer, // SP incremented only if carry flag is set 49 | programCounter: carryFlag ? returnAddress + 1 : 0x0005, // PC set to return address if carry; otherwise, next instruction 50 | }; 51 | 52 | await runTest(code, initialCpuState, expectedCpuState); 53 | } 54 | ), 55 | { verbose: true, numRuns: 100 } // Run 100 variations of stack pointer, return address, and carry flag 56 | ); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/tests/rlc.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import * as fc from 'fast-check'; 3 | import { runTest } from './test-utils'; 4 | 5 | describe('RLC Instruction Property-Based Tests', () => { 6 | test('RLC: Rotates accumulator and modifies only carry flag', async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | // Generate an 8-bit value for the accumulator and random flag states 10 | fc.integer({ min: 0x00, max: 0xFF }), 11 | fc.record({ 12 | z: fc.boolean(), 13 | s: fc.boolean(), 14 | p: fc.boolean(), 15 | ac: fc.boolean(), 16 | }), 17 | async (initialAccumulator, initialFlags) => { 18 | const code = ` 19 | rlc 20 | hlt 21 | `; 22 | 23 | // Calculate the expected results 24 | const carryFlag = (initialAccumulator >> 7) & 0x1; // High-order bit becomes the carry flag 25 | const rotatedAccumulator = ((initialAccumulator << 1) | carryFlag) & 0xFF; // Rotate left 26 | 27 | const initialCpuState = { 28 | accumulator: initialAccumulator, 29 | registers: { 30 | bc: { high: 0x00, low: 0x00 }, 31 | de: { high: 0x00, low: 0x00 }, 32 | hl: { high: 0x00, low: 0x00 }, 33 | }, 34 | flags: { 35 | ...initialFlags, 36 | c: false, // Carry flag starts undefined 37 | }, 38 | programCounter: 0x0000, 39 | memory: {}, 40 | }; 41 | 42 | const expectedCpuState = { 43 | ...initialCpuState, 44 | accumulator: rotatedAccumulator, 45 | flags: { 46 | ...initialFlags, // Other flags should remain unchanged 47 | c: !!carryFlag, // Carry flag equals the original high-order bit 48 | }, 49 | programCounter: 0x0002, 50 | }; 51 | 52 | await runTest(code, initialCpuState, expectedCpuState); 53 | } 54 | ), 55 | { 56 | verbose: true, 57 | numRuns: 100, // Test with 100 random accumulator and flag states 58 | } 59 | ); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/tests/rnc.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import * as fc from 'fast-check'; 3 | import { runTest } from './test-utils'; 4 | 5 | describe('RNC Instruction Property-Based Tests', () => { 6 | test('RNC: Pops two bytes and updates program counter only if carry flag is not set', async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | // Max has to be 2 less than 0xffff because there cannot be an RNC without a call 10 | fc.integer({ min: 0x1000, max: 0xFFFD }), // Stack pointer range (valid memory locations) 11 | fc.integer({ min: 0x0005, max: 0xFFFE }), // Random return address 12 | fc.boolean(), // Random carry flag state 13 | async (stackPointer, returnAddress, carryFlag) => { 14 | const lowByte = returnAddress & 0xFF; 15 | const highByte = (returnAddress >> 8) & 0xFF; 16 | 17 | const code = ` 18 | org 0x0000 19 | lxi sp, ${stackPointer} 20 | rnc 21 | hlt 22 | org ${returnAddress} 23 | hlt 24 | `; 25 | 26 | const initialCpuState = { 27 | accumulator: 0x00, 28 | registers: { 29 | bc: { high: 0x00, low: 0x00 }, 30 | de: { high: 0x00, low: 0x00 }, 31 | }, 32 | flags: { 33 | z: false, 34 | s: false, 35 | p: false, 36 | c: carryFlag, // Randomized carry flag state 37 | ac: false, 38 | }, 39 | programCounter: 0x0000, // Initial PC 40 | memory: { 41 | [stackPointer]: lowByte, // Low byte of return address 42 | [stackPointer + 1]: highByte, // High byte of return address 43 | }, 44 | }; 45 | 46 | const expectedCpuState = { 47 | ...initialCpuState, 48 | stackPointer: carryFlag ? stackPointer : stackPointer + 2, // SP incremented only if carry flag is not set 49 | programCounter: carryFlag ? 0x0005 : returnAddress + 1, // PC set to return address if no carry; otherwise, next instruction 50 | }; 51 | 52 | await runTest(code, initialCpuState, expectedCpuState); 53 | } 54 | ), 55 | { verbose: true, numRuns: 100 } // Run 100 variations of stack pointer, return address, and carry flag 56 | ); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/tests/rnz.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import * as fc from 'fast-check'; 3 | import { runTest } from './test-utils'; 4 | 5 | describe('RNZ Instruction Property-Based Tests', () => { 6 | test('RNZ: Pops two bytes and updates program counter only if zero flag is not set', async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | // Max has to be 2 less than 0xffff because there cannot be an RNZ without a call 10 | fc.integer({ min: 0x1000, max: 0xFFFD }), // Stack pointer range (valid memory locations) 11 | fc.integer({ min: 0x0005, max: 0xFFFE }), // Random return address 12 | fc.boolean(), // Random zero flag state 13 | async (stackPointer, returnAddress, zeroFlag) => { 14 | const lowByte = returnAddress & 0xFF; 15 | const highByte = (returnAddress >> 8) & 0xFF; 16 | 17 | const code = ` 18 | org 0x0000 19 | lxi sp, ${stackPointer} 20 | rnz 21 | hlt 22 | org ${returnAddress} 23 | hlt 24 | `; 25 | 26 | const initialCpuState = { 27 | accumulator: 0x00, 28 | registers: { 29 | bc: { high: 0x00, low: 0x00 }, 30 | de: { high: 0x00, low: 0x00 }, 31 | }, 32 | flags: { 33 | z: zeroFlag, // Randomized zero flag state 34 | s: false, 35 | p: false, 36 | c: false, 37 | ac: false, 38 | }, 39 | programCounter: 0x0000, // Initial PC 40 | memory: { 41 | [stackPointer]: lowByte, // Low byte of return address 42 | [stackPointer + 1]: highByte, // High byte of return address 43 | }, 44 | }; 45 | 46 | const expectedCpuState = { 47 | ...initialCpuState, 48 | stackPointer: zeroFlag ? stackPointer : stackPointer + 2, // SP incremented only if zero flag is not set 49 | programCounter: zeroFlag ? 0x0005 : returnAddress + 1, // PC set to return address if not zero; otherwise, next instruction 50 | }; 51 | 52 | await runTest(code, initialCpuState, expectedCpuState); 53 | } 54 | ), 55 | { verbose: true, numRuns: 100 } // Run 100 variations of stack pointer, return address, and zero flag 56 | ); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/tests/rp.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import * as fc from 'fast-check'; 3 | import { runTest } from './test-utils'; 4 | 5 | describe('RP Instruction Property-Based Tests', () => { 6 | test('RP: Pops two bytes and updates program counter only if sign flag is not set', async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | // Max has to be 2 less than 0xffff because there cannot be an RP without a call 10 | fc.integer({ min: 0x1000, max: 0xFFFD }), // Stack pointer range (valid memory locations) 11 | fc.integer({ min: 0x0005, max: 0xFFFE }), // Random return address 12 | fc.boolean(), // Random sign flag state 13 | async (stackPointer, returnAddress, signFlag) => { 14 | const lowByte = returnAddress & 0xFF; 15 | const highByte = (returnAddress >> 8) & 0xFF; 16 | 17 | const code = ` 18 | org 0x0000 19 | lxi sp, ${stackPointer} 20 | rp 21 | hlt 22 | org ${returnAddress} 23 | hlt 24 | `; 25 | 26 | const initialCpuState = { 27 | accumulator: 0x00, 28 | registers: { 29 | bc: { high: 0x00, low: 0x00 }, 30 | de: { high: 0x00, low: 0x00 }, 31 | }, 32 | flags: { 33 | z: false, 34 | s: signFlag, // Randomized sign flag state 35 | p: false, 36 | c: false, 37 | ac: false, 38 | }, 39 | programCounter: 0x0000, // Initial PC 40 | memory: { 41 | [stackPointer]: lowByte, // Low byte of return address 42 | [stackPointer + 1]: highByte, // High byte of return address 43 | }, 44 | }; 45 | 46 | const expectedCpuState = { 47 | ...initialCpuState, 48 | stackPointer: signFlag ? stackPointer : stackPointer + 2, // SP incremented only if sign flag is not set 49 | programCounter: signFlag ? 0x0005 : returnAddress + 1, // PC set to return address if positive; otherwise, next instruction 50 | }; 51 | 52 | await runTest(code, initialCpuState, expectedCpuState); 53 | } 54 | ), 55 | { verbose: true, numRuns: 100 } // Run 100 variations of stack pointer, return address, and sign flag 56 | ); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/tests/rrc.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import * as fc from 'fast-check'; 3 | import { runTest } from './test-utils'; 4 | 5 | describe('RRC Instruction Property-Based Tests', () => { 6 | test('RRC: Rotates the accumulator right and sets the carry flag', async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | fc.integer({ min: 0x00, max: 0xFF }), // Generate 8-bit accumulator values 10 | async (initialAccumulator) => { 11 | const code = ` 12 | rrc 13 | hlt 14 | `; 15 | 16 | // Calculate the expected results 17 | const carryFlag = initialAccumulator & 0x1; // Low-order bit becomes the carry flag 18 | const rotatedAccumulator = ((initialAccumulator >> 1) | (initialAccumulator << 7)) & 0xFF; // Rotate right 19 | 20 | const initialCpuState = { 21 | accumulator: initialAccumulator, 22 | registers: { 23 | bc: { high: 0x00, low: 0x00 }, 24 | de: { high: 0x00, low: 0x00 }, 25 | hl: { high: 0x00, low: 0x00 }, 26 | }, 27 | flags: { 28 | z: false, 29 | s: false, 30 | p: false, 31 | c: false, // Carry flag starts undefined 32 | ac: false, 33 | }, 34 | programCounter: 0x0000, 35 | memory: {}, 36 | }; 37 | 38 | const expectedCpuState = { 39 | ...initialCpuState, 40 | accumulator: rotatedAccumulator, 41 | flags: { 42 | ...initialCpuState.flags, 43 | c: !!carryFlag, // Carry flag equals the original low-order bit 44 | }, 45 | programCounter: 0x0002, // Program counter increments by 1 46 | }; 47 | 48 | await runTest(code, initialCpuState, expectedCpuState); 49 | } 50 | ), 51 | { verbose: true, numRuns: 100 } // Test with 100 random accumulator values 52 | ); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/tests/rz.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import * as fc from 'fast-check'; 3 | import { runTest } from './test-utils'; 4 | 5 | describe('RZ Instruction Property-Based Tests', () => { 6 | test('RZ: Pops two bytes and updates program counter only if zero flag is set', async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | // Max has to be 2 less than 0xffff because there cannot be an RZ without a call 10 | fc.integer({ min: 0x1000, max: 0xFFFD }), // Stack pointer range (valid memory locations) 11 | fc.integer({ min: 0x0005, max: 0xFFFE }), // Random return address 12 | fc.boolean(), // Random zero flag state 13 | async (stackPointer, returnAddress, zeroFlag) => { 14 | const lowByte = returnAddress & 0xFF; 15 | const highByte = (returnAddress >> 8) & 0xFF; 16 | 17 | const code = ` 18 | org 0x0000 19 | lxi sp, ${stackPointer} 20 | rz 21 | hlt 22 | org ${returnAddress} 23 | hlt 24 | `; 25 | 26 | const initialCpuState = { 27 | accumulator: 0x00, 28 | registers: { 29 | bc: { high: 0x00, low: 0x00 }, 30 | de: { high: 0x00, low: 0x00 }, 31 | }, 32 | flags: { 33 | z: zeroFlag, // Randomized zero flag state 34 | s: false, 35 | p: false, 36 | c: false, 37 | ac: false, 38 | }, 39 | programCounter: 0x0000, // Initial PC 40 | memory: { 41 | [stackPointer]: lowByte, // Low byte of return address 42 | [stackPointer + 1]: highByte, // High byte of return address 43 | }, 44 | }; 45 | 46 | const expectedCpuState = { 47 | ...initialCpuState, 48 | stackPointer: zeroFlag ? stackPointer + 2 : stackPointer, // SP incremented only if zero flag is set 49 | programCounter: zeroFlag ? returnAddress + 1 : 0x0005, // PC set to return address if zero; otherwise, next instruction 50 | }; 51 | 52 | await runTest(code, initialCpuState, expectedCpuState); 53 | } 54 | ), 55 | { verbose: true, numRuns: 100 } // Run 100 variations of stack pointer, return address, and zero flag 56 | ); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/tests/sbb.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import { runTest } from './test-utils'; 3 | 4 | describe('SBB Instruction Tests', () => { 5 | 6 | // Test 1: SBB with carry, A = 4, B = 2, carry set 7 | test('SBB: Subtract with Carry', async () => { 8 | const code = ` 9 | MVI A, 4h ; Load 4 into the accumulator (A) 10 | MVI B, 2h ; Load 2 into register B 11 | STC ; Set carry flag 12 | SBB B ; Subtract B and carry from A (A = A - B - CY) 13 | HLT 14 | `; 15 | const expectedCpuState = { 16 | accumulator: 0x01, // Result: 4 - 2 - 1 (carry) = 1 17 | flags: { 18 | z: false, 19 | s: false, 20 | p: false, 21 | c: false, 22 | ac: true 23 | }, 24 | programCounter: 0x0007 // After HLT, PC should increment by 7 25 | }; 26 | await runTest(code, {}, expectedCpuState); 27 | }); 28 | }); -------------------------------------------------------------------------------- /src/tests/shld.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import * as fc from "fast-check"; 3 | import { runTest } from "./test-utils"; 4 | 5 | describe("SHLD Instruction Tests", () => { 6 | test("SHLD: Stores L and H register values into specified memory locations", async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | fc.integer({ min: 0x0005, max: 0xfffe }), // Random 16-bit memory address 10 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit L register value 11 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit H register value 12 | fc.record({ 13 | z: fc.boolean(), 14 | s: fc.boolean(), 15 | p: fc.boolean(), 16 | c: fc.boolean(), 17 | ac: fc.boolean(), 18 | }), // Random initial flag states 19 | async (memoryAddress, lValue, hValue, flags) => { 20 | const code = ` 21 | org 0x0000 22 | shld ${memoryAddress} 23 | hlt 24 | `; 25 | 26 | // Define the initial state of the CPU 27 | const initialCpuState = { 28 | accumulator: 0x00, // Initial accumulator value 29 | memory: {}, // Empty memory 30 | registers: { 31 | bc: { high: 0x00, low: 0x00 }, 32 | de: { high: 0x00, low: 0x00 }, 33 | hl: { high: hValue, low: lValue }, // HL register pair values 34 | }, 35 | flags, // Randomized flags from fast-check 36 | }; 37 | 38 | // Define the expected state of the CPU after execution 39 | const expectedCpuState = { 40 | ...initialCpuState, 41 | memory: { 42 | ...initialCpuState.memory, 43 | [memoryAddress]: lValue, // L register value at specified memory address 44 | [memoryAddress + 1]: hValue, // H register value at next memory address 45 | }, 46 | programCounter: 0x0004, // PC should increment by 3 after SHLD (3-byte instruction) 47 | }; 48 | 49 | await runTest(code, initialCpuState, expectedCpuState); 50 | }, 51 | ), 52 | { verbose: true, numRuns: 100 }, // Run 100 variations for SHLD instruction 53 | ); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /src/tests/sphl.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import * as fc from 'fast-check'; 3 | import { runTest } from './test-utils'; 4 | 5 | describe('SPHL Instruction Property-Based Tests', () => { 6 | test('SPHL: Sets SP to the value in HL', async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | // Generate values for H and L registers 10 | fc.integer({ min: 0x00, max: 0xFF }), 11 | fc.integer({ min: 0x00, max: 0xFF }), 12 | async (high, low) => { 13 | const code = ` 14 | sphl 15 | hlt 16 | `; 17 | 18 | const initialCpuState = { 19 | accumulator: 0x00, 20 | registers: { 21 | bc: { high: 0x00, low: 0x00 }, 22 | de: { high: 0x00, low: 0x00 }, 23 | hl: { high, low }, // Random values for H and L 24 | }, 25 | flags: { 26 | z: false, 27 | s: false, 28 | p: false, 29 | c: false, 30 | ac: false, 31 | }, 32 | stackPointer: 0x0000, // Initial SP is 0 33 | programCounter: 0x0000, 34 | memory: {}, 35 | }; 36 | 37 | const expectedCpuState = { 38 | ...initialCpuState, 39 | stackPointer: (high << 8) | low, // SP = (H << 8) | L 40 | programCounter: 0x0002, 41 | }; 42 | 43 | await runTest(code, initialCpuState, expectedCpuState); 44 | } 45 | ), 46 | { 47 | verbose: true, 48 | numRuns: 100, // Test with 100 random combinations of H and L 49 | } 50 | ); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/tests/sta.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import * as fc from "fast-check"; 3 | import { runTest } from "./test-utils"; 4 | 5 | describe("STA Instruction Tests", () => { 6 | test("STA: Stores accumulator value in specified memory location", async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit accumulator value 10 | fc.integer({ min: 0x0005, max: 0xffff }), // Random 16-bit memory address 11 | fc.record({ 12 | z: fc.boolean(), 13 | s: fc.boolean(), 14 | p: fc.boolean(), 15 | c: fc.boolean(), 16 | ac: fc.boolean(), 17 | }), // Random initial flag states 18 | async (accumulator, memoryAddress, flags) => { 19 | const code = ` 20 | org 0x0000 21 | sta ${memoryAddress} 22 | hlt 23 | `; 24 | 25 | // Define the initial state of the CPU 26 | const initialCpuState = { 27 | accumulator, 28 | memory: {}, // Empty memory 29 | registers: { 30 | bc: { high: 0x00, low: 0x00 }, 31 | de: { high: 0x00, low: 0x00 }, 32 | hl: { high: 0x00, low: 0x00 }, 33 | }, 34 | flags, // Randomized flags from fast-check 35 | }; 36 | 37 | // Define the expected state of the CPU after execution 38 | const expectedCpuState = { 39 | ...initialCpuState, 40 | memory: { 41 | ...initialCpuState.memory, 42 | [memoryAddress]: accumulator, // Memory at the address is updated with the accumulator value 43 | }, 44 | programCounter: 0x0004, // PC should increment by 3 after STA (3-byte instruction) 45 | }; 46 | 47 | await runTest(code, initialCpuState, expectedCpuState); 48 | }, 49 | ), 50 | { verbose: true, numRuns: 100 }, // Run 100 variations for STA instruction 51 | ); 52 | }); 53 | }); -------------------------------------------------------------------------------- /src/tests/stax.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import { runTest } from "./test-utils"; 3 | 4 | describe("STAX Instruction Tests", () => { 5 | test("STAX B: Stores value from A register into memory using BC pair", async () => { 6 | const code = ` 7 | mvi a, 9h 8 | mvi b, 3fh 9 | mvi c, 16h 10 | stax b 11 | hlt 12 | `; 13 | const expectedCpuState = { 14 | accumulator: 0x09, 15 | flags: { 16 | z: false, 17 | s: false, 18 | p: false, 19 | c: false, 20 | ac: false, 21 | }, 22 | programCounter: 0x0008, 23 | memory: { 24 | 0x3f16: 0x09, 25 | }, 26 | }; 27 | await runTest(code, {}, expectedCpuState); 28 | }); 29 | 30 | test("STAX D: Stores value from A register into memory using DE pair", async () => { 31 | const code = ` 32 | mvi a, 12h 33 | mvi d, 4fh 34 | mvi e, 22h 35 | stax d 36 | hlt 37 | `; 38 | const expectedCpuState = { 39 | accumulator: 0x12, 40 | flags: { 41 | z: false, 42 | s: false, 43 | p: false, 44 | c: false, 45 | ac: false, 46 | }, 47 | programCounter: 0x0008, 48 | memory: { 49 | 0x4f22: 0x12, 50 | }, 51 | }; 52 | await runTest(code, {}, expectedCpuState); 53 | }); 54 | }); -------------------------------------------------------------------------------- /src/tests/stc.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import { runTest } from './test-utils'; 3 | 4 | describe('STC Instruction Tests', () => { 5 | test('STC: Sets carry flag without modifying other flags or registers', async () => { 6 | const code = ` 7 | stc 8 | hlt 9 | `; 10 | 11 | const initialCpuState = { 12 | flags: { 13 | z: false, 14 | s: false, 15 | p: false, 16 | c: false, 17 | ac: false, 18 | }, 19 | }; 20 | 21 | const expectedCpuState = { 22 | flags: { 23 | ...initialCpuState.flags, 24 | c: true, // Carry flag should be set to 1 25 | }, 26 | programCounter: 0x0002, 27 | }; 28 | 29 | await runTest(code, initialCpuState, expectedCpuState); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/tests/sui.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import * as fc from "fast-check"; 3 | import { runTest } from "./test-utils"; 4 | 5 | describe("SUI Instruction Tests", () => { 6 | test("SUI: Subtracts immediate data from accumulator and updates flags", async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit accumulator value 10 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit immediate value 11 | fc.record({ 12 | z: fc.boolean(), 13 | s: fc.boolean(), 14 | p: fc.boolean(), 15 | c: fc.boolean(), 16 | ac: fc.boolean(), 17 | }), // Random initial flag states 18 | async (accumulator, immediateValue, flags) => { 19 | const result = accumulator - immediateValue; 20 | 21 | // Calculate expected flags 22 | const zeroFlag = (result & 0xff) === 0; 23 | const signFlag = (result & 0x80) !== 0; 24 | const parityFlag = 25 | (result & 0xff) 26 | .toString(2) 27 | .split("") 28 | .filter((bit) => bit === "1").length % 29 | 2 === 30 | 0; 31 | const carryFlag = accumulator < immediateValue; 32 | const auxCarryFlag = (accumulator & 0x0f) + ((~immediateValue + 1) & 0x0f) > 0x0f; 33 | 34 | const code = ` 35 | org 0x0000 36 | sui ${immediateValue} 37 | hlt 38 | `; 39 | 40 | // Define the initial state of the CPU 41 | const initialCpuState = { 42 | accumulator, 43 | registers: { 44 | bc: { high: 0x00, low: 0x00 }, 45 | de: { high: 0x00, low: 0x00 }, 46 | hl: { high: 0x00, low: 0x00 }, 47 | }, 48 | flags, // Randomized flags from fast-check 49 | }; 50 | 51 | // Define the expected state of the CPU after execution 52 | const expectedCpuState = { 53 | ...initialCpuState, 54 | accumulator: result & 0xff, // Result masked to 8 bits 55 | flags: { 56 | z: zeroFlag, 57 | s: signFlag, 58 | p: parityFlag, 59 | c: carryFlag, // Carry flag indicates borrow 60 | ac: auxCarryFlag, // Auxiliary carry flag indicates lower nibble borrow 61 | }, 62 | programCounter: 0x0003, // PC should increment by 2 after SUI (2-byte instruction) 63 | }; 64 | 65 | await runTest(code, initialCpuState, expectedCpuState); 66 | }, 67 | ), 68 | { verbose: true, numRuns: 100 }, // Run 100 variations for SUI instruction 69 | ); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /src/tests/xchg.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import * as fc from 'fast-check'; 3 | import { runTest } from './test-utils'; 4 | 5 | describe('XCHG Instruction Property-Based Tests', () => { 6 | test('XCHG: Exchanges contents of H/L with D/E without modifying flags', async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | // Generate 8-bit values for H, L, D, and E 10 | fc.integer({ min: 0x00, max: 0xFF }), // High byte of HL 11 | fc.integer({ min: 0x00, max: 0xFF }), // Low byte of HL 12 | fc.integer({ min: 0x00, max: 0xFF }), // High byte of DE 13 | fc.integer({ min: 0x00, max: 0xFF }), // Low byte of DE 14 | fc.record({ 15 | z: fc.boolean(), 16 | s: fc.boolean(), 17 | p: fc.boolean(), 18 | c: fc.boolean(), 19 | ac: fc.boolean(), 20 | }), // Randomized initial flags 21 | async (h, l, d, e, initialFlags) => { 22 | const code = ` 23 | xchg 24 | hlt 25 | `; 26 | 27 | const initialCpuState = { 28 | accumulator: 0x00, 29 | registers: { 30 | bc: { high: 0x00, low: 0x00 }, 31 | de: { high: d, low: e }, 32 | hl: { high: h, low: l }, 33 | }, 34 | flags: initialFlags, // Randomized initial flags 35 | programCounter: 0x0000, 36 | memory: {}, 37 | }; 38 | 39 | const expectedCpuState = { 40 | ...initialCpuState, 41 | registers: { 42 | ...initialCpuState.registers, 43 | de: { high: h, low: l }, // D = H, E = L 44 | hl: { high: d, low: e }, // H = D, L = E 45 | }, 46 | flags: initialFlags, // Flags should remain unchanged 47 | programCounter: 0x0002, // PC advances to the next instruction 48 | }; 49 | 50 | await runTest(code, initialCpuState, expectedCpuState); 51 | } 52 | ), 53 | { verbose: true, numRuns: 100 } 54 | ); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/tests/xri.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import * as fc from "fast-check"; 3 | import { runTest } from "./test-utils"; 4 | 5 | describe("XRI Instruction Tests", () => { 6 | test("XRI: Performs logical XOR between accumulator and immediate data, resets carry and auxiliary carry flags", async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit accumulator value 10 | fc.integer({ min: 0x00, max: 0xff }), // Random 8-bit immediate value 11 | fc.record({ 12 | z: fc.boolean(), 13 | s: fc.boolean(), 14 | p: fc.boolean(), 15 | c: fc.boolean(), 16 | ac: fc.boolean(), 17 | }), // Random initial flag states 18 | async (accumulator, immediateValue, flags) => { 19 | const result = accumulator ^ immediateValue; 20 | 21 | // Calculate expected flags 22 | const zeroFlag = result === 0; 23 | const signFlag = (result & 0x80) !== 0; 24 | const parityFlag = 25 | result 26 | .toString(2) 27 | .split("") 28 | .filter((bit) => bit === "1").length % 29 | 2 === 30 | 0; 31 | 32 | const code = ` 33 | org 0x0000 34 | xri ${immediateValue} 35 | hlt 36 | `; 37 | 38 | // Define the initial state of the CPU 39 | const initialCpuState = { 40 | accumulator, 41 | registers: { 42 | bc: { high: 0x00, low: 0x00 }, 43 | de: { high: 0x00, low: 0x00 }, 44 | hl: { high: 0x00, low: 0x00 }, 45 | }, 46 | flags, // Randomized flags from fast-check 47 | }; 48 | 49 | // Define the expected state of the CPU after execution 50 | const expectedCpuState = { 51 | ...initialCpuState, 52 | accumulator: result, // Result of the XOR operation 53 | flags: { 54 | z: zeroFlag, 55 | s: signFlag, 56 | p: parityFlag, 57 | c: false, // Carry flag is always reset to 0 by XRI 58 | ac: false, // Auxiliary carry flag is always reset to 0 by XRI 59 | }, 60 | programCounter: 0x0003, 61 | }; 62 | 63 | await runTest(code, initialCpuState, expectedCpuState); 64 | }, 65 | ), 66 | { verbose: true, numRuns: 100 }, // Run 100 variations for XRI instruction 67 | ); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /src/tests/xthl.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'vitest'; 2 | import * as fc from 'fast-check'; 3 | import { runTest } from './test-utils'; 4 | 5 | describe('XTHL Instruction Property-Based Tests', () => { 6 | test('XTHL: Exchanges contents of H/L with top of stack without modifying flags', async () => { 7 | await fc.assert( 8 | fc.asyncProperty( 9 | // Generate 8-bit values for H, L, SP contents, and flags 10 | fc.integer({ min: 0x00, max: 0xFF }), // High byte of HL (H) 11 | fc.integer({ min: 0x00, max: 0xFF }), // Low byte of HL (L) 12 | fc.integer({ min: 0x00, max: 0xFF }), // Top of stack (SP) 13 | fc.integer({ min: 0x00, max: 0xFF }), // Top of stack + 1 (SP + 1) 14 | fc.record({ 15 | z: fc.boolean(), 16 | s: fc.boolean(), 17 | p: fc.boolean(), 18 | c: fc.boolean(), 19 | ac: fc.boolean(), 20 | }), // Randomized initial flags 21 | fc.integer({ min: 0x1000, max: 0xFFFF - 1 }), // Stack pointer (SP) 22 | async (h, l, spLow, spHigh, initialFlags, stackPointer) => { 23 | const code = ` 24 | lxi sp, ${stackPointer} 25 | xthl 26 | hlt 27 | `; 28 | 29 | const initialCpuState = { 30 | accumulator: 0x00, 31 | registers: { 32 | bc: { high: 0x00, low: 0x00 }, 33 | de: { high: 0x00, low: 0x00 }, 34 | hl: { high: h, low: l }, // HL register contents 35 | }, 36 | flags: initialFlags, // Randomized initial flags 37 | stackPointer, 38 | programCounter: 0x0000, 39 | memory: { 40 | [stackPointer]: spLow, // Top of stack (low byte) 41 | [stackPointer + 1]: spHigh, // Top of stack + 1 (high byte) 42 | }, 43 | }; 44 | 45 | const expectedCpuState = { 46 | ...initialCpuState, 47 | registers: { 48 | ...initialCpuState.registers, 49 | hl: { high: spHigh, low: spLow }, // HL gets the stack's top values 50 | }, 51 | memory: { 52 | ...initialCpuState.memory, 53 | [stackPointer]: l, // Stack top gets L (low byte of HL) 54 | [stackPointer + 1]: h, // Stack top + 1 gets H (high byte of HL) 55 | }, 56 | flags: initialFlags, // Flags should remain unchanged 57 | programCounter: 0x0005, // PC advances to the next instruction 58 | }; 59 | 60 | await runTest(code, initialCpuState, expectedCpuState); 61 | } 62 | ), 63 | { verbose: true, numRuns: 100 } 64 | ); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/themes/dark.css: -------------------------------------------------------------------------------- 1 | html[data-theme="dark"] { 2 | --sm-primary-50: 238 251 243; 3 | --sm-primary-100: 214 245 225; 4 | --sm-primary-200: 177 233 200; 5 | --sm-primary-300: 126 215 169; 6 | --sm-primary-400: 73 190 132; 7 | --sm-primary-500: 38 162 105; 8 | --sm-primary-600: 24 131 84; 9 | --sm-primary-700: 19 105 69; 10 | --sm-primary-800: 18 83 57; 11 | --sm-primary-900: 15 69 48; 12 | --sm-primary-950: 8 38 27; 13 | 14 | --sm-primary: var(--sm-primary-600); 15 | 16 | --sm-gray-50: 248 248 248; /* #f8f8f8 */ 17 | --sm-gray-100: 240 240 240; /* # */ 18 | --sm-gray-200: 220 220 220; /* #dcdcdc */ 19 | --sm-gray-300: 240 240 240; /* # */ 20 | --sm-gray-400: 115 115 115; /* #989898 */ 21 | --sm-gray-500: 125 126 130; /* #7c7c7c */ 22 | --sm-gray-600: 101 101 101; /* #656565 */ 23 | --sm-gray-700: 82 82 82; /* #525252 */ 24 | --sm-gray-800: 69 74 84; /* #464646 */ 25 | --sm-gray-900: 32 35 39; /* #3d3d3d */ 26 | --sm-gray-950: 21 24 30; /* #151814 */ 27 | 28 | --sm-page-background: 46 49 56; /* #2e3138 */ 29 | --sm-main-background: var(--sm-gray-950); 30 | --sm-secondary-background: var(--sm-gray-900); 31 | --sm-main-border: var(--sm-gray-800); 32 | 33 | --sm-primary-foreground: var(--sm-gray-100); 34 | --sm-primary-border: var(--sm-gray-600); 35 | 36 | --sm-secondary-foreground: var(--sm-gray-500); 37 | --sm-secondary-border: var(--sm-gray-600); 38 | 39 | --sm-active-foreground: var(--sm-gray-100); 40 | --sm-active-background: var(--sm-gray-800); 41 | --sm-active-border: var(--sm-gray-700); 42 | 43 | --sm-inactive-foreground: var(--sm-gray-400); 44 | --sm-inactive-border: var(--sm-gray-800); 45 | 46 | --sm-red-foreground: 248 113 113; /* #*/ 47 | --sm-green-foreground: 21 128 61; /* #15803d */ 48 | --sm-yellow-foreground: 234 179 8; /* #eab308 */ 49 | --sm-blue-foreground: 96 165 250; /* #60a5fa */ 50 | /* --sm-orange-foreground: 251 146 60; /* #fb923c */ 51 | --sm-orange-foreground: 253 186 116; /* #fdba74 */ 52 | 53 | /* Editor colors */ 54 | --sm-editor-active-line: 23 42 81; /* #1f2937 */ 55 | --sm-editor-debug-line: 37 99 235; /* #2563eb */ 56 | --sm-editor-active-line-gutter: 255 255 255; 57 | --sm-editor-cursor: 209 213 219; /* #d1d5db */ 58 | --sm-editor-selection-background: 30 58 138; /* #1e3a8a */ 59 | 60 | --sm-editor-opcode: 74 222 128; 61 | --sm-editor-directive: 96 165 250; 62 | --sm-editor-label: 250 204 21; 63 | --sm-editor-comment: 107 114 128; 64 | --sm-editor-register: 248 113 113; 65 | --sm-editor-number: 251 146 60; 66 | --sm-editor-string: 234 88 12; 67 | --sm-editor-operator: 244 114 182; 68 | --sm-editor-punctuation: 75 85 99; 69 | } 70 | -------------------------------------------------------------------------------- /src/themes/light.css: -------------------------------------------------------------------------------- 1 | html[data-theme="light"] { 2 | --sm-primary-50: 238 251 243; 3 | --sm-primary-100: 214 245 225; 4 | --sm-primary-200: 177 233 200; 5 | --sm-primary-300: 126 215 169; 6 | --sm-primary-400: 73 190 132; 7 | --sm-primary-500: 38 162 105; 8 | --sm-primary-600: 24 131 84; 9 | --sm-primary-700: 19 105 69; 10 | --sm-primary-800: 18 83 57; 11 | --sm-primary-900: 15 69 48; 12 | --sm-primary-950: 8 38 27; 13 | 14 | --sm-primary: var(--sm-primary-500); 15 | 16 | --sm-gray-50: 251 251 251; /* #fbfbfb */ 17 | --sm-gray-100: 239 239 239; /* #efefef */ 18 | --sm-gray-200: 220 220 220; /* #dcdcdc */ 19 | --sm-gray-300: 189 189 189; /* #bdbdbd */ 20 | --sm-gray-400: 152 152 152; /* #989898 */ 21 | --sm-gray-500: 124 124 124; /* #7c7c7c */ 22 | --sm-gray-600: 101 101 101; /* #656565 */ 23 | --sm-gray-700: 82 82 82; /* #525252 */ 24 | --sm-gray-800: 70 70 70; /* #464646 */ 25 | --sm-gray-900: 61 61 61; /* #3d3d3d */ 26 | --sm-gray-950: 41 41 41; /* #292929 */ 27 | 28 | --sm-page-background: 232 232 232; /* #e8e8e8 */ 29 | --sm-main-background: var(--sm-gray-50); 30 | --sm-secondary-background: var(--sm-gray-100); 31 | --sm-main-border: var(--sm-gray-300); 32 | 33 | --sm-primary-foreground: var(--sm-gray-700); 34 | --sm-primary-border: var(--sm-gray-600); 35 | 36 | --sm-secondary-foreground: var(--sm-gray-500); 37 | --sm-secondary-border: var(--sm-gray-600); 38 | 39 | --sm-active-foreground: var(--sm-gray-700); 40 | --sm-active-background: var(--sm-gray-200); 41 | --sm-active-border: var(--sm-gray-300); 42 | 43 | --sm-inactive-foreground: var(--sm-gray-400); 44 | --sm-inactive-border: var(--sm-gray-200); 45 | 46 | --sm-red-foreground: 185 28 28; /* #b91c1c */ 47 | --sm-green-foreground: 21 128 61; /* #15803d */ 48 | --sm-yellow-foreground: 234 179 8; /* #eab308 */ 49 | --sm-blue-foreground: 96 165 250; /* #60a5fa */ 50 | --sm-orange-foreground: 234 88 12; /* #ea580c */ 51 | 52 | /* Editor colors */ 53 | --sm-editor-active-line: 191 219 254; /* #bfdbfe */ 54 | --sm-editor-debug-line: 96 165 250; /* #60a5fa */ 55 | --sm-editor-active-line-gutter: 255 255 255; 56 | --sm-editor-cursor: 75 85 99; /* #4b5563 */ 57 | --sm-editor-selection-background: 191 219 254; /* #bfdbfe */ 58 | 59 | --sm-editor-opcode: 28 128 61; 60 | --sm-editor-directive: 29 78 216; 61 | --sm-editor-label: 168 91 7; 62 | --sm-editor-comment: 107 114 128; 63 | --sm-editor-register: 185 28 28; 64 | --sm-editor-number: 194 65 12; 65 | --sm-editor-string: 234 88 12; 66 | --sm-editor-operator: 190 24 93; 67 | --sm-editor-punctuation: 75 85 99; 68 | } 69 | -------------------------------------------------------------------------------- /src/utils/NumberFormat.js: -------------------------------------------------------------------------------- 1 | export function toByteString(n) { 2 | if (n == null) return ""; 3 | 4 | if (n < 16) return `0${toRadix(16, n)}`; 5 | else return toRadix(16, n); 6 | } 7 | 8 | export function toRadix(r, n) { 9 | if (n == null || r == null) return ""; 10 | 11 | function getChr(c) { 12 | return c < 10 ? c.toString() : String.fromCharCode(87 + c); 13 | } 14 | 15 | function getStr(b) { 16 | return n < b ? getChr(n) : toRadix(r, Math.floor(n / b)) + getChr(n % b); 17 | } 18 | 19 | if (r >= 2 && r <= 16) { 20 | return getStr(r).toUpperCase(); 21 | } else { 22 | return n.toString().toUpperCase(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/deepMerge.js: -------------------------------------------------------------------------------- 1 | function isObject(item) { 2 | return (item && typeof item === 'object' && !Array.isArray(item)); 3 | } 4 | 5 | /** 6 | * Deep merge two objects. 7 | * @param target 8 | * @param ...sources 9 | */ 10 | export function deepMerge(target, ...sources) { 11 | if (!sources.length) return target; 12 | const source = sources.shift(); 13 | 14 | if (isObject(target) && isObject(source)) { 15 | for (const key in source) { 16 | if (isObject(source[key])) { 17 | if (!target[key]) Object.assign(target, { [key]: {} }); 18 | deepMerge(target[key], source[key]); 19 | } else { 20 | Object.assign(target, { [key]: source[key] }); 21 | } 22 | } 23 | } 24 | 25 | return deepMerge(target, ...sources); 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/lens.js: -------------------------------------------------------------------------------- 1 | export function createLens(path) { 2 | return { 3 | get: (obj) => path.reduce((acc, key) => acc[key], obj), // Dynamically traverse the path for the getter 4 | set: (obj, value) => { 5 | // Traverse to the second-last key 6 | const lastKey = path.pop(); 7 | const target = path.reduce((acc, key) => acc[key], obj); 8 | target[lastKey] = value; // Set the value at the last key 9 | } 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /tailwind.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | darkMode: ["class", '[data-theme="dark"]'], 4 | }; 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "jsxImportSource": "solid-js", 6 | "strictNullChecks": true, 7 | "allowJs": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /vitest.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | import peggy from 'vite-plugin-peggy-loader'; 3 | 4 | export default defineConfig({ 5 | plugins: [peggy()], 6 | test: {}, 7 | }) 8 | --------------------------------------------------------------------------------