├── .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 | }
--------------------------------------------------------------------------------