├── media ├── switch.mp3 ├── toggle.mp3 └── beepbeep.mp3 ├── design ├── led-on.afdesign ├── panel.afdesign ├── led-off.afdesign ├── switch-up.afdesign ├── switch-down.afdesign ├── switch-mid.afdesign ├── altair-8800-panel.afdesign ├── led-on.svg ├── led-off.svg ├── switch-mid.svg ├── switch-up.svg ├── switch-down.svg └── panel.svg ├── screenshots ├── sim-debug.png ├── sim-panel.png └── sim-mobile.png ├── .gitignore ├── doc ├── altair_8800_operator_manual.pdf └── 8080_instructions.txt ├── asm ├── adder.asm └── tiny_programs.md ├── README.md ├── css └── style.css ├── js ├── l10n.js ├── sim8800.js └── panel.js ├── text-mode.html ├── LICENSE └── index.html /media/switch.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/media/switch.mp3 -------------------------------------------------------------------------------- /media/toggle.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/media/toggle.mp3 -------------------------------------------------------------------------------- /media/beepbeep.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/media/beepbeep.mp3 -------------------------------------------------------------------------------- /design/led-on.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/design/led-on.afdesign -------------------------------------------------------------------------------- /design/panel.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/design/panel.afdesign -------------------------------------------------------------------------------- /design/led-off.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/design/led-off.afdesign -------------------------------------------------------------------------------- /design/switch-up.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/design/switch-up.afdesign -------------------------------------------------------------------------------- /screenshots/sim-debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/screenshots/sim-debug.png -------------------------------------------------------------------------------- /screenshots/sim-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/screenshots/sim-panel.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | *~ 4 | .DS_Store 5 | build/* 6 | dist/* 7 | .coverage 8 | .tern-port 9 | -------------------------------------------------------------------------------- /design/switch-down.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/design/switch-down.afdesign -------------------------------------------------------------------------------- /design/switch-mid.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/design/switch-mid.afdesign -------------------------------------------------------------------------------- /screenshots/sim-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/screenshots/sim-mobile.png -------------------------------------------------------------------------------- /design/altair-8800-panel.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/design/altair-8800-panel.afdesign -------------------------------------------------------------------------------- /doc/altair_8800_operator_manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wixette/8800-simulator/HEAD/doc/altair_8800_operator_manual.pdf -------------------------------------------------------------------------------- /asm/adder.asm: -------------------------------------------------------------------------------- 1 | ;;;-------------------------------------------------------------------------------- 2 | ;;; Simple 8080 addition program. 3 | ;;;-------------------------------------------------------------------------------- 4 | LDA 0080H ; 00 111 010 10 000 000 00 000 000 5 | MOV B,A ; 01 000 111 6 | LDA 0081H ; 00 111 010 10 000 001 00 000 000 7 | ADD B ; 10 000 000 8 | STA 0082H ; 00 110 010 10 000 010 00 000 000 9 | JMP 0000H ; 11 000 011 00 000 000 00 000 000 10 | ;;;-------------------------------------------------------------------------------- 11 | -------------------------------------------------------------------------------- /asm/tiny_programs.md: -------------------------------------------------------------------------------- 1 | # Hex dump of tiny 8080 programs. 2 | 3 | Each program can be loaded into the simulator via the 4 | loadDataAsHexString() interface or the "LOAD DATA" input box. 5 | 6 | The HEX bytes in the strings are the binary sequence of 8080 machine 7 | instructions. 8 | 9 | ## Simple adder 10 | 11 | Adds the two values stored at 0080H and 0081H, then writes the result 12 | to 0082H. 13 | 14 | ``` 15 | 3a 80 00 47 3a 81 00 80 32 82 00 c3 00 00 16 | ``` 17 | 18 | ## Pattern shift 19 | 20 | Keep right shifting the value of register A. 21 | 22 | ``` 23 | 3e 8c d3 ff 0f c3 02 00 24 | ``` 25 | 26 | ## I/O test 27 | 28 | Echos IN to OUT. Reads from SENSE SW. switches, then outputs the 29 | value. 30 | 31 | ``` 32 | db ff d3 ff c3 00 00 33 | ``` 34 | 35 | ## More I/O test 36 | 37 | ``` 38 | 0e ff 16 01 7a fe 80 ca 0f 00 fe 01 c2 12 00 79 2f 4f 79 fe 00 c2 1e 00 7a 17 57 c3 21 00 7a 1f 57 7a 2f d3 ff db ff 3c 06 02 1e ff 1d c2 2c 00 05 c2 2a 00 3d c2 28 00 c3 04 00 39 | ``` 40 | -------------------------------------------------------------------------------- /design/led-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /design/led-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /design/switch-mid.svg: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /design/switch-up.svg: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /design/switch-down.svg: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Altair 8800 simulator. 2 | 3 | A JavaScript simulator to demonstrate the front panel operations of Altair 8800. 4 | 5 | ## Usage 6 | 7 | Simply open index.html in browser, or copy the entire dir to your web server's root dir. 8 | 9 | The simulator UI supports English and Chinese for now. In a desktop browser, you may use mouse to toggle or click the switches on the panel directly. 10 | 11 | ![8800 Panel](./screenshots/sim-panel.png) 12 | 13 | There is a Debugger tab where you can check the internal status of the simulated 8080 CPU, or the contents of the simulator's memory. 14 | 15 | ![8800 Debugger](./screenshots/sim-debug.png) 16 | 17 | The simulator works fine with modern mobile browsers, except that it is a bit challenging to touch a single switch on the panel on a mobile screen. Although, the helper switch buttons below the panel can be used as an alternative solution. 18 | 19 | ![8800 Mobile](./screenshots/sim-mobile.png) 20 | 21 | ## A Quick Tutorial 22 | 23 | With a running Altair 8800 simulator, how to input and run the following program to calculate 1 + 2 = 3: 24 | 25 | ``` 26 | LDA 0080H ; 00 111 010 27 | ; 10 000 000 28 | ; 00 000 000 29 | MOV B,A ; 01 000 111 30 | LDA 0081H ; 00 111 010 31 | ; 10 000 001 32 | ; 00 000 000 33 | ADD B ; 10 000 000 34 | STA 0082H ; 00 110 010 35 | ; 10 000 010 36 | ; 00 000 000 37 | JMP 0000H ; 11 000 011 38 | ; 00 000 000 39 | ; 00 000 000 40 | ``` 41 | 42 | 1. Turn on Altair 8800 by clicking OFF/ON switch. 43 | 1. Set switches A7-A0 to 00 111 010 (up for 1, down for 0). 44 | 1. Click "DEPOSIT". 45 | 1. Set switches A7-A0 to 10 000 000. 46 | 1. Click "DEPOSIT NEXT". 47 | 1. Repeat step 4-5 to input the following bytes one by one: 00 000 000, 01 000 111, 00 111 010, 10 000 001, 00 000 000, 10 000 000, 00 110 010, 10 000 010, 00 000 000, 11 000 011, 00 000 000, 00 000 000. 48 | 1. Set switches A7-A0 to 10 000 000. 49 | 1. Click "EXAMINE". 50 | 1. Set switches A7-A0 to 00 000 001 (the first number to be added, or 1 in decimal). 51 | 1. Click "DEPOSIT". 52 | 1. Set switches A7-A0 to 00 000 010 (the second number to be added, or 2 in decimal). 53 | 1. Click "DEPOSIT NEXT". 54 | 1. Click "RESET". 55 | 1. Click "RUN" and wait for a few seconds. 56 | 1. Click "STOP". 57 | 1. Set switches A7-A0 to 10 000 010 (the address that holds the sum). 58 | 1. Click "EXAMINE". 59 | 1. The LEDs D7-D0 show the result 00 000 011 (3 in decimal). 60 | 1. Turn off Altair 8800. 61 | 62 | ## References 63 | 64 | - [Wikipedia: Altair 8800](https://en.wikipedia.org/wiki/Altair_8800) 65 | - [Wikipedia: Intel 8080 CPU](https://en.wikipedia.org/wiki/Intel_8080) 66 | - [Intel 8080 instruction set](http://www.classiccmp.org/dunfield/r/8080.txt) 67 | - [Original Altair 8800 manuals](https://altairclone.com/altair_manuals.html) 68 | - [Altair 8800 Operator's Manual](https://altairclone.com/downloads/manuals/Altair%208800%20Operator's%20Manual.pdf) 69 | - [Intel 8080 Assembly Language Programming Manual](http://www.classiccmp.org/dunfield/r/8080asm.pdf) 70 | - [Another Altair 8800 simulator](https://s2js.com/altair/) 71 | 72 | ## Acknowledgements 73 | 74 | I use https://github.com/maly/8080js to execute Intel 8080 instruments. 75 | 76 | The Quick Tutoral in the simulator UI refers to an example program got from the original [Altair 8800 Operator's Manual](https://altairclone.com/downloads/manuals/Altair%208800%20Operator's%20Manual.pdf). 77 | 78 | The interaction design took [another Altair 8800 simulator](https://s2js.com/altair/) as a reference. 79 | -------------------------------------------------------------------------------- /doc/8080_instructions.txt: -------------------------------------------------------------------------------- 1 | ;; http://www.classiccmp.org/dunfield/r/8080.txt 2 | 3 | 8080 instruction encoding: 4 | 5 | Conventions in instruction source: 6 | D = Destination register (8 bit) 7 | S = Source register (8 bit) 8 | RP = Register pair (16 bit) 9 | # = 8 or 16 bit immediate operand 10 | a = 16 bit Memory address 11 | p = 8 bit port address 12 | ccc = Conditional 13 | 14 | Conventions in instruction encoding: 15 | db = Data byte (8 bit) 16 | lb = Low byte of 16 bit value 17 | hb = High byte of 16 bit value 18 | pa = Port address (8 bit) 19 | 20 | Dest and Source reg fields: 21 | 111=A (Accumulator) 22 | 000=B 23 | 001=C 24 | 010=D 25 | 011=E 26 | 100=H 27 | 101=L 28 | 110=M (Memory reference through address in H:L) 29 | 30 | Register pair 'RP' fields: 31 | 00=BC (B:C as 16 bit register) 32 | 01=DE (D:E as 16 bit register) 33 | 10=HL (H:L as 16 bit register) 34 | 11=SP (Stack pointer, refers to PSW (FLAGS:A) for PUSH/POP) 35 | 36 | Condition code 'CCC' fields: (FLAGS: S Z x A x P x C) 37 | 000=NZ ('Z'ero flag not set) 38 | 001=Z ('Z'ero flag set) 39 | 010=NC ('C'arry flag not set) 40 | 011=C ('C'arry flag set) 41 | 100=PO ('P'arity flag not set - ODD) 42 | 101=PE ('P'arity flag set - EVEN) 43 | 110=P ('S'ign flag not set - POSITIVE) 44 | 111=M ('S'ign flag set - MINUS) 45 | 46 | Inst Encoding Flags Description 47 | ---------------------------------------------------------------------- 48 | MOV D,S 01DDDSSS - Move register to register 49 | MVI D,# 00DDD110 db - Move immediate to register 50 | LXI RP,# 00RP0001 lb hb - Load register pair immediate 51 | LDA a 00111010 lb hb - Load A from memory 52 | STA a 00110010 lb hb - Store A to memory 53 | LHLD a 00101010 lb hb - Load H:L from memory 54 | SHLD a 00100010 lb hb - Store H:L to memory 55 | LDAX RP 00RP1010 *1 - Load indirect through BC or DE 56 | STAX RP 00RP0010 *1 - Store indirect through BC or DE 57 | XCHG 11101011 - Exchange DE and HL content 58 | ADD S 10000SSS ZSPCA Add register to A 59 | ADI # 11000110 db ZSCPA Add immediate to A 60 | ADC S 10001SSS ZSCPA Add register to A with carry 61 | ACI # 11001110 db ZSCPA Add immediate to A with carry 62 | SUB S 10010SSS ZSCPA Subtract register from A 63 | SUI # 11010110 db ZSCPA Subtract immediate from A 64 | SBB S 10011SSS ZSCPA Subtract register from A with borrow 65 | SBI # 11011110 db ZSCPA Subtract immediate from A with borrow 66 | INR D 00DDD100 ZSPA Increment register 67 | DCR D 00DDD101 ZSPA Decrement register 68 | INX RP 00RP0011 - Increment register pair 69 | DCX RP 00RP1011 - Decrement register pair 70 | DAD RP 00RP1001 C Add register pair to HL (16 bit add) 71 | DAA 00100111 ZSPCA Decimal Adjust accumulator 72 | ANA S 10100SSS ZSCPA AND register with A 73 | ANI # 11100110 db ZSPCA AND immediate with A 74 | ORA S 10110SSS ZSPCA OR register with A 75 | ORI # 11110110 ZSPCA OR immediate with A 76 | XRA S 10101SSS ZSPCA ExclusiveOR register with A 77 | XRI # 11101110 db ZSPCA ExclusiveOR immediate with A 78 | CMP S 10111SSS ZSPCA Compare register with A 79 | CPI # 11111110 ZSPCA Compare immediate with A 80 | RLC 00000111 C Rotate A left 81 | RRC 00001111 C Rotate A right 82 | RAL 00010111 C Rotate A left through carry 83 | RAR 00011111 C Rotate A right through carry 84 | CMA 00101111 - Compliment A 85 | CMC 00111111 C Compliment Carry flag 86 | STC 00110111 C Set Carry flag 87 | JMP a 11000011 lb hb - Unconditional jump 88 | Jccc a 11CCC010 lb hb - Conditional jump 89 | CALL a 11001101 lb hb - Unconditional subroutine call 90 | Cccc a 11CCC100 lb hb - Conditional subroutine call 91 | RET 11001001 - Unconditional return from subroutine 92 | Rccc 11CCC000 - Conditional return from subroutine 93 | RST n 11NNN111 - Restart (Call n*8) 94 | PCHL 11101001 - Jump to address in H:L 95 | PUSH RP 11RP0101 *2 - Push register pair on the stack 96 | POP RP 11RP0001 *2 *2 Pop register pair from the stack 97 | XTHL 11100011 - Swap H:L with top word on stack 98 | SPHL 11111001 - Set SP to content of H:L 99 | IN p 11011011 pa - Read input port into A 100 | OUT p 11010011 pa - Write A to output port 101 | EI 11111011 - Enable interrupts 102 | DI 11110011 - Disable interrupts 103 | HLT 01110110 - Halt processor 104 | NOP 00000000 - No operation 105 | 106 | *1 = Only RP=00(BC) and 01(DE) are allowed for LDAX/STAX 107 | 108 | *2 = RP=11 refers to PSW for PUSH/POP (cannot push/pop SP). 109 | When PSW is POP'd, ALL flags are affected. 110 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f0f0f0; 3 | color: #eee; 4 | font-family: 'Consolas', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Liberation Mono', 'DejaVu Sans Mono', 'Courier New', monospace; 5 | font-size: 16px; 6 | margin: 15px; 7 | padding: 0; 8 | } 9 | 10 | input { 11 | font-family: 'Consolas', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Liberation Mono', 'DejaVu Sans Mono', 'Courier New', monospace; 12 | } 13 | 14 | div, img, pre { 15 | border: 0; 16 | margin: 0; 17 | padding: 0; 18 | } 19 | 20 | a:link { 21 | color: #6cf; 22 | } 23 | a:visited { 24 | color: #6cf; 25 | } 26 | a:hover { 27 | color: #6cf; 28 | } 29 | a:active { 30 | color: #6cf; 31 | } 32 | 33 | .h-box { 34 | display: flex; 35 | flex-direction: row; 36 | flex-wrap: wrap; 37 | } 38 | 39 | .v-box { 40 | display: flex; 41 | flex-direction: column; 42 | flex-wrap: nowrap; 43 | } 44 | 45 | .justify-start { 46 | justify-content: start; 47 | } 48 | 49 | .justify-end { 50 | justify-content: end; 51 | } 52 | 53 | .justify-center { 54 | justify-content: center; 55 | } 56 | 57 | .justify-space-between { 58 | justify-content: space-between; 59 | } 60 | 61 | .justify-space-around { 62 | justify-content: space-around; 63 | } 64 | 65 | .justify-space-evenly { 66 | justify-content: space-evenly; 67 | } 68 | 69 | .align-items-center { 70 | align-items: center; 71 | } 72 | 73 | .m-8 { 74 | margin: 8px; 75 | } 76 | 77 | .mt-8 { 78 | margin-top: 8px; 79 | } 80 | 81 | .ml-8 { 82 | margin-left: 8px; 83 | } 84 | 85 | .mr-8 { 86 | margin-right: 8px; 87 | } 88 | 89 | .mb-8 { 90 | margin-bottom: 8px; 91 | } 92 | 93 | .flex-grow-1 { 94 | flex-grow: 1; 95 | } 96 | 97 | .flex-grow-2 { 98 | flex-grow: 2; 99 | } 100 | 101 | .flex-grow-4 { 102 | flex-grow: 4; 103 | } 104 | 105 | main { 106 | background-color: #222; 107 | border-radius: 10px; 108 | margin: 0 auto; 109 | max-width: 1200px; 110 | padding: 10px; 111 | } 112 | 113 | .header-bar { 114 | align-items: center; 115 | color: #eee; 116 | display: flex; 117 | flex-direction: row; 118 | flex-wrap: wrap; 119 | justify-content: center; 120 | margin: 10px 0; 121 | min-height: 64px; 122 | } 123 | 124 | #header-title { 125 | font-size: 18px; 126 | font-weight: bold; 127 | } 128 | 129 | #switch-locale { 130 | background-color: #369; 131 | border-radius: 10px; 132 | color: #6cf; 133 | cursor: pointer; 134 | font-size: 12px; 135 | margin: 20px; 136 | padding: 5px 10px; 137 | user-select: none; 138 | -moz-user-select: none; 139 | -ms-user-select: none; 140 | -webkit-user-select: none; 141 | } 142 | 143 | .subheader-bar { 144 | background-color: #666; 145 | border-radius: 10px; 146 | color: #eee; 147 | display: flex; 148 | flex-direction: row; 149 | flex-wrap: nowrap; 150 | justify-content: center; 151 | line-height: 3em; 152 | margin: 10px 0; 153 | padding: 0; 154 | } 155 | 156 | nav { 157 | color: #222; 158 | display: flex; 159 | flex-direction: row; 160 | flex-wrap: nowrap; 161 | justify-content: space-around; 162 | min-height: 40px; 163 | padding: 0; 164 | } 165 | 166 | .nav-item { 167 | background-color: #eee; 168 | border-radius: 30px; 169 | cursor: pointer; 170 | flex-grow: 1; 171 | line-height: 40px; 172 | margin: 0 5px; 173 | padding: 0 5px; 174 | text-align: center; 175 | user-select: none; 176 | -moz-user-select: none; 177 | -ms-user-select: none; 178 | -webkit-user-select: none; 179 | } 180 | 181 | nav .selected { 182 | background-color: #f90; 183 | cursor: auto; 184 | font-weight: bold; 185 | } 186 | 187 | #panel { 188 | user-select: none; 189 | -moz-user-select: none; 190 | -ms-user-select: none; 191 | -webkit-user-select: none; 192 | margin: 10px 0; 193 | } 194 | 195 | footer { 196 | color: #999; 197 | font-size: 14px; 198 | margin: 40px auto 0 auto; 199 | max-width: 960px; 200 | text-align: center; 201 | } 202 | 203 | .comments { 204 | color: #999; 205 | font-size: 14px; 206 | line-height: 1.5em; 207 | padding: 0 10px; 208 | } 209 | 210 | #debug-data-input { 211 | background-color: #eee; 212 | border-radius: 10px; 213 | line-height: 24px; 214 | padding: 5px 10px; 215 | } 216 | 217 | #debug-load-data { 218 | border: #ccc 1px solid; 219 | } 220 | 221 | #cpu-dump { 222 | background-color: #ccc; 223 | border-radius: 10px; 224 | color: #222; 225 | font-size: 14px; 226 | line-height: 24px; 227 | min-height: 24px; 228 | overflow-x: hidden; 229 | padding: 5px 10px; 230 | } 231 | 232 | #mem-dump { 233 | background-color: #ccc; 234 | border-radius: 10px; 235 | color: #222; 236 | font-size: 14px; 237 | line-height: 24px; 238 | min-height: 24px; 239 | overflow-x: hidden; 240 | padding: 5px 10px; 241 | } 242 | 243 | .tutorial { 244 | background-color: #ccc; 245 | border-radius: 10px; 246 | color: #222; 247 | font-size: 14px; 248 | line-height: 1.5em; 249 | min-height: 20px; 250 | overflow-x: hidden; 251 | padding: 5px 10px; 252 | } 253 | 254 | .button { 255 | background-color: #eee; 256 | border: 1px solid #bbb; 257 | border-radius: 10px; 258 | color: #222; 259 | cursor: pointer; 260 | font-size: 14px; 261 | line-height: 2em; 262 | padding: 2px 10px; 263 | text-align: center; 264 | user-select: none; 265 | -moz-user-select: none; 266 | -ms-user-select: none; 267 | -webkit-user-select: none; 268 | } 269 | 270 | #tab-sim { 271 | margin: 10px 0; 272 | } 273 | 274 | #tab-debug { 275 | display: none; 276 | margin: 10px 0; 277 | } 278 | 279 | #tab-ref { 280 | display: none; 281 | margin: 10px 0; 282 | } 283 | -------------------------------------------------------------------------------- /js/l10n.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 wixette@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * @fileoverview Localization utilities and message translations. 17 | */ 18 | 19 | /** 20 | * Simple namespace. 21 | * @type {Object} 22 | */ 23 | l10n = {}; 24 | 25 | /** 26 | * Pre-defined locales. 27 | * @type {Array} 28 | */ 29 | l10n.LOCALES = [ 30 | 'en', 31 | 'zh', 32 | ]; 33 | 34 | /** 35 | * Localized messages. 36 | */ 37 | l10n.MESSAGES = { 38 | 'title': { 39 | 'en': 'Sim-8800: Altair 8800 Simulator', 40 | 'zh': 'Sim-8800: Altair 8800 模拟器', 41 | }, 42 | 43 | 'header-title': { 44 | 'en': 'Altair 8800 Simulator', 45 | 'zh': 'Altair 8800 模拟器', 46 | }, 47 | 48 | 'nav-sim': { 49 | 'en': 'Simulator', 50 | 'zh': '模拟器', 51 | }, 52 | 53 | 'nav-debug': { 54 | 'en': 'Debugger', 55 | 'zh': '调试器', 56 | }, 57 | 58 | 'nav-ref': { 59 | 'en': 'Tutorial', 60 | 'zh': '使用手册', 61 | }, 62 | 63 | 'switchboard-helper': { 64 | 'en': 'Switch Board Helper', 65 | 'zh': '辅助开关面板', 66 | }, 67 | 68 | 'back-home': { 69 | 'en': 'Source Code', 70 | 'zh': '源代码', 71 | }, 72 | 73 | 'source-code': { 74 | 'en': 'Source code', 75 | 'zh': '源代码', 76 | }, 77 | 78 | 'debug-load-data-title': { 79 | 'en': 'Load Data to Addr #0', 80 | 'zh': '从地址0开始加载数据', 81 | }, 82 | 83 | 'debug-load-data': { 84 | 'en': 'Load Data', 85 | 'zh': '加载数据', 86 | }, 87 | 88 | 'debug-data-sample': { 89 | 'en': 'Bytes in HEX string, such as \'c3 00 00\'', 90 | 'zh': '十六进制字节序列,如 c3 00 00', 91 | }, 92 | 93 | 'debug-cpu-dump-title': { 94 | 'en': '8080 CPU Status Dump', 95 | 'zh': '8080 CPU 的状态信息', 96 | }, 97 | 98 | 'debug-mem-dump-title': { 99 | 'en': 'Memory Dump', 100 | 'zh': '内存信息', 101 | }, 102 | 103 | 'tutorial-title': { 104 | 'en': 'Quick Tutorial', 105 | 'zh': '快速教程', 106 | }, 107 | 108 | 'tutorial-desc': { 109 | 'en': 'How to input and run the following program to calculate 1 + 2 = 3:', 110 | 'zh': '如何输入并运行以下加法程序,并计算 1 + 2 = 3:', 111 | }, 112 | 113 | 'tutorial-1': { 114 | 'en': 'Turn on Altair 8800 by clicking OFF/ON switch.', 115 | 'zh': '点击 OFF/ON 开关,打开 Altair 8800', 116 | }, 117 | 118 | 'tutorial-2': { 119 | 'en': 'Set switches A7-A0 to 00 111 010 (up for 1, down for 0).', 120 | 'zh': '将开关 A7-A0 依次设置为 00 111 010 (开关朝上为 1,开关朝下为 0)', 121 | }, 122 | 123 | 'tutorial-3': { 124 | 'en': 'Click "DEPOSIT".', 125 | 'zh': '点击 DEPOSIT', 126 | }, 127 | 128 | 'tutorial-4': { 129 | 'en': 'Set switches A7-A0 to 10 000 000.', 130 | 'zh': '将开关 A7-A0 依次设置为 10 000 000', 131 | }, 132 | 133 | 'tutorial-5': { 134 | 'en': 'Click "DEPOSIT NEXT".', 135 | 'zh': '点击 DEPOSIT NEXT', 136 | }, 137 | 138 | 'tutorial-6': { 139 | 'en': 'Repeat step 4-5 to input the following bytes one by one: 00 000 000, 01 000 111, 00 111 010, 10 000 001, 00 000 000, 10 000 000, 00 110 010, 10 000 010, 00 000 000, 11 000 011, 00 000 000, 00 000 000.', 140 | 'zh': '重复步骤 4 到步骤 5,逐个输入以下字节:00 000 000, 01 000 111, 00 111 010, 10 000 001, 00 000 000, 10 000 000, 00 110 010, 10 000 010, 00 000 000, 11 000 011, 00 000 000, 00 000 000', 141 | }, 142 | 143 | 'tutorial-7': { 144 | 'en': 'Set switches A7-A0 to 10 000 000.', 145 | 'zh': '将开关 A7-A0 依次设置为 10 000 000', 146 | }, 147 | 148 | 'tutorial-8': { 149 | 'en': 'Click "EXAMINE".', 150 | 'zh': '点击 EXAMINE', 151 | }, 152 | 153 | 'tutorial-9': { 154 | 'en': 'Set switches A7-A0 to 00 000 001 (the first number to be added, or 1 in decimal).', 155 | 'zh': '将开关 A7-A0 依次设置为 00 000 001(即第一个加数的值,也就是十进制的 1)', 156 | }, 157 | 158 | 'tutorial-10': { 159 | 'en': 'Click "DEPOSIT".', 160 | 'zh': '点击 DEPOSIT', 161 | }, 162 | 163 | 'tutorial-11': { 164 | 'en': 'Set switches A7-A0 to 00 000 010 (the second number to be added, or 2 in decimal).', 165 | 'zh': '将开关 A7-A0 依次设置为 00 000 010(即第二个加数的值,也就是十进制的 2)', 166 | }, 167 | 168 | 'tutorial-12': { 169 | 'en': 'Click "DEPOSIT NEXT".', 170 | 'zh': '点击 DEPOSIT NEXT', 171 | }, 172 | 173 | 'tutorial-13': { 174 | 'en': 'Click "RESET".', 175 | 'zh': '点击 RESET', 176 | }, 177 | 178 | 'tutorial-14': { 179 | 'en': 'Click "RUN" and wait for a few seconds.', 180 | 'zh': '点击 RUN 并等待几秒钟', 181 | }, 182 | 183 | 'tutorial-15': { 184 | 'en': 'Click "STOP".', 185 | 'zh': '点击 STOP', 186 | }, 187 | 188 | 'tutorial-16': { 189 | 'en': 'Set switches A7-A0 to 10 000 010 (the address that holds the sum).', 190 | 'zh': '将开关 A7-A0 依次设置为 10 000 010(即存储计算结果的地址)', 191 | }, 192 | 193 | 'tutorial-17': { 194 | 'en': 'Click "EXAMINE".', 195 | 'zh': '点击 EXAMINE', 196 | }, 197 | 198 | 'tutorial-18': { 199 | 'en': 'The LEDs D7-D0 show the result 00 000 011 (3 in decimal).', 200 | 'zh': 'LED 灯 D7-D0 显示出计算结果 00 000 011(即十进制的 3)', 201 | }, 202 | 203 | 'tutorial-19': { 204 | 'en': 'Turn off Altair 8800.', 205 | 'zh': '关闭 Altair 8800', 206 | }, 207 | 208 | 'reference-title': { 209 | 'en': 'References', 210 | 'zh': '参考资料', 211 | }, 212 | }; 213 | 214 | /** 215 | * Current locale index. 216 | * @type {number} 217 | */ 218 | l10n.current = 0; 219 | 220 | /** 221 | * Local storage key. 222 | */ 223 | l10n.localStorageKey = 'sim8800locale'; 224 | 225 | /** 226 | * Switches to the next locale. 227 | */ 228 | l10n.nextLocale = function() { 229 | l10n.current++; 230 | l10n.current = l10n.current % l10n.LOCALES.length; 231 | l10n.updateMessages(); 232 | localStorage.setItem(l10n.localStorageKey, l10n.current); 233 | }; 234 | 235 | /** 236 | * Restores the last locale from local storage. 237 | */ 238 | l10n.restoreLocale = function() { 239 | var val = localStorage.getItem(l10n.localStorageKey); 240 | if (!val) { 241 | val = '0'; 242 | } 243 | var index = parseInt(val); 244 | if (!isNaN(index)) { 245 | l10n.current = index % l10n.LOCALES.length; 246 | l10n.updateMessages(); 247 | } 248 | }; 249 | 250 | /** 251 | * Updates UI messages to the current locale. 252 | */ 253 | l10n.updateMessages = function() { 254 | elems = document.getElementsByClassName('l10n'); 255 | for (let i = 0; i < elems.length; i++) { 256 | if (l10n.MESSAGES.hasOwnProperty(elems[i].id)) { 257 | var locale = l10n.LOCALES[l10n.current]; 258 | var msg = ''; 259 | if (l10n.MESSAGES[elems[i].id].hasOwnProperty(locale)) { 260 | msg = l10n.MESSAGES[elems[i].id][locale]; 261 | } else { 262 | msg = l10n.MESSAGES[elems[i].id]['en']; 263 | } 264 | elems[i].innerHTML = msg; 265 | } 266 | } 267 | }; 268 | -------------------------------------------------------------------------------- /text-mode.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 56 | 57 | 58 |

Text-mode Altair 8800 simulator

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 | In HEX string, e.g.: 00 01 02 03 0a 0b 0c 0d 90 |

91 | 92 |
93 | 94 |

LEDs: 95 |

96 |

97 | INTE   98 | PROT   99 | MEMR   100 | INP   101 | MI   102 | OUT   103 | HLTA   104 | STACK   105 | WO   106 | INT   107 |

108 |
109 | 110 |
111 |

112 | WAIT   113 | HLDA   114 |

115 |
116 | 117 |
118 |

119 | D7 D6 D5 D4 D3 D2 D1 D0
120 |   121 |   122 |   123 |   124 |   125 |   126 |   127 |   128 |

129 |
130 |
131 |

132 | A15 A14 A13 A12 A11 A10 A9  A8  A7  A6  A5  A4  A3  A2  A1  A0
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 |

CPU dump: 178 |

179 |

180 | 181 |
182 | 183 |

Memory dump: 184 |

185 |

186 | 187 | 188 | 189 | 292 | 293 | 294 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /js/sim8800.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 wixette@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * @fileoverview Altair 8800 front panel simulator. 17 | */ 18 | 19 | 20 | /** 21 | * The simulator. 22 | */ 23 | class Sim8800 { 24 | /** 25 | * @param {number} memSize The memory size, in bytes. 26 | * @param {number} clockRate The clock rate. 27 | * @param {function(Array)?} setAddressLedsCallback The 28 | * callback to set address LEDs. 29 | * @param {function(Array)?} setDataLedsCallback The 30 | * callback to set data LEDs. 31 | * @param {function(boolean)?} setWaitLedCallback The callback to 32 | * set the WAIT LED. 33 | * @param {function(boolean)?} setStatusLedsCallback The callback to 34 | * set the STATUS LEDs. 35 | * @param {function():number?} getInputAddressCallback The 36 | * callback to get the input word from address/data switches. 37 | * @param {function(string)?} dumpCpuCallback The callback to receive 38 | * CPU status dump, in HTML string. 39 | * @param {function(string)?} dumpMemCallback The callback to receive 40 | * memory contents dump, in HTML string. 41 | */ 42 | constructor(memSize, clockRate, 43 | setAddressLedsCallback, setDataLedsCallback, 44 | setWaitLedCallback, setStatusLedsCallback, 45 | getInputAddressCallback, 46 | dumpCpuCallback, dumpMemCallback) { 47 | this.clockRate = clockRate; 48 | this.mem = new Array(memSize); 49 | this.setAddressLedsCallback = setAddressLedsCallback; 50 | this.setDataLedsCallback = setDataLedsCallback; 51 | this.setWaitLedCallback = setWaitLedCallback; 52 | this.setStatusLedsCallback = setStatusLedsCallback; 53 | this.getInputAddressCallback = getInputAddressCallback; 54 | this.dumpCpuCallback = dumpCpuCallback; 55 | this.dumpMemCallback = dumpMemCallback; 56 | this.isPoweredOn = false; 57 | this.isRunning = false; 58 | this.lastAddress = 0; 59 | this.initMem(); 60 | CPU8080.init(this.getWriteByteCallback(), 61 | this.getReadByteCallback(), 62 | null, /* not used. */ 63 | this.getWritePortCallback(), 64 | this.getReadPortCallback()); 65 | } 66 | 67 | /** 68 | * Formats a number to fixed length hex string. 69 | * @param {number} n The number to be formatted. 70 | * @param {number} len The output length, with leading zeros. 71 | */ 72 | static toHex(n, len) { 73 | var leadingZeros = (new Array(len)).fill('0').join(''); 74 | return (leadingZeros + n.toString(16)).substr(-len); 75 | } 76 | 77 | /** 78 | * Parses a number into an array of binary bits. 79 | * @param {number} data The data to be parsed. 80 | * @param {number} numBits Number of bits to be parsed. 81 | * @return {Array} Sequence of 0 or 1, from the lowest bit to 82 | * the highest bit. 83 | */ 84 | static parseBits(data, numBits) { 85 | var bits = []; 86 | for (let i = 0; i < numBits; i++) { 87 | bits.push(data & 1 != 0 ? 1 : 0); 88 | data >>>= 1; 89 | } 90 | return bits; 91 | } 92 | 93 | /** 94 | * Fills the memory with dummy bytes. 95 | */ 96 | initMem(random = false) { 97 | if (random) { 98 | for (let i = 0; i < this.mem.length; i++) { 99 | this.mem[i] = Math.floor(Math.random() * 256); 100 | } 101 | } else { 102 | this.mem.fill(0); 103 | } 104 | } 105 | 106 | /** 107 | * Loads data into memory. 108 | * @param {number} address The start address to load the data/program. 109 | * @param {Array} data The array of data. 110 | */ 111 | loadData(address, data) { 112 | if (!this.isPoweredOn) 113 | return; 114 | for (let i = 0; i < data.length && address < this.mem.length; i++) { 115 | this.mem[address++] = data[i]; 116 | } 117 | this.dumpMem(); 118 | } 119 | 120 | /** 121 | * Loads data into memory. 122 | * @param {number} address The start address to load the data/program. 123 | * @param {string} hexString Data encoded in hex string, like 'c3 00 00'. 124 | */ 125 | loadDataAsHexString(address, hexString) { 126 | if (!this.isPoweredOn || !hexString) 127 | return; 128 | var data = hexString.split(' '); 129 | for (let i = 0; i < data.length && address < this.mem.length; i++) { 130 | var byte = parseInt('0x' + data[i]); 131 | if (!isNaN(byte)) { 132 | this.mem[address++] = byte; 133 | } 134 | } 135 | this.dumpMem(); 136 | } 137 | 138 | /** 139 | * Dumps the memory to HTML, for debugging or monitoring. 140 | */ 141 | dumpMem() { 142 | if (this.dumpMemCallback) { 143 | var sb = ['
\n'];
144 |             for (let i = 0; i < this.mem.length; i += 16) {
145 |                 sb.push(Sim8800.toHex(i, 4));
146 |                 sb.push('  ');
147 |                 for (let j = i;
148 |                      j < Math.min(this.mem.length, i + 16);
149 |                      j++) {
150 |                     sb.push(Sim8800.toHex(this.mem[j], 2));
151 |                     sb.push((j + 1) % 8 == 0 ? '  ' : ' ');
152 |                 }
153 |                 sb.push('\n');
154 |             }
155 |             sb.push('
\n'); 156 | this.dumpMemCallback(sb.join('')); 157 | } 158 | } 159 | 160 | /** 161 | * Decodes the FLAGs register. 162 | * @param {number} flags The value of the FLAGs register. 163 | * @return {Object} The decoded flags. 164 | */ 165 | decodeFlags(flags) { 166 | var ret = {}; 167 | ret.sign = flags & 0x80 != 0; 168 | ret.zero = flags & 0x40 != 0; 169 | ret.auxiliaryCarry = flags & 0x10 != 0; 170 | ret.parity = flags & 0x04 != 0; 171 | ret.carry = flags & 0x01 != 0; 172 | return ret; 173 | } 174 | 175 | /** 176 | * Dumps the internal CPU status to HTML, for debugging or mornitoring. 177 | */ 178 | dumpCpu() { 179 | if (this.dumpCpuCallback) { 180 | var cpu = CPU8080.status(); 181 | var sb = ['
\n'];
182 |             sb.push('PC = ' + Sim8800.toHex(cpu.pc, 4) + '  ');
183 |             sb.push('SP = ' + Sim8800.toHex(cpu.sp, 4) + '\n');
184 |             sb.push('A = ' + Sim8800.toHex(cpu.a, 2) + '  ');
185 |             sb.push('B = ' + Sim8800.toHex(cpu.b, 2) + '  ');
186 |             sb.push('C = ' + Sim8800.toHex(cpu.c, 2) + '  ');
187 |             sb.push('D = ' + Sim8800.toHex(cpu.d, 2) + '\n');
188 |             sb.push('E = ' + Sim8800.toHex(cpu.e, 2) + '  ');
189 |             sb.push('F = ' + Sim8800.toHex(cpu.f, 2) + '  ');
190 |             sb.push('H = ' + Sim8800.toHex(cpu.h, 2) + '  ');
191 |             sb.push('L = ' + Sim8800.toHex(cpu.l, 2) + '\n');
192 |             var flags = this.decodeFlags(cpu.f);
193 |             sb.push('FLAGS: ');
194 |             if (flags.sign) sb.push('SIGN ');
195 |             if (flags.zero) sb.push('ZERO ');
196 |             if (flags.auxiliaryCarry) sb.push('AC ');
197 |             if (flags.parity) sb.push('PARITY ');
198 |             if (flags.carry) sb.push('CARRY ');
199 |             sb.push('
\n'); 200 | this.dumpCpuCallback(sb.join('')); 201 | } 202 | } 203 | 204 | /** 205 | * Returns the byteTo (write memory) callback. 206 | * @return {function(number, number)} 207 | */ 208 | getWriteByteCallback() { 209 | var self = this; 210 | return function(address, value) { 211 | address = address % self.mem.length; 212 | self.mem[address] = value; 213 | }; 214 | } 215 | 216 | /** 217 | * Returns the byteAt (read memory) callback. 218 | * @return {function(number): number} 219 | */ 220 | getReadByteCallback() { 221 | var self = this; 222 | return function(address) { 223 | address = address % self.mem.length; 224 | var value = self.mem[address]; 225 | return value; 226 | }; 227 | } 228 | 229 | /** 230 | * Returns the porto (write port) callback. 231 | * @return {function(number, number)} 232 | */ 233 | getWritePortCallback() { 234 | var self = this; 235 | return function(address, value) { 236 | if (address == 0xff && self.setDataLedsCallback) { 237 | var bits = Sim8800.parseBits(value, 8); 238 | self.setDataLedsCallback(bits); 239 | } 240 | }; 241 | } 242 | 243 | /** 244 | * Returns the byteAt (read memory) callback. 245 | * @return {function(number): number} 246 | */ 247 | getReadPortCallback() { 248 | var self = this; 249 | return function(address) { 250 | var value = 0; 251 | // We only care about the port 0xff. 252 | if (address == 0xff && self.getInputAddressCallback) { 253 | var word = self.getInputAddressCallback(); 254 | return word >> 8; 255 | } 256 | return value; 257 | }; 258 | } 259 | 260 | /** 261 | * Gets the clock ticker callback. 262 | * @return {function()} 263 | */ 264 | getClockTickerCallback() { 265 | var self = this; 266 | return function(timestamp) { 267 | if (self.isRunning) { 268 | var cycles = self.clockRate / 1000; 269 | self.step(cycles); 270 | window.setTimeout(self.getClockTickerCallback(), 1); 271 | } 272 | }; 273 | } 274 | 275 | /** 276 | * Powers on the machine. 277 | */ 278 | powerOn() { 279 | this.isPoweredOn = true; 280 | this.initMem(); 281 | this.reset(); 282 | if (this.setStatusLedsCallback) { 283 | this.setStatusLedsCallback(true); 284 | } 285 | if (this.setWaitLedCallback) { 286 | this.setWaitLedCallback(false); 287 | } 288 | } 289 | 290 | /** 291 | * Powers off the machine. 292 | */ 293 | powerOff() { 294 | if (this.setStatusLedsCallback) { 295 | this.setStatusLedsCallback(false); 296 | } 297 | if (this.setWaitLedCallback) { 298 | this.setWaitLedCallback(true); 299 | } 300 | if (this.setAddressLedsCallback) { 301 | this.setAddressLedsCallback(new Array(16).fill(0)); 302 | } 303 | if (this.setDataLedsCallback) { 304 | this.setDataLedsCallback(new Array(8).fill(0)); 305 | } 306 | if (this.dumpCpuCallback) { 307 | this.dumpCpuCallback(''); 308 | } 309 | if (this.dumpMemCallback) { 310 | this.dumpMemCallback(''); 311 | } 312 | this.isPoweredOn = false; 313 | } 314 | 315 | /** 316 | * Resets the machine. 317 | */ 318 | reset() { 319 | if (!this.isPoweredOn) 320 | return; 321 | CPU8080.reset(); 322 | this.stop(); 323 | this.lastAddress = 0; 324 | if (this.setAddressLedsCallback) { 325 | this.setAddressLedsCallback(new Array(16).fill(1)); 326 | } 327 | if (this.setDataLedsCallback) { 328 | this.setDataLedsCallback(new Array(8).fill(1)); 329 | } 330 | this.dumpCpu(); 331 | this.dumpMem(); 332 | var self = this; 333 | window.setTimeout(function() { 334 | if (self.setAddressLedsCallback) { 335 | self.setAddressLedsCallback(new Array(16).fill(0)); 336 | } 337 | if (self.setDataLedsCallback) { 338 | self.setDataLedsCallback(new Array(8).fill(0)); 339 | } 340 | }, 400); 341 | } 342 | 343 | /** 344 | * Stops the CPU. 345 | */ 346 | stop() { 347 | if (!this.isPoweredOn) 348 | return; 349 | this.isRunning = false; 350 | if (this.setWaitLedCallback) { 351 | this.setWaitLedCallback(this.isRunning); 352 | } 353 | } 354 | 355 | /** 356 | * Starts the CPU. 357 | */ 358 | start() { 359 | if (!this.isPoweredOn) 360 | return; 361 | this.isRunning = true; 362 | if (this.setWaitLedCallback) { 363 | this.setWaitLedCallback(this.isRunning); 364 | } 365 | window.setTimeout(this.getClockTickerCallback(), 1); 366 | } 367 | 368 | /** 369 | * Runs a given number of CPU cycles. 370 | * @param {number} cycles The number of CPU cycles to step on. 371 | */ 372 | step(cycles) { 373 | if (!this.isPoweredOn) 374 | return; 375 | CPU8080.steps(cycles); 376 | this.dumpCpu(); 377 | this.dumpMem(); 378 | if (this.setAddressLedsCallback) { 379 | let cpu = CPU8080.status(); 380 | let bits = Sim8800.parseBits(cpu.pc, 16); 381 | this.setAddressLedsCallback(bits); 382 | } 383 | } 384 | 385 | /** 386 | * Shows the address and the byte at the address via LEDs. 387 | */ 388 | showAddressAndData() { 389 | if (this.setAddressLedsCallback) { 390 | let bits = Sim8800.parseBits(this.lastAddress, 16); 391 | this.setAddressLedsCallback(bits); 392 | } 393 | if (this.setDataLedsCallback) { 394 | let bits = Sim8800.parseBits(this.mem[this.lastAddress], 8); 395 | this.setDataLedsCallback(bits); 396 | } 397 | } 398 | 399 | /** 400 | * Reads a byte from the given address. 401 | */ 402 | examine() { 403 | if (!this.isPoweredOn) 404 | return; 405 | if (this.getInputAddressCallback) { 406 | var address = this.getInputAddressCallback(); 407 | this.lastAddress = address; 408 | this.showAddressAndData(); 409 | } 410 | } 411 | 412 | /** 413 | * Reads a byte from the next address. 414 | */ 415 | examineNext() { 416 | if (!this.isPoweredOn) 417 | return; 418 | this.lastAddress++; 419 | this.showAddressAndData(); 420 | } 421 | 422 | /** 423 | * Writes a byte to the given address. 424 | */ 425 | deposit() { 426 | if (!this.isPoweredOn) 427 | return; 428 | if (this.getInputAddressCallback) { 429 | // Only 8 bits of input is considered. 430 | var value = this.getInputAddressCallback() & 0xff; 431 | this.getWriteByteCallback()(this.lastAddress, value); 432 | this.showAddressAndData(); 433 | this.dumpMem(); 434 | } 435 | } 436 | 437 | /** 438 | * Writes a byte to the next address. 439 | */ 440 | depositNext() { 441 | if (!this.isPoweredOn) 442 | return; 443 | this.lastAddress++; 444 | this.deposit(); 445 | } 446 | }; 447 | -------------------------------------------------------------------------------- /js/panel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 wixette@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * @fileoverview The main logic to control the front panel UI. 17 | */ 18 | 19 | /** 20 | * Simple namespace. 21 | * @type {Object} 22 | */ 23 | panel = {}; 24 | 25 | /** 26 | * When STOP switch is pressed. 27 | */ 28 | panel.onStop = function() { 29 | panel.sim.stop(); 30 | }; 31 | 32 | /** 33 | * When RUN switch is pressed. 34 | */ 35 | panel.onRun = function() { 36 | panel.sim.start(); 37 | }; 38 | 39 | /** 40 | * When SINGLE STEP switch is pressed. 41 | */ 42 | panel.onSingle = function() { 43 | panel.sim.step(1); 44 | }; 45 | 46 | /** 47 | * When EXAMINE switch is pressed. 48 | */ 49 | panel.onExamine = function() { 50 | panel.sim.examine(); 51 | }; 52 | 53 | /** 54 | * When EXAMINE NEXT switch is pressed. 55 | */ 56 | panel.onExamineNext = function() { 57 | panel.sim.examineNext(); 58 | }; 59 | 60 | /** 61 | * When DEPOSIT switch is pressed. 62 | */ 63 | panel.onDeposit = function() { 64 | panel.sim.deposit(); 65 | }; 66 | 67 | /** 68 | * When DEPOSIT NEXT switch is pressed. 69 | */ 70 | panel.onDepositNext = function() { 71 | panel.sim.depositNext(); 72 | }; 73 | 74 | /** 75 | * When RESET switch is pressed. 76 | */ 77 | panel.onReset = function() { 78 | panel.sim.reset(); 79 | }; 80 | 81 | /** 82 | * When power is turned on. 83 | */ 84 | panel.onPowerOn = function() { 85 | panel.sim.powerOn(); 86 | window.setTimeout(function() { 87 | panel.playBeepbeep(); 88 | }, 500); 89 | }; 90 | 91 | /** 92 | * When power is turned off. 93 | */ 94 | panel.onPowerOff = function() { 95 | panel.sim.powerOff(); 96 | }; 97 | 98 | /** 99 | * When CPU sets the address LEDs. 100 | */ 101 | panel.setAddressLedsCallback = function(bits) { 102 | for (let i = 0; i < bits.length; i++) { 103 | var ledId = 'A' + i; 104 | if (bits[i]) { 105 | panel.ledOn(ledId); 106 | } else { 107 | panel.ledOff(ledId); 108 | } 109 | } 110 | }; 111 | 112 | /** 113 | * When CPU sets the data LEDs. 114 | */ 115 | panel.setDataLedsCallback = function(bits) { 116 | for (let i = 0; i < bits.length; i++) { 117 | var ledId = 'D' + i; 118 | if (bits[i]) { 119 | panel.ledOn(ledId); 120 | } else { 121 | panel.ledOff(ledId); 122 | } 123 | } 124 | }; 125 | 126 | /** 127 | * When CPU sets the WAIT LED. 128 | */ 129 | panel.setWaitLedCallback = function(isRunning) { 130 | var ledId = 'WAIT'; 131 | if (!isRunning) { 132 | panel.ledOn(ledId); 133 | } else { 134 | panel.ledOff(ledId); 135 | } 136 | }; 137 | 138 | /** 139 | * When CPU sets the status LEDs. 140 | */ 141 | panel.setStatusLedsCallback = function(isPoweredOn) { 142 | var ledIds = ['MEMR', 'MI', 'WO']; 143 | for (let i = 0; i < ledIds.length; i++) { 144 | if (isPoweredOn) { 145 | panel.ledOn(ledIds[i]); 146 | } else { 147 | panel.ledOff(ledIds[i]); 148 | } 149 | } 150 | }; 151 | 152 | /** 153 | * When CPU reads the number input by the address/data switches. 154 | */ 155 | panel.getInputAddressCallback = function() { 156 | var word = 0; 157 | for (let i = 0; i < 16; i++) { 158 | if (panel.addressSwitchStates[i]) { 159 | word |= 1 << i; 160 | } 161 | } 162 | return word; 163 | }; 164 | 165 | /** 166 | * When CPU dumps the CPU status for debug. 167 | */ 168 | panel.dumpCpuCallback = function(dumpHtml) { 169 | var dumpCpuElem = document.getElementById('cpu-dump'); 170 | dumpCpuElem.innerHTML = dumpHtml; 171 | }; 172 | 173 | /** 174 | * When CPU dumps the MEM contents for debug. 175 | */ 176 | panel.dumpMemCallback = function(dumpHtml) { 177 | var dumpMemElem = document.getElementById('mem-dump'); 178 | dumpMemElem.innerHTML = dumpHtml; 179 | }; 180 | 181 | /** 182 | * Deposites data into MEM directly in debug panel. 183 | */ 184 | panel.debugLoadData = function() { 185 | var data = document.getElementById("debug-data-input").value; 186 | panel.sim.loadDataAsHexString(0, data); 187 | }; 188 | 189 | /** 190 | * The info of all LEDs. 191 | */ 192 | panel.LED_INFO = [ 193 | { 194 | id: 'INTE', 195 | x: 194, 196 | y: 120 197 | }, 198 | { 199 | id: 'PROT', 200 | x: 245, 201 | y: 120 202 | }, 203 | { 204 | id: 'MEMR', 205 | x: 296, 206 | y: 120 207 | }, 208 | { 209 | id: 'INP', 210 | x: 347, 211 | y: 120 212 | }, 213 | { 214 | id: 'MI', 215 | x: 398, 216 | y: 120 217 | }, 218 | { 219 | id: 'OUT', 220 | x: 449, 221 | y: 120 222 | }, 223 | { 224 | id: 'HLTA', 225 | x: 500, 226 | y: 120 227 | }, 228 | { 229 | id: 'STACK', 230 | x: 551, 231 | y: 120 232 | }, 233 | { 234 | id: 'WO', 235 | x: 602, 236 | y: 120 237 | }, 238 | { 239 | id: 'INT', 240 | x: 653, 241 | y: 120 242 | }, 243 | { 244 | id: 'D7', 245 | x: 830, 246 | y: 120 247 | }, 248 | { 249 | id: 'D6', 250 | x: 880, 251 | y: 120 252 | }, 253 | { 254 | id: 'D5', 255 | x: 959, 256 | y: 120 257 | }, 258 | { 259 | id: 'D4', 260 | x: 1009, 261 | y: 120 262 | }, 263 | { 264 | id: 'D3', 265 | x: 1059, 266 | y: 120 267 | }, 268 | { 269 | id: 'D2', 270 | x: 1138, 271 | y: 120 272 | }, 273 | { 274 | id: 'D1', 275 | x: 1188, 276 | y: 120 277 | }, 278 | { 279 | id: 'D0', 280 | x: 1238, 281 | y: 120 282 | }, 283 | { 284 | id: 'WAIT', 285 | x: 194, 286 | y: 230 287 | }, 288 | { 289 | id: 'HLDA', 290 | x: 245, 291 | y: 230 292 | }, 293 | { 294 | id: 'A15', 295 | x: 346, 296 | y: 230 297 | }, 298 | { 299 | id: 'A14', 300 | x: 423, 301 | y: 230 302 | }, 303 | { 304 | id: 'A13', 305 | x: 473, 306 | y: 230 307 | }, 308 | { 309 | id: 'A12', 310 | x: 523, 311 | y: 230 312 | }, 313 | { 314 | id: 'A11', 315 | x: 602, 316 | y: 230 317 | }, 318 | { 319 | id: 'A10', 320 | x: 652, 321 | y: 230 322 | }, 323 | { 324 | id: 'A9', 325 | x: 702, 326 | y: 230 327 | }, 328 | { 329 | id: 'A8', 330 | x: 780, 331 | y: 230 332 | }, 333 | { 334 | id: 'A7', 335 | x: 830, 336 | y: 230 337 | }, 338 | { 339 | id: 'A6', 340 | x: 880, 341 | y: 230 342 | }, 343 | { 344 | id: 'A5', 345 | x: 959, 346 | y: 230 347 | }, 348 | { 349 | id: 'A4', 350 | x: 1009, 351 | y: 230 352 | }, 353 | { 354 | id: 'A3', 355 | x: 1059, 356 | y: 230 357 | }, 358 | { 359 | id: 'A2', 360 | x: 1138, 361 | y: 230 362 | }, 363 | { 364 | id: 'A1', 365 | x: 1188, 366 | y: 230 367 | }, 368 | { 369 | id: 'A0', 370 | x: 1238, 371 | y: 230 372 | }, 373 | ]; 374 | 375 | /** 376 | * The info of all toggle switches. 377 | * 378 | * A toggle switch has an upper state (which means 1 for address 379 | * switches) and a lower state (which means 0 for address switches). 380 | */ 381 | panel.TOGGLE_SWITCH_INFO = [ 382 | { 383 | id: 'OFF-ON', 384 | x: 105, 385 | y: 439 386 | }, 387 | { 388 | id: 'S15', 389 | x: 346, 390 | y: 334 391 | }, 392 | { 393 | id: 'S14', 394 | x: 423, 395 | y: 334 396 | }, 397 | { 398 | id: 'S13', 399 | x: 473, 400 | y: 334 401 | }, 402 | { 403 | id: 'S12', 404 | x: 523, 405 | y: 334 406 | }, 407 | { 408 | id: 'S11', 409 | x: 602, 410 | y: 334 411 | }, 412 | { 413 | id: 'S10', 414 | x: 652, 415 | y: 334 416 | }, 417 | { 418 | id: 'S9', 419 | x: 702, 420 | y: 334 421 | }, 422 | { 423 | id: 'S8', 424 | x: 780, 425 | y: 334 426 | }, 427 | { 428 | id: 'S7', 429 | x: 830, 430 | y: 334 431 | }, 432 | { 433 | id: 'S6', 434 | x: 880, 435 | y: 334 436 | }, 437 | { 438 | id: 'S5', 439 | x: 959, 440 | y: 334 441 | }, 442 | { 443 | id: 'S4', 444 | x: 1009, 445 | y: 334 446 | }, 447 | { 448 | id: 'S3', 449 | x: 1059, 450 | y: 334 451 | }, 452 | { 453 | id: 'S2', 454 | x: 1138, 455 | y: 334 456 | }, 457 | { 458 | id: 'S1', 459 | x: 1188, 460 | y: 334 461 | }, 462 | { 463 | id: 'S0', 464 | x: 1238, 465 | y: 334 466 | }, 467 | ]; 468 | 469 | /** 470 | * The info of all stateless switches. 471 | * 472 | * A stateless switch may has a upper command and a lower 473 | * command. When a command is clicked, the switch moves up or down 474 | * then back to its middle position, without keeping upper or lower 475 | * state. 476 | */ 477 | panel.STATELESS_SWITCH_INFO = [ 478 | { 479 | id: 'STOP-RUN', 480 | x: 348, 481 | y: 439, 482 | upperCmd: { textId: 'SW-STOP', callback: panel.onStop }, 483 | lowerCmd: { textId: 'SW-RUN', callback: panel.onRun }, 484 | }, 485 | { 486 | id: 'SINGLE', 487 | x: 446, 488 | y: 439, 489 | upperCmd: { textId: 'SW-SINGLE', callback: panel.onSingle }, 490 | lowerCmd: null, 491 | }, 492 | { 493 | id: 'EXAMINE', 494 | x: 550, 495 | y: 439, 496 | upperCmd: { textId: 'SW-EXAMINE', callback: panel.onExamine }, 497 | lowerCmd: { textId: 'SW-EXAMINE-NEXT', callback: panel.onExamineNext }, 498 | }, 499 | { 500 | id: 'DEPOSIT', 501 | x: 650, 502 | y: 439, 503 | upperCmd: { textId: 'SW-DEPOSIT', callback: panel.onDeposit }, 504 | lowerCmd: { textId: 'SW-DEPOSIT-NEXT', callback: panel.onDepositNext }, 505 | }, 506 | { 507 | id: 'RESET', 508 | x: 753, 509 | y: 439, 510 | upperCmd: { textId: 'SW-RESET', callback: panel.onReset }, 511 | lowerCmd: null, 512 | }, 513 | { 514 | id: 'PROTECT', 515 | x: 853, 516 | y: 439, 517 | upperCmd: null, 518 | lowerCmd: null, 519 | }, 520 | { 521 | id: 'AUX1', 522 | x: 957, 523 | y: 439, 524 | upperCmd: null, 525 | lowerCmd: null, 526 | }, 527 | { 528 | id: 'AUX2', 529 | x: 1060, 530 | y: 439, 531 | upperCmd: null, 532 | lowerCmd: null, 533 | }, 534 | ]; 535 | 536 | /** 537 | * The type ID of toggle switch. 538 | */ 539 | panel.TOGGLE_SWITCH = 0; 540 | 541 | /** 542 | * The type ID of stateless switch. 543 | */ 544 | panel.STATELESS_SWITCH = 1; 545 | 546 | /** The current state of all the address switches. */ 547 | panel.addressSwitchStates = new Array(16); 548 | 549 | /** If the power is turned on. */ 550 | panel.isPoweredOn = false; 551 | 552 | /** The simulator object. */ 553 | panel.sim = null; 554 | 555 | /** 556 | * Initializes thie UI. 557 | */ 558 | panel.init = function() { 559 | // Restores the last locale if it exists. 560 | l10n.restoreLocale(); 561 | 562 | // Initializes event listener for nav buttons. 563 | var button = document.getElementById('switch-locale'); 564 | button.addEventListener('click', l10n.nextLocale, false); 565 | button = document.getElementById('nav-sim'); 566 | button.addEventListener('click', panel.showTabSim, false); 567 | button = document.getElementById('nav-debug'); 568 | button.addEventListener('click', panel.showTabDebug, false); 569 | button = document.getElementById('nav-ref'); 570 | button.addEventListener('click', panel.showTabRes, false); 571 | panel.showTabSim(); 572 | 573 | // Initializes event listener for debug controls. 574 | button = document.getElementById('debug-load-data'); 575 | button.addEventListener('click', panel.debugLoadData, false); 576 | 577 | // Initializes svg components for all LEDs. 578 | for (let i = 0; i < panel.LED_INFO.length; i++) { 579 | let info = panel.LED_INFO[i]; 580 | let led = panel.createLed(info.id, info.x, info.y); 581 | } 582 | 583 | // Initializes svg components for all switches. 584 | for (let i = 0; i < panel.TOGGLE_SWITCH_INFO.length; i++) { 585 | let info = panel.TOGGLE_SWITCH_INFO[i]; 586 | let sw = panel.createSwitch(info.id, panel.TOGGLE_SWITCH, 587 | info.x, info.y, 588 | null, null); 589 | } 590 | for (let i = 0; i < panel.STATELESS_SWITCH_INFO.length; i++) { 591 | let info = panel.STATELESS_SWITCH_INFO[i]; 592 | let sw = panel.createSwitch(info.id, panel.STATELESS_SWITCH, 593 | info.x, info.y, 594 | info.upperCmd, info.lowerCmd); 595 | } 596 | 597 | // Initializes internal states. 598 | panel.isPoweredOn = false; 599 | panel.addressSwitchStates.fill(0); 600 | panel.switchUp('OFF-ON'); 601 | 602 | // Initializes the simulator. 603 | panel.sim = new Sim8800( 604 | 256, /* 256B MEM */ 605 | 1000000, /* 1MHz */ 606 | panel.setAddressLedsCallback, panel.setDataLedsCallback, 607 | panel.setWaitLedCallback, panel.setStatusLedsCallback, 608 | panel.getInputAddressCallback, 609 | panel.dumpCpuCallback, panel.dumpMemCallback); 610 | }; 611 | 612 | /** 613 | * Creates a new LED inside the panel svg. 614 | * @param {string} id The LED ID. This ID will be used as the prefix 615 | * of DOM element's ID. 616 | * @param {number} x The x position. 617 | * @param {number} y The y position. 618 | */ 619 | panel.createLed = function(id, x, y) { 620 | var panelElem = document.getElementById('panel'); 621 | var ledOnElem = document.getElementById('led-on'); 622 | var ledOffElem = document.getElementById('led-off'); 623 | 624 | ledOnElem.style.display = 'none'; 625 | ledOffElem.style.display = 'none'; 626 | 627 | var onElem = ledOnElem.cloneNode(true); 628 | onElem.id = id + '-on'; 629 | onElem.x.baseVal.value = '' + x; 630 | onElem.y.baseVal.value = '' + y; 631 | onElem.style.display = 'none'; 632 | 633 | var offElem = ledOffElem.cloneNode(true); 634 | offElem.id = id + '-off'; 635 | offElem.x.baseVal.value = '' + x; 636 | offElem.y.baseVal.value = '' + y; 637 | offElem.style.display = 'inline'; 638 | 639 | panelElem.appendChild(onElem); 640 | panelElem.appendChild(offElem); 641 | }; 642 | 643 | /** 644 | * Turns on the specified LED. 645 | * @param {string} id The LED ID. 646 | */ 647 | panel.ledOn = function(id) { 648 | document.getElementById(id + '-on').style.display = 'inline'; 649 | document.getElementById(id + '-off').style.display = 'none'; 650 | }; 651 | 652 | /** 653 | * Turns off the specified LED. 654 | * @param {string} id The LED ID. 655 | */ 656 | panel.ledOff = function(id) { 657 | document.getElementById(id + '-on').style.display = 'none'; 658 | document.getElementById(id + '-off').style.display = 'inline'; 659 | }; 660 | 661 | /** 662 | * Creates a new toggle switch inside the panel svg. 663 | * @param {string} id The switch ID. This ID will be used as the 664 | * prefix of DOM element's ID. 665 | * @param {number} type The type of the switch. 666 | * @param {number} x The x position. 667 | * @param {number} y The y position. 668 | * @param {Object} upperCmd The upperCmd info, for STATELESS_SWITCH 669 | * only. 670 | * @param {Object} lowerCmd The lowerCmd info, for STATELESS_SWITCH 671 | * only. 672 | */ 673 | panel.createSwitch = function(id, type, x, y, upperCmd, lowerCmd) { 674 | var panelElem = document.getElementById('panel'); 675 | var switchMidElem = document.getElementById('switch-mid'); 676 | var switchUpElem = document.getElementById('switch-up'); 677 | var switchDownElem = document.getElementById('switch-down'); 678 | 679 | switchMidElem.style.display = 'none'; 680 | switchUpElem.style.display = 'none'; 681 | switchDownElem.style.display = 'none'; 682 | 683 | var midElem = switchMidElem.cloneNode(true); 684 | midElem.id = id + '-mid'; 685 | midElem.x.baseVal.value = '' + x; 686 | midElem.y.baseVal.value = '' + y; 687 | midElem.style.display = (type == panel.STATELESS_SWITCH) ? 'inline' : 'none'; 688 | 689 | var upElem = switchUpElem.cloneNode(true); 690 | upElem.id = id + '-up'; 691 | upElem.x.baseVal.value = '' + x; 692 | upElem.y.baseVal.value = '' + y; 693 | if (type == panel.TOGGLE_SWITCH) { 694 | upElem.style.cursor = 'pointer'; 695 | } 696 | upElem.style.display = 'none'; 697 | 698 | var downElem = switchDownElem.cloneNode(true); 699 | downElem.id = id + '-down'; 700 | downElem.x.baseVal.value = '' + x; 701 | downElem.y.baseVal.value = '' + y; 702 | if (type == panel.TOGGLE_SWITCH) { 703 | downElem.style.cursor = 'pointer'; 704 | } 705 | downElem.style.display = (type == panel.TOGGLE_SWITCH) ? 'inline' : 'none'; 706 | 707 | if (type == panel.TOGGLE_SWITCH) { 708 | var sourceId = id; 709 | upElem.addEventListener('click', 710 | function() { 711 | panel.onToggle(sourceId); 712 | }, 713 | false); 714 | downElem.addEventListener('click', 715 | function() { 716 | panel.onToggle(sourceId); 717 | }, 718 | false); 719 | // Also installs helper switch handlers. 720 | let softSwitchId = 'S-' + id; 721 | let elem = document.getElementById(softSwitchId); 722 | elem.addEventListener( 723 | 'click', 724 | function() { 725 | panel.onToggle(sourceId); 726 | }, 727 | false 728 | ); 729 | } else { 730 | if (upperCmd) { 731 | let cmdElem = document.getElementById(upperCmd.textId); 732 | cmdElem.style.cursor = 'pointer'; 733 | cmdElem.addEventListener( 734 | 'click', 735 | function() { 736 | panel.switchUpThenBack(id); 737 | panel.playSwitch(); 738 | upperCmd.callback(); 739 | }, 740 | false); 741 | // Also installs helper switch handlers. 742 | cmdElem = document.getElementById('S' + upperCmd.textId); 743 | cmdElem.addEventListener( 744 | 'click', 745 | function() { 746 | panel.switchUpThenBack(id); 747 | panel.playSwitch(); 748 | upperCmd.callback(); 749 | }, 750 | false); 751 | } 752 | if (lowerCmd) { 753 | let cmdElem = document.getElementById(lowerCmd.textId); 754 | cmdElem.style.cursor = 'pointer'; 755 | cmdElem.addEventListener( 756 | 'click', 757 | function() { 758 | panel.switchDownThenBack(id); 759 | panel.playSwitch(); 760 | lowerCmd.callback(); 761 | }, 762 | false); 763 | // Also installs helper switch handlers. 764 | cmdElem = document.getElementById('S' + lowerCmd.textId); 765 | cmdElem.addEventListener( 766 | 'click', 767 | function() { 768 | panel.switchDownThenBack(id); 769 | panel.playSwitch(); 770 | lowerCmd.callback(); 771 | }, 772 | false); 773 | } 774 | } 775 | 776 | panelElem.appendChild(midElem); 777 | panelElem.appendChild(upElem); 778 | panelElem.appendChild(downElem); 779 | }; 780 | 781 | /** 782 | * Moves the switch handle up - for TOGGLE_SWITCH only. 783 | * @param {string} id The switch ID. 784 | */ 785 | panel.switchUp = function(id) { 786 | var midElem = document.getElementById(id + '-mid'); 787 | var upElem = document.getElementById(id + '-up'); 788 | var downElem = document.getElementById(id + '-down'); 789 | 790 | upElem.style.display = 'inline'; 791 | midElem.style.display = 'none'; 792 | downElem.style.display = 'none'; 793 | }; 794 | 795 | /** 796 | * Moves the switch handle down - for TOGGLE_SWITCH only. 797 | * @param {string} id The switch ID. 798 | */ 799 | panel.switchDown = function(id) { 800 | var midElem = document.getElementById(id + '-mid'); 801 | var upElem = document.getElementById(id + '-up'); 802 | var downElem = document.getElementById(id + '-down'); 803 | 804 | upElem.style.display = 'none'; 805 | midElem.style.display = 'none'; 806 | downElem.style.display = 'inline'; 807 | }; 808 | 809 | /** 810 | * Moves the switch handle up, then back to the middle position - for 811 | * STATELESS_SWITCH only. 812 | * @param {string} id The switch ID. 813 | */ 814 | panel.switchUpThenBack = function(id) { 815 | var midElem = document.getElementById(id + '-mid'); 816 | var upElem = document.getElementById(id + '-up'); 817 | var downElem = document.getElementById(id + '-down'); 818 | 819 | upElem.style.display = 'none'; 820 | midElem.style.display = 'inline'; 821 | downElem.style.display = 'none'; 822 | 823 | window.setTimeout(function() { 824 | upElem.style.display = 'inline'; 825 | midElem.style.display = 'none'; 826 | downElem.style.display = 'none'; 827 | 828 | window.setTimeout(function() { 829 | upElem.style.display = 'none'; 830 | midElem.style.display = 'inline'; 831 | downElem.style.display = 'none'; 832 | }, 300); 833 | }, 300); 834 | }; 835 | 836 | /** 837 | * Moves the switch handle down, then back to the middle position - 838 | * for STATELESS_SWITCH only. 839 | * @param {string} id The switch ID. 840 | */ 841 | panel.switchDownThenBack = function(id) { 842 | var midElem = document.getElementById(id + '-mid'); 843 | var upElem = document.getElementById(id + '-up'); 844 | var downElem = document.getElementById(id + '-down'); 845 | 846 | upElem.style.display = 'none'; 847 | midElem.style.display = 'inline'; 848 | downElem.style.display = 'none'; 849 | 850 | window.setTimeout(function() { 851 | upElem.style.display = 'none'; 852 | midElem.style.display = 'none'; 853 | downElem.style.display = 'inline'; 854 | 855 | window.setTimeout(function() { 856 | upElem.style.display = 'none'; 857 | midElem.style.display = 'inline'; 858 | downElem.style.display = 'none'; 859 | }, 400); 860 | }, 100); 861 | }; 862 | 863 | /** 864 | * Handles the click event for all TOGGLE_SWITCH controls. 865 | * @param {string} id The switch ID which has been clicked. 866 | */ 867 | panel.onToggle = function(id) { 868 | panel.playToggle(); 869 | if (id[0] == 'S') { 870 | var bitIndex = parseInt(id.substr(1)); 871 | var state = panel.addressSwitchStates[bitIndex]; 872 | if (state == 0) { 873 | panel.switchUp(id); 874 | } else { 875 | panel.switchDown(id); 876 | } 877 | panel.addressSwitchStates[bitIndex] = state ? 0 : 1; 878 | } else if (id == 'OFF-ON') { 879 | if (panel.isPoweredOn) { 880 | panel.onPowerOff(); 881 | panel.switchUp(id); 882 | panel.isPoweredOn = false; 883 | } else { 884 | panel.onPowerOn(); 885 | panel.switchDown(id); 886 | panel.isPoweredOn = true; 887 | } 888 | } 889 | }; 890 | 891 | /** 892 | * Plays a sound audio. 893 | */ 894 | panel.playSound = function(id) { 895 | var sound = document.getElementById(id); 896 | sound.currentTime = 0; 897 | sound.play(); 898 | }; 899 | 900 | /** 901 | * Plays beep beep. 902 | */ 903 | panel.playBeepbeep = function() { 904 | panel.playSound('sound-beepbeep'); 905 | }; 906 | 907 | /** 908 | * Plays the sound of toggle click. 909 | */ 910 | panel.playToggle = function() { 911 | panel.playSound('sound-toggle'); 912 | }; 913 | 914 | /** 915 | * Plays the sound of stateless switch click. 916 | */ 917 | panel.playSwitch = function() { 918 | panel.playSound('sound-switch'); 919 | }; 920 | 921 | /** 922 | * Highlights a nav tab or removes the effect. 923 | * @param {Element} elem The DOM element of the nav tab. 924 | * @param {boolean} highlight Whether highlight the tab. 925 | */ 926 | panel.highlightNavTab = function(elem, highlight) { 927 | if (highlight) { 928 | elem.classList.add('selected'); 929 | } else { 930 | elem.classList.remove('selected'); 931 | } 932 | }; 933 | 934 | /** 935 | * Shows the simulator tab, and hides the other two. 936 | */ 937 | panel.showTabSim = function() { 938 | document.getElementById('tab-sim').style.display = 'block'; 939 | document.getElementById('tab-debug').style.display = 'none'; 940 | document.getElementById('tab-ref').style.display = 'none'; 941 | panel.highlightNavTab(document.getElementById('nav-sim'), true); 942 | panel.highlightNavTab(document.getElementById('nav-debug'), false); 943 | panel.highlightNavTab(document.getElementById('nav-ref'), false); 944 | }; 945 | 946 | /** 947 | * Shows the debug tab, and hides the other two. 948 | */ 949 | panel.showTabDebug = function() { 950 | document.getElementById('tab-sim').style.display = 'none'; 951 | document.getElementById('tab-debug').style.display = 'block'; 952 | document.getElementById('tab-ref').style.display = 'none'; 953 | panel.highlightNavTab(document.getElementById('nav-sim'), false); 954 | panel.highlightNavTab(document.getElementById('nav-debug'), true); 955 | panel.highlightNavTab(document.getElementById('nav-ref'), false); 956 | }; 957 | 958 | /** 959 | * Shows the resource tab, and hides the other two. 960 | */ 961 | panel.showTabRes = function() { 962 | document.getElementById('tab-sim').style.display = 'none'; 963 | document.getElementById('tab-debug').style.display = 'none'; 964 | document.getElementById('tab-ref').style.display = 'block'; 965 | panel.highlightNavTab(document.getElementById('nav-sim'), false); 966 | panel.highlightNavTab(document.getElementById('nav-debug'), false); 967 | panel.highlightNavTab(document.getElementById('nav-ref'), true); 968 | }; 969 | -------------------------------------------------------------------------------- /design/panel.svg: -------------------------------------------------------------------------------- 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 | INTE 196 | 197 | 198 | PROT 199 | 200 | 201 | MEMR 202 | 203 | 204 | INP 205 | 206 | 207 | MI 208 | 209 | 210 | OUT 211 | 212 | 213 | HLTA 214 | 215 | 216 | STACK 217 | 218 | 219 | WO 220 | 221 | 222 | INT 223 | 224 | 225 | D7 226 | 227 | 228 | D6 229 | 230 | 231 | D5 232 | 233 | 234 | D4 235 | 236 | 237 | D3 238 | 239 | 240 | D2 241 | 242 | 243 | D1 244 | 245 | 246 | D0 247 | 248 | 249 | A7 250 | 251 | 252 | A6 253 | 254 | 255 | A5 256 | 257 | 258 | A4 259 | 260 | 261 | A3 262 | 263 | 264 | A2 265 | 266 | 267 | A1 268 | 269 | 270 | A0 271 | 272 | 273 | A15 274 | 275 | 276 | HLDA 277 | 278 | 279 | A14 280 | 281 | 282 | A13 283 | 284 | 285 | A12 286 | 287 | 288 | A11 289 | 290 | 291 | A10 292 | 293 | 294 | A9 295 | 296 | 297 | A8 298 | 299 | 300 | WAIT 301 | 302 | 303 | SENSE SW. 304 | 305 | 306 | Address 307 | 308 | 309 | Data 310 | 311 | 312 | STOP 313 | 314 | 315 | RUN 316 | 317 | 318 | EXAMINE 319 | 320 | 321 | EXAMINE 322 | NEXT 323 | 324 | 325 | SINGLE 326 | STEP 327 | 328 | 329 | DEPOSIT 330 | 331 | 332 | DEPOSIT 333 | NEXT 334 | 335 | 336 | RESET 337 | 338 | 339 | CLR 340 | 341 | 342 | PROTECT 343 | 344 | 345 | AUX 346 | 347 | 348 | UNPROTECT 349 | 350 | 351 | AUX 352 | 353 | 354 | 7 355 | 356 | 357 | 6 358 | 359 | 360 | 5 361 | 362 | 363 | 4 364 | 365 | 366 | 3 367 | 368 | 369 | 2 370 | 371 | 372 | 1 373 | 374 | 375 | 0 376 | 377 | 378 | 15 379 | 380 | 381 | 14 382 | 383 | 384 | 13 385 | 386 | 387 | 12 388 | 389 | 390 | 11 391 | 392 | 393 | 10 394 | 395 | 396 | 9 397 | 398 | 399 | 8 400 | 401 | 402 | STATUS 403 | 404 | 405 | OFF 406 | 407 | 408 | ON 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | Sim-8800: Altair 8800 Simulator 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 | Sim-8800: Altair 8800 Simulator 30 |
31 |
En|中文
32 |
33 | 34 | 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 | INTE 235 | 236 | 237 | PROT 238 | 239 | 240 | MEMR 241 | 242 | 243 | INP 244 | 245 | 246 | MI 247 | 248 | 249 | OUT 250 | 251 | 252 | HLTA 253 | 254 | 255 | STACK 256 | 257 | 258 | WO 259 | 260 | 261 | INT 262 | 263 | 264 | D7 265 | 266 | 267 | D6 268 | 269 | 270 | D5 271 | 272 | 273 | D4 274 | 275 | 276 | D3 277 | 278 | 279 | D2 280 | 281 | 282 | D1 283 | 284 | 285 | D0 286 | 287 | 288 | A7 289 | 290 | 291 | A6 292 | 293 | 294 | A5 295 | 296 | 297 | A4 298 | 299 | 300 | A3 301 | 302 | 303 | A2 304 | 305 | 306 | A1 307 | 308 | 309 | A0 310 | 311 | 312 | A15 313 | 314 | 315 | HLDA 316 | 317 | 318 | A14 319 | 320 | 321 | A13 322 | 323 | 324 | A12 325 | 326 | 327 | A11 328 | 329 | 330 | A10 331 | 332 | 333 | A9 334 | 335 | 336 | A8 337 | 338 | 339 | WAIT 340 | 341 | 342 | SENSE SW. 343 | 344 | 345 | Address 346 | 347 | 348 | Data 349 | 350 | 351 | STOP 352 | 353 | 354 | RUN 355 | 356 | 357 | EXAMINE 358 | 359 | 360 | EXAMINE 361 | NEXT 362 | 363 | 364 | SINGLE 365 | STEP 366 | 367 | 368 | DEPOSIT 369 | 370 | 371 | DEPOSIT 372 | NEXT 373 | 374 | 375 | RESET 376 | 377 | 378 | CLR 379 | 380 | 381 | PROTECT 382 | 383 | 384 | AUX 385 | 386 | 387 | UNPROTECT 388 | 389 | 390 | AUX 391 | 392 | 393 | 7 394 | 395 | 396 | 6 397 | 398 | 399 | 5 400 | 401 | 402 | 4 403 | 404 | 405 | 3 406 | 407 | 408 | 2 409 | 410 | 411 | 1 412 | 413 | 414 | 0 415 | 416 | 417 | 15 418 | 419 | 420 | 14 421 | 422 | 423 | 13 424 | 425 | 426 | 12 427 | 428 | 429 | 11 430 | 431 | 432 | 10 433 | 434 | 435 | 9 436 | 437 | 438 | 8 439 | 440 | 441 | STATUS 442 | 443 | 444 | OFF 445 | 446 | 447 | ON 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 |
569 |
Switch Board Helper
570 |
571 |
572 |
573 |
A15
574 |
A14
575 |
A13
576 |
A12
577 |
A11
578 |
A10
579 |
A09
580 |
A08
581 |
A07
582 |
A06
583 |
A05
584 |
A04
585 |
A03
586 |
A02
587 |
A01
588 |
A00
589 |
590 |
591 |
OFF/ON
592 |
STOP
593 |
RUN
594 |
SINGLE STEP
595 |
EXAMINE
596 |
EXAMINE-NEXT
597 |
DEPOSIT
598 |
DEPOSIT-NEXT
599 |
RESET
600 |
601 |
602 | 603 |
604 | 605 |
606 |
607 |
Load Data to Addr #0
608 |
609 |
610 | 611 |
Load Data
612 |
613 |
614 | In HEX string, such as 'c3 00 00' 615 |
616 | 617 |
618 |
8080 CPU Status Dump
619 |
620 |
621 | 622 |
623 |
Memory Dump
624 |
625 |
626 |
627 | 628 |
629 |
630 |
Quick Tutorial
631 |
632 |
633 |

634 |

    635 |
  • How to input and run the following program to calculate 1 + 2 = 3:
  • 636 |
637 |

638 |
639 |         LDA 0080H  ; 00 111 010
640 |                    ; 10 000 000
641 |                    ; 00 000 000
642 |         MOV B,A    ; 01 000 111
643 |         LDA 0081H  ; 00 111 010
644 |                    ; 10 000 001
645 |                    ; 00 000 000
646 |         ADD B      ; 10 000 000
647 |         STA 0082H  ; 00 110 010
648 |                    ; 10 000 010
649 |                    ; 00 000 000
650 |         JMP 0000H  ; 11 000 011
651 |                    ; 00 000 000
652 |                    ; 00 000 000
653 |

654 |

    655 |
  1. Turn on Altair 8800 by clicking OFF/ON switch.
  2. 656 |
  3. Set switches A7-A0 to 00 111 010 (up for 1, down for 0).
  4. 657 |
  5. Click "DEPOSIT".
  6. 658 |
  7. Set switches A7-A0 to 10 000 000.
  8. 659 |
  9. Click "DEPOSIT NEXT".
  10. 660 |
  11. Repeat step 4-5 to input the following bytes one by one: 00 000 000, 01 000 111, 00 111 010, 10 000 001, 00 000 000, 10 000 000, 00 110 010, 10 000 010, 00 000 000, 11 000 011, 00 000 000, 00 000 000.
  12. 661 |
  13. Set switches A7-A0 to 10 000 000.
  14. 662 |
  15. Click "EXAMINE".
  16. 663 |
  17. Set switches A7-A0 to 00 000 001 (the first number to be added, or 1 in decimal).
  18. 664 |
  19. Click "DEPOSIT".
  20. 665 |
  21. Set switches A7-A0 to 00 000 010 (the second number to be added, or 2 in decimal).
  22. 666 |
  23. Click "DEPOSIT NEXT".
  24. 667 |
  25. Click "RESET".
  26. 668 |
  27. Click "RUN" and wait for a few seconds.
  28. 669 |
  29. Click "STOP".
  30. 670 |
  31. Set switches A7-A0 to 10 000 010 (the address that holds the sum).
  32. 671 |
  33. Click "EXAMINE".
  34. 672 |
  35. The LEDs D7-D0 show the result 00 000 011 (3 in decimal).
  36. 673 |
  37. Turn off Altair 8800.
  38. 674 |
675 |

676 |
677 | 678 |
679 |
Source code
680 |
681 |
682 |

683 |

684 |

685 |
686 | 687 |
688 |
References
689 |
690 | 703 | 704 |
705 |
706 | 707 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 731 | 732 | 733 | --------------------------------------------------------------------------------