├── .gitignore ├── FAQ.md ├── LS8-cheatsheet.md ├── LS8-spec.md ├── README.md ├── asm ├── README.md ├── asm.js ├── buildall ├── call.asm ├── interrupts.asm ├── keyboard.asm ├── mult.asm ├── print8.asm ├── printstr.asm ├── sctest.asm ├── stack.asm └── stackoverflow.asm └── ls8 ├── Makefile ├── README.md ├── cpu.c ├── cpu.h ├── examples ├── call.ls8 ├── interrupts.ls8 ├── keyboard.ls8 ├── mult.ls8 ├── print8.ls8 ├── printstr.ls8 ├── stack.ls8 └── stackoverflow.ls8 └── ls8.c /.gitignore: -------------------------------------------------------------------------------- 1 | ls8/ls8 2 | ls8/ls8.exe 3 | 4 | # Prerequisites 5 | *.d 6 | 7 | # Object files 8 | *.o 9 | *.ko 10 | *.obj 11 | *.elf 12 | 13 | # Linker output 14 | *.ilk 15 | *.map 16 | *.exp 17 | 18 | # Precompiled Headers 19 | *.gch 20 | *.pch 21 | 22 | # Libraries 23 | *.lib 24 | *.a 25 | *.la 26 | *.lo 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | # Debug files 43 | *.dSYM/ 44 | *.su 45 | *.idb 46 | *.pdb 47 | 48 | # Kernel Module Compile Results 49 | *.mod* 50 | *.cmd 51 | .tmp_versions/ 52 | modules.order 53 | Module.symvers 54 | Mkfile.old 55 | dkms.conf 56 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # Computer Architecture FAQ 2 | 3 | ## Contents 4 | 5 | ### Common Problems 6 | 7 | * [When I print out the value of my `LDI` instruction, it prints as `-126` when I know the value is `0b10000010`, or `130`. What gives?](#q3900) 8 | * [Do you have some handy code for helping trace what the CPU is doing?](#q2000) 9 | 10 | ### General 11 | 12 | * [How much of the emulator do I need to implement?](#q100) 13 | * [Once we get the `HLT` instruction, what should the emulator do?](#q200) 14 | * [Is the flags `FL` register one of the `Rx` registers, or is it a special register?](#q900) 15 | * [What about the `IR`, `MAR`, and `MDR` registers?](#q1000) 16 | * [If RAM is faster than an SSD, why not just store everything in RAM?](#q1200) 17 | * [Do CPUs get hot because of the power constantly running through them?](#q1300) 18 | * [What's the `NULL` in the `strtoul()` call?](#q1500) 19 | * [Can we use `memset()` to clear RAM and the registers in `cpu_init()`, or do we have to use a loop?](#q1600) 20 | * [How do I move the `PC` to the next instruction without hardcoding the instruction length?](#q1700) 21 | * [Can I use `getline()` instead of `fgets()` for reading lines from the files?](#q1800) 22 | * [Why are the ALU and the RAM read/write functions broken out? Can we just code the lines to do the work directly?](#q1900) 23 | * [Why do opcodes have the numeric values that they do?](#q2200) 24 | * [What is a "cache hit" or "cache miss"?](#q2300) 25 | * [How are logic gates built?](#q2400) 26 | * [How does the CPU use logic gates?](#q2500) 27 | * [Why is half a byte called a _nibble_?](#q2600) 28 | * [What are the `<<` and `>>` shift operators useful for?](#q2700) 29 | * [On a multicore CPU, is there some kind of overseer that coordinates between the cores?](#q3100) 30 | * [On a multicore CPU, do cores share registers or do they have their own sets?](#q3200) 31 | * [Does the ALU handle conditionals/`CMP`?](#q3400) 32 | * [How are floating point numbers represented in binary?](#q3600) 33 | * [How are signed integers represented in binary?](#q3700) 34 | * [How does the CPU cache work? What is L1, L2, L3 and so on?](#q3800) 35 | 36 | ### `CALL`/`RET`, Subroutines 37 | 38 | * [How do you return values from subroutines?](#q300) 39 | 40 | ### Interrupts 41 | 42 | * [With interrupts, why do we push everything on the stack?](#q400) 43 | 44 | ### The CPU Stack 45 | 46 | * [What is "stack overflow"?](#q500) 47 | * [What is "stack underflow"?](#q600) 48 | * [On the LS-8, why does the stack pointer start at address `F4`, when the first stack element is at `F3`?](#q700) 49 | * [How are stacks and subroutines used by higher-level languages like C?](#q800) 50 | * [Why does the CPU allow for stack overflow or underflow?](#q2900) 51 | * [Why does the CPU support a stack and not some other data structure?](#q3000) 52 | * [On the LS-8, why does `POP` need an operand?](#q3500) 53 | 54 | ### Registers 55 | 56 | * [What are the registers for, and what do they do?](#q1100) 57 | * [What is the difference between general-purpose registers and internal, special-purpose registers?](#q2800) 58 | * [Is the flags `FL` register one of the `Rx` registers, or is it a special register?](#q900) 59 | * [What about the `IR`, `MAR`, and `MDR` registers?](#q1000) 60 | * [How do I move the `PC` to the next instruction without hardcoding the instruction length?](#q1700) 61 | * [Why is `R7` set to something other than zero?](#q2100) 62 | * [Are the flags on the LS-8 stored on the stack or in a register?](#q3300) 63 | 64 | ### Number Bases and Conversions 65 | 66 | * [Why is hex base 16? Seems so random.](#q1400) 67 | * [What's the `NULL` in the `strtoul()` call?](#q1500) 68 | 69 | ## Questions 70 | 71 | 72 | ### How much of the emulator do I need to implement? 73 | 74 | As little as possible to get a particular LS-8 program running. 75 | 76 | Add features incrementally. Once `print8.ls8` is working, then add a `MULT` 77 | instruction to get `mult.ls8` running. And so on. 78 | 79 | Of course, you're _allowed_ to implement as many instructions are you'd like. 80 | 81 | This goes for individual components like registers, as well. Do you need to 82 | implement the `FL` register? If you want to use any functionality that depends 83 | on it, then yes. The spec will tell you if the thing you're implementing needs 84 | the `FL` register to work. 85 | 86 | ------------------------------------------------------------------------ 87 | 88 | 89 | ### Once we get the `HLT` instruction, what should the emulator do? 90 | 91 | You should exit the emulator. 92 | 93 | If you `malloc()`d any memory, be sure to `free()` it. 94 | 95 | You don't need to worry about any of the LS-8 internals at that point since 96 | you're exiting anyway. 97 | 98 | ------------------------------------------------------------------------ 99 | 100 | 101 | ### How do you return values from subroutines? 102 | 103 | Since the `RET` instruction doesn't allow you to specify a return value, you'll 104 | have to get the value back by other means. 105 | 106 | One of the most common is to set a register (e.g. `R0`) to the return value, and 107 | the caller will just know, by convention, that the `R0` register will hold that 108 | value once the `CALL` returns. 109 | 110 | But you could also push that value on the stack and have the caller pop it off. 111 | This would have the advantage of supporting an arbitrary number of return 112 | values. 113 | 114 | There are no fixed rules when writing code in assembly language. Returning 115 | values in registers just happens to be a common convention. 116 | 117 | ------------------------------------------------------------------------ 118 | 119 | 120 | ### With interrupts, why do we push everything on the stack? 121 | 122 | The idea is that if you save the machine state on the stack, then after you 123 | service the interrupt you can restore it and seamlessly pick up where you left 124 | off. 125 | 126 | The CPU might have been in the middle of something important when the interrupt 127 | occurred, and it'll want to get back to that once the interrupt handler is 128 | complete. 129 | 130 | So we push the general purpose registers and internal registers on the stack, 131 | then do interrupt stuff, then restore all those registers from the stack so the 132 | CPU can carry on with what it was doing before the interrupt occurred. 133 | 134 | ------------------------------------------------------------------------ 135 | 136 | 137 | ### What is "stack overflow"? 138 | 139 | Short answer: it's when the stack grows into some area of memory that something 140 | else was using. 141 | 142 | In the LS-8, this would mean the stack grew down in RAM to the point that it 143 | overwrote some of the instructions in the program. 144 | 145 | With a C program, this would mean the stack grew down and impacted the heap. (Or 146 | that the heap grew up and impacted the stack.) 147 | 148 | If the stack grows down to address `0x00` on the LS-8, it wraps around to 149 | address `0xff`. 150 | 151 | On modern machines with [virtual 152 | memory](https://en.wikipedia.org/wiki/Virtual_memory), this isn't a practical 153 | concern since you'll run out of physical RAM before the stack overflow occurs. 154 | 155 | Some interpreted languages like Python track how large their internal stacks 156 | have grown and crash out if the stack grows too large. But this is happening 157 | within the Python virtual machine, not on the hardware. 158 | 159 | ------------------------------------------------------------------------ 160 | 161 | 162 | ### What is "stack underflow"? 163 | 164 | This means you `POP`ped more times than you `PUSH`ed. Basically you popped an 165 | empty stack. 166 | 167 | The CPU is more than happy to let you do this, but it's considered an error on 168 | the part of the programmer. 169 | 170 | If the stack pointer is at address `0xff` on the LS-8, then you `POP`, it will 171 | wrap around to address `0x00`. 172 | 173 | ------------------------------------------------------------------------ 174 | 175 | 176 | ### On the LS-8, why does the stack pointer start at address `F4`, when the first stack element is at `F3`? 177 | 178 | Since the first thing a `PUSH` instruction does is decrement the stack pointer, 179 | it means that the stack pointer is moved to `F3` first and _then_ the value is 180 | stored there. Exactly where we wanted it. 181 | 182 | ------------------------------------------------------------------------ 183 | 184 | 185 | ### How are stacks and subroutines used by higher-level languages like C? 186 | 187 | In C, when you make a function call, a bunch of space is allocated (pushed) on 188 | the stack to hold a number of things: 189 | 190 | * The return address to come back to after the function completes 191 | * Space for all the function parameters 192 | * Space for all the other local variables in the function 193 | 194 | This allocated chunk of stack is called a [stack 195 | frame](https://en.wikipedia.org/wiki/Call_stack#STACK-FRAME). 196 | 197 | When you call any function (including when `main()` gets called in C): 198 | 199 | 1. A new stack frame is allocated (pushed) 200 | 2. Parameter values are copied from the function arguments to their spots on the 201 | stack frame 202 | 203 | When you return from any function: 204 | 205 | 1. Any return value is copied from the stack frame into a dedicated register 206 | 2. The stack frame is deallocated (popped) 207 | 208 | In assembly language, `CALL` doesn't allow any arguments to be passed, and `RET` 209 | doesn't allow any values to be returned. 210 | 211 | Using stack frames gives `CALL` the power to give parameters to subtroutines. 212 | 213 | And we can use a dedicated register, like `R0`, to pass returned values back to 214 | the caller over a `RET` instruction. 215 | 216 | Since all the local variables for a function are stored in the stack frame, they 217 | all vaporize as soon as the stack is popped when the function returned. This is 218 | why local variables are not persistent from call to call. 219 | 220 | Furthermore, using the stack to hold frames allows us to call functions to an 221 | arbitrary nesting level. Indeed, it is what allows for recursion at all. 222 | 223 | ------------------------------------------------------------------------ 224 | 225 | 226 | ### Is the flags `FL` register one of the `Rx` registers, or is it a special register? 227 | 228 | It's a special purpose register that can be added separately to the `struct cpu` 229 | similar to how `PC` works. 230 | 231 | In `struct cpu`, it's convenient to have an array to store `R0` through `R7`, 232 | but the other registers are just fields in the `struct`. 233 | 234 | ------------------------------------------------------------------------ 235 | 236 | 237 | ### What about the `IR`, `MAR`, and `MDR` registers? 238 | 239 | You can store those special-purpose registers similar to how `PC` and `FL` are 240 | stored in the `struct`. 241 | 242 | ...Or, if you're not using them in any place except a single function, maybe 243 | they can be locals or function parameters. 244 | 245 | It's a matter of which way you think produces more readable code. 246 | 247 | ------------------------------------------------------------------------ 248 | 249 | 250 | ### What are the registers for, and what do they do? 251 | 252 | You can think of the registers as the CPU's variables. They hold numbers. You 253 | use them like you would variable in another langauge. 254 | 255 | In a high-level language, you can make all the variables you need. But in a CPU, 256 | there are a fixed number of them, and they have fixed names, and they only hold 257 | numbers. You cannot make more. 258 | 259 | (The reason you can't make more is because registers are literally built out of 260 | the hardware--you can't make more without changing the hardware.) 261 | 262 | Most operations (like math) in the CPU work on registers. 263 | 264 | But if we have RAM, why do we need registers? 265 | 266 | While some CPUs like the x86 can use either values in RAM or registers to do 267 | work, RAM is far, far slower to access. Nothing is faster to access in the CPU 268 | than a register. For that reason, assembly language programs use registers 269 | whenever possible to keep speed up. 270 | 271 | ------------------------------------------------------------------------ 272 | 273 | 274 | ### If RAM is faster than an SSD, why not just store everything in RAM? 275 | 276 | Cost. 1 TB SSD is orders of magnitude cheaper than 1 TB of RAM. And finding a 277 | motherboard that supports 1 TB of RAM is a challenge. 278 | 279 | Also the SSD continues to store data even if power is removed, unlike RAM. 280 | 281 | Someday someone will discover RAM that is cheap, fast, and will permanently 282 | store data, and when that happens, SSDs will vanish. 283 | 284 | ------------------------------------------------------------------------ 285 | 286 | 287 | ### Do CPUs get hot because of the power constantly running through them? 288 | 289 | Yup. When you run current through any regular conductor, heat is generated. 290 | 291 | In that regard, a CPU is like a tiny, expensive electric blanket that is capable 292 | of arbitrary computation but really bad at giving you a good night's sleep. 293 | 294 | ------------------------------------------------------------------------ 295 | 296 | 297 | ### Why is hex base 16? Seems so random. 298 | 299 | Conveniently, one hex digit represents exactly 4 bits (AKA a _nibble_). 300 | 301 | This means a byte can be represented by exactly 2 hex digits (assuming you put a 302 | leading zero on numbers less than `0x10`). And the biggest byte's value roundly 303 | ends at `0xff`. 304 | 305 | It's compact, and easy to convert to and from binary. 306 | 307 | Compare to decimal, where one decimal digit represents somewhere between 3 and 4 308 | bits. And a byte is represented by 3 digits, isn't easily convertible to binary, 309 | and ends quite unroundly on `255` for the largest value. 310 | 311 | ------------------------------------------------------------------------ 312 | 313 | 314 | ### What's the `NULL` in the `strtoul()` call? 315 | 316 | That's part of a mechanism where `strtoul()` can tell you the first invalid 317 | character it found, or if it found no digits to convert at all. 318 | 319 | If you pass a pointer to a `char*` into the function there, it will point to the 320 | first bad character, or to the beginning of the string if no digits were found. 321 | 322 | If we call this: 323 | 324 | ```c 325 | char *endchar; 326 | 327 | unsigned val = strtoul("1030", &endchar, 2); // convert to base 2 328 | ``` 329 | 330 | then `endchar` will point at the `3` in `"1030"`, because `3` is an invalid 331 | digit in base 2. 332 | 333 | If we call this: 334 | 335 | ```c 336 | char *endchar; 337 | 338 | unsigned val = strtoul("# Hello, world!", &endchar, 10); // convert to base 10 339 | ``` 340 | 341 | then `endchar` will point at the `#` because no digits were found at all. 342 | 343 | You might find this useful for parsing data from the `.ls8` input files. 344 | 345 | ------------------------------------------------------------------------ 346 | 347 | 348 | ### Can we use `memset()` to clear RAM and the registers in `cpu_init()`, or do we have to use a loop? 349 | 350 | You can use `memset()`. It's probably faster than a hand-rolled loop, anyway. 351 | 352 | ------------------------------------------------------------------------ 353 | 354 | 355 | ### How do I move the `PC` to the next instruction without hardcoding the instruction length? 356 | 357 | Check out the spec where it talks about instruction layout. 358 | 359 | The two high bits of the instruction tell you how many operands the instruction 360 | has. The value of those two bits plus one is the number of bytes you have to 361 | move the `PC`. 362 | 363 | Use `>>` and an `&` mask to extract those two bits, then add one to the result, 364 | then add that to the `PC` to get to the next instruction. 365 | 366 | > Note that some instructions (like `CALL`, `RET`, and all the `JMP` variants) 367 | > move the `PC` to a specific destination. In those cases, you _do not_ want to 368 | > advance the PC to the next instruction. 369 | 370 | ------------------------------------------------------------------------ 371 | 372 | 373 | ### Can I use `getline()` instead of `fgets()` for reading lines from the files? 374 | 375 | We recommend `fgets()` because it's more standard, and also because it does 376 | fewer things behind your back. 377 | 378 | But if you use `getline()`, we won't stop you. 379 | 380 | ------------------------------------------------------------------------ 381 | 382 | 383 | ### Why are the ALU and the RAM read/write functions broken out? Can we just code the lines to do the work directly? 384 | 385 | Because the ALU is a separate component on the CPU, and the RAM is a separate 386 | component off the CPU, it makes logical sense from a learning perspective to 387 | have different pieces of code handle the work. 388 | 389 | Plus having the RAM access function there makes the code easier to read, and 390 | easier to change if the structure of RAM were to change somehow in the future. 391 | 392 | ------------------------------------------------------------------------ 393 | 394 | 395 | ### Do you have some handy code for helping trace what the CPU is doing? 396 | 397 | If you call this before your `switch`, it'll print out the CPU state just before 398 | the instruction executes. 399 | 400 | ```c 401 | void trace(struct cpu *cpu) 402 | { 403 | printf("%02X | ", cpu->PC); 404 | 405 | printf("%02X %02X %02X |", 406 | cpu_ram_read(cpu, cpu->PC), 407 | cpu_ram_read(cpu, cpu->PC + 1), 408 | cpu_ram_read(cpu, cpu->PC + 2)); 409 | 410 | for (int i = 0; i < 8; i++) { 411 | printf(" %02X", cpu->reg[i]); 412 | } 413 | 414 | printf("\n"); 415 | } 416 | ``` 417 | 418 | ------------------------------------------------------------------------ 419 | 420 | 421 | ### Why is `R7` set to something other than zero? 422 | 423 | `R7` has additional meaning: it is the _stack pointer_. So it needs to start 424 | just past the top of the stack so that the `PUSH` and `POP` (and `CALL` and 425 | `RET`) functions operate normally. 426 | 427 | ------------------------------------------------------------------------ 428 | 429 | 430 | ### Why do opcodes have the numeric values that they do? 431 | 432 | See the "Instruction Layout" part of the LS-8 spec for what the specific bits 433 | mean in any particular instruction. 434 | 435 | In a real CPU, these bits correspond to wires that will have voltage or 436 | no-voltage on them depending on whether or not the bit in the instruction is `0` 437 | or `1`. 438 | 439 | So the instruction bits are close to the metal, literally. Their exact meanings 440 | are closely tied with how the CPU will be physically constructed. 441 | 442 | ------------------------------------------------------------------------ 443 | 444 | 445 | ### What is a "cache hit" or "cache miss"? 446 | 447 | If a program accesses a byte of RAM at some address that's in the cache already, 448 | that's a _cache hit_. The byte is returned immediately. 449 | 450 | If a program accesses a byte of RAM at some address that's not in the cache, 451 | that's a _cache miss_, and the cache must be updated by going out to RAM to get 452 | that data. 453 | 454 | The cache is fast memory that sits between main RAM and the CPU. 455 | 456 | It's common that if you access a byte of RAM, that you will soon access 457 | subsequent bytes in RAM. (E.g. like when printing a string, or doing a 458 | `strlen()`.) The cache makes use of this assumption. 459 | 460 | The cache figures, if you're going to spend the time making a relatively slow 461 | RAM request for a single byte, why not go ahead and transfer the next, say 128 462 | bytes at the same time into the faster cache. If the user then goes on to access 463 | the subsequent bytes, like they probably will, the data will already be in cache 464 | ready to use. 465 | 466 | ------------------------------------------------------------------------ 467 | 468 | 469 | ### How are logic gates built? 470 | 471 | They're made out of transistors. Details are getting into the realm of materials 472 | science and is beyond the scope of the course. 473 | 474 | ------------------------------------------------------------------------ 475 | 476 | 477 | ### How does the CPU use logic gates? 478 | 479 | Logic gates can be composed into circuits that can do far more than Boolean 480 | logical operations. 481 | 482 | You can build an ALU, for example, that does arithmetic and comparisons using 483 | only logic gates. 484 | 485 | You can even build [circuits that store 486 | data](https://en.wikipedia.org/wiki/Flip-flop_(electronics)). 487 | 488 | The fantastic book [_The Elements of Computing 489 | Systems_](https://www.nand2tetris.org/) talks about this in great detail from 490 | the ground up. 491 | 492 | ------------------------------------------------------------------------ 493 | 494 | 495 | ### Why is half a byte called a _nibble_? 496 | 497 | It's a pun, playing off byte/bite. Sometimes it's spelled _nybble_. 498 | 499 | ------------------------------------------------------------------------ 500 | 501 | 502 | ### What are the `<<` and `>>` shift operators useful for? 503 | 504 | Most commonly, they're used to get or set individual bits within a number. 505 | 506 | This is useful if multiple values are packed into a single byte. Bytes hold 507 | numbers from 0 to 255, but parts of a byte can hold smaller numbers. For 508 | example, if you have 4 values that you know only go from 0-3 each, you can pack 509 | that into a byte as four 2-bit numbers. 510 | 511 | Packing the numbers 3, 0, 2, and 1 into a single byte: 512 | 513 | ``` 514 | Three 515 | || 516 | || Two 517 | vv vv 518 | 0b11001001 519 | ^^ ^^ 520 | || One 521 | || 522 | Zero 523 | ``` 524 | 525 | This technique is normally only used in high-performance situations where you 526 | absolutely must save space or bandwidth. 527 | 528 | For example, if we wanted to extract these 3 bits from this number: 529 | 530 | ``` 531 | vvv 532 | 0b10110101 533 | ``` 534 | 535 | We'd get `110`, which is 6 decimal. But the whole number is 181 decimal. How to 536 | extract the 6? 537 | 538 | First, we can shift right by 3: 539 | 540 | ``` 541 | vvv 542 | 0b00010110 543 | ``` 544 | 545 | Then we can bitwise-AND with the mask `0b111` to filter out just the bits we 546 | want: 547 | 548 | ``` 549 | vvv 550 | 0b00010110 <-- Right-shifted original number 551 | & 0b00000111 <-- AND mask 552 | ------------ 553 | 110 554 | ``` 555 | 556 | And there's our 6! 557 | 558 | On the flip side, what if we wanted to set these bits to the value 2 (`0b010`)? 559 | Right now the three bits have the value 7 (`0b111`): 560 | 561 | ``` 562 | vvv 563 | 0b10111101 564 | ``` 565 | 566 | First let's take our 2: 567 | 568 | ``` 569 | 0b010 570 | ``` 571 | 572 | and left shift it by 3: 573 | 574 | ``` 575 | 0b010000 576 | ``` 577 | 578 | Secondly, let's use a bitwise-AND on the original number to mask out those bits 579 | and set them all to zero: 580 | 581 | ``` 582 | vvv 583 | 0b10111101 <-- original number 584 | & 0b11000111 <-- AND mask 585 | ------------ 586 | 0b10000101 587 | ^^^ 588 | These three bits set to 0, others unchanged 589 | ``` 590 | 591 | Lastly, let's bitwise-OR the shifted value with the result from the previous step: 592 | 593 | ``` 594 | vvv 595 | 0b10000101 <-- masked-out original number from previous step 596 | | 0b00010000 <-- our left-shifted 2 597 | ------------ 598 | 0b10010101 599 | ^^^ 600 | Now these three bits set to 2, others unchanged 601 | ``` 602 | 603 | And there we have it. The three bits in the middle of the number have been 604 | changed from the value 7 to the value 2. 605 | 606 | ------------------------------------------------------------------------ 607 | 608 | 609 | ### What is the difference between general-purpose registers and internal, special-purpose registers? 610 | 611 | The general-purpose registers are `R0` through `R7`. 612 | 613 | Special-purpose registers are things like `PC`, `FL`, and maybe `IR`, `MAR`, and 614 | `MDR`. 615 | 616 | The main difference is this: general-purpose registers can be used directly by 617 | instructions. Special-purpose registers cannot. 618 | 619 | ```assembly 620 | LDI R0,4 ; Valid 621 | LDI PC,5 ; INVALID--PC is not a general-purpose register 622 | 623 | ADD R0,R1 ; Valid 624 | ADD FL,R0 ; INVALID--FL is not a general-purpose register 625 | ``` 626 | 627 | In `struct cpu`, it's convenient to represent the general purpose registers with 628 | an array for easy indexing from `0` to `7`. 629 | 630 | ------------------------------------------------------------------------ 631 | 632 | 633 | ### Why does the CPU allow for stack overflow or underflow? 634 | 635 | It takes time for the CPU to check to see if either condition has occurred. And 636 | most of the time it won't have. 637 | 638 | CPUs are interested in running instructions as quickly as possible. 639 | 640 | Also, you'd need additional hardware in place to make those checks, and that 641 | costs money. 642 | 643 | Because assemnbly language is so low-level, the CPU is already putting basically 644 | ultimate trust in the developer to not do something they shouldn't do. 645 | 646 | > If you didn't want me to overflow the stack, why did you tell me to overflow 647 | > the stack? 648 | > 649 | > --The CPU 650 | 651 | ------------------------------------------------------------------------ 652 | 653 | 654 | ### Why does the CPU support a stack and not some other data structure? 655 | 656 | Turns out a stack is a really useful data structure for a number of reasons: 657 | 658 | * It's a great place to temporarily store data. 659 | * It's useful for holding a return address for a subroutine/function. 660 | * It's a place to pass arguments to subroutines. 661 | * It's a good place to hold a subroutine's local variables. 662 | * It can hold all the information that needs to be saved while the CPU is 663 | servicing an interrupt. 664 | 665 | Additionally, it's pretty cheap to implement. All CPUs already come with this 666 | functionality: 667 | 668 | * Memory (for the stack data) 669 | * Registers (for the stack pointer) 670 | * A way to decrement and increment registers (to move the stack pointer) 671 | * A way to read and write data to and from RAM (to retrieve and store data on 672 | the stack) 673 | 674 | Since the CPU was doing all that anyway, adding `PUSH` and `POP` instructions is 675 | a pretty low-hanging fruit. 676 | 677 | ------------------------------------------------------------------------ 678 | 679 | 680 | ### On a multicore CPU, is there some kind of overseer that coordinates between the cores? 681 | 682 | Not really, and from a programmer perspective, no. 683 | 684 | Cores have their own registers, own PCs, and generally run autonomously on their 685 | own. 686 | 687 | What they _do_ share is RAM (and usually at least some cache) and peripherals. 688 | 689 | The real "overseer" is the operating system, which decides which programs run on 690 | which core at any particular time. 691 | 692 | ------------------------------------------------------------------------ 693 | 694 | 695 | ### On a multicore CPU, do cores share registers or do they have their own sets? 696 | 697 | They have their own. 698 | 699 | Cores generally run autonomously on their own. 700 | 701 | What they _do_ share is RAM (and usually at least some cache) and peripherals. 702 | 703 | ------------------------------------------------------------------------ 704 | 705 | 706 | ### Are the flags on the LS-8 stored on the stack or in a register? 707 | 708 | Flags (the `FL` register) are their own special-purpose register, similar to the 709 | `PC`. 710 | 711 | Each bit of the `FL` register has special meaning as laid out in the LS-8 spec. 712 | 713 | ------------------------------------------------------------------------ 714 | 715 | 716 | ### Does the ALU handle conditionals/`CMP`? 717 | 718 | Yes. 719 | 720 | The compare instruction `CMP` will set the flags register appropriately 721 | indicating whether or not the values of the registers compared are less-than, 722 | greater-than, or equal. 723 | 724 | This is actually quite similar to a subtraction, which the ALU can already do. 725 | 726 | If I give you two numbers, `a` and `b`, and you compute the difference `b - a`, 727 | you can look at the result and determine if the values are equal, or if one is 728 | greater than the other. 729 | 730 | If `b - a` is a positive number, it means that `a` is less than `b`. 731 | 732 | If `b - a` is a negative number, it means that `a` is greater than `b`. 733 | 734 | If `b - a` is zero, it means that `a` equals `b`. 735 | 736 | So the ALU can use its subtraction circuitry to do a `CMP`, saving money and 737 | construction complexity. 738 | 739 | ------------------------------------------------------------------------ 740 | 741 | 742 | ### On the LS-8, why does `POP` need an operand? 743 | 744 | Because you probably want to know what the value was you popped off the stack, 745 | rather than just throwing it away. 746 | 747 | Basically, `POP R0` is saying "pop the value from the top of the stack and store 748 | it in `R0`." 749 | 750 | ------------------------------------------------------------------------ 751 | 752 | 753 | ### How are floating point numbers represented in binary? 754 | 755 | There is a standard binary format for storing floating point numbers called 756 | [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754). 757 | 758 | It basically breaks a number into three parts: 759 | 760 | * **Sign**--indicating positive or negative, 1 bit 761 | * **Mantissa** (AKA "Significand")--the actual binary digits of the number, 762 | unsigned, e.g. 22 bits 763 | * **Exponent**--signed binary exponent to apply to the mantissa, e.g. 8 bits 764 | 765 | A simpler-to-comprehend example might be in base 10, decimal. 766 | 767 | For example, the components that make up the decimal number `-98.273` are: 768 | 769 | * **Sign**: `-1` (because it's -98, not 98) 770 | * **Mantissa**: `98273` (all the digits) 771 | * **Exponent**: `-3` (tells us where the decimal place is) 772 | 773 | The result (again for base 10) is: 774 | 775 | `sign * mantissa * 10 ^ exponent` 776 | 777 | or: 778 | 779 | `-1 * 98273 * 10 ^ -3 == -98.273` 780 | 781 | Basically the exponent tells us how to left (if it's negative) or right (if it's 782 | positive) to shift the decimal point. 783 | 784 | It works exactly the same way in binary (base 2): 785 | 786 | The components that make up the binary number `0b101.11` are: 787 | 788 | * **Sign**: `0b1` (because it's 101, not -101) 789 | * **Mantissa**: `0b10111` (all the digits) 790 | * **Exponent**: `-2` (tells us where the decimal place is) 791 | 792 | Then the formula is: 793 | 794 | `sign * mantissa * 2 ^ exponent` 795 | 796 | or: 797 | 798 | `0b1 * 0b10111 * 2 ^ -2` 799 | 800 | Again, the exponent tells us how far to shift the decimal; in this case, shift 801 | it 2 bits left for `0b101.11`. 802 | 803 | Printing out binary floating point numbers in decimal is a bit weird because you 804 | have to think in fractions of two instead of 10. 805 | 806 | Decimal example: 807 | 808 | `12.34` is written as: 809 | 810 | * `1` 10s (10 == 101) 811 | * `2` 1s (1 == 100) 812 | * `3` 1/10ths (1/10 == 10-1) 813 | * `4` 1/100ths (1/100 = 10-2) 814 | 815 | Of course you see powers of 10 all over because it's base 10. 816 | 817 | With base two, binary: 818 | 819 | `11.01` is written as: 820 | 821 | * `1` 2s (2 == 21) 822 | * `1` 1s (1 == 20) 823 | * `0` 1/2s (1/2 == 2-1) 824 | * `1` 1/4s (1/4 == 2-2) 825 | 826 | Which would give us (in decimal): `2 + 1 + 1/4` or `3.25`. 827 | 828 | `11.01` binary is `3.25` decimal. 829 | 830 | Luckily `printf()` handles that with `%f` for us. 831 | 832 | ------------------------------------------------------------------------ 833 | 834 | 835 | ### How are signed integers represented in binary? 836 | 837 | It's a format known as [_two's 838 | complement_](https://en.wikipedia.org/wiki/Two%27s_complement). 839 | 840 | Basically, about half of the bit patterns are used for negative numbers. 841 | 842 | The following is an example with 3-bit integers, though it applies equally well 843 | to integers of any number of bits. 844 | 845 | Unsigned, binary on the left, decimal on the right: 846 | 847 | ``` 848 | 111 7 849 | 110 6 850 | 101 5 851 | 100 4 852 | 010 2 853 | 011 3 854 | 001 1 855 | 000 0 856 | ``` 857 | 858 | Signed (same on the right, but in sorted numeric order): 859 | 860 | ``` 861 | 111 -1 010 2 862 | 110 -2 011 3 863 | 101 -3 001 1 864 | 100 -4 000 0 865 | 011 3 111 -1 866 | 010 2 110 -2 867 | 001 1 101 -3 868 | 000 0 100 -4 869 | ``` 870 | 871 | Notice how the bit pattern for `7` (unsigned) is `111`, which is the same bit 872 | pattern for `-1` (signed). 873 | 874 | So is `111` `-1` or is it `7`? It depends only on whether or not _you_ are 875 | looking at those bits as signed or unsigned. 876 | 877 | Another thing to notice is that if the high (leftmost) bit of a signed number is 878 | `1`, it means that number is negative. 879 | 880 | Also notice how the positive signed numbers have the same bit patterns as their 881 | unsigned counterparts. 882 | 883 | If you have a signed number (either sign) and you want to find it's two's 884 | complement opposite, first you subtract one from it and then take the 885 | bitwise-NOT of that result. 886 | 887 | * `2` is `0b010` binary. 888 | * Subtract `1` to get `0b001`. 889 | * Then bitwise-NOT to get `0b110`. 890 | * `0b110` is `-2`. 891 | 892 | Using two's complement to represent signed numbers has one great advantage: the 893 | exact same circuitry in the ALU can be used to add or subtract numbers 894 | regardless of whether they are signed or unsigned. The ALU doesn't have to care. 895 | 896 | ------------------------------------------------------------------------ 897 | 898 | 899 | ### How does the CPU cache work? What is L1, L2, L3 and so on? 900 | 901 | _You don't have to implement any kind of emulated cache on the LS-8._ 902 | 903 | Generally speaking, the cache is some fast memory that's closer to the CPU than 904 | RAM. 905 | 906 | The assumption is that if a program wants a byte at address `x`, it's 907 | likely to very soon thereafter want the byte at address `x + 1`. 908 | 909 | (Imagine printing a string, for example. You got the first byte, printed it, and 910 | now you're going to print the next one.) 911 | 912 | So CPU first looks in the cache to see if the byte is there. If it's not, it 913 | goes out and requests a block of memory from RAM be copied into the cache. The 914 | block includes the byte in question, but also the subsequent bytes. 915 | 916 | Then when you go to read the subsequent bytes, they're in the super fast cache 917 | and you don't have to go to RAM again, thus increasing the speed of your run. 918 | 919 | In a modern CPU, cache is arranged as a hierarchy. Closest to the CPU is L1 920 | (_Level 1_), which is fast and doesn't hold much. If the data isn't in L1, L1 921 | looks to see if it's in L2. If it's not there L2 looks to see if it's in L3. If 922 | it's not there, it looks in the next level again for however many levels of 923 | cache your CPU has. Eventually if it can't be found in any level, it is obtained 924 | from RAM. 925 | 926 | | Level | Capacity | Lookup Time (nanoseconds) | 927 | |:-----:|:------------:|:-------------------------:| 928 | | L1 | 2-8 KB | ~1 ns | 929 | | L2 | 256-512 KB | ~3 ns | 930 | | L3 | 1024-8192 KB | ~12 ns | 931 | | RAM | 8-32 **GB** | ~100 ns | 932 | 933 | > Capacities and speeds are examples only. Actual number vary. 934 | 935 | ------------------------------------------------------------------------ 936 | 937 | 938 | ### When I print out the value of my `LDI` instruction, it prints as `-126` when I know the value is `0b10000010`, or `130`. What gives? 939 | 940 | Due to the fact that C uses [two's 941 | complement](https://en.wikipedia.org/wiki/Two%27s_complement) for representing 942 | signed numbers, that bit pattern actually represents _both_ numbers depending on 943 | how we print it. 944 | 945 | | Binary Value | Decimal `signed char` | Decimal `unsigned char` | 946 | |:------------:|:---------------------:|:-----------------------:| 947 | | `0b10000010` | `-126` | `130` | 948 | 949 | Make sure you use the right type to hold your value, and then also make sure you 950 | use the proper `printf()` format specifier to display it. `%d` is signed decimal 951 | and `%u` is unsigned decimal. 952 | 953 | Here's an example. Notice that the bit pattern is identical for both variables. 954 | 955 | ```c 956 | #include 957 | 958 | int main(void) 959 | { 960 | signed char x = 0b10000010; 961 | unsigned char y = 0b10000010; 962 | 963 | printf("%d\n", x); // prints -126 964 | printf("%u\n", y); // prints 130 965 | } 966 | ``` 967 | 968 | ------------------------------------------------------------------------ -------------------------------------------------------------------------------- /LS8-cheatsheet.md: -------------------------------------------------------------------------------- 1 | # LS-8 Cheatsheet 2 | 3 | This document is non-authoritative. In cases where it differs from the spec, the 4 | spec is correct. 5 | 6 | ## ALU ops 7 | ``` 8 | ADD 10100000 00000aaa 00000bbb 9 | SUB 10100001 00000aaa 00000bbb 10 | MUL 10100010 00000aaa 00000bbb 11 | DIV 10100011 00000aaa 00000bbb 12 | MOD 10100100 00000aaa 00000bbb 13 | 14 | INC 01100101 00000rrr 15 | DEC 01100110 00000rrr 16 | 17 | CMP 10100111 00000aaa 00000bbb 18 | 19 | AND 10101000 00000aaa 00000bbb 20 | NOT 01101001 00000rrr 21 | OR 10101010 00000aaa 00000bbb 22 | XOR 10101011 00000aaa 00000bbb 23 | SHL 10101100 00000aaa 00000bbb 24 | SHR 10101101 00000aaa 00000bbb 25 | ``` 26 | 27 | ## PC mutators 28 | ``` 29 | CALL 01010000 00000rrr 30 | RET 00010001 31 | 32 | INT 01010010 00000rrr 33 | IRET 00010011 34 | 35 | JMP 01010100 00000rrr 36 | JEQ 01010101 00000rrr 37 | JNE 01010110 00000rrr 38 | JGT 01010111 00000rrr 39 | JLT 01011000 00000rrr 40 | JLE 01011001 00000rrr 41 | JGE 01011010 00000rrr 42 | ``` 43 | 44 | ## Other 45 | ``` 46 | NOP 00000000 47 | 48 | HLT 00000001 49 | 50 | LDI 10000010 00000rrr iiiiiiii 51 | 52 | LD 10000011 00000aaa 00000bbb 53 | ST 10000100 00000aaa 00000bbb 54 | 55 | PUSH 01000101 00000rrr 56 | POP 01000110 00000rrr 57 | 58 | PRN 01000111 00000rrr 59 | PRA 01001000 00000rrr 60 | ``` -------------------------------------------------------------------------------- /LS8-spec.md: -------------------------------------------------------------------------------- 1 | # LS-8 Microcomputer Spec v4.0 2 | 3 | ## Registers 4 | 5 | 8 general-purpose 8-bit numeric registers R0-R7. 6 | 7 | * R5 is reserved as the interrupt mask (IM) 8 | * R6 is reserved as the interrupt status (IS) 9 | * R7 is reserved as the stack pointer (SP) 10 | 11 | > These registers only hold values between 0-255. After performing math on 12 | > registers in the emulator, bitwise-AND the result with 0xFF (255) to keep the 13 | > register values in that range. 14 | 15 | 16 | ## Internal Registers 17 | 18 | * `PC`: Program Counter, address of the currently executing instruction 19 | * `IR`: Instruction Register, contains a copy of the currently executing instruction 20 | * `MAR`: Memory Address Register, holds the memory address we're reading or writing 21 | * `MDR`: Memory Data Register, holds the value to write or the value just read 22 | * `FL`: Flags, see below 23 | 24 | 25 | ## Flags 26 | 27 | The flags register `FL` holds the current flags status. These flags 28 | can change based on the operands given to the `CMP` opcode. 29 | 30 | The register is made up of 8 bits. If a particular bit is set, that flag is "true". 31 | 32 | `FL` bits: `00000LGE` 33 | 34 | * `L` Less-than: during a `CMP`, set to 1 if registerA is less than registerB, 35 | zero otherwise. 36 | * `G` Greater-than: during a `CMP`, set to 1 if registerA is greater than 37 | registerB, zero otherwise. 38 | * `E` Equal: during a `CMP`, set to 1 if registerA is equal to registerB, zero 39 | otherwise. 40 | 41 | 42 | ## Memory 43 | 44 | The LS-8 has 8-bit addressing, so can address 256 bytes of RAM total. 45 | 46 | Memory map: 47 | 48 | ``` 49 | top of RAM 50 | +-----------------------+ 51 | | FF I7 vector | Interrupt vector table 52 | | FE I6 vector | 53 | | FD I5 vector | 54 | | FC I4 vector | 55 | | FB I3 vector | 56 | | FA I2 vector | 57 | | F9 I1 vector | 58 | | F8 I0 vector | 59 | | F7 Reserved | 60 | | F6 Reserved | 61 | | F5 Reserved | 62 | | F4 Key pressed | Holds the most recent key pressed on the keyboard 63 | | F3 Start of Stack | 64 | | F2 [more stack] | Stack grows down 65 | | ... | 66 | | 01 [more program] | 67 | | 00 Program entry | Program loaded upward in memory starting at 0 68 | +-----------------------+ 69 | bottom of RAM 70 | ``` 71 | 72 | ## Stack 73 | 74 | The SP points at the value at the top of the stack (most recently pushed), or at 75 | address `F4` if the stack is empty. 76 | 77 | 78 | ## Interrupts 79 | 80 | There are 8 interrupts, I0-I7. 81 | 82 | When an interrupt occurs from an external source or from an `INT` 83 | instruction, the appropriate bit in the IS register will be set. 84 | 85 | Prior to instruction fetch, the following steps occur: 86 | 87 | 1. The IM register is bitwise AND-ed with the IS register and the 88 | results stored as `maskedInterrupts`. 89 | 2. Each bit of `maskedInterrupts` is checked, starting from 0 and going 90 | up to the 7th bit, one for each interrupt. 91 | 3. If a bit is found to be set, follow the next sequence of steps. Stop 92 | further checking of `maskedInterrupts`. 93 | 94 | If a bit is set: 95 | 96 | 1. Disable further interrupts. 97 | 2. Clear the bit in the IS register. 98 | 3. The `PC` register is pushed on the stack. 99 | 4. The `FL` register is pushed on the stack. 100 | 5. Registers R0-R6 are pushed on the stack in that order. 101 | 6. The address (_vector_ in interrupt terminology) of the appropriate 102 | handler is looked up from the interrupt vector table. 103 | 7. Set the PC is set to the handler address. 104 | 105 | While an interrupt is being serviced (between the handler being called 106 | and the `IRET`), further interrupts are disabled. 107 | 108 | See `IRET`, below, for returning from an interrupt. 109 | 110 | ### Interrupt numbers 111 | 112 | * 0: Timer interrupt. This interrupt triggers once per second. 113 | * 1: Keyboard interrupt. This interrupt triggers when a key is pressed. 114 | The value of the key pressed is stored in address `0xF4`. 115 | 116 | ## Power on State 117 | 118 | When the LS-8 is booted, the following steps occur: 119 | 120 | * `R0`-`R6` are cleared to `0`. 121 | * `R7` is set to `0xF4`. 122 | * `PC` and `FL` registers are cleared to `0`. 123 | * RAM is cleared to `0`. 124 | 125 | Subsequently, the program can be loaded into RAM starting at address `0x00`. 126 | 127 | ## Execution Sequence 128 | 129 | 1. The instruction pointed to by the `PC` is fetched from RAM, decoded, and 130 | executed. 131 | 2. If the instruction does _not_ set the `PC` itself, the `PC` is advanced to 132 | point to the subsequent instruction. 133 | 3. If the CPU is not halted by a `HLT` instruction, go to step 1. 134 | 135 | Some instructions set the PC directly. These are: 136 | 137 | * CALL 138 | * INT 139 | * IRET 140 | * JMP 141 | * JNE 142 | * JEQ 143 | * JGT 144 | * JGE 145 | * JLT 146 | * JLE 147 | * RET 148 | 149 | In these cases, the `PC` does not automatically advance to the next instruction, 150 | since it was set explicitly. 151 | 152 | ## Instruction Layout 153 | 154 | Meanings of the bits in the first byte of each instruction: `AABCDDDD` 155 | 156 | * `AA` Number of operands for this opcode, 0-2 157 | * `B` 1 if this is an ALU operation 158 | * `C` 1 if this instruction sets the PC 159 | * `DDDD` Instruction identifier 160 | 161 | The number of operands `AA` is useful to know because the total number of bytes in any 162 | instruction is the number of operands + 1 (for the opcode). This 163 | allows you to know how far to advance the `PC` with each instruction. 164 | 165 | It might also be useful to check the other bits in an emulator implementation, but 166 | there are other ways to code it that don't do these checks. 167 | 168 | ## Instruction Set 169 | 170 | Glossary: 171 | 172 | * **immediate**: takes a constant integer value as an argument 173 | * **register**: takes a register number as an argument 174 | 175 | * `iiiiiiii`: 8-bit immediate value 176 | * `00000rrr`: Register number 177 | * `00000aaa`: Register number 178 | * `00000bbb`: Register number 179 | 180 | Machine code values shown in both binary and hexadecimal. 181 | 182 | ### ADD 183 | 184 | *This is an instruction handled by the ALU.* 185 | 186 | `ADD registerA registerB` 187 | 188 | Add the value in two registers and store the result in registerA. 189 | 190 | Machine code: 191 | ``` 192 | 10100000 00000aaa 00000bbb 193 | A0 0a 0b 194 | ``` 195 | 196 | ### AND 197 | 198 | *This is an instruction handled by the ALU.* 199 | 200 | `AND registerA registerB` 201 | 202 | Bitwise-AND the values in registerA and registerB, then store the result in 203 | registerA. 204 | 205 | Machine code: 206 | ``` 207 | 10101000 00000aaa 00000bbb 208 | A8 0a 0b 209 | ``` 210 | 211 | ### CALL register 212 | 213 | `CALL register` 214 | 215 | Calls a subroutine (function) at the address stored in the register. 216 | 217 | 1. The address of the ***instruction*** _directly after_ `CALL` is 218 | pushed onto the stack. This allows us to return to where we left off when the subroutine finishes executing. 219 | 2. The PC is set to the address stored in the given register. We jump to that location in RAM and execute the first instruction in the subroutine. The PC can move forward or backwards from its current location. 220 | 221 | Machine code: 222 | ``` 223 | 01010000 00000rrr 224 | 50 0r 225 | ``` 226 | 227 | ### CMP 228 | 229 | *This is an instruction handled by the ALU.* 230 | 231 | `CMP registerA registerB` 232 | 233 | Compare the values in two registers. 234 | 235 | * If they are equal, set the Equal `E` flag to 1, otherwise set it to 0. 236 | 237 | * If registerA is less than registerB, set the Less-than `L` flag to 1, 238 | otherwise set it to 0. 239 | 240 | * If registerA is greater than registerB, set the Greater-than `G` flag 241 | to 1, otherwise set it to 0. 242 | 243 | Machine code: 244 | ``` 245 | 10100111 00000aaa 00000bbb 246 | A7 0a 0b 247 | ``` 248 | 249 | ### DEC 250 | 251 | *This is an instruction handled by the ALU.* 252 | 253 | `DEC register` 254 | 255 | Decrement (subtract 1 from) the value in the given register. 256 | 257 | Machine code: 258 | ``` 259 | 01100110 00000rrr 260 | 66 0r 261 | ``` 262 | 263 | ### DIV 264 | 265 | *This is an instruction handled by the ALU.* 266 | 267 | `DIV registerA registerB` 268 | 269 | Divide the value in the first register by the value in the second, 270 | storing the result in registerA. 271 | 272 | If the value in the second register is 0, the system should print an 273 | error message and halt. 274 | 275 | Machine code: 276 | ``` 277 | 10100011 00000aaa 00000bbb 278 | A3 0a 0b 279 | ``` 280 | 281 | ### HLT 282 | 283 | `HLT` 284 | 285 | Halt the CPU (and exit the emulator). 286 | 287 | Machine code: 288 | ``` 289 | 00000001 290 | 01 291 | ``` 292 | 293 | ### INC 294 | 295 | *This is an instruction handled by the ALU.* 296 | 297 | `INC register` 298 | 299 | Increment (add 1 to) the value in the given register. 300 | 301 | Machine code: 302 | ``` 303 | 01100101 00000rrr 304 | 65 0r 305 | ``` 306 | 307 | ### INT 308 | 309 | `INT register` 310 | 311 | Issue the interrupt number stored in the given register. 312 | 313 | This will set the _n_th bit in the `IS` register to the value in the given 314 | register. 315 | 316 | Machine code: 317 | ``` 318 | 01010010 00000rrr 319 | 52 0r 320 | ``` 321 | 322 | ### IRET 323 | 324 | `IRET` 325 | 326 | Return from an interrupt handler. 327 | 328 | The following steps are executed: 329 | 330 | 1. Registers R6-R0 are popped off the stack in that order. 331 | 2. The `FL` register is popped off the stack. 332 | 3. The return address is popped off the stack and stored in `PC`. 333 | 4. Interrupts are re-enabled 334 | 335 | Machine code: 336 | ``` 337 | 00010011 338 | 13 339 | ``` 340 | 341 | ### JEQ 342 | 343 | `JEQ register` 344 | 345 | If `equal` flag is set (true), jump to the address stored in the given register. 346 | 347 | Machine code: 348 | ``` 349 | 01010101 00000rrr 350 | 55 0r 351 | ``` 352 | 353 | ### JGE 354 | 355 | `JGE register` 356 | 357 | If `greater-than` flag or `equal` flag is set (true), jump to the address stored 358 | in the given register. 359 | 360 | ``` 361 | 01011010 00000rrr 362 | 5A 0r 363 | ``` 364 | 365 | ### JGT 366 | 367 | `JGT register` 368 | 369 | If `greater-than` flag is set (true), jump to the address stored in the given 370 | register. 371 | 372 | Machine code: 373 | ``` 374 | 01010111 00000rrr 375 | 57 0r 376 | ``` 377 | 378 | ### JLE 379 | 380 | `JLE register` 381 | 382 | If `less-than` flag or `equal` flag is set (true), jump to the address stored in the given 383 | register. 384 | 385 | ``` 386 | 01011001 00000rrr 387 | 59 0r 388 | ``` 389 | 390 | ### JLT 391 | 392 | `JLT register` 393 | 394 | If `less-than` flag is set (true), jump to the address stored in the given 395 | register. 396 | 397 | Machine code: 398 | ``` 399 | 01011000 00000rrr 400 | 58 0r 401 | ``` 402 | 403 | ### JMP 404 | 405 | `JMP register` 406 | 407 | Jump to the address stored in the given register. 408 | 409 | Set the `PC` to the address stored in the given register. 410 | 411 | Machine code: 412 | ``` 413 | 01010100 00000rrr 414 | 54 0r 415 | ``` 416 | 417 | ### JNE 418 | 419 | `JNE register` 420 | 421 | If `E` flag is clear (false, 0), jump to the address stored in the given 422 | register. 423 | 424 | Machine code: 425 | ``` 426 | 01010110 00000rrr 427 | 56 0r 428 | ``` 429 | 430 | ### LD 431 | 432 | `LD registerA registerB` 433 | 434 | Loads registerA with the value at the memory address stored in registerB. 435 | 436 | This opcode reads from memory. 437 | 438 | Machine code: 439 | ``` 440 | 10000011 00000aaa 00000bbb 441 | 83 0a 0b 442 | ``` 443 | 444 | ### LDI 445 | 446 | `LDI register immediate` 447 | 448 | Set the value of a register to an integer. 449 | 450 | Machine code: 451 | ``` 452 | 10000010 00000rrr iiiiiiii 453 | 82 0r ii 454 | ``` 455 | 456 | ### MOD 457 | 458 | *This is an instruction handled by the ALU.* 459 | 460 | `MOD registerA registerB` 461 | 462 | Divide the value in the first register by the value in the second, 463 | storing the _remainder_ of the result in registerA. 464 | 465 | If the value in the second register is 0, the system should print an 466 | error message and halt. 467 | 468 | Machine code: 469 | ``` 470 | 10100100 00000aaa 00000bbb 471 | A4 0a 0b 472 | ``` 473 | 474 | ### MUL 475 | 476 | *This is an instruction handled by the ALU.* 477 | 478 | `MUL registerA registerB` 479 | 480 | Multiply the values in two registers together and store the result in registerA. 481 | 482 | Machine code: 483 | ``` 484 | 10100010 00000aaa 00000bbb 485 | A2 0a 0b 486 | ``` 487 | 488 | ### NOP 489 | 490 | `NOP` 491 | 492 | No operation. Do nothing for this instruction. 493 | 494 | Machine code: 495 | ``` 496 | 00000000 497 | 00 498 | ``` 499 | 500 | ### NOT 501 | 502 | *This is an instruction handled by the ALU.* 503 | 504 | `NOT register` 505 | 506 | Perform a bitwise-NOT on the value in a register. 507 | 508 | Machine code: 509 | ``` 510 | 01101001 00000rrr 511 | 69 0r 512 | ``` 513 | 514 | ### OR 515 | 516 | *This is an instruction handled by the ALU.* 517 | 518 | `OR registerA registerB` 519 | 520 | Perform a bitwise-OR between the values in registerA and registerB, storing the 521 | result in registerA. 522 | 523 | Machine code: 524 | ``` 525 | 10101010 00000aaa 00000bbb 526 | AA 0a 0b 527 | ``` 528 | 529 | ### POP 530 | 531 | `POP register` 532 | 533 | Pop the value at the top of the stack into the given register. 534 | 535 | 1. Copy the value from the address pointed to by `SP` to the given register. 536 | 2. Increment `SP`. 537 | 538 | Machine code: 539 | ``` 540 | 01000110 00000rrr 541 | 46 0r 542 | ``` 543 | 544 | ### PRA 545 | 546 | `PRA register` pseudo-instruction 547 | 548 | Print alpha character value stored in the given register. 549 | 550 | Print to the console the ASCII character corresponding to the value in the 551 | register. 552 | 553 | Machine code: 554 | ``` 555 | 01001000 00000rrr 556 | 48 0r 557 | ``` 558 | 559 | ### PRN 560 | 561 | `PRN register` pseudo-instruction 562 | 563 | Print numeric value stored in the given register. 564 | 565 | Print to the console the decimal integer value that is stored in the given 566 | register. 567 | 568 | Machine code: 569 | ``` 570 | 01000111 00000rrr 571 | 47 0r 572 | ``` 573 | 574 | ### PUSH 575 | 576 | `PUSH register` 577 | 578 | Push the value in the given register on the stack. 579 | 580 | 1. Decrement the `SP`. 581 | 2. Copy the value in the given register to the address pointed to by 582 | `SP`. 583 | 584 | Machine code: 585 | ``` 586 | 01000101 00000rrr 587 | 45 0r 588 | ``` 589 | 590 | ### RET 591 | 592 | `RET` 593 | 594 | Return from subroutine. 595 | 596 | Pop the value from the top of the stack and store it in the `PC`. 597 | 598 | Machine Code: 599 | ``` 600 | 00010001 601 | 11 602 | ``` 603 | 604 | ### SHL 605 | 606 | *This is an instruction handled by the ALU.* 607 | 608 | Shift the value in registerA left by the number of bits specified in registerB, 609 | filling the low bits with 0. 610 | 611 | ``` 612 | 10101100 00000aaa 00000bbb 613 | AC 0a 0b 614 | ``` 615 | 616 | ### SHR 617 | 618 | *This is an instruction handled by the ALU.* 619 | 620 | Shift the value in registerA right by the number of bits specified in registerB, 621 | filling the high bits with 0. 622 | 623 | ``` 624 | 10101101 00000aaa 00000bbb 625 | AD 0a 0b 626 | ``` 627 | 628 | ### ST 629 | 630 | `ST registerA registerB` 631 | 632 | Store value in registerB in the address stored in registerA. 633 | 634 | This opcode writes to memory. 635 | 636 | Machine code: 637 | ``` 638 | 10000100 00000aaa 00000bbb 639 | 84 0a 0b 640 | ``` 641 | 642 | ### SUB 643 | 644 | *This is an instruction handled by the ALU.* 645 | 646 | `SUB registerA registerB` 647 | 648 | Subtract the value in the second register from the first, storing the 649 | result in registerA. 650 | 651 | Machine code: 652 | ``` 653 | 10100001 00000aaa 00000bbb 654 | A1 0a 0b 655 | ``` 656 | 657 | ### XOR 658 | 659 | *This is an instruction handled by the ALU.* 660 | 661 | `XOR registerA registerB` 662 | 663 | Perform a bitwise-XOR between the values in registerA and registerB, storing the 664 | result in registerA. 665 | 666 | Machine code: 667 | ``` 668 | 10101011 00000aaa 00000bbb 669 | AB 0a 0b 670 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Computer Architecture 2 | 3 | ## Project 4 | 5 | * [Implement the LS-8 Emulator](ls8/) 6 | 7 | ## Task List: add this to the first comment of your Pull Request 8 | 9 | ### Day 1: Get `print8.ls8` running 10 | 11 | - [ ] Inventory what is here 12 | - [ ] Implement `struct cpu` in `cpu.h` 13 | - [ ] Add RAM functions `cpu_ram_read()` and `cpu_ram_write()` 14 | - [ ] Implement `cpu_init()` 15 | - [ ] Implement the core of `cpu_run()` 16 | - [ ] Implement the `HLT` instruction handler 17 | - [ ] Add the `LDI` instruction 18 | - [ ] Add the `PRN` instruction 19 | 20 | ### Day 2: Add the ability to load files dynamically, get `mult.ls8` and `stack.ls8` running 21 | 22 | - [ ] Un-hardcode the machine code 23 | - [ ] Implement the `cpu_load()` function to load an `.ls8` file given the 24 | filename passed in as an argument 25 | - [ ] Implement a Multiply instruction and Print the result (run `mult8.ls8`) 26 | 27 | ### Day 3; Stack 28 | 29 | - [ ] Implement the System Stack and be able to run the `stack.ls8` program 30 | 31 | ### Day 4: Get `call.ls8` running 32 | 33 | - [ ] Implement the CALL and RET instructions 34 | - [ ] Implement Subroutine Calls and be able to run the `call.ls8` program 35 | 36 | ### Stretch 37 | 38 | - [ ] Add the timer interrupt to the LS-8 emulator 39 | - [ ] Add the keyboard interrupt to the LS-8 emulator 40 | - [ ] Write an LS-8 assembly program to draw a curved histogram on the screen -------------------------------------------------------------------------------- /asm/README.md: -------------------------------------------------------------------------------- 1 | # LS-8 Assembler 2 | 3 | This takes LS-8 assembler source and converts it into `.ls8` "binary" 4 | files. 5 | 6 | ## Prerequisites 7 | 8 | * NodeJS 9 | 10 | ## Usage 11 | 12 | This will produce a `.ls8` file for a given source. 13 | 14 | ``` 15 | node asm source.asm 16 | ``` 17 | 18 | ## Features 19 | 20 | * Labels 21 | * String constants 22 | * Numeric constants 23 | * Comments 24 | 25 | ## TODO 26 | 27 | * Port this to Python 28 | -------------------------------------------------------------------------------- /asm/asm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Assembler for LS-8 v4.0 3 | * 4 | * Example code: 5 | * 6 | * INC R0 ; A comment 7 | * Label1: 8 | * DEC R2 9 | * LDI R3,Label1 10 | * 11 | * DS A String that is declared 12 | * DB 0x0a ; a hex byte 13 | * DB 12 ; a decimal byte 14 | * DB 0b0001 ; a binary byte 15 | */ 16 | 17 | const fs = require('fs'); 18 | const readline = require('readline'); 19 | 20 | // Process command line 21 | 22 | const args = process.argv.slice(2); 23 | let input, output; 24 | 25 | if (args.length === 0) { 26 | input = process.stdin; 27 | output = process.stdout.fd; 28 | 29 | } else if (args.length === 1) { 30 | input = fs.createReadStream(args[0]); 31 | output = process.stdout.fd; 32 | 33 | } else if (args.length == 2) { 34 | input = fs.createReadStream(args[0]); 35 | output = fs.openSync(args[1], 'w'); 36 | 37 | } else { 38 | console.error("usage: asm infile.asm [outfile.ls8]"); 39 | process.exit(1); 40 | } 41 | 42 | // Set up the readline interface 43 | 44 | const rl = readline.createInterface({ 45 | input: input 46 | }); 47 | 48 | // Set up the symbol table 49 | const sym = {}; 50 | 51 | // Operands: 52 | const ops = { 53 | "ADD": { type: 2, code: '10100000' }, 54 | "AND": { type: 2, code: '10101000' }, 55 | "CALL": { type: 1, code: '01010000' }, 56 | "CMP": { type: 2, code: '10100111' }, 57 | "DEC": { type: 1, code: '01100110' }, 58 | "DIV": { type: 2, code: '10100011' }, 59 | "HLT": { type: 0, code: '00000001' }, 60 | "INC": { type: 1, code: '01100101' }, 61 | "INT": { type: 1, code: '01010010' }, 62 | "IRET": { type: 0, code: '00010011' }, 63 | "JEQ": { type: 1, code: '01010101' }, 64 | "JGE": { type: 1, code: '01011010' }, 65 | "JGT": { type: 1, code: '01010111' }, 66 | "JLE": { type: 1, code: '01011001' }, 67 | "JLT": { type: 1, code: '01011000' }, 68 | "JMP": { type: 1, code: '01010100' }, 69 | "JNE": { type: 1, code: '01010110' }, 70 | "LD": { type: 2, code: '10000011' }, 71 | "LDI": { type: 8, code: '10000010' }, 72 | "MOD": { type: 2, code: '10100100' }, 73 | "MUL": { type: 2, code: '10100010' }, 74 | "NOP": { type: 0, code: '00000000' }, 75 | "NOT": { type: 1, code: '01101001' }, 76 | "OR": { type: 2, code: '10101010' }, 77 | "POP": { type: 1, code: '01000110' }, 78 | "PRA": { type: 1, code: '01001000' }, 79 | "PRN": { type: 1, code: '01000111' }, 80 | "PUSH": { type: 1, code: '01000101' }, 81 | "RET": { type: 0, code: '00010001' }, 82 | "SHL": { type: 2, code: '10101100' }, 83 | "SHR": { type: 2, code: '10101101' }, 84 | "ST": { type: 2, code: '10000100' }, 85 | "SUB": { type: 2, code: '10100001' }, 86 | "XOR": { type: 2, code: '10101011' }, 87 | }; 88 | 89 | // Type to function mapping 90 | const typeF = { 91 | 0: out0, 92 | 1: out1, 93 | 2: out2, 94 | 8: out8, 95 | }; 96 | 97 | // Set up the machine code output 98 | const code = []; 99 | 100 | // Current code address (for labels) 101 | let addr = 0; 102 | 103 | // Source line number 104 | let line = 0; 105 | 106 | // Regex for matching lines 107 | // Capturing groups: label, opcode, operandA, operandB 108 | const regex = /(?:(\w+?):)?\s*(?:(\w+)\s*(?:(\w+)(?:\s*,\s*(\w+))?)?)?/; 109 | 110 | // Regex for capturing DS and DB data 111 | const regexDS = /(?:(\w+?):)?\s*DS\s*(.+)/i; 112 | const regexDB = /(?:(\w+?):)?\s*DB\s*(.+)/i; 113 | 114 | /** 115 | * Pass 1 116 | * 117 | * Read the source code lines 118 | * Parse labels, opcodes, and operands 119 | * Record label offsets 120 | * Emit machine code 121 | */ 122 | rl.on('line', (input) => { 123 | line++; 124 | 125 | // Strip comments 126 | const commentIndex = input.indexOf(';'); 127 | if (commentIndex !== -1) { 128 | input = input.substr(0, commentIndex); 129 | } 130 | 131 | // Normalize 132 | input = input.trim(); 133 | 134 | // Ignore blank lines 135 | if (input === '') { 136 | return; 137 | } 138 | 139 | //console.log(input); 140 | const m = input.match(regex); 141 | 142 | if (m) { 143 | let [, label, opcode, opA, opB] = m; 144 | 145 | label = uppercase(label); 146 | opcode = uppercase(opcode); 147 | opA = uppercase(opA); 148 | opB = uppercase(opB); 149 | 150 | //console.log(label, opcode, opA, opB); 151 | 152 | // Track label address 153 | if (label) { 154 | sym[label] = addr; 155 | //console.log("Label " + label + ": " + addr); 156 | code.push(`# ${label} (address ${addr}):`); 157 | } 158 | 159 | if (opcode !== undefined) { 160 | switch (opcode) { 161 | case 'DS': 162 | handleDS(input); 163 | break; 164 | case 'DB': 165 | handleDB(input); 166 | break; 167 | default: 168 | { 169 | // Check operand count 170 | checkOps(opcode, opA, opB); 171 | 172 | // Handle opcodes 173 | const opInfo = ops[opcode]; 174 | const handler = typeF[opInfo.type]; 175 | handler(opcode, opA, opB, opInfo.code); 176 | } 177 | break; 178 | } 179 | } 180 | 181 | } else { 182 | console.log("No match: " + input); 183 | process.exit(3); 184 | } 185 | 186 | }); 187 | 188 | /** 189 | * Pass 2 190 | * 191 | * Output the code, substituting in any symbols 192 | */ 193 | rl.on('close', () => { 194 | // Pass two 195 | 196 | // Output result 197 | for (let i = 0; i < code.length; i++) { 198 | let c = code[i]; 199 | 200 | // Replace symbols 201 | if (c.substr(0,4) == 'sym:') { 202 | let s = c.substr(4).trim(); 203 | 204 | if (s in sym) { 205 | c = p8(sym[s]); 206 | } else { 207 | console.error('unknown symbol: ' + s); 208 | process.exit(2); 209 | } 210 | } 211 | 212 | fs.writeSync(output, c + '\n'); 213 | } 214 | }); 215 | 216 | /** 217 | * Check operands for sanity with a particular opcode 218 | */ 219 | function checkOps(opcode, opA, opB) { 220 | 221 | // Makes sure we have right operand count 222 | function checkOpsCount(desired, found) { 223 | if (found < desired) { 224 | console.error(`Line ${line}: missing operand to ${opcode}`); 225 | process.exit(1); 226 | } else if (found > desired) { 227 | console.error(`Line ${line}: unexpected operand to ${opcode}`); 228 | process.exit(1); 229 | } 230 | } 231 | 232 | // Make sure we know this opcode at all 233 | if (!(opcode in ops)) { 234 | console.error(`line ${line}: unknown opcode ${opcode}`); 235 | process.exit(2); 236 | } 237 | 238 | const type = ops[opcode].type; 239 | 240 | const totalOperands = (opA !== undefined? 1: 0) + (opB !== undefined? 1: 0); 241 | 242 | if (type === 0 || type === 1 || type === 2) { 243 | // 0, 1, or 2 register operands 244 | checkOpsCount(type, totalOperands); 245 | 246 | } else if (type === 8) { 247 | // LDI r,i or LDI r,label 248 | checkOpsCount(2, totalOperands); 249 | } 250 | } 251 | 252 | /** 253 | * Get a register number from a string, e.g. "R2" -> 2 254 | */ 255 | function getReg(op, fatal=true) { 256 | const m = op.match(/R([0-7])/); 257 | 258 | if (m === null) { 259 | if (fatal) { 260 | console.error(`Line ${line}: unknown register ${op}`); 261 | process.exit(1); 262 | } else { 263 | return null; 264 | } 265 | } 266 | 267 | return m[1]|0; 268 | } 269 | 270 | /** 271 | * Return a value as an 8-digit binary number 272 | */ 273 | function p8(v) { 274 | return v.toString(2).padStart(8, '0'); 275 | } 276 | 277 | /** 278 | * Helper function to uppercase a string 279 | */ 280 | function uppercase(s) { 281 | if (s === undefined || s === null) { 282 | return s; 283 | } 284 | 285 | return s.toUpperCase(); 286 | } 287 | 288 | /** 289 | * Handle opcodes with zero operands 290 | */ 291 | function out0(opcode, opA, opB, machineCode) { 292 | code.push(`${machineCode} # ${opcode}`); 293 | 294 | addr++; 295 | } 296 | 297 | /** 298 | * Handle opcodes with one operand 299 | */ 300 | function out1(opcode, opA, opB, machineCode) { 301 | let regA = getReg(opA); 302 | code.push(`${machineCode} # ${opcode} ${opA}`); 303 | code.push(p8(regA)); 304 | 305 | addr += 2; 306 | } 307 | 308 | /** 309 | * Handle opcodes with two operands 310 | */ 311 | function out2(opcode, opA, opB, machineCode) { 312 | let regA = getReg(opA); 313 | let regB = getReg(opB); 314 | 315 | code.push(`${machineCode} # ${opcode} ${opA},${opB}`); 316 | code.push(p8(regA)); 317 | code.push(p8(regB)); 318 | 319 | addr += 3; 320 | } 321 | 322 | /** 323 | * Handle LDI opcode (type 8) 324 | */ 325 | function out8(opcode, opA, opB, machineCode) { 326 | let regA = getReg(opA); 327 | let valB = parseInt(opB); 328 | let outB; 329 | 330 | if (isNaN(valB)) { 331 | // If it's not a value, it might be a symbol 332 | outB = `sym:${opB}`; 333 | } else { 334 | outB = p8(valB); 335 | } 336 | 337 | code.push(`${machineCode} # ${opcode} ${opA},${opB}`); 338 | code.push(p8(regA)); 339 | code.push(outB); 340 | 341 | addr += 3; 342 | } 343 | 344 | /** 345 | * Handle DS pseudo-opcode 346 | */ 347 | function handleDS(input) { 348 | const m = input.match(regexDS); 349 | 350 | if (m === null || m[2] === '') { 351 | console.error(`line ${line}: missing argument to DS`); 352 | process.exit(2); 353 | } 354 | 355 | const data = m[2]; 356 | 357 | for (let i = 0; i < data.length; i++) { 358 | let printChar = data.charAt(i); 359 | 360 | if (printChar === ' ') { 361 | printChar = '[space]'; 362 | } 363 | 364 | code.push(`${p8(data.charCodeAt(i))} # ${printChar}`); 365 | } 366 | 367 | addr += data.length; 368 | } 369 | 370 | /** 371 | * Handle the DB pseudo-opcode 372 | */ 373 | function handleDB(input) { 374 | const m = input.match(regexDB); 375 | 376 | if (m === null || m[2] === '') { 377 | console.error(`line ${line}: missing argument to DB`); 378 | process.exit(2); 379 | } 380 | 381 | const data = m[2]; 382 | 383 | let val = parseInt(data); 384 | 385 | if (isNaN(val)) { 386 | console.error(`line ${line}: invalid integer argument to DB`); 387 | process.exit(2); 388 | } 389 | 390 | // Force to byte size 391 | val &= 0xff; 392 | 393 | code.push(`${p8(val)} # ${data}`); 394 | 395 | addr += 1; 396 | } 397 | -------------------------------------------------------------------------------- /asm/buildall: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for a in *.asm; do 4 | outfile=$(basename $a .asm).ls8 5 | node asm $a > ../project/ls8/$outfile 6 | done -------------------------------------------------------------------------------- /asm/call.asm: -------------------------------------------------------------------------------- 1 | ; Demonstrate calls 2 | ; 3 | ; Expected output: 4 | ; 20 5 | ; 30 6 | ; 36 7 | ; 60 8 | 9 | ; MAIN 10 | 11 | LDI R1,Mult2Print ; Load R1 with the subroutine address 12 | 13 | ; multiply a bunch of numbers by 2 and print them 14 | LDI R0,10 15 | CALL R1 16 | 17 | LDI R0,15 18 | CALL R1 19 | 20 | LDI R0,18 21 | CALL R1 22 | 23 | LDI R0,30 24 | CALL R1 25 | 26 | HLT 27 | 28 | ; Mult2Print 29 | ; 30 | ; Multiply a number in R0 by 2 and print it out 31 | 32 | Mult2Print: 33 | ADD R0,R0 ; or fake it by adding it to itself 34 | PRN R0 35 | RET -------------------------------------------------------------------------------- /asm/interrupts.asm: -------------------------------------------------------------------------------- 1 | ; interrupts.ls8 2 | ; 3 | ; Hook the timer interrupt 4 | ; 5 | ; Expected output: sequence of "A"s, one per second. 6 | 7 | LDI R0,0xF8 ; R0 holds the interrupt vector for I0 (timer) 8 | LDI R1,IntHandler ; R1 holds the address of the handler 9 | ST R0,R1 ; Store handler addr in int vector 10 | LDI R5,1 ; Enable timer interrupts 11 | LDI R0,Loop 12 | Loop: 13 | JMP R0 ; Infinite spin loop 14 | 15 | ; Interrupt handler 16 | IntHandler: 17 | LDI R0,65 ; Load R0 with 'A' 18 | PRA R0 ; Print it 19 | IRET 20 | -------------------------------------------------------------------------------- /asm/keyboard.asm: -------------------------------------------------------------------------------- 1 | ; Keyboard.asm 2 | ; 3 | ; A simple program to test the keyboard and echo to console. 4 | ; 5 | ; Does not interpret anything; CR just moves the cursor to the start of the 6 | ; line, BS doesn't work, etc. 7 | 8 | ; Hook the keyboard interrupt 9 | 10 | LDI R0,0xF9 ; R0 holds the interrupt vector for I1 (keyboard) 11 | LDI R1,IntHandler ; R1 holds the address of the handler 12 | ST R0,R1 ; Store handler addr in int vector 13 | LDI R5,2 ; Enable keyboard interrupts 14 | LDI R0,Loop 15 | Loop: 16 | JMP R0 ; Infinite spin loop 17 | 18 | ; Interrupt handler 19 | IntHandler: 20 | LDI R0,0xF4 ; Memory location of most recent key pressed 21 | LD R1,R0 ; load R1 from that memory address 22 | PRA R1 ; Print it 23 | IRET -------------------------------------------------------------------------------- /asm/mult.asm: -------------------------------------------------------------------------------- 1 | # mult.asm 2 | # 3 | # Expected output: 72 4 | 5 | LDI R0,8 6 | LDI R1,9 7 | MUL R0,R1 8 | PRN R0 9 | HLT 10 | -------------------------------------------------------------------------------- /asm/print8.asm: -------------------------------------------------------------------------------- 1 | # print8.asm 2 | # 3 | # Expected output: 8 4 | 5 | LDI R0,8 6 | PRN R0 7 | HLT -------------------------------------------------------------------------------- /asm/printstr.asm: -------------------------------------------------------------------------------- 1 | ; Prints Hello, world! 2 | ; 3 | ; Declares a subroutine that prints a string at a given address 4 | ; 5 | ; Expected output: Hello, world! 6 | 7 | LDI R0,Hello ; address of "Hello, world!" bytes 8 | LDI R1,14 ; number of bytes to print 9 | LDI R2,PrintStr ; address of PrintStr 10 | CALL R2 ; call PrintStr 11 | HLT ; halt 12 | 13 | ; Subroutine: PrintStr 14 | ; R0 the address of the string 15 | ; R1 the number of bytes to print 16 | 17 | PrintStr: 18 | 19 | LDI R2,0 ; SAVE 0 into R2 for later CMP 20 | 21 | PrintStrLoop: 22 | 23 | CMP R1,R2 ; Compare R1 to 0 (in R2) 24 | LDI R3,PrintStrEnd ; Jump to end if we're done 25 | JEQ R3 26 | 27 | LD R3,R0 ; Load R3 from address in R0 28 | PRA R3 ; Print character 29 | 30 | INC R0 ; Increment pointer to next character 31 | DEC R1 ; Decrement number of characters 32 | 33 | LDI R3,PrintStrLoop ; Keep processing 34 | JMP R3 35 | 36 | PrintStrEnd: 37 | 38 | RET ; Return to caller 39 | 40 | ; Start of printable data 41 | 42 | Hello: 43 | 44 | ds Hello, world! 45 | db 0x0a ; newline 46 | 47 | -------------------------------------------------------------------------------- /asm/sctest.asm: -------------------------------------------------------------------------------- 1 | ; Code to test the Sprint Challenge 2 | ; 3 | ; Expected output: 4 | ; 1 5 | ; 4 6 | ; 5 7 | 8 | LDI R0,10 9 | LDI R1,20 10 | LDI R2,Test1 11 | CMP R0,R1 12 | JEQ R2 ; Does not jump because R0 != R1 13 | LDI R3,1 14 | PRN R3 ; Prints 1 15 | 16 | Test1: 17 | 18 | LDI R2,Test2 19 | CMP R0,R1 20 | JNE R2 ; Jumps because R0 != R1 21 | LDI R3,2 22 | PRN R3 ; Skipped--does not print 23 | 24 | Test2: 25 | 26 | LDI R1,10 27 | LDI R2,Test3 28 | CMP R0,R1 29 | JEQ R2 ; Jumps becuase R0 == R1 30 | LDI R3,3 31 | PRN R3 ; Skipped--does not print 32 | 33 | Test3: 34 | 35 | LDI R2,Test4 36 | CMP R0,R1 37 | JNE R2 ; Does not jump because R0 == R1 38 | LDI R3,4 39 | PRN R3 ; Prints 4 40 | 41 | Test4: 42 | 43 | LDI R3,5 44 | PRN R3 ; Prints 5 45 | LDI R2,Test5 46 | JMP R2 ; Jumps unconditionally 47 | PRN R3 ; Skipped-does not print 48 | 49 | Test5: 50 | 51 | HLT 52 | 53 | -------------------------------------------------------------------------------- /asm/stack.asm: -------------------------------------------------------------------------------- 1 | ; Stack tester 2 | ; 3 | ; Expected output: 4 | ; 2 5 | ; 4 6 | ; 1 7 | 8 | LDI R0,1 9 | LDI R1,2 10 | PUSH R0 11 | PUSH R1 12 | LDI R0,3 13 | POP R0 14 | PRN R0 ; "2" 15 | 16 | LDI R0,4 17 | PUSH R0 18 | POP R2 19 | POP R1 20 | PRN R2 ; "4" 21 | 22 | PRN R1 ; "1" 23 | HLT 24 | -------------------------------------------------------------------------------- /asm/stackoverflow.asm: -------------------------------------------------------------------------------- 1 | LDI R0, 0 2 | LDI R1, 1 3 | LDI R3, Loop 4 | Loop: 5 | PRN R0 6 | ADD R0, R1 7 | PUSH R0 8 | JMP R3 9 | 10 | -------------------------------------------------------------------------------- /ls8/Makefile: -------------------------------------------------------------------------------- 1 | SRC=$(wildcard *.c) 2 | HEADERS=$(wildcard *.h) 3 | DEPS=$(SRC) $(HEADERS) 4 | 5 | ls8: $(DEPS) 6 | gcc -Wall -Wextra -g -o $@ $(SRC) 7 | 8 | .PHONY: clean 9 | 10 | clean: 11 | rm -rf ls8 *.dSYM 12 | -------------------------------------------------------------------------------- /ls8/README.md: -------------------------------------------------------------------------------- 1 | # Project: The LS-8 Emulator 2 | 3 | ## Implementation of the LS-8 Emulator 4 | 5 | _Objective_: to gain a deeper understanding of how a CPU functions at a 6 | low level. 7 | 8 | We're going to write an emulator for the world-famous LambdaSchool-8 computer, 9 | otherwise known as LS-8! This is an 8-bit computer with 8-bit memory addressing, 10 | which is about as simple as it gets. 11 | 12 | An 8 bit CPU is one that only has 8 wires available for addresses (specifying 13 | where something is in memory), computations, and instructions. With 8 bits, our 14 | CPU has a total of 256 bytes of memory and can only compute values up to 255. 15 | The CPU could support 256 instructions, as well, but we won't need them. 16 | 17 | For starters, we'll execute code that stores the value 8 in a register, 18 | then prints it out: 19 | 20 | ``` 21 | # print8.ls8: Print the number 8 on the screen 22 | 23 | 10000010 # LDI R0,8 24 | 00000000 25 | 00001000 26 | 01000111 # PRN R0 27 | 00000000 28 | 00000001 # HLT 29 | ``` 30 | 31 | The binary numeric value on the left in the `print8.ls8` code above is either: 32 | 33 | * the machine code value of the instruction (e.g. `10000010` for `LDI`), also 34 | known as the _opcode_ 35 | 36 | or 37 | 38 | * one of the opcode's arguments (e.g. `00000000` for `R0` or `00001000` for the 39 | value `8`), also known as the _operands_. 40 | 41 | This code above requires the implementation of three instructions: 42 | 43 | * `LDI`: load "immediate", store a value in a register, or "set this register to 44 | this value". 45 | * `PRN`: a pseudo-instruction that prints the numeric value stored in a 46 | register. 47 | * `HLT`: halt the CPU and exit the emulator. 48 | 49 | See [the LS-8 spec](../LS8-spec.md) for more details. 50 | 51 | The above program is already hardcoded into the source file `cpu.c`. To run it, 52 | you will eventually: 53 | 54 | ``` 55 | make 56 | ./ls8 57 | ``` 58 | 59 | but you'll have to implement those three above instructions first! 60 | 61 | ## Step 0: IMPORTANT: inventory what is here! 62 | 63 | * Make a list of files here. 64 | * Write a short 3-10-word description of what each file does. 65 | * Note what has been implemented, and what hasn't. 66 | * Read this whole file. 67 | * Skim the spec. 68 | 69 | ## Step 1: Implement `struct cpu` in `cpu.h` 70 | 71 | This structure holds information about the CPU and associated components. 72 | 73 | The type for a single unsigned byte in C is: 74 | 75 | ```c 76 | unsigned char x; 77 | ``` 78 | 79 | (Weirdly in C, if you don't specific `signed` or `unsigned` with a `char`, it's 80 | up to the compiler which it uses.) 81 | 82 | ## Step 2: Add RAM functions 83 | 84 | In `cpu.c`, add functions `cpu_ram_read()` and `cpu_ram_write()` that access the 85 | RAM inside the `struct cpu`. 86 | 87 | We'll make use of these helper function later. 88 | 89 | ## Step 3: Implement the core of `cpu_init()` 90 | 91 | The `cpu_init()` function takes a pointer to a `struct cpu` and initializes it 92 | as necessary. At first, the PC, registers, and RAM should be cleared to zero. 93 | (`memset()` might help you clear registers and RAM.) 94 | 95 | Later on, you might do further initialization here, e.g. setting the initial 96 | value of the stack pointer. 97 | 98 | ## Step 4: Implement the core of `cpu_run()` 99 | 100 | This is the workhorse function of the entire processor. It's the most difficult 101 | part to write. 102 | 103 | It needs to read the memory address that's stored in register `PC`, and store 104 | that result in `IR`, the _Instruction Register_. This can just be a local 105 | variable in `cpu_run()`. 106 | 107 | Some instructions requires up to the next two bytes of data _after_ the `PC` in 108 | memory to perform operations on. Sometimes the byte value is a register number, 109 | other times it's a constant value (in the case of `LDI`). Using 110 | `cpu_ram_read()`, read the bytes at `PC+1` and `PC+2` from RAM into variables 111 | `operandA` and `operandB` in case the instruction needs them. 112 | 113 | Then, depending on the value of the opcode, perform the actions needed for the 114 | instruction per the LS-8 spec. Maybe a `switch` statement...? Plenty of options. 115 | 116 | After the handler returns, the `PC` needs to be updated to point to the next 117 | instruction for the next iteration of the loop in `cpu_run()`. The number of 118 | bytes an instruction uses can be determined from the two high bits (bits 6-7) of 119 | the instruction opcode. See the LS-8 spec for details. 120 | 121 | ## Step 5: Implement the `HLT` instruction handler 122 | 123 | Add the `HLT` instruction define to `cpu.h`. 124 | 125 | In `cpu_run()` in your switch, exit the loop if a `HLT` instruction is 126 | encountered, regardless of whether or not there are more lines of code in the LS-8 program you loaded. 127 | 128 | We can consider `HLT` to be similar to a `return` or `exit()` in that we stop whatever we are doing, wherever we are. 129 | 130 | ## Step 6: Add the `LDI` instruction 131 | 132 | This instruction sets a specified register to a specified value. 133 | 134 | See the LS-8 spec for the details of what this instructions does and its opcode 135 | value. 136 | 137 | ## Step 7: Add the `PRN` instruction 138 | 139 | This is a very similar process to adding `LDI`, but the handler is simpler. See 140 | the LS-8 spec. 141 | 142 | *At this point, you should be able to run the program and have it print `8` to 143 | the console!* 144 | 145 | ## Step 8: Un-hardcode the machine code 146 | 147 | In `cpu.c`, the LS-8 programs you've been running so far have been hardcoded 148 | into the source. This isn't particularly user-friendly. 149 | 150 | Make changes to `cpu.c` and `ls8.c` so that the program can be specified on the 151 | command line like so: 152 | 153 | ``` 154 | ./ls8 examples/mult.ls8 155 | ``` 156 | 157 | (The programs `print8.ls8` and `mult.ls8` are provided in the `examples/` 158 | directory for your convenience.) 159 | 160 | For processing the command line, the signature of `main()` should be changed to: 161 | 162 | ```c 163 | int main(int argc, char *argv[]) 164 | ``` 165 | 166 | `argc` is the argument count, and `argv` is an array of strings that hold the 167 | individual arguments, starting with the command name itself. 168 | 169 | If the user runs `./ls8 examples/mult.ls8`, the values in `argv` will be: 170 | 171 | ```c 172 | argv[0] == "./ls8" 173 | argv[1] == "examples/mult.ls8" 174 | ``` 175 | 176 | so you can look in `argv[1]` for the name of the file to load. 177 | 178 | > Bonus: check to make sure the user has put a command line argument where you 179 | > expect, and print an error and exit if they didn't. 180 | 181 | In `cpu_load()`, you will now want to use those command line arguments to open a file, read in its contents line by line, and save appropriate data into RAM. 182 | 183 | As you process lines from the file, you should be on the lookout for blank lines 184 | (ignore them), and you should ignore everything after a `#`, since that's a 185 | comment. 186 | 187 | You'll have to convert the binary strings to integer values to store in RAM. The 188 | built-in `strtoul()` library function might help you here. 189 | 190 | ## Step 9: Implement a Multiply and Print the Result 191 | 192 | Extend your LS8 emulator to support the following program: 193 | 194 | ``` 195 | # mult.ls8: Multiply 8x9 and print 72 196 | 197 | 10000010 # LDI R0,8 198 | 00000000 199 | 00001000 200 | 10000010 # LDI R1,9 201 | 00000001 202 | 00001001 203 | 10100010 # MUL R0,R1 204 | 00000000 205 | 00000001 206 | 01000111 # PRN R0 207 | 00000000 208 | 00000001 # HLT 209 | ``` 210 | 211 | One you run it with `./ls8 examples/mult.ls8`, you should see: 212 | 213 | ``` 214 | 72 215 | ``` 216 | 217 | Check the LS-8 spec for what the `MUL` instruction does. 218 | 219 | > Note: `MUL` is the responsiblity of the ALU, so it would be 220 | nice if your code eventually called the `alu()` function with appropriate 221 | arguments to get the work done. 222 | > 223 | 224 | 225 | ## Step 10: Beautify your `cpu_run()` loop, if needed 226 | 227 | Do you have a big `if-else-if` block or `switch` block in your `cpu_run()` 228 | function? Is there a way to better modularize your code? 229 | 230 | If you haven't done so, consider having independent handler functions, one per 231 | instruction, that does each instruction's work. 232 | 233 | Another option is to use something called a _branch table_ or _dispatch table_ 234 | to simplify the instruction handler dispatch code. This is an array of functions 235 | that you can index by opcode value. The upshot is that you fetch the instruction 236 | value from RAM, then use that value to look up the handler function in the 237 | branch table. Then call it. 238 | 239 | ```js 240 | // !PSEUDOCODE! 241 | 242 | const LDI = 0b10011001; // From the LS-8 spec 243 | const HLT = 0b00000001; 244 | 245 | function handle_LDI() { ... } 246 | function handle_HLT() { ... } 247 | 248 | branchTable[LDI] = handle_LDI; 249 | branchTable[HLT] = handle_HLT; 250 | 251 | let IR = ram_read(this.reg.PC); // Fetch instruction 252 | let handler = branchTable[IR]; // Look up handler in branch table 253 | 254 | handler(); // Call it 255 | 256 | // etc. 257 | ``` 258 | 259 | This solution involves _pointers to functions_. This is something you've already 260 | likely used for callbacks in other languages, but in C, the syntax is somewhat 261 | **insane**. 262 | 263 | Some examples in C: 264 | 265 | ```c 266 | // Normal function to take two floats and return an int 267 | int foo(float x, float y) 268 | { 269 | return x * y; 270 | } 271 | 272 | int main(void) 273 | { 274 | // Declare fp to be a pointer to a function that takes two floats and 275 | // returns an int: 276 | 277 | int (*fp)(float, float); // points at garbage until initialized 278 | 279 | // Initialize fp to point to function foo() 280 | fp = foo; 281 | 282 | // Now you can call foo() like this: 283 | int x = fp(2.0, 4.5); 284 | 285 | // Or normally like this: 286 | int y = foo(2.0, 4.5); 287 | 288 | return 0; 289 | } 290 | ``` 291 | 292 | Arrays of pointers to functions are even zanier: 293 | 294 | ```c 295 | // Declare an array, bar, of 10 pointers to functions that take an int and 296 | // a float, and return void: 297 | 298 | void (*bar[10])(int, float); // Array as yet uninitialized! 299 | 300 | // Do the same with an array baz, but also init all the pointers to NULL: 301 | 302 | void (*baz[10])(int, float) = {0}; 303 | ``` 304 | 305 | Whether you do a `switch` or a branch table or anything else is up to you. 306 | 307 | ## Step 11: Implement System Stack 308 | 309 | All CPUs manage a _stack_ that can be used to store information temporarily. 310 | This stack resides in main memory and typically starts at the top of memory (at 311 | a high address) and grows _downward_ as things are pushed on. The LS-8 is no 312 | exception to this. 313 | 314 | Implement a system stack per the spec. Add `PUSH` and `POP` instructions. Read 315 | the beginning of the spec to see which register is the stack pointer. 316 | 317 | * Values themselves should be saved in the ***portion of RAM*** _that is allocated for the stack_. 318 | - Use the stack pointer to modify the correct block of memory. 319 | - Make sure you update the stack pointer appropriately as you `PUSH` and `POP` items to and from the stack. 320 | 321 | If you run `./ls8 examples/stack.ls8` you should see the output: 322 | 323 | ``` 324 | 2 325 | 4 326 | 1 327 | ``` 328 | 329 | ## Step 12: Implement Subroutine Calls 330 | 331 | Back in the old days, functions were called _subroutines_. In machine code, subroutines 332 | enable you to jump to any address with the `CALL` instruction, and then return 333 | back to where you called from with the `RET` instruction. This enables you to 334 | create reusable functions. 335 | 336 | Subroutines have many similarities to functions in higher-level languages. Just as a function in C, JavaScript or Python will jump from the function call, to its definition, and then return back to the line of code following the call, subroutines will also allow us to execute instructions non-sequentially. 337 | 338 | The stack is used to hold the return address used by `RET`, so you **must** implement the 339 | stack in step 11, first. Then, add subroutine instructions `CALL` and `RET`. 340 | 341 | * For `CALL`, you will likely have to modify your handler call in `cpu_run()`. The 342 | problem is that some instructions want to execute and move to the next 343 | instruction like normal, but others, like `CALL` and `JMP` want to go to a 344 | specific address. 345 | 346 | > Note: `CALL` is very similar to the `JMP` instruction. However, there is one key difference between them. Can you find it in the specs? 347 | > 348 | 349 | * In **any** case where the instruction handler sets the `PC` directly, you 350 | _don't_ want to advance the PC to the next instruction. So you'll have to set up 351 | a special case for those types of instructions. This can be a flag you 352 | explicitly set per-instruction... but can also be computed from the value in 353 | `IR`. Check out the spec for more. 354 | 355 | If you run `./ls8 examples/call.ls8` you should see the output: 356 | 357 | ``` 358 | 20 359 | 30 360 | 36 361 | 60 362 | ``` 363 | 364 | ## Stretch Goal: Timer Interrupts 365 | 366 | Add interrupts to the LS-8 emulator. 367 | 368 | **You must have implemented a CPU stack before doing this.** 369 | 370 | **You must have implmented the `ST` instruction before doing this.** 371 | 372 | See the [LS-8 373 | spec](https://github.com/LambdaSchool/Computer-Architecture-One/blob/master/LS8-SPEC.md) 374 | for details on implementation. 375 | 376 | The LS-8 should fire a timer interrupt one time per second. This could be 377 | implemented by calling `gettimeofday()` each iteration of the main loop and 378 | checking to see if one second has elapsed. 379 | 380 | Another option is the `setitimer()` call, which requires you to set up a signal 381 | handler for the timer when it expires. 382 | 383 | When the timer is ready to fire, set bit 0 of the IS register (R6). 384 | 385 | Later in the main instruction loop, you'll check to see if bit 0 of the 386 | IS register is set, and if it is, you'll push the registers on the 387 | stack, look up the interrupt handler address in the interrupt vector 388 | table at address `0xF8`, and set the PC to it. Execution continues in 389 | the interrupt handler. 390 | 391 | Then when an `IRET` instruction is found, the registers and PC are 392 | popped off the stack and execution continues normally. 393 | 394 | ## Example 395 | 396 | This code prints out the letter `A` from the timer interrupt handler 397 | that fires once per second. 398 | 399 | ``` 400 | # interrupts.ls8 401 | 402 | 10000010 # LDI R0,0XF8 403 | 00000000 404 | 11111000 405 | 10000010 # LDI R1,INTHANDLER 406 | 00000001 407 | 00010001 408 | 10000100 # ST R0,R1 409 | 00000000 410 | 00000001 411 | 10000010 # LDI R5,1 412 | 00000101 413 | 00000001 414 | 10000010 # LDI R0,LOOP 415 | 00000000 416 | 00001111 417 | 418 | # LOOP (address 15): 419 | 01010100 # JMP R0 420 | 00000000 421 | 422 | # Timer interrupt Handler 423 | # When the timer interrupt occurs, output an 'A' 424 | # INTHANDLER (address 17): 425 | 10000010 # LDI R0,65 426 | 00000000 427 | 01000001 428 | 01001000 # PRA R0 429 | 00000000 430 | 00010011 # IRET 431 | ``` 432 | 433 | 434 | The assembly program is interested in getting timer interrupts, so it sets the 435 | IM register to `00000001` with `LDI R5,1`. 436 | 437 | The interrupt timer gets to 1 second, and sets bit #0 in IS. 438 | 439 | At the beginning of each `cpu_run()` loop, the CPU checks to see if interrupts 440 | are enabled. If not, it continues processing instructions as normal. Otherwise: 441 | 442 | Bitwise-AND the IM register with the IS register. This masks out all the 443 | interrupts we're not interested in, leaving the ones we are interested in: 444 | 445 | ```c 446 | interrupts = cpu.reg[IM] & cpu.reg[IS]; 447 | ``` 448 | 449 | Step through each bit of `interrupts` and see which interrupts are set. 450 | 451 | ```c 452 | for (int i = 0; i < 8; i++) { 453 | // Right shift interrupts down by i, then mask with 1 to see if that bit was set 454 | int interrupt_happened = ((interrupts >> i) & 1) == 1; 455 | 456 | // ... 457 | } 458 | ``` 459 | 460 | (If the no interrupt bits are set, then stop processing interrupts and continue 461 | executing the current instruction as per usual.) 462 | 463 | If `interrupt_happened`, check the LS-8 spec for details on what to do. 464 | 465 | 466 | ## Stretch Goal: Keyboard Interrupts 467 | 468 | This gets into some serious Unix system programming esoterica. Google for 469 | `tcsetattr` and `raw mode` and `non-blocking`. 470 | 471 | If you find the command line is no longer showing what you type after your 472 | program exits, you should type this command (in the blind) to return it to 473 | normal: 474 | 475 | ``` 476 | stty sane 477 | ``` 478 | 479 | ## Stretch Goal: Curve Histogram 480 | 481 | Write an LS-8 assembly program that prints this curve on the screen: 482 | 483 | ``` 484 | * 485 | ** 486 | **** 487 | ******** 488 | **************** 489 | ******************************** 490 | **************************************************************** 491 | ``` 492 | 493 | Each subsequent line has two-times the number of asterisks as the previous line. 494 | 495 | Use loops to get this done. 496 | 497 | Doing this correctly requires implementing `CMP`, and some comparative forms of 498 | `JMP`, such as `JLT` or `JNE` or `JEQ`. 499 | 500 | Hint: Look in the `asm/` directory and learn how to use the `asm.js` assembler. 501 | This way you can write your code in assembly language and use the assembler to 502 | build it to machine code and then run it on your emulator. -------------------------------------------------------------------------------- /ls8/cpu.c: -------------------------------------------------------------------------------- 1 | #include "cpu.h" 2 | 3 | #define DATA_LEN 6 4 | 5 | /** 6 | * Load the binary bytes from a .ls8 source file into a RAM array 7 | */ 8 | void cpu_load(struct cpu *cpu) 9 | { 10 | char data[DATA_LEN] = { 11 | // From print8.ls8 12 | 0b10000010, // LDI R0,8 13 | 0b00000000, 14 | 0b00001000, 15 | 0b01000111, // PRN R0 16 | 0b00000000, 17 | 0b00000001 // HLT 18 | }; 19 | 20 | int address = 0; 21 | 22 | for (int i = 0; i < DATA_LEN; i++) { 23 | cpu->ram[address++] = data[i]; 24 | } 25 | 26 | // TODO: Replace this with something less hard-coded 27 | } 28 | 29 | /** 30 | * ALU 31 | */ 32 | void alu(struct cpu *cpu, enum alu_op op, unsigned char regA, unsigned char regB) 33 | { 34 | switch (op) { 35 | case ALU_MUL: 36 | // TODO 37 | break; 38 | 39 | // TODO: implement more ALU ops 40 | } 41 | } 42 | 43 | /** 44 | * Run the CPU 45 | */ 46 | void cpu_run(struct cpu *cpu) 47 | { 48 | int running = 1; // True until we get a HLT instruction 49 | 50 | while (running) { 51 | // TODO 52 | // 1. Get the value of the current instruction (in address PC). 53 | // 2. Figure out how many operands this next instruction requires 54 | // 3. Get the appropriate value(s) of the operands following this instruction 55 | // 4. switch() over it to decide on a course of action. 56 | // 5. Do whatever the instruction should do according to the spec. 57 | // 6. Move the PC to the next instruction. 58 | } 59 | } 60 | 61 | /** 62 | * Initialize a CPU struct 63 | */ 64 | void cpu_init(struct cpu *cpu) 65 | { 66 | // TODO: Initialize the PC and other special registers 67 | } 68 | -------------------------------------------------------------------------------- /ls8/cpu.h: -------------------------------------------------------------------------------- 1 | #ifndef _CPU_H_ 2 | #define _CPU_H_ 3 | 4 | // Holds all information about the CPU 5 | struct cpu { 6 | // TODO 7 | // PC 8 | // registers (array) 9 | // ram (array) 10 | }; 11 | 12 | // ALU operations 13 | enum alu_op { 14 | ALU_MUL 15 | // Add more here 16 | }; 17 | 18 | // Instructions 19 | 20 | // These use binary literals. If these aren't available with your compiler, hex 21 | // literals should be used. 22 | 23 | #define LDI 0b10000010 24 | #define PRN 0b01000111 25 | // TODO: more instructions here. These can be used in cpu_run(). 26 | 27 | // Function declarations 28 | 29 | extern void cpu_load(struct cpu *cpu); 30 | extern void cpu_init(struct cpu *cpu); 31 | extern void cpu_run(struct cpu *cpu); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /ls8/examples/call.ls8: -------------------------------------------------------------------------------- 1 | 10000010 # LDI R1,MULT2PRINT 2 | 00000001 3 | 00011000 4 | 10000010 # LDI R0,10 5 | 00000000 6 | 00001010 7 | 01010000 # CALL R1 8 | 00000001 9 | 10000010 # LDI R0,15 10 | 00000000 11 | 00001111 12 | 01010000 # CALL R1 13 | 00000001 14 | 10000010 # LDI R0,18 15 | 00000000 16 | 00010010 17 | 01010000 # CALL R1 18 | 00000001 19 | 10000010 # LDI R0,30 20 | 00000000 21 | 00011110 22 | 01010000 # CALL R1 23 | 00000001 24 | 00000001 # HLT 25 | # MULT2PRINT (address 24): 26 | 10100000 # ADD R0,R0 27 | 00000000 28 | 00000000 29 | 01000111 # PRN R0 30 | 00000000 31 | 00010001 # RET 32 | -------------------------------------------------------------------------------- /ls8/examples/interrupts.ls8: -------------------------------------------------------------------------------- 1 | 10000010 # LDI R0,0XF8 2 | 00000000 3 | 11111000 4 | 10000010 # LDI R1,INTHANDLER 5 | 00000001 6 | 00010001 7 | 10000100 # ST R0,R1 8 | 00000000 9 | 00000001 10 | 10000010 # LDI R5,1 11 | 00000101 12 | 00000001 13 | 10000010 # LDI R0,LOOP 14 | 00000000 15 | 00001111 16 | # LOOP (address 15): 17 | 01010100 # JMP R0 18 | 00000000 19 | # INTHANDLER (address 17): 20 | 10000010 # LDI R0,65 21 | 00000000 22 | 01000001 23 | 01001000 # PRA R0 24 | 00000000 25 | 00010011 # IRET 26 | -------------------------------------------------------------------------------- /ls8/examples/keyboard.ls8: -------------------------------------------------------------------------------- 1 | 10000010 # LDI R0,0XF9 2 | 00000000 3 | 11111001 4 | 10000010 # LDI R1,INTHANDLER 5 | 00000001 6 | 00010001 7 | 10000100 # ST R0,R1 8 | 00000000 9 | 00000001 10 | 10000010 # LDI R5,2 11 | 00000101 12 | 00000010 13 | 10000010 # LDI R0,LOOP 14 | 00000000 15 | 00001111 16 | # LOOP (address 15): 17 | 01010100 # JMP R0 18 | 00000000 19 | # INTHANDLER (address 17): 20 | 10000010 # LDI R0,0XF4 21 | 00000000 22 | 11110100 23 | 10000011 # LD R1,R0 24 | 00000001 25 | 00000000 26 | 01001000 # PRA R1 27 | 00000001 28 | 00010011 # IRET 29 | -------------------------------------------------------------------------------- /ls8/examples/mult.ls8: -------------------------------------------------------------------------------- 1 | 10000010 # LDI R0,8 2 | 00000000 3 | 00001000 4 | 10000010 # LDI R1,9 5 | 00000001 6 | 00001001 7 | 10100010 # MUL R0,R1 8 | 00000000 9 | 00000001 10 | 01000111 # PRN R0 11 | 00000000 12 | 00000001 # HLT 13 | -------------------------------------------------------------------------------- /ls8/examples/print8.ls8: -------------------------------------------------------------------------------- 1 | # Print the number 8 2 | 3 | # This comment and blank line is here to make sure 4 | # they are handled correctly by the file reading code. 5 | 6 | 10000010 # LDI R0,8 7 | 00000000 8 | 00001000 9 | 01000111 # PRN R0 10 | 00000000 11 | 00000001 # HLT 12 | -------------------------------------------------------------------------------- /ls8/examples/printstr.ls8: -------------------------------------------------------------------------------- 1 | 10000010 # LDI R0,HELLO 2 | 00000000 3 | 00100110 4 | 10000010 # LDI R1,14 5 | 00000001 6 | 00001110 7 | 10000010 # LDI R2,PRINTSTR 8 | 00000010 9 | 00001100 10 | 01010000 # CALL R2 11 | 00000010 12 | 00000001 # HLT 13 | # PRINTSTR (address 12): 14 | 10000010 # LDI R2,0 15 | 00000010 16 | 00000000 17 | # PRINTSTRLOOP (address 15): 18 | 10100111 # CMP R1,R2 19 | 00000001 20 | 00000010 21 | 10000010 # LDI R3,PRINTSTREND 22 | 00000011 23 | 00100101 24 | 01010101 # JEQ R3 25 | 00000011 26 | 10000011 # LD R3,R0 27 | 00000011 28 | 00000000 29 | 01001000 # PRA R3 30 | 00000011 31 | 01100101 # INC R0 32 | 00000000 33 | 01100110 # DEC R1 34 | 00000001 35 | 10000010 # LDI R3,PRINTSTRLOOP 36 | 00000011 37 | 00001111 38 | 01010100 # JMP R3 39 | 00000011 40 | # PRINTSTREND (address 37): 41 | 00010001 # RET 42 | # HELLO (address 38): 43 | 01001000 # H 44 | 01100101 # e 45 | 01101100 # l 46 | 01101100 # l 47 | 01101111 # o 48 | 00101100 # , 49 | 00100000 # [space] 50 | 01110111 # w 51 | 01101111 # o 52 | 01110010 # r 53 | 01101100 # l 54 | 01100100 # d 55 | 00100001 # ! 56 | 00001010 # 0x0a 57 | -------------------------------------------------------------------------------- /ls8/examples/stack.ls8: -------------------------------------------------------------------------------- 1 | 10000010 # LDI R0,1 2 | 00000000 3 | 00000001 4 | 10000010 # LDI R1,2 5 | 00000001 6 | 00000010 7 | 01000101 # PUSH R0 8 | 00000000 9 | 01000101 # PUSH R1 10 | 00000001 11 | 10000010 # LDI R0,3 12 | 00000000 13 | 00000011 14 | 01000110 # POP R0 15 | 00000000 16 | 01000111 # PRN R0 17 | 00000000 18 | 10000010 # LDI R0,4 19 | 00000000 20 | 00000100 21 | 01000101 # PUSH R0 22 | 00000000 23 | 01000110 # POP R2 24 | 00000010 25 | 01000110 # POP R1 26 | 00000001 27 | 01000111 # PRN R2 28 | 00000010 29 | 01000111 # PRN R1 30 | 00000001 31 | 00000001 # HLT 32 | -------------------------------------------------------------------------------- /ls8/examples/stackoverflow.ls8: -------------------------------------------------------------------------------- 1 | 10000010 # LDI R0,0 2 | 00000000 3 | 00000000 4 | 10000010 # LDI R1,1 5 | 00000001 6 | 00000001 7 | 10000010 # LDI R3,LOOP 8 | 00000011 9 | 00001001 10 | # LOOP (address 9): 11 | 01000111 # PRN R0 12 | 00000000 13 | 10100000 # ADD R0,R1 14 | 00000000 15 | 00000001 16 | 01000101 # PUSH R0 17 | 00000000 18 | 01010100 # JMP R3 19 | 00000011 20 | -------------------------------------------------------------------------------- /ls8/ls8.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "cpu.h" 3 | 4 | /** 5 | * Main 6 | */ 7 | int main(void) 8 | { 9 | struct cpu cpu; 10 | 11 | cpu_init(&cpu); 12 | cpu_load(&cpu); 13 | cpu_run(&cpu); 14 | 15 | return 0; 16 | } --------------------------------------------------------------------------------