use grid layout. begin ui cleanup
This commit is contained in:
parent
e2d3bae806
commit
e7cb198123
3
TODO
3
TODO
|
@ -1,6 +1,5 @@
|
|||
Add screen (VRAM?)
|
||||
Live memory and register editing (Probably should pause autostep when it reaches the cell you're modifying)
|
||||
Draw lines between registers and memory when used
|
||||
Explain mode to explain what instructions are doing
|
||||
Window showing ISA
|
||||
|
||||
Save binary button
|
31
index.html
31
index.html
|
@ -4,30 +4,27 @@
|
|||
<meta charset="UTF-8" />
|
||||
<script src="./dist/main.js"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<title>Virtual 8-Bit Computer</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<div id="title">VIRTUAL 8-BIT COMPUTER</div>
|
||||
<div id="container">
|
||||
<div id="subcontainer">
|
||||
<div id="registers"></div>
|
||||
<div id="labelcontainer">
|
||||
<div id="registers_label">←REGISTERS</div>
|
||||
<div id="memory_label">MEMORY↯</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="memory"></div>
|
||||
<div id="registers"></div>
|
||||
<div id="labelcontainer">
|
||||
<div id="registers_label">←REGISTERS</div>
|
||||
<div id="memory_label">MEMORY↯</div>
|
||||
</div>
|
||||
<div id="memory"></div>
|
||||
<div id="instruction_explainer"></div>
|
||||
<div id="printout"></div>
|
||||
</div>
|
||||
<div id="controls_bar">
|
||||
<button type="button" id="pause_play_button">Start</button>
|
||||
<button type="button" id="step_button">Step</button>
|
||||
<label for="binary_upload" class="button">Load Binary</label>
|
||||
<input id="binary_upload" name="binary_upload" id="binary_upload" style="visibility: hidden" type="file" />
|
||||
<div id="controls_bar">
|
||||
<button type="button" id="pause_play_button">Start</button>
|
||||
<button type="button" id="step_button">Step</button>
|
||||
<label for="binary_upload" class="button">Load Binary</label>
|
||||
<input id="binary_upload" name="binary_upload" id="binary_upload" style="display: none" type="file" />
|
||||
<input type="range" name="" id="delay_range" min="0" max="1500" />
|
||||
</div>
|
||||
<span id="cycles"></span>
|
||||
</div>
|
||||
<pre id="ISA"></pre>
|
||||
</body>
|
||||
|
|
394
package-lock.json
generated
394
package-lock.json
generated
|
@ -7,10 +7,14 @@
|
|||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"css-loader": "^6.10.0",
|
||||
"eslint": "^8.44.0",
|
||||
"sass": "^1.71.0",
|
||||
"sass-loader": "^14.1.0",
|
||||
"style-loader": "^3.3.4",
|
||||
"ts-loader": "^9.4.4",
|
||||
"typescript": "^5.1.6",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack": "^5.90.2",
|
||||
"webpack-cli": "^5.1.4"
|
||||
}
|
||||
},
|
||||
|
@ -782,6 +786,19 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
|
@ -803,6 +820,15 @@
|
|||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
|
@ -907,6 +933,42 @@
|
|||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/chrome-trace-event": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
|
||||
|
@ -980,6 +1042,53 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-loader": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz",
|
||||
"integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"icss-utils": "^5.1.0",
|
||||
"postcss": "^8.4.33",
|
||||
"postcss-modules-extract-imports": "^3.0.0",
|
||||
"postcss-modules-local-by-default": "^4.0.4",
|
||||
"postcss-modules-scope": "^3.1.1",
|
||||
"postcss-modules-values": "^4.0.0",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@rspack/core": "0.x || 1.x",
|
||||
"webpack": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@rspack/core": {
|
||||
"optional": true
|
||||
},
|
||||
"webpack": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"cssesc": "bin/cssesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
|
@ -1397,6 +1506,20 @@
|
|||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
|
@ -1534,6 +1657,18 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/icss-utils": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
|
||||
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >= 14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||
|
@ -1543,6 +1678,12 @@
|
|||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
|
||||
"integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
|
@ -1612,6 +1753,18 @@
|
|||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
||||
|
@ -1898,6 +2051,24 @@
|
|||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/natural-compare": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
|
@ -1916,6 +2087,15 @@
|
|||
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
|
@ -2117,6 +2297,112 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.35",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
|
||||
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-modules-extract-imports": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
|
||||
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >= 14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-modules-local-by-default": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz",
|
||||
"integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"icss-utils": "^5.0.0",
|
||||
"postcss-selector-parser": "^6.0.2",
|
||||
"postcss-value-parser": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >= 14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-modules-scope": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz",
|
||||
"integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "^6.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >= 14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-modules-values": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
|
||||
"integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"icss-utils": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >= 14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz",
|
||||
"integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-value-parser": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
|
@ -2164,6 +2450,18 @@
|
|||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rechoir": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
|
||||
|
@ -2291,6 +2589,63 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.71.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.71.0.tgz",
|
||||
"integrity": "sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-loader": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.0.tgz",
|
||||
"integrity": "sha512-LS2mLeFWA+orYxHNu+O18Xe4jR0kyamNOOUsE3NyBP4DvIL+8stHpNX0arYTItdPe80kluIiJ7Wfe/9iHSRO0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"neo-async": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.12.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@rspack/core": "0.x || 1.x",
|
||||
"node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
|
||||
"sass": "^1.3.0",
|
||||
"sass-embedded": "*",
|
||||
"webpack": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@rspack/core": {
|
||||
"optional": true
|
||||
},
|
||||
"node-sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass-embedded": {
|
||||
"optional": true
|
||||
},
|
||||
"webpack": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
||||
|
@ -2384,6 +2739,15 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
|
@ -2427,6 +2791,22 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/style-loader": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz",
|
||||
"integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 12.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
@ -2644,6 +3024,12 @@
|
|||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
|
||||
|
@ -2658,9 +3044,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.90.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz",
|
||||
"integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==",
|
||||
"version": "5.90.2",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.2.tgz",
|
||||
"integrity": "sha512-ziXu8ABGr0InCMEYFnHrYweinHK2PWrMqnwdHk2oK3rRhv/1B+2FnfwYv5oD+RrknK/Pp/Hmyvu+eAsaMYhzCw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
|
|
|
@ -3,14 +3,18 @@
|
|||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"css-loader": "^6.10.0",
|
||||
"eslint": "^8.44.0",
|
||||
"sass": "^1.71.0",
|
||||
"sass-loader": "^14.1.0",
|
||||
"style-loader": "^3.3.4",
|
||||
"ts-loader": "^9.4.4",
|
||||
"typescript": "^5.1.6",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack": "^5.90.2",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --mode=production",
|
||||
"watch": "webpack --mode=development --watch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { CpuEvent, CpuEventHandler, MemoryCellType, UiEvent, UiEventHandler } from "./events";
|
||||
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
|
||||
import { byte_array_to_js_source, format_hex, u8 } from "./etc";
|
||||
import { EventHandler } from "./eventHandler";
|
||||
import { ConstParam, Instruction, ISA, MemorParam, RegisParam } from "./instructionSet";
|
||||
import { Instruction, ISA } from "./instructionSet";
|
||||
|
||||
export type TempInstrState = {
|
||||
pos: u8;
|
||||
|
@ -9,16 +9,11 @@ export type TempInstrState = {
|
|||
instr: Instruction;
|
||||
params: Uint8Array;
|
||||
};
|
||||
export type ComputerState = {
|
||||
memory: Uint8Array;
|
||||
program_counter: u8;
|
||||
registers: Uint8Array;
|
||||
current_instruction: TempInstrState | null;
|
||||
};
|
||||
|
||||
export class Computer {
|
||||
private memory = new Uint8Array(256);
|
||||
private registers = new Uint8Array(8);
|
||||
private call_stack: Array<u8> = [];
|
||||
private program_counter: u8 = 0;
|
||||
private current_instr: TempInstrState | null = null;
|
||||
events: CpuEventHandler = new EventHandler<CpuEvent>() as CpuEventHandler;
|
||||
|
@ -36,13 +31,13 @@ export class Computer {
|
|||
if (this.current_instr === null) {
|
||||
const parsed_instruction = ISA.getInstruction(current_byte);
|
||||
if (parsed_instruction === null) {
|
||||
this.events.dispatch(CpuEvent.MemoryByteParsed, {
|
||||
type: MemoryCellType.InvalidInstruction,
|
||||
this.events.dispatch(CpuEvent.InvalidParsed, {
|
||||
pos: this.program_counter,
|
||||
code: current_byte,
|
||||
});
|
||||
console.log(`Invalid instruction: ${format_hex(current_byte)}`);
|
||||
this.step_forward();
|
||||
this.events.dispatch(CpuEvent.ClockCycle, null);
|
||||
return;
|
||||
}
|
||||
this.current_instr = {
|
||||
|
@ -51,8 +46,7 @@ export class Computer {
|
|||
params_found: 0,
|
||||
params: new Uint8Array(parsed_instruction.params.length),
|
||||
};
|
||||
this.events.dispatch(CpuEvent.MemoryByteParsed, {
|
||||
type: MemoryCellType.Instruction,
|
||||
this.events.dispatch(CpuEvent.InstructionParsed, {
|
||||
pos: this.program_counter,
|
||||
instr: parsed_instruction,
|
||||
code: current_byte,
|
||||
|
@ -61,32 +55,23 @@ export class Computer {
|
|||
|
||||
if (this.current_instr.pos === this.program_counter && this.current_instr.params.length > 0) {
|
||||
this.step_forward();
|
||||
this.events.dispatch(CpuEvent.ClockCycle, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.current_instr.params.length !== this.current_instr.params_found) {
|
||||
let a;
|
||||
const b = this.current_instr.instr.params[this.current_instr.params_found];
|
||||
if (b instanceof ConstParam) {
|
||||
a = MemoryCellType.Constant;
|
||||
} else if (b instanceof RegisParam) {
|
||||
a = MemoryCellType.Register;
|
||||
} else if (b instanceof MemorParam) {
|
||||
a = MemoryCellType.Memory;
|
||||
}
|
||||
if (a === undefined) {
|
||||
throw new Error("Shouldn't");
|
||||
}
|
||||
this.events.dispatch(CpuEvent.MemoryByteParsed, {
|
||||
type: a,
|
||||
|
||||
this.events.dispatch(CpuEvent.ParameterParsed, {
|
||||
param: b,
|
||||
pos: this.program_counter,
|
||||
code: current_byte,
|
||||
param: b,
|
||||
});
|
||||
this.current_instr.params[this.current_instr.params_found] = current_byte;
|
||||
this.current_instr.params_found += 1;
|
||||
if (this.current_instr.params.length !== this.current_instr.params_found) {
|
||||
this.step_forward();
|
||||
this.events.dispatch(CpuEvent.ClockCycle, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +89,7 @@ export class Computer {
|
|||
if (execution_post_action_state.should_step) {
|
||||
this.step_forward();
|
||||
}
|
||||
this.events.dispatch(CpuEvent.ClockCycle, null);
|
||||
}
|
||||
|
||||
getMemory(address: u8): u8 {
|
||||
|
@ -130,18 +116,30 @@ export class Computer {
|
|||
this.program_counter = new_value;
|
||||
}
|
||||
|
||||
pushCallStack(address: u8): boolean {
|
||||
if (this.call_stack.length >= 8) return false;
|
||||
this.call_stack.push(address);
|
||||
return true;
|
||||
}
|
||||
|
||||
popCallStack(): u8 | null {
|
||||
return this.call_stack.pop() ?? null;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.events.dispatch(CpuEvent.Reset, null);
|
||||
this.memory = new Uint8Array(256);
|
||||
this.registers = new Uint8Array(8);
|
||||
this.call_stack = [];
|
||||
this.current_instr = null;
|
||||
this.program_counter = 0;
|
||||
this.events.dispatch(CpuEvent.Reset, null);
|
||||
}
|
||||
|
||||
init_events(ui: UiEventHandler): void {
|
||||
ui.listen(UiEvent.RequestCpuCycle, () => {
|
||||
this.cycle();
|
||||
ui.listen(UiEvent.RequestCpuCycle, (n) => {
|
||||
for (let i = 0; i < n; i++) this.cycle();
|
||||
});
|
||||
ui.listen(UiEvent.RequestMemoryChange, ({ address, value }) => this.setMemory(address, value));
|
||||
}
|
||||
|
||||
load_memory(program: Array<u8>): void {
|
||||
|
@ -149,9 +147,8 @@ export class Computer {
|
|||
const max_loop = Math.min(this.memory.length, program.length);
|
||||
for (let i = 0; i < max_loop; i++) {
|
||||
// Don't fire event if no change is made
|
||||
if (this.memory[i] === program[i]) {
|
||||
continue;
|
||||
}
|
||||
if (this.memory[i] === program[i]) continue;
|
||||
|
||||
this.memory[i] = program[i];
|
||||
this.events.dispatch(CpuEvent.MemoryChanged, { address: i, value: program[i] });
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { UiEventHandler } from "./events";
|
||||
|
||||
// The u8 type represents an unsigned 8bit integer: byte. It does not add any safety, other than as a hint to the programmer.
|
||||
// export type u8 = 0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116|117|118|119|120|121|122|123|124|125|126|127|128|129|130|131|132|133|134|135|136|137|138|139|140|141|142|143|144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159|160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175|176|177|178|179|180|181|182|183|184|185|186|187|188|189|190|191|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240|241|242|243|244|245|246|247|248|249|250|251|252|253|254|255;
|
||||
export type u8 = number;
|
||||
|
||||
// Jquery lite
|
||||
|
|
|
@ -27,7 +27,7 @@ export class EventHandler<T> {
|
|||
const event = new Event<T>(identifier);
|
||||
this.events.push(event);
|
||||
}
|
||||
dispatch(identifier: T, event_data: unknown): void {
|
||||
dispatch(identifier: T, event_data?: unknown): void {
|
||||
const event = this.events.find((e) => e.identifier === identifier);
|
||||
if (event === undefined) {
|
||||
throw new Error("Event not found");
|
||||
|
|
|
@ -6,18 +6,14 @@ export enum CpuEvent {
|
|||
MemoryChanged,
|
||||
RegisterChanged,
|
||||
ProgramCounterChanged,
|
||||
MemoryByteParsed,
|
||||
InstructionParsed,
|
||||
ParameterParsed,
|
||||
InvalidParsed,
|
||||
InstructionExecuted,
|
||||
ClockCycle,
|
||||
Print,
|
||||
Reset,
|
||||
}
|
||||
|
||||
export enum MemoryCellType {
|
||||
Instruction,
|
||||
InvalidInstruction,
|
||||
Register,
|
||||
Memory,
|
||||
Constant,
|
||||
Halt,
|
||||
}
|
||||
|
||||
// Handily explained in https://www.cgjennings.ca/articles/typescript-events/
|
||||
|
@ -25,8 +21,12 @@ interface CpuEventMap {
|
|||
[CpuEvent.MemoryChanged]: { address: u8; value: u8 };
|
||||
[CpuEvent.RegisterChanged]: { register_no: u8; value: u8 };
|
||||
[CpuEvent.ProgramCounterChanged]: { counter: u8 };
|
||||
[CpuEvent.Halt]: null;
|
||||
[CpuEvent.Reset]: null;
|
||||
[CpuEvent.MemoryByteParsed]: { type: MemoryCellType; pos: u8; code: u8; param?: ParameterType; instr?: Instruction };
|
||||
[CpuEvent.ClockCycle]: null;
|
||||
[CpuEvent.InstructionParsed]: { pos: u8; code: u8; instr: Instruction };
|
||||
[CpuEvent.ParameterParsed]: { pos: u8; code: u8; param: ParameterType };
|
||||
[CpuEvent.InvalidParsed]: { pos: u8; code: u8 };
|
||||
[CpuEvent.InstructionExecuted]: { instr: Instruction };
|
||||
[CpuEvent.Print]: string;
|
||||
}
|
||||
|
@ -38,10 +38,12 @@ export interface CpuEventHandler extends EventHandler<CpuEvent> {
|
|||
|
||||
export enum UiEvent {
|
||||
RequestCpuCycle,
|
||||
RequestMemoryChange,
|
||||
}
|
||||
|
||||
interface UiEventMap {
|
||||
[UiEvent.RequestCpuCycle]: null;
|
||||
[UiEvent.RequestCpuCycle]: number;
|
||||
[UiEvent.RequestMemoryChange]: { address: u8; value: u8 };
|
||||
}
|
||||
|
||||
export interface UiEventHandler extends EventHandler<UiEvent> {
|
||||
|
|
34
src/index.ts
34
src/index.ts
|
@ -4,9 +4,28 @@ import { ISA } from "./instructionSet";
|
|||
import { generate_isa } from "./isaGenerator";
|
||||
import { UI } from "./ui";
|
||||
|
||||
import "./style.scss";
|
||||
|
||||
function main(): void {
|
||||
// const program = [
|
||||
// 0x2f, 0x00, 0xf0, 0x20, 0x07, 0x00, 0x50, 0x05, 0x06, 0x07, 0x11, 0x00, 0x05, 0xfe, 0x07, 0x30, 0x00, 0x10, 0x03,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57,
|
||||
// 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x00,
|
||||
// ];
|
||||
|
||||
const program = [
|
||||
0x2f, 0x00, 0xf0, 0x20, 0x07, 0x00, 0x50, 0x05, 0x06, 0x07, 0x11, 0x00, 0x05, 0xfe, 0x07, 0x30, 0x00, 0x10, 0x03,
|
||||
0x01, 0x00, 0x01, 0x00, 0xa0, 0x10, 0xff, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0xa1,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
|
@ -22,28 +41,25 @@ function main(): void {
|
|||
0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
const container = $("container");
|
||||
if (container === null) {
|
||||
throw new Error("no");
|
||||
}
|
||||
const computer = new Computer();
|
||||
|
||||
const ui = new UI(container);
|
||||
const ui = new UI();
|
||||
ui.init_events(computer.events);
|
||||
computer.load_memory(program);
|
||||
computer.init_events(ui.events);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(<any>window).comp = computer;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(<any>window).ui = ui;
|
||||
|
||||
$("ISA").textContent = generate_isa(ISA);
|
||||
|
||||
// eslint-disable-next-line prefer-arrow-callback
|
||||
$("binary_upload").addEventListener("change", function (e) {
|
||||
$("binary_upload").addEventListener("change", (e) => {
|
||||
if (e.target === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, prefer-destructuring
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const file: File = (<any>e.target).files[0];
|
||||
const reader = new FileReader();
|
||||
console.log(file);
|
||||
|
|
|
@ -1,16 +1,36 @@
|
|||
import { CpuEvent, CpuEventHandler } from "./events";
|
||||
import { format_hex, u8 } from "./etc";
|
||||
|
||||
export class ParameterType {
|
||||
export enum ParamType {
|
||||
Const,
|
||||
Register,
|
||||
Memory,
|
||||
}
|
||||
|
||||
export abstract class ParameterType {
|
||||
readonly desc: string;
|
||||
constructor(description: string) {
|
||||
readonly type: ParamType;
|
||||
constructor(description: string, p_type: ParamType) {
|
||||
this.desc = description;
|
||||
this.type = p_type;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConstParam extends ParameterType {}
|
||||
export class RegisParam extends ParameterType {}
|
||||
export class MemorParam extends ParameterType {}
|
||||
class ConstParam extends ParameterType {
|
||||
constructor(d: string) {
|
||||
super(d, ParamType.Const);
|
||||
}
|
||||
}
|
||||
class RegisParam extends ParameterType {
|
||||
constructor(d: string) {
|
||||
super(d, ParamType.Register);
|
||||
}
|
||||
}
|
||||
class MemorParam extends ParameterType {
|
||||
constructor(d: string) {
|
||||
super(d, ParamType.Memory);
|
||||
}
|
||||
}
|
||||
|
||||
interface GenericComputer {
|
||||
getMemory: (address: u8) => u8;
|
||||
|
@ -19,6 +39,8 @@ interface GenericComputer {
|
|||
getProgramCounter: () => u8;
|
||||
getRegister: (number: u8) => u8;
|
||||
setRegister: (number: u8, value: u8) => void;
|
||||
pushCallStack: (address: u8) => boolean;
|
||||
popCallStack: () => u8 | null;
|
||||
}
|
||||
|
||||
interface AfterExecutionComputerAction {
|
||||
|
@ -31,7 +53,11 @@ export interface Instruction {
|
|||
readonly name: string;
|
||||
readonly desc: string;
|
||||
readonly params: Array<ParameterType>;
|
||||
execute: (computer_reference: GenericComputer, parameters: Uint8Array, a: AfterExecutionComputerAction) => void;
|
||||
readonly execute: (
|
||||
computer_reference: GenericComputer,
|
||||
parameters: Uint8Array,
|
||||
a: AfterExecutionComputerAction
|
||||
) => void;
|
||||
}
|
||||
|
||||
export class InstructionSet {
|
||||
|
@ -55,6 +81,8 @@ export class InstructionSet {
|
|||
|
||||
export const ISA = new InstructionSet();
|
||||
|
||||
// The definitions for actual instructions.
|
||||
|
||||
ISA.insertInstruction(0x00, {
|
||||
name: "NoOp",
|
||||
desc: "No operation; do nothing",
|
||||
|
@ -86,7 +114,7 @@ ISA.insertInstruction(0x20, {
|
|||
ISA.insertInstruction(0x21, {
|
||||
name: "SaveToMemory",
|
||||
desc: "Writes the byte in register (P1) to the memory cell (P2)",
|
||||
params: [new RegisParam(""), new MemorParam("")],
|
||||
params: [new RegisParam("Write the byte in this register"), new MemorParam("To this memory address")],
|
||||
execute(c, p) {
|
||||
const [register_no, mem_address] = p;
|
||||
c.setMemory(mem_address, c.getRegister(register_no));
|
||||
|
@ -115,6 +143,7 @@ ISA.insertInstruction(0x11, {
|
|||
}
|
||||
},
|
||||
});
|
||||
|
||||
ISA.insertInstruction(0x30, {
|
||||
name: "Increment",
|
||||
desc: "Increments the value within register (P1) by 1",
|
||||
|
@ -122,7 +151,8 @@ ISA.insertInstruction(0x30, {
|
|||
execute(c, p) {
|
||||
const register_no = p[0];
|
||||
const current_value = c.getRegister(register_no);
|
||||
c.setRegister(register_no, current_value + 1);
|
||||
const new_value = (current_value + 1) % 256;
|
||||
c.setRegister(register_no, new_value);
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -133,14 +163,18 @@ ISA.insertInstruction(0x31, {
|
|||
execute(c, p) {
|
||||
const register_no = p[0];
|
||||
const current_value = c.getRegister(register_no);
|
||||
c.setRegister(register_no, current_value - 1);
|
||||
let new_value = current_value - 1;
|
||||
if (new_value === -1) {
|
||||
new_value = 255;
|
||||
}
|
||||
c.setRegister(register_no, new_value);
|
||||
},
|
||||
});
|
||||
|
||||
ISA.insertInstruction(0x40, {
|
||||
name: "Add",
|
||||
desc: "Adds the contents of (P1) and (P2) and stores result to register (P1). (Overflow will be taken mod 256)",
|
||||
params: [new RegisParam(""), new RegisParam("")],
|
||||
params: [new RegisParam("set this register to"), new RegisParam("it's sum with the value in this register")],
|
||||
execute(c, p) {
|
||||
const [register_no_1, register_no_2] = p;
|
||||
const new_value = (c.getRegister(register_no_1) + c.getRegister(register_no_2)) % 256;
|
||||
|
@ -175,3 +209,81 @@ ISA.insertInstruction(0xfe, {
|
|||
a.dispatch(CpuEvent.Print, char);
|
||||
},
|
||||
});
|
||||
|
||||
ISA.insertInstruction(0x48, {
|
||||
name: "Bitwise And",
|
||||
desc: "Takes each bit in register (P1) and compares to the respective bit in register (P2). Each bit in register (P1) is set to 1 if the respective bit in both registers are 1",
|
||||
params: [new RegisParam(""), new RegisParam("")],
|
||||
execute(c, p) {
|
||||
const [register_no_1, register_no_2] = p;
|
||||
const new_value = c.getRegister(register_no_1) & c.getMemory(register_no_2);
|
||||
c.setRegister(register_no_1, new_value);
|
||||
},
|
||||
});
|
||||
|
||||
ISA.insertInstruction(0xff, {
|
||||
name: "Print",
|
||||
desc: "Prints the byte in register (P1) to console as base 10",
|
||||
params: [new RegisParam("Register to print from")],
|
||||
execute(c, p, a) {
|
||||
const register_no = p[0];
|
||||
const byte = c.getRegister(register_no);
|
||||
|
||||
a.dispatch(CpuEvent.Print, byte.toString(10));
|
||||
},
|
||||
});
|
||||
|
||||
ISA.insertInstruction(0xfd, {
|
||||
name: "Print 16 bit",
|
||||
desc: "Prints the byte in register (P1) as the upper half and the byte in register (P2) as the lower half of a 16 bit number. Formats to decimal",
|
||||
params: [new RegisParam("Upper 8 bits of number"), new RegisParam("Lower 8 bits of number")],
|
||||
execute(c, p, a) {
|
||||
const [upper_register_no, lower_register_no] = p;
|
||||
const upper = c.getRegister(upper_register_no);
|
||||
const lower = c.getRegister(lower_register_no);
|
||||
const sum = upper * 16 * 16 + lower;
|
||||
|
||||
a.dispatch(CpuEvent.Print, sum.toString(10));
|
||||
},
|
||||
});
|
||||
|
||||
ISA.insertInstruction(0x66, {
|
||||
name: "Halt and Catch Fire",
|
||||
desc: "Stops program execu..... Fire! FIRE EVERYWHERE!",
|
||||
params: [],
|
||||
execute(c, p, a) {
|
||||
a.dispatch(CpuEvent.Halt, null);
|
||||
},
|
||||
});
|
||||
|
||||
ISA.insertInstruction(0xa0, {
|
||||
name: "Call",
|
||||
desc: "",
|
||||
params: [new ConstParam("the subroutine at this memory address")],
|
||||
execute(c, p, a) {
|
||||
const current_address = c.getProgramCounter();
|
||||
const success = c.pushCallStack(current_address);
|
||||
// TODO Handle success/failure
|
||||
|
||||
const new_address = p[0];
|
||||
c.setProgramCounter(new_address);
|
||||
|
||||
a.noStep();
|
||||
},
|
||||
});
|
||||
|
||||
ISA.insertInstruction(0xa1, {
|
||||
name: "Return",
|
||||
desc: "",
|
||||
params: [],
|
||||
execute(c, p, a) {
|
||||
const new_address = c.popCallStack();
|
||||
if (new_address === null) {
|
||||
throw new Error("TODO handle this");
|
||||
}
|
||||
|
||||
c.setProgramCounter(new_address + 1);
|
||||
|
||||
a.noStep();
|
||||
},
|
||||
});
|
||||
|
|
|
@ -3,15 +3,14 @@ import { Instruction, InstructionSet } from "./instructionSet";
|
|||
|
||||
export function generate_isa(iset: InstructionSet): string {
|
||||
const instructions: Array<[u8, Instruction]> = [];
|
||||
for (const kv of iset.instructions.entries()) {
|
||||
instructions.push(kv);
|
||||
}
|
||||
|
||||
for (const kv of iset.instructions.entries()) instructions.push(kv);
|
||||
|
||||
let output_string = "INSTRUCTIONS\n";
|
||||
let max_instr_name_len = 0;
|
||||
for (const instruction of instructions) {
|
||||
const short_description = instruction[1].name;
|
||||
max_instr_name_len = Math.max(max_instr_name_len, short_description.length);
|
||||
}
|
||||
|
||||
const max_instr_name_len = instructions.map((i) => i[1].name.length).reduce((acc, p) => Math.max(p, acc), 0);
|
||||
instructions.sort((a, b) => a[0] - b[0]);
|
||||
|
||||
for (const instruction of instructions) {
|
||||
const hex_code = format_hex(instruction[0]);
|
||||
const short_description = instruction[1].name.padEnd(max_instr_name_len, " ");
|
||||
|
|
192
src/style.scss
Normal file
192
src/style.scss
Normal file
|
@ -0,0 +1,192 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 0.5em;
|
||||
}
|
||||
|
||||
:root {
|
||||
--Border: Yellow;
|
||||
--mem-instruction: greenyellow;
|
||||
--mem-register: Purple;
|
||||
--mem-constant: lightgray;
|
||||
--mem-memory: lightgray;
|
||||
--mem-invalid: red;
|
||||
}
|
||||
|
||||
body {
|
||||
color: gray;
|
||||
background-color: black;
|
||||
font-size: 2.5em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#labelcontainer {
|
||||
column-gap: 18px;
|
||||
font-size: 0.85em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
grid-area: regmemlabel;
|
||||
}
|
||||
|
||||
#main {
|
||||
justify-content: center;
|
||||
display: grid;
|
||||
grid-template-columns: min-content max-content max-content 10px 500px;
|
||||
grid-template-rows: min-content 1.5fr 10px 2fr 2fr min-content;
|
||||
grid-template-areas:
|
||||
"cycles registers regmemlabel . explainer "
|
||||
"title . . . explainer "
|
||||
"title . . . ."
|
||||
"title . . . printout "
|
||||
"title . . . printout "
|
||||
". buttons buttons buttons buttons";
|
||||
#memory {
|
||||
grid-column: 2/4;
|
||||
grid-row: 2/6;
|
||||
}
|
||||
#cycles {
|
||||
grid-area: cycles;
|
||||
text-align: left;
|
||||
align-self: center;
|
||||
font-size: 0.48em;
|
||||
}
|
||||
#title {
|
||||
grid-area: title;
|
||||
writing-mode: vertical-lr;
|
||||
text-align: left;
|
||||
user-select: none;
|
||||
transform: scale(-1, -1);
|
||||
}
|
||||
}
|
||||
|
||||
#printout {
|
||||
border: 4px dashed yellow;
|
||||
grid-area: printout;
|
||||
padding: 10px;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
max-height: 200px;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
#instruction_explainer {
|
||||
grid-area: explainer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
border: 5px dashed yellow;
|
||||
#expl_box {
|
||||
padding-inline: 20px;
|
||||
padding-block-start: 10px;
|
||||
}
|
||||
|
||||
#expl_text {
|
||||
font-size: 0.6em;
|
||||
text-align: center;
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
#expl_icon {
|
||||
margin-inline-end: 0.5em;
|
||||
font-size: 30px;
|
||||
padding: 5px;
|
||||
color: var(--color);
|
||||
outline: 3px dashed var(--color);
|
||||
}
|
||||
}
|
||||
|
||||
.invalid {
|
||||
--color: var(--mem-invalid);
|
||||
}
|
||||
.constant {
|
||||
--color: var(--mem-constant);
|
||||
}
|
||||
.register {
|
||||
--color: var(--mem-register);
|
||||
}
|
||||
.memory {
|
||||
--color: var(--mem-memory);
|
||||
}
|
||||
.instruction {
|
||||
--color: var(--mem-instruction);
|
||||
}
|
||||
|
||||
#memory {
|
||||
grid-area: memory;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(16, min-content);
|
||||
gap: 5px;
|
||||
padding: 10px;
|
||||
border: 5px solid yellow;
|
||||
div {
|
||||
text-align: center;
|
||||
color: var(--color);
|
||||
}
|
||||
.program_counter {
|
||||
outline: 3px solid orange;
|
||||
}
|
||||
.instruction_argument {
|
||||
outline: 3px dashed purple;
|
||||
}
|
||||
.current_instruction {
|
||||
outline: 3px dashed greenyellow;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
&::after {
|
||||
user-select: none;
|
||||
float: right;
|
||||
position: relative;
|
||||
right: 0.5em;
|
||||
width: 0px;
|
||||
font-size: 0.5em;
|
||||
content: "!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#registers {
|
||||
grid-area: registers;
|
||||
border: 5px solid yellow;
|
||||
border-bottom: none;
|
||||
|
||||
grid-template-columns: repeat(8, min-content);
|
||||
max-width: fit-content;
|
||||
display: grid;
|
||||
column-gap: 5px;
|
||||
color: lightgray;
|
||||
padding: 10px;
|
||||
|
||||
div {
|
||||
max-height: min-content;
|
||||
// margin-block: auto;
|
||||
}
|
||||
}
|
||||
|
||||
button,
|
||||
label.button {
|
||||
border: 4px solid yellow;
|
||||
color: gray;
|
||||
margin: 10px;
|
||||
margin-inline: 0px;
|
||||
padding: 10px;
|
||||
font-size: 0.8em;
|
||||
font-family: monospace;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
label.button:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
#controls_bar {
|
||||
grid-area: buttons;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
165
src/ui.ts
165
src/ui.ts
|
@ -1,41 +1,40 @@
|
|||
import { ComputerState } from "./computer";
|
||||
import { CpuEvent, CpuEventHandler, MemoryCellType, UiEvent, UiEventHandler } from "./events";
|
||||
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
|
||||
import { $, el, format_hex, u8 } from "./etc";
|
||||
import { Instruction, ParameterType } from "./instructionSet";
|
||||
import { EventHandler } from "./eventHandler";
|
||||
import { InstructionExplainer } from "./ui/instructionExplainer";
|
||||
import { MemoryView } from "./ui/memoryView";
|
||||
import { ParamType } from "./instructionSet";
|
||||
import { frequencyIndicator } from "./ui/frequencyIndicator";
|
||||
// Certainly the messiest portion of this program
|
||||
// Needs to be broken into components
|
||||
|
||||
let delay = 100;
|
||||
|
||||
export class UI {
|
||||
container: HTMLElement;
|
||||
program_memory: HTMLElement;
|
||||
program_memory_cells: Array<HTMLElement> = [];
|
||||
registers: HTMLElement;
|
||||
printout: HTMLElement;
|
||||
instruction_explainer: HTMLElement;
|
||||
register_cells: Array<HTMLElement> = [];
|
||||
instruction_parsing_addresses: Array<u8> = [];
|
||||
program_counter: u8 = 0;
|
||||
|
||||
auto_running: boolean;
|
||||
|
||||
events: UiEventHandler = new EventHandler<UiEvent>() as UiEventHandler;
|
||||
|
||||
constructor(parent: HTMLElement) {
|
||||
frequencyIndicator: frequencyIndicator;
|
||||
memory: MemoryView;
|
||||
instruction_explainer: InstructionExplainer;
|
||||
|
||||
constructor() {
|
||||
for (const [, e_type] of Object.entries(UiEvent)) {
|
||||
this.events.register_event(e_type as UiEvent);
|
||||
}
|
||||
this.events.seal();
|
||||
this.container = parent;
|
||||
|
||||
this.memory = new MemoryView($("memory"));
|
||||
this.frequencyIndicator = new frequencyIndicator($("cycles"));
|
||||
|
||||
this.instruction_explainer = new InstructionExplainer($("instruction_explainer"));
|
||||
|
||||
this.printout = $("printout");
|
||||
this.instruction_explainer = $("instruction_explainer");
|
||||
const program_mem = $("memory");
|
||||
for (let i = 0; i < 256; i++) {
|
||||
const mem_cell = el("div", `p_${i}`);
|
||||
mem_cell.textContent = "00";
|
||||
program_mem.appendChild(mem_cell);
|
||||
this.program_memory_cells.push(mem_cell);
|
||||
}
|
||||
this.program_memory_cells[0].classList.add("div", "program_counter");
|
||||
this.program_memory = program_mem;
|
||||
|
||||
const registers = $("registers");
|
||||
for (let i = 0; i < 8; i++) {
|
||||
|
@ -66,21 +65,24 @@ export class UI {
|
|||
this.stop_auto();
|
||||
}
|
||||
|
||||
this.events.dispatch(UiEvent.RequestCpuCycle, null);
|
||||
this.events.dispatch(UiEvent.RequestCpuCycle, 1);
|
||||
});
|
||||
|
||||
$("delay_range").addEventListener("input", (e) => {
|
||||
delay = parseInt((e.target as HTMLInputElement).value, 10);
|
||||
// console.log(delay);
|
||||
});
|
||||
}
|
||||
|
||||
init_events(cpu_events: CpuEventHandler): void {
|
||||
cpu_events.listen(CpuEvent.MemoryChanged, ({ address, value }) => {
|
||||
this.program_memory_cells[address].textContent = format_hex(value);
|
||||
this.memory.set_cell_value(address, value);
|
||||
});
|
||||
cpu_events.listen(CpuEvent.RegisterChanged, ({ register_no, value }) => {
|
||||
this.register_cells[register_no].textContent = format_hex(value);
|
||||
});
|
||||
cpu_events.listen(CpuEvent.ProgramCounterChanged, ({ counter }) => {
|
||||
this.program_memory_cells[this.program_counter].classList.remove("program_counter");
|
||||
this.program_memory_cells[counter].classList.add("program_counter");
|
||||
this.program_counter = counter;
|
||||
this.memory.set_program_counter(counter);
|
||||
});
|
||||
cpu_events.listen(CpuEvent.Print, (char) => {
|
||||
this.printout.textContent = (this.printout.textContent ?? "") + char;
|
||||
|
@ -89,78 +91,51 @@ export class UI {
|
|||
this.reset();
|
||||
});
|
||||
|
||||
const map: Map<MemoryCellType, string> = new Map();
|
||||
this.frequencyIndicator.init_cpu_events(cpu_events);
|
||||
this.memory.init_cpu_events(cpu_events);
|
||||
this.instruction_explainer.init_cpu_events(cpu_events);
|
||||
|
||||
map.set(MemoryCellType.Constant, "constant");
|
||||
map.set(MemoryCellType.Instruction, "instruction");
|
||||
map.set(MemoryCellType.InvalidInstruction, "invalid_instruction");
|
||||
map.set(MemoryCellType.Memory, "memory");
|
||||
map.set(MemoryCellType.Register, "register");
|
||||
cpu_events.listen(CpuEvent.MemoryByteParsed, (e) => {
|
||||
const { type, pos, code } = e;
|
||||
const css_class = map.get(type);
|
||||
if (css_class === undefined) {
|
||||
throw new Error("Something went wrong");
|
||||
cpu_events.listen(CpuEvent.ParameterParsed, ({ param, code, pos }) => {
|
||||
this.memory.add_cell_class(pos, "instruction_argument");
|
||||
this.instruction_explainer.add_param(param, pos, code);
|
||||
const t = param.type;
|
||||
this.memory.remove_cell_class(pos, "constant", "register", "memory", "instruction", "invalid");
|
||||
let name: string = "";
|
||||
if (t === ParamType.Const) {
|
||||
name = "constant";
|
||||
} else if (t === ParamType.Memory) {
|
||||
name = "memory";
|
||||
} else if (t === ParamType.Register) {
|
||||
name = "register";
|
||||
} else {
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
for (const other_class of map.values()) {
|
||||
if (other_class === css_class) continue;
|
||||
this.program_memory_cells[pos].classList.remove(other_class);
|
||||
}
|
||||
if (type === MemoryCellType.Instruction) {
|
||||
while (this.instruction_parsing_addresses.length > 0) {
|
||||
const num = this.instruction_parsing_addresses.pop();
|
||||
if (num === undefined) {
|
||||
throw new Error("Shouldn't happen");
|
||||
}
|
||||
this.program_memory_cells[num].classList.remove("instruction_argument");
|
||||
this.program_memory_cells[num].classList.remove("current_instruction");
|
||||
}
|
||||
this.instruction_explainer.innerHTML = "";
|
||||
const { instr } = e as { instr: Instruction };
|
||||
this.program_memory_cells[pos].classList.add("current_instruction");
|
||||
this.instruction_parsing_addresses.push(pos);
|
||||
const instr_box = el("div", "expl_box");
|
||||
const instr_icon = el("span", "expl_icon");
|
||||
instr_icon.classList.add(css_class);
|
||||
instr_icon.setAttribute("title", css_class.toUpperCase());
|
||||
instr_icon.textContent = format_hex(code);
|
||||
const instr_box_text = el("span", "expl_text");
|
||||
instr_box_text.textContent = `${instr.name}`;
|
||||
instr_box.appendChild(instr_icon);
|
||||
instr_box.appendChild(instr_box_text);
|
||||
this.instruction_explainer.appendChild(instr_box);
|
||||
} else if (type !== MemoryCellType.InvalidInstruction) {
|
||||
const { param } = e as { param: ParameterType };
|
||||
this.program_memory_cells[pos].classList.add("instruction_argument");
|
||||
this.instruction_parsing_addresses.push(pos);
|
||||
const instr_box = el("div", "expl_box");
|
||||
const instr_icon = el("span", "expl_icon");
|
||||
instr_icon.classList.add(css_class);
|
||||
instr_icon.setAttribute("title", css_class.toUpperCase());
|
||||
instr_icon.textContent = format_hex(code);
|
||||
const instr_box_text = el("span", "expl_text");
|
||||
instr_box_text.textContent = `${param.desc}`;
|
||||
instr_box.appendChild(instr_icon);
|
||||
instr_box.appendChild(instr_box_text);
|
||||
this.instruction_explainer.appendChild(instr_box);
|
||||
}
|
||||
this.program_memory_cells[pos].classList.add(css_class);
|
||||
this.memory.add_cell_class(pos, name);
|
||||
});
|
||||
cpu_events.listen(CpuEvent.InstructionParsed, ({ instr, code, pos }) => {
|
||||
this.instruction_explainer.add_instruction(instr, pos, code);
|
||||
|
||||
cpu_events.listen(CpuEvent.InstructionExecuted, ({ instr }) => {});
|
||||
this.memory.remove_all_cell_class("instruction_argument");
|
||||
this.memory.remove_all_cell_class("current_instruction");
|
||||
this.memory.add_cell_class(pos, "current_instruction");
|
||||
this.memory.remove_cell_class(pos, "constant", "register", "memory", "invalid");
|
||||
this.memory.add_cell_class(pos, "instruction");
|
||||
});
|
||||
cpu_events.listen(CpuEvent.InvalidParsed, ({ code, pos }) => {
|
||||
this.memory.remove_cell_class(pos, "constant", "register", "memory", "instruction");
|
||||
this.memory.add_cell_class(pos, "invalid");
|
||||
this.instruction_explainer.add_invalid(pos, code);
|
||||
});
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.stop_auto();
|
||||
this.program_memory_cells.forEach((c) => {
|
||||
c.className = "";
|
||||
c.textContent = "00";
|
||||
});
|
||||
this.register_cells.forEach((r) => {
|
||||
r.textContent = "00";
|
||||
});
|
||||
this.program_counter = 0;
|
||||
this.program_memory_cells[0].classList.add("program_counter");
|
||||
this.frequencyIndicator.reset();
|
||||
this.instruction_explainer.reset();
|
||||
this.memory.reset();
|
||||
this.printout.textContent = "";
|
||||
}
|
||||
|
||||
|
@ -173,9 +148,8 @@ export class UI {
|
|||
if (this.auto_running === false) {
|
||||
return;
|
||||
}
|
||||
this.events.dispatch(UiEvent.RequestCpuCycle, null);
|
||||
setTimeout(loop, speed);
|
||||
// requestAnimationFrame(loop);
|
||||
this.events.dispatch(UiEvent.RequestCpuCycle, 1);
|
||||
setTimeout(loop, delay);
|
||||
};
|
||||
loop();
|
||||
}
|
||||
|
@ -183,15 +157,4 @@ export class UI {
|
|||
stop_auto(): void {
|
||||
this.auto_running = false;
|
||||
}
|
||||
|
||||
state_update_event(state: ComputerState): void {
|
||||
const current_instr = state.current_instruction;
|
||||
if (current_instr !== null) {
|
||||
this.program_memory_cells[current_instr.pos].classList.add("current_instruction");
|
||||
for (let i = 0; i < current_instr.params_found; i++) {
|
||||
const offset = i + 1 + current_instr.pos;
|
||||
this.program_memory_cells[offset].classList.add("instruction_argument");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
122
src/ui/editableHex.ts
Normal file
122
src/ui/editableHex.ts
Normal file
|
@ -0,0 +1,122 @@
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function set_caret(el: any, pos: number): boolean {
|
||||
const selection = window.getSelection();
|
||||
const range = document.createRange();
|
||||
if (selection === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
selection.removeAllRanges();
|
||||
range.selectNode(el);
|
||||
|
||||
range.setStart(el, pos);
|
||||
range.setEnd(el, pos);
|
||||
range.collapse(true);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
el.focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
function get_caret(el: HTMLElement): null | number {
|
||||
const sel = window.getSelection();
|
||||
if (sel === null) {
|
||||
return null;
|
||||
}
|
||||
const pos = sel.getRangeAt(0).startOffset;
|
||||
const endPos = pos + Array.from(el.innerHTML.slice(0, pos)).length - el.innerHTML.slice(0, pos).split("").length;
|
||||
return endPos;
|
||||
}
|
||||
|
||||
const hex_characters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
|
||||
function replace_non_hex(c: string): string {
|
||||
if (hex_characters.includes(c)) {
|
||||
return c;
|
||||
}
|
||||
return "0";
|
||||
}
|
||||
|
||||
function editable_constraints(e: Event): boolean {
|
||||
const target = e.target as HTMLDivElement;
|
||||
const text = target.innerHTML ?? "";
|
||||
if (text.length !== 2) {
|
||||
const pos = get_caret(target);
|
||||
const new_str = [...(target.textContent ?? "").substring(0, 2).padStart(2, "0").toUpperCase()]
|
||||
.map(replace_non_hex)
|
||||
.join("");
|
||||
target.innerHTML = "";
|
||||
// For the caret selection to work right, each character must be its own node, complicating this greatly
|
||||
target.append(new_str.substring(0, 1), new_str.substring(1));
|
||||
|
||||
if (pos !== null) {
|
||||
if (pos >= 2) {
|
||||
return true;
|
||||
}
|
||||
set_caret(target, pos);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function at<T>(l: Array<T>, i: number): T | null {
|
||||
if (i < 0) {
|
||||
return null;
|
||||
}
|
||||
if (i >= l.length) {
|
||||
return null;
|
||||
}
|
||||
return l[i];
|
||||
}
|
||||
|
||||
export function make_editable(
|
||||
list: Array<HTMLElement>,
|
||||
width: number,
|
||||
height: number,
|
||||
on_edit: (n: number, value: string) => void
|
||||
): void {
|
||||
for (const [i, cell] of list.entries()) {
|
||||
cell.setAttribute("contenteditable", "true");
|
||||
cell.setAttribute("spellcheck", "false");
|
||||
const next: null | HTMLElement = at(list, i + 1);
|
||||
const prev: null | HTMLElement = at(list, i - 1);
|
||||
const up: null | HTMLElement = at(list, i - width);
|
||||
const down: null | HTMLElement = at(list, i + width);
|
||||
cell.addEventListener("keydown", (e) => {
|
||||
const caret_position = get_caret(cell);
|
||||
const k = e.key;
|
||||
if (k === "ArrowUp") {
|
||||
(up ?? prev)?.focus();
|
||||
cell.blur();
|
||||
} else if (k === "ArrowDown") {
|
||||
(down ?? next)?.focus();
|
||||
cell.blur();
|
||||
} else if ((k === "ArrowLeft" || k === "Backspace") && caret_position === 0) {
|
||||
prev?.focus();
|
||||
cell.blur();
|
||||
} else if (k === "ArrowRight" && caret_position === 1) {
|
||||
next?.focus();
|
||||
cell.blur();
|
||||
} else if (k === "Enter") {
|
||||
cell.blur();
|
||||
} else if (k === "Escape") {
|
||||
cell.blur();
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
});
|
||||
let previous_text = cell.textContent ?? "";
|
||||
cell.addEventListener("input", (e) => {
|
||||
const current_text = cell.textContent ?? "";
|
||||
if (current_text !== previous_text) {
|
||||
previous_text = cell.textContent ?? "";
|
||||
on_edit(i, current_text);
|
||||
}
|
||||
if (editable_constraints(e) === true) {
|
||||
next?.focus();
|
||||
cell.blur();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
52
src/ui/frequencyIndicator.ts
Normal file
52
src/ui/frequencyIndicator.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
|
||||
import { UiComponent } from "./uiComponent";
|
||||
|
||||
export class frequencyIndicator implements UiComponent {
|
||||
element: HTMLElement;
|
||||
private running: number | null = null;
|
||||
private count: number = 0;
|
||||
private last_value: number = 0;
|
||||
private last_time: number = 0;
|
||||
constructor(element: HTMLElement) {
|
||||
this.element = element;
|
||||
this.start();
|
||||
}
|
||||
|
||||
start(): void {
|
||||
if (this.running !== null) {
|
||||
throw new Error("Tried starting frequencyIndicator twice!");
|
||||
}
|
||||
setInterval(this.update_indicator.bind(this), 1000);
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (this.running === null) return;
|
||||
clearInterval(this.running);
|
||||
this.running = null;
|
||||
}
|
||||
|
||||
update_indicator(): void {
|
||||
if (this.last_value !== this.count) {
|
||||
this.element.textContent = `${this.count}hz`;
|
||||
this.last_value = this.count;
|
||||
}
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
init_events(eh: UiEventHandler): void {
|
||||
this;
|
||||
}
|
||||
clock_cycle(): void {
|
||||
this.count += 1;
|
||||
}
|
||||
reset(): void {
|
||||
this.stop();
|
||||
this.count = 0;
|
||||
this.last_value = 0;
|
||||
}
|
||||
init_cpu_events(c: CpuEventHandler) {
|
||||
c.listen(CpuEvent.ClockCycle, () => {
|
||||
this.count += 1;
|
||||
});
|
||||
}
|
||||
}
|
55
src/ui/instructionExplainer.ts
Normal file
55
src/ui/instructionExplainer.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { el, format_hex, u8 } from "../etc";
|
||||
import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
|
||||
import { Instruction, ParamType, ParameterType } from "../instructionSet";
|
||||
import { UiComponent } from "./uiComponent";
|
||||
|
||||
export class InstructionExplainer implements UiComponent {
|
||||
element: HTMLElement;
|
||||
constructor(element: HTMLElement) {
|
||||
this.element = element;
|
||||
}
|
||||
add_instruction(instr: Instruction, pos: u8, byte: u8): void {
|
||||
this.reset();
|
||||
this.add_box(format_hex(byte), instr.name, "instruction");
|
||||
}
|
||||
|
||||
private add_box(box_icon_text: string, name: string, css_class: string): void {
|
||||
const instr_box = el("div", "expl_box");
|
||||
const instr_icon = el("span", "expl_icon");
|
||||
instr_icon.classList.add(css_class);
|
||||
instr_icon.setAttribute("title", css_class.toUpperCase());
|
||||
instr_icon.textContent = box_icon_text;
|
||||
const instr_box_text = el("span", "expl_text");
|
||||
instr_box_text.textContent = name;
|
||||
instr_box.appendChild(instr_icon);
|
||||
instr_box.appendChild(instr_box_text);
|
||||
this.element.appendChild(instr_box);
|
||||
}
|
||||
|
||||
add_param(param: ParameterType, pos: u8, byte: u8): void {
|
||||
const t = param.type;
|
||||
let name;
|
||||
if (t === ParamType.Const) {
|
||||
name = "constant";
|
||||
} else if (t === ParamType.Memory) {
|
||||
name = "memory";
|
||||
} else if (t === ParamType.Register) {
|
||||
name = "register";
|
||||
} else {
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
this.add_box(format_hex(byte), param.desc, name);
|
||||
}
|
||||
|
||||
add_invalid(pos: u8, byte: u8) {
|
||||
this.reset();
|
||||
this.add_box(format_hex(byte), "Invalid Instruction", "invalid");
|
||||
}
|
||||
|
||||
init_events(eh: UiEventHandler): void {}
|
||||
init_cpu_events(c: CpuEventHandler) {}
|
||||
|
||||
reset(): void {
|
||||
this.element.innerHTML = "";
|
||||
}
|
||||
}
|
78
src/ui/memoryView.ts
Normal file
78
src/ui/memoryView.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { el, format_hex, u8 } from "../etc";
|
||||
import { CpuEventHandler, UiEventHandler } from "../events";
|
||||
import { UiComponent } from "./uiComponent";
|
||||
|
||||
export enum CellTag {
|
||||
ProgramCounter = "program_counter",
|
||||
}
|
||||
|
||||
type MemoryCell = {
|
||||
el: HTMLElement;
|
||||
};
|
||||
|
||||
export class MemoryView implements UiComponent {
|
||||
element: HTMLElement;
|
||||
cells: Array<MemoryCell> = [];
|
||||
program_counter: number = 0;
|
||||
constructor(element: HTMLElement) {
|
||||
this.element = element;
|
||||
for (let i = 0; i < 256; i++) {
|
||||
const mem_cell_el = el("div");
|
||||
mem_cell_el.textContent = "00";
|
||||
element.appendChild(mem_cell_el);
|
||||
const mem_cell = { el: mem_cell_el, tags: [] };
|
||||
this.cells.push(mem_cell);
|
||||
}
|
||||
this.set_program_counter(0);
|
||||
}
|
||||
|
||||
add_cell_class(address: u8, ...css_class: string[]): void {
|
||||
for (const str of css_class) {
|
||||
this.cells[address].el.classList.add(str);
|
||||
}
|
||||
}
|
||||
|
||||
remove_cell_class(address: u8, ...css_class: string[]): void {
|
||||
for (const str of css_class) {
|
||||
this.cells[address].el.classList.remove(str);
|
||||
}
|
||||
}
|
||||
|
||||
remove_all_cell_class(css_class: string): void {
|
||||
for (const cell of this.cells) {
|
||||
cell.el.classList.remove(css_class);
|
||||
}
|
||||
}
|
||||
|
||||
add_cell_class_exclusive(address: u8, css_class: string): void {
|
||||
this.remove_all_cell_class(css_class);
|
||||
this.add_cell_class(address, css_class);
|
||||
}
|
||||
|
||||
set_cell_value(address: u8, value: u8): void {
|
||||
this.cells[address].el.textContent = format_hex(value);
|
||||
}
|
||||
|
||||
set_program_counter(position: number): void {
|
||||
this.cells[this.program_counter].el.classList.remove("program_counter");
|
||||
this.cells[position].el.classList.add("program_counter");
|
||||
this.program_counter = position;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.element.innerHTML = "";
|
||||
for (let i = 0; i < 256; i++) {
|
||||
const mem_cell_el = el("div");
|
||||
mem_cell_el.textContent = "00";
|
||||
this.element.appendChild(mem_cell_el);
|
||||
const mem_cell = { el: mem_cell_el };
|
||||
this.cells.push(mem_cell);
|
||||
}
|
||||
this.set_program_counter(0);
|
||||
}
|
||||
|
||||
init_events(eh: UiEventHandler): void {
|
||||
this;
|
||||
}
|
||||
init_cpu_events(c: CpuEventHandler) {}
|
||||
}
|
9
src/ui/uiComponent.ts
Normal file
9
src/ui/uiComponent.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { u8 } from "../etc";
|
||||
import { CpuEventHandler, UiEventHandler } from "../events";
|
||||
|
||||
export interface UiComponent {
|
||||
element: HTMLElement;
|
||||
reset: () => void;
|
||||
init_events: (ui: UiEventHandler) => void;
|
||||
init_cpu_events: (c: CpuEventHandler) => void;
|
||||
}
|
136
style.css
136
style.css
|
@ -1,136 +0,0 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#memory {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
grid-gap: 5px;
|
||||
padding: 10px;
|
||||
border: 5px solid yellow;
|
||||
/* color: lightgray; */
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 0.5em;
|
||||
}
|
||||
|
||||
#memory .instruction {
|
||||
color: greenyellow;
|
||||
}
|
||||
#memory .constant {
|
||||
color: purple;
|
||||
}
|
||||
#memory .register {
|
||||
color: orange;
|
||||
}
|
||||
#memory .memory {
|
||||
color: pink;
|
||||
}
|
||||
#memory .invalid_instruction {
|
||||
color: maroon;
|
||||
}
|
||||
|
||||
#memory div {
|
||||
aspect-ratio: 1;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
color: gray;
|
||||
background-color: black;
|
||||
font-size: 2.5em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#registers div {
|
||||
max-height: min-content;
|
||||
margin-block: auto;
|
||||
}
|
||||
|
||||
#subcontainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
column-gap: 15px;
|
||||
}
|
||||
|
||||
#labelcontainer {
|
||||
column-gap: 18px;
|
||||
font-size: 0.85em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#title {
|
||||
writing-mode: vertical-lr;
|
||||
text-align: left;
|
||||
user-select: none;
|
||||
transform: scale(-1, -1);
|
||||
}
|
||||
|
||||
#printout {
|
||||
border: 4px dashed yellow;
|
||||
width: 1000px;
|
||||
padding: 10px;
|
||||
margin-left: 20px;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#memory div.program_counter {
|
||||
outline: 3px solid orange;
|
||||
}
|
||||
|
||||
#memory div.instruction_argument {
|
||||
outline: 3px dashed purple;
|
||||
}
|
||||
#memory div.current_instruction {
|
||||
outline: 3px dashed greenyellow;
|
||||
}
|
||||
|
||||
#registers {
|
||||
border: 5px solid yellow;
|
||||
border-bottom: none;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
max-width: fit-content;
|
||||
display: grid;
|
||||
column-gap: 5px;
|
||||
color: lightgray;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#container {
|
||||
font-family: monospace;
|
||||
max-width: min-content;
|
||||
max-height: min-content;
|
||||
}
|
||||
|
||||
button,
|
||||
label.button {
|
||||
border: 4px solid yellow;
|
||||
color: gray;
|
||||
margin: 10px;
|
||||
margin-inline: 8px;
|
||||
padding: 10px;
|
||||
font-size: 0.8em;
|
||||
font-family: monospace;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
button:hover,
|
||||
label.button:hover {
|
||||
color: white;
|
||||
}
|
||||
#controls_bar {
|
||||
margin-left: 1.12em;
|
||||
display: flex;
|
||||
}
|
|
@ -11,12 +11,23 @@ const config = {
|
|||
},
|
||||
{
|
||||
test: /\.html/,
|
||||
type: "asset/resource"
|
||||
}
|
||||
type: "asset/resource",
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: [
|
||||
// Creates `style` nodes from JS strings
|
||||
"style-loader",
|
||||
// Translates CSS into CommonJS
|
||||
"css-loader",
|
||||
// Compiles Sass to CSS
|
||||
"sass-loader",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts"],
|
||||
extensions: [".ts", ".scss", ".css"],
|
||||
},
|
||||
output: {
|
||||
filename: "main.js",
|
||||
|
@ -32,4 +43,4 @@ module.exports = (env, argv) => {
|
|||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue