From e2d8ea82b209f5585e0fbf983e7be7e36fbaa421 Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Sat, 9 Jul 2022 18:27:55 -0600 Subject: [PATCH 01/10] remove duplicated code from windows section --- docs/index.html | 378 +++++++++++++++++++++++------------------------- index.lit | 56 +++---- 2 files changed, 201 insertions(+), 233 deletions(-) diff --git a/docs/index.html b/docs/index.html index 2866dc9..61bcbc8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -68,11 +68,11 @@

Memory

-Memory Storage +Memory Storage
#define MEMORY_MAX (1 << 16)
 uint16_t memory[MEMORY_MAX];  /* 65536 locations */
 
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Registers

@@ -88,7 +88,7 @@

Registers

-Registers +Registers
enum
 {
     R_R0 = 0,
@@ -104,17 +104,17 @@ 

Registers

R_COUNT };
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Just like the memory, we will store the registers in an array:

-Register Storage +Register Storage
uint16_t reg[R_COUNT];
 
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Instruction Set

@@ -127,7 +127,7 @@

Instruction Set

-Opcodes +Opcodes
enum
 {
     OP_BR = 0, /* branch */
@@ -148,7 +148,7 @@ 

Instruction Set

OP_TRAP /* execute trap */ };
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Note: The Intel x86 architecture has hundreds of instructions, while others such as ARM and LC-3 have very few. Small instruction sets are referred to as RISCs while larger ones are called CISCs. Larger instruction sets typically do not provide any fundamentally new possibilities, but they often make it more convenient to write assembly for. A single instruction in CISC might take the place of several in RISC. However, they tend to be more complex and expensive for engineers to design and manufacture. This and other tradeoffs cause the designs to come in and out of style.

@@ -161,7 +161,7 @@

Condition Flags

-Condition Flags +Condition Flags
enum
 {
     FL_POS = 1 << 0, /* P */
@@ -169,7 +169,7 @@ 

Condition Flags

FL_NEG = 1 << 2, /* N */ };
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Note: (The << symbol is called the left bitshift operator. (n << k) shifts the bits of n to the left k places. Thus 1 << 2 will equal 4. Read that link if you are not familiar. It will be important.)

@@ -178,12 +178,12 @@

Condition Flags

-/lc3.c -
@{Includes}
+/lc3.c
+
@{Includes}
 
-@{Registers}
-@{Opcodes}
-@{Condition Flags}
+@{Registers}
+@{Opcodes}
+@{Condition Flags}
 
@@ -197,7 +197,7 @@

3. Assembly Examples

-Hello World Assembly +Hello World Assembly
.ORIG x3000                        ; this is the address in memory where the program will be loaded
 LEA R0, HELLO_STR                  ; load the address of the HELLO_STR string into R0
 PUTs                               ; output the string pointed to by R0 to the console
@@ -224,7 +224,7 @@ 

3. Assembly Examples

-Loop Assembly +Loop Assembly
AND R0, R0, 0                      ; clear R0
 LOOP                               ; label at the top of our loop
 ADD R0, R0, 1                      ; add 1 to R0 and store back in R0
@@ -265,11 +265,11 @@ 

Procedure

-Main Loop +Main Loop
int main(int argc, const char* argv[])
 {
-    @{Load Arguments}
-    @{Setup}
+    @{Load Arguments}
+    @{Setup}
 
     /* since exactly one condition flag should be set at any given time, set the Z flag */
     reg[R_COND] = FL_ZRO;
@@ -289,58 +289,58 @@ 

Procedure

switch (op) { case OP_ADD: - @{ADD} + @{ADD} break; case OP_AND: - @{AND} + @{AND} break; case OP_NOT: - @{NOT} + @{NOT} break; case OP_BR: - @{BR} + @{BR} break; case OP_JMP: - @{JMP} + @{JMP} break; case OP_JSR: - @{JSR} + @{JSR} break; case OP_LD: - @{LD} + @{LD} break; case OP_LDI: - @{LDI} + @{LDI} break; case OP_LDR: - @{LDR} + @{LDR} break; case OP_LEA: - @{LEA} + @{LEA} break; case OP_ST: - @{ST} + @{ST} break; case OP_STI: - @{STI} + @{STI} break; case OP_STR: - @{STR} + @{STR} break; case OP_TRAP: - @{TRAP} + @{TRAP} break; case OP_RES: case OP_RTI: default: - @{BAD OPCODE} + @{BAD OPCODE} break; } } - @{Shutdown} + @{Shutdown} }
-

Used by 1 2

+

Used by 1 2

While we are at the main loop let’s handle command line input to make our program usable. @@ -348,7 +348,7 @@

Procedure

-Load Arguments +Load Arguments
if (argc < 2)
 {
     /* show usage string */
@@ -365,7 +365,7 @@ 

Procedure

} }
-

Used by 1 2 3

+

Used by 1 2 3

@@ -387,7 +387,7 @@

ADD

-Add Register Assembly +Add Register Assembly
ADD R2 R0 R1 ; add the contents of R0 to R1 and store in R2.
 
@@ -402,7 +402,7 @@

ADD

-Add Immediate Assembly +Add Immediate Assembly
ADD R0 R0 1 ; add 1 to R0 and store back in R0
 
@@ -416,7 +416,7 @@

ADD

-Sign Extend +Sign Extend
uint16_t sign_extend(uint16_t x, int bit_count)
 {
     if ((x >> (bit_count - 1)) & 1) {
@@ -425,7 +425,7 @@ 

ADD

return x; }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Note: If you are interested in exactly how negative numbers can be represented in binary, you can read about Two’s Complement. However, this is not essential. You can just copy the code above and use it whenever the specification says to sign extend numbers.

@@ -438,7 +438,7 @@

ADD

-Update Flags +Update Flags
void update_flags(uint16_t r)
 {
     if (reg[r] == 0)
@@ -455,14 +455,14 @@ 

ADD

} }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Now we are ready to write the code for the ADD case:

-ADD +ADD
{
     /* destination register (DR) */
     uint16_t r0 = (instr >> 9) & 0x7;
@@ -485,7 +485,7 @@ 

ADD

update_flags(r0); }
-

Used by 1

+

Used by 1

This section contained a lot of information, so let’s summarize. @@ -515,7 +515,7 @@

LDI

-C LDI Sample +C LDI Sample
// the value of far_data is an address
 // of course far_data itself (the location in memory containing the address) has an address
 char* far_data = "apple";
@@ -542,7 +542,7 @@ 

LDI

-LDI +LDI
{
     /* destination register (DR) */
     uint16_t r0 = (instr >> 9) & 0x7;
@@ -553,7 +553,7 @@ 

LDI

update_flags(r0); }
-

Used by 1

+

Used by 1

As I said, this instruction shared a lot of the code and knowledge learned from ADD. You will find this is the case with the remaining instructions.

@@ -571,10 +571,10 @@

RTI & RES

-BAD OPCODE +BAD OPCODE
abort();
 
-

Used by 1

+

Used by 1

Bitwise and

@@ -583,7 +583,7 @@

Bitwise and

-AND +AND
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t r1 = (instr >> 6) & 0x7;
@@ -602,7 +602,7 @@ 

Bitwise and

update_flags(r0); }
-

Used by 1

+

Used by 1

Bitwise not

@@ -611,7 +611,7 @@

Bitwise not

-NOT +NOT
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t r1 = (instr >> 6) & 0x7;
@@ -620,14 +620,14 @@ 

Bitwise not

update_flags(r0); }
-

Used by 1

+

Used by 1

Branch

-BR +BR
{
     uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
     uint16_t cond_flag = (instr >> 9) & 0x7;
@@ -637,7 +637,7 @@ 

Branch

} }
-

Used by 1

+

Used by 1

Jump

@@ -646,21 +646,21 @@

Jump

-JMP +JMP
{
     /* Also handles RET */
     uint16_t r1 = (instr >> 6) & 0x7;
     reg[R_PC] = reg[r1];
 }
 
-

Used by 1

+

Used by 1

Jump Register

-JSR +JSR
{
     uint16_t long_flag = (instr >> 11) & 1;
     reg[R_R7] = reg[R_PC];
@@ -676,14 +676,14 @@ 

Jump Register

} }
-

Used by 1

+

Used by 1

Load

-LD +LD
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
@@ -691,14 +691,14 @@ 

Load

update_flags(r0); }
-

Used by 1

+

Used by 1

Load Register

-LDR +LDR
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t r1 = (instr >> 6) & 0x7;
@@ -707,14 +707,14 @@ 

Load Register

update_flags(r0); }
-

Used by 1

+

Used by 1

Load Effective Address

-LEA +LEA
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
@@ -722,42 +722,42 @@ 

Load Effective Address

update_flags(r0); }
-

Used by 1

+

Used by 1

Store

-ST +ST
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
     mem_write(reg[R_PC] + pc_offset, reg[r0]);
 }
 
-

Used by 1

+

Used by 1

Store Indirect

-STI +STI
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
     mem_write(mem_read(reg[R_PC] + pc_offset), reg[r0]);
 }
 
-

Used by 1

+

Used by 1

Store Register

-STR +STR
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t r1 = (instr >> 6) & 0x7;
@@ -765,7 +765,7 @@ 

Store Register

mem_write(reg[r1] + offset, reg[r0]); }
-

Used by 1

+

Used by 1

@@ -781,7 +781,7 @@

7. Trap Routines

-TRAP Codes +TRAP Codes
enum
 {
     TRAP_GETC = 0x20,  /* get character from keyboard, not echoed onto the terminal */
@@ -792,7 +792,7 @@ 

7. Trap Routines

TRAP_HALT = 0x25 /* halt the program */ };
-

Used by 1 2 3 4

+

Used by 1 2 3 4

You may be wondering why the trap codes are not included in the instructions. @@ -816,32 +816,32 @@

7. Trap Routines

-TRAP +TRAP
reg[R_R7] = reg[R_PC];
 
 switch (instr & 0xFF)
 {
     case TRAP_GETC:
-        @{TRAP GETC}
+        @{TRAP GETC}
         break;
     case TRAP_OUT:
-        @{TRAP OUT}
+        @{TRAP OUT}
         break;
     case TRAP_PUTS:
-        @{TRAP PUTS}
+        @{TRAP PUTS}
         break;
     case TRAP_IN:
-        @{TRAP IN}
+        @{TRAP IN}
         break;
     case TRAP_PUTSP:
-        @{TRAP PUTSP}
+        @{TRAP PUTSP}
         break;
     case TRAP_HALT:
-        @{TRAP HALT}
+        @{TRAP HALT}
         break;
 }
 
-

Used by 1 2

+

Used by 1 2

As with instructions, I will show you how to implement a single trap routine and leave the rest to you.

@@ -861,7 +861,7 @@

PUTS

-TRAP PUTS +TRAP PUTS
{
     /* one char per word */
     uint16_t* c = memory + reg[R_R0];
@@ -873,7 +873,7 @@ 

PUTS

fflush(stdout); }
-

Used by 1

+

Used by 1

That’s all for this routine. The trap routines are pretty straightforward if you are familiar with C. Go back to the specification and implement the others now. As with the instructions, the full code can be found at the end of the tutorial.

@@ -887,30 +887,30 @@

8. Trap Routine Cheat Sheet

-TRAP GETC +TRAP GETC
/* read a single ASCII char */
 reg[R_R0] = (uint16_t)getchar();
 update_flags(R_R0);
 
-

Used by 1

+

Used by 1

Output Character

-TRAP OUT +TRAP OUT
putc((char)reg[R_R0], stdout);
 fflush(stdout);
 
-

Used by 1

+

Used by 1

Prompt for Input Character

-TRAP IN +TRAP IN
{
     printf("Enter a character: ");
     char c = getchar();
@@ -920,14 +920,14 @@ 

8. Trap Routine Cheat Sheet

update_flags(R_R0); }
-

Used by 1

+

Used by 1

Output String

-TRAP PUTSP +TRAP PUTSP
{
     /* one char per byte (two bytes per word)
        here we need to swap back to
@@ -944,19 +944,56 @@ 

8. Trap Routine Cheat Sheet

fflush(stdout); }
-

Used by 1

+

Used by 1

Halt Program

-TRAP HALT +TRAP HALT
puts("HALT");
 fflush(stdout);
 running = 0;
 
-

Used by 1

+

Used by 1

+ + +

To properly handle trap inputs in the terminal we need to adjust some settings. +The implementation varies for each platform and will be shown later.

+ +
+ +Setup +
signal(SIGINT, handle_interrupt);
+disable_input_buffering();
+
+

Used by 1 2 3

+ + +

When the program is interrupted, we want to restore the terminal settings back to normal.

+ +
+ +Handle Interrupt +
void handle_interrupt(int signal)
+{
+    restore_input_buffering();
+    printf("\n");
+    exit(-2);
+}
+
+

Used by 1 2 3 4

+ + + + +
+ +Shutdown +
restore_input_buffering();
+
+

Used by 1 2 3

@@ -972,7 +1009,7 @@

9. Loading Programs

-Read Image File +Read Image File
void read_image_file(FILE* file)
 {
     /* the origin tells us where in memory to place the image */
@@ -993,20 +1030,20 @@ 

9. Loading Programs

} }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Notice that swap16 is called on each loaded value. LC-3 programs are big-endian, but most modern computers are little-endian. So, we need to swap each uint16 that is loaded. (If you happen to be using an obscure computer, like an old PPC Mac, then do not swap.)

-Swap +Swap
uint16_t swap16(uint16_t x)
 {
     return (x << 8) | (x >> 8);
 }
 
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Note: Endianness refers to how bytes of an integer are interpreted. In little-endian, the first byte is the least significant digit, and in big-endian, it is reversed. As far as I know, the decision is mostly arbitrary. Different companies made different decisions, so now we are left with varying implementations. You do not need to know anything else about endianness for this project.

@@ -1015,7 +1052,7 @@

9. Loading Programs

-Read Image +Read Image
int read_image(const char* image_path)
 {
     FILE* file = fopen(image_path, "rb");
@@ -1025,7 +1062,7 @@ 

9. Loading Programs

return 1; }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

@@ -1041,21 +1078,21 @@

10. Memory Mapped Registers

-Memory Mapped Registers +Memory Mapped Registers
enum
 {
     MR_KBSR = 0xFE00, /* keyboard status */
     MR_KBDR = 0xFE02  /* keyboard data */
 };
 
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Memory mapped registers make memory access a bit more complicated. We can’t read and write to the memory array directly, but must instead call setter and getter functions. When memory is read from KBSR, the getter will check the keyboard and update both memory locations.

-Memory Access +Memory Access
void mem_write(uint16_t address, uint16_t val)
 {
     memory[address] = val;
@@ -1078,7 +1115,7 @@ 

10. Memory Mapped Registers

return memory[address]; }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

That completes the last component of the VM! Provided that you implemented the rest of the trap routines and instructions, you are almost ready to try it out!

@@ -1087,24 +1124,24 @@

10. Memory Mapped Registers

@@ -1121,7 +1158,7 @@

11. Platform Specifics (Unix)

-Check Key +Check Key
uint16_t check_key()
 {
     fd_set readfds;
@@ -1134,14 +1171,14 @@ 

11. Platform Specifics (Unix)

return select(1, &readfds, NULL, NULL, &timeout) != 0; }
-

Used by 1 2

+

Used by 1 2

This is Unix specific code for setting up terminal input.

-Input Buffering +Input Buffering
struct termios original_tio;
 
 void disable_input_buffering()
@@ -1157,51 +1194,14 @@ 

11. Platform Specifics (Unix)

tcsetattr(STDIN_FILENO, TCSANOW, &original_tio); }
-

Used by 1 2

+

Used by 1 2

-Handle Interrupt -
void handle_interrupt(int signal)
-{
-    restore_input_buffering();
-    printf("\n");
-    exit(-2);
-}
-
-

Used by 1 2 3 4

- - - - -
- -Setup -
signal(SIGINT, handle_interrupt);
-disable_input_buffering();
-
-

Used by 1 2 3

- - -

When the program is interrupted, we want to restore the terminal -settings back to normal.

- -
- -Shutdown -
restore_input_buffering();
-
-

Used by 1 2 3

- - - - -
- -Includes +Includes
#include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
@@ -1216,7 +1216,7 @@ 

11. Platform Specifics (Unix)

#include <sys/termios.h> #include <sys/mman.h>
-

Used by 1 2

+

Used by 1 2

@@ -1231,20 +1231,20 @@

12. Platform Specifics (Windows)

-Check Key Windows +Check Key Windows
uint16_t check_key()
 {
     return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit();
 }
 
-

Used by 1 2

+

Used by 1 2

-Input Buffering Windows +Input Buffering Windows
DWORD fdwMode, fdwOldMode;
 
 void disable_input_buffering()
@@ -1264,36 +1264,14 @@ 

12. Platform Specifics (Windows)

SetConsoleMode(hStdin, fdwOldMode); }
-

Used by 1 2

- - - - -
- -Windows Setup -
signal(SIGINT, handle_interrupt);
-disable_input_buffering();
-
-
- - -

When the program is interrupted, we want to restore the terminal -settings back to normal.

- -
- -Windows Shutdown -
restore_input_buffering();
-
-
+

Used by 1 2

-Windows Includes +Windows Includes
#include <stdint.h> // uint16_t
 #include <stdio.h>  // FILE
 #include <signal.h> // SIGINT
@@ -1303,7 +1281,7 @@ 

12. Platform Specifics (Windows)

HANDLE hStdin = INVALID_HANDLE_VALUE;
-

Used by 1 2

+

Used by 1 2

@@ -1360,7 +1338,7 @@

14. Alternate C++ Technique

-Instruction C++ +Instruction C++
template <unsigned op>
 void ins(uint16_t instr)
 {
@@ -1446,20 +1424,20 @@ 

14. Alternate C++ Technique

if (0x0080 & opbit) { mem_write(base_plus_off, reg[r0]); } // STR if (0x8000 & opbit) // TRAP { - @{TRAP} + @{TRAP} } //if (0x0100 & opbit) { } // RTI if (0x4666 & opbit) { update_flags(r0); } }
-

Used by 1 2

+

Used by 1 2

-Op Table +Op Table
static void (*op_table[16])(uint16_t) = {
     ins<0>, ins<1>, ins<2>, ins<3>,
     ins<4>, ins<5>, ins<6>, ins<7>,
@@ -1467,7 +1445,7 @@ 

14. Alternate C++ Technique

ins<12>, NULL, ins<14>, ins<15> };
-

Used by 1 2

+

Used by 1 2

Note: I learned about this technique from Bisqwit’s NES emulator. diff --git a/index.lit b/index.lit index 106fa4f..a52006c 100644 --- a/index.lit +++ b/index.lit @@ -764,6 +764,29 @@ fflush(stdout); running = 0; --- +To properly handle trap inputs in the terminal we need to adjust some settings. +The implementation varies for each platform and will be shown later. + +--- Setup +signal(SIGINT, handle_interrupt); +disable_input_buffering(); +--- + +When the program is interrupted, we want to restore the terminal settings back to normal. + +--- Handle Interrupt +void handle_interrupt(int signal) +{ + restore_input_buffering(); + printf("\n"); + exit(-2); +} +--- + +--- Shutdown +restore_input_buffering(); +--- + ## Loading Programs We have mentioned a lot about loading and executing instructions from memory, but how do instructions get into memory in the first place? When an assembly program is converted to machine code, the result is a file containing an array of instructions and data. This can be loaded by just copying the contents right into an address in memory. @@ -924,27 +947,6 @@ void restore_input_buffering() } --- ---- Handle Interrupt -void handle_interrupt(int signal) -{ - restore_input_buffering(); - printf("\n"); - exit(-2); -} ---- - ---- Setup -signal(SIGINT, handle_interrupt); -disable_input_buffering(); ---- - -When the program is interrupted, we want to restore the terminal -settings back to normal. - ---- Shutdown -restore_input_buffering(); ---- - --- Includes #include #include @@ -996,18 +998,6 @@ void restore_input_buffering() } --- ---- Windows Setup -signal(SIGINT, handle_interrupt); -disable_input_buffering(); ---- - -When the program is interrupted, we want to restore the terminal -settings back to normal. - ---- Windows Shutdown -restore_input_buffering(); ---- - --- Windows Includes #include // uint16_t #include // FILE From fa0c8d635c8d386aef3992cd1c93399f94b5e4c6 Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Sat, 9 Jul 2022 18:33:01 -0600 Subject: [PATCH 02/10] shift input buffering to new section --- docs/index.html | 195 ++++++++++++++++++++++++------------------------ index.lit | 95 +++++++++++------------ 2 files changed, 147 insertions(+), 143 deletions(-) diff --git a/docs/index.html b/docs/index.html index 61bcbc8..5397c8f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -24,7 +24,7 @@

Write your Own Virtual Machine

The final code is about 250 lines of C (unix, windows). All you need to know is how to read basic C or C++ and how to do binary arithmetic.

-
  1. What is a virtual machine?
  2. LC-3 Architecture
  3. Assembly Examples
  4. Executing Programs
  5. Implementing Instructions
  6. Instruction Cheat Sheet
  7. Trap Routines
  8. Trap Routine Cheat Sheet
  9. Loading Programs
  10. Memory Mapped Registers
  11. Platform Specifics (Unix)
  12. Platform Specifics (Windows)
  13. Running the VM
  14. Alternate C++ Technique
  15. Contributions
+
  1. What is a virtual machine?
  2. LC-3 Architecture
  3. Assembly Examples
  4. Executing Programs
  5. Implementing Instructions
  6. Instruction Cheat Sheet
  7. Trap Routines
  8. Trap Routine Cheat Sheet
  9. Loading Programs
  10. Memory Mapped Registers
  11. Platform Specifics (Unix)
  12. Platform Specifics (Windows)
  13. Final details
  14. Running the VM
  15. Alternate C++ Technique
  16. Contributions

Note: This tutorial is a literate program. @@ -72,7 +72,7 @@

Memory

#define MEMORY_MAX (1 << 16)
 uint16_t memory[MEMORY_MAX];  /* 65536 locations */
 
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Registers

@@ -114,7 +114,7 @@

Registers

Register Storage
uint16_t reg[R_COUNT];
 
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Instruction Set

@@ -179,7 +179,7 @@

Condition Flags

/lc3.c -
@{Includes}
+
@{Includes}
 
 @{Registers}
 @{Opcodes}
@@ -269,7 +269,7 @@ 

Procedure

int main(int argc, const char* argv[])
 {
     @{Load Arguments}
-    @{Setup}
+    @{Setup}
 
     /* since exactly one condition flag should be set at any given time, set the Z flag */
     reg[R_COND] = FL_ZRO;
@@ -337,10 +337,10 @@ 

Procedure

break; } } - @{Shutdown} + @{Shutdown} }
-

Used by 1 2

+

Used by 1 2

While we are at the main loop let’s handle command line input to make our program usable. @@ -425,7 +425,7 @@

ADD

return x; }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Note: If you are interested in exactly how negative numbers can be represented in binary, you can read about Two’s Complement. However, this is not essential. You can just copy the code above and use it whenever the specification says to sign extend numbers.

@@ -455,7 +455,7 @@

ADD

} }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Now we are ready to write the code for the ADD case:

@@ -792,7 +792,7 @@

7. Trap Routines

TRAP_HALT = 0x25 /* halt the program */ }; -

Used by 1 2 3 4

+

Used by 1 2 3 4

You may be wondering why the trap codes are not included in the instructions. @@ -959,43 +959,6 @@

8. Trap Routine Cheat Sheet

Used by 1

-

To properly handle trap inputs in the terminal we need to adjust some settings. -The implementation varies for each platform and will be shown later.

- -
- -Setup -
signal(SIGINT, handle_interrupt);
-disable_input_buffering();
-
-

Used by 1 2 3

- - -

When the program is interrupted, we want to restore the terminal settings back to normal.

- -
- -Handle Interrupt -
void handle_interrupt(int signal)
-{
-    restore_input_buffering();
-    printf("\n");
-    exit(-2);
-}
-
-

Used by 1 2 3 4

- - - - -
- -Shutdown -
restore_input_buffering();
-
-

Used by 1 2 3

- -

9. Loading Programs

@@ -1009,7 +972,7 @@

9. Loading Programs

-Read Image File +Read Image File
void read_image_file(FILE* file)
 {
     /* the origin tells us where in memory to place the image */
@@ -1030,20 +993,20 @@ 

9. Loading Programs

} }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Notice that swap16 is called on each loaded value. LC-3 programs are big-endian, but most modern computers are little-endian. So, we need to swap each uint16 that is loaded. (If you happen to be using an obscure computer, like an old PPC Mac, then do not swap.)

-Swap +Swap
uint16_t swap16(uint16_t x)
 {
     return (x << 8) | (x >> 8);
 }
 
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Note: Endianness refers to how bytes of an integer are interpreted. In little-endian, the first byte is the least significant digit, and in big-endian, it is reversed. As far as I know, the decision is mostly arbitrary. Different companies made different decisions, so now we are left with varying implementations. You do not need to know anything else about endianness for this project.

@@ -1052,7 +1015,7 @@

9. Loading Programs

-Read Image +Read Image
int read_image(const char* image_path)
 {
     FILE* file = fopen(image_path, "rb");
@@ -1062,7 +1025,7 @@ 

9. Loading Programs

return 1; }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

@@ -1078,21 +1041,21 @@

10. Memory Mapped Registers

-Memory Mapped Registers +Memory Mapped Registers
enum
 {
     MR_KBSR = 0xFE00, /* keyboard status */
     MR_KBDR = 0xFE02  /* keyboard data */
 };
 
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Memory mapped registers make memory access a bit more complicated. We can’t read and write to the memory array directly, but must instead call setter and getter functions. When memory is read from KBSR, the getter will check the keyboard and update both memory locations.

-Memory Access +Memory Access
void mem_write(uint16_t address, uint16_t val)
 {
     memory[address] = val;
@@ -1115,39 +1078,11 @@ 

10. Memory Mapped Registers

return memory[address]; }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

That completes the last component of the VM! Provided that you implemented the rest of the trap routines and instructions, you are almost ready to try it out!

-

All that we have written should have been added to the C file in the following order:

- - - - - -

11. Platform Specifics (Unix)

@@ -1158,7 +1093,7 @@

11. Platform Specifics (Unix)

-Check Key +Check Key
uint16_t check_key()
 {
     fd_set readfds;
@@ -1171,14 +1106,14 @@ 

11. Platform Specifics (Unix)

return select(1, &readfds, NULL, NULL, &timeout) != 0; }
-

Used by 1 2

+

Used by 1 2

This is Unix specific code for setting up terminal input.

-Input Buffering +Input Buffering
struct termios original_tio;
 
 void disable_input_buffering()
@@ -1194,14 +1129,14 @@ 

11. Platform Specifics (Unix)

tcsetattr(STDIN_FILENO, TCSANOW, &original_tio); }
-

Used by 1 2

+

Used by 1 2

-Includes +Includes
#include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
@@ -1231,7 +1166,7 @@ 

12. Platform Specifics (Windows)

-Check Key Windows +Check Key Windows
uint16_t check_key()
 {
     return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit();
@@ -1244,7 +1179,7 @@ 

12. Platform Specifics (Windows)

-Input Buffering Windows +Input Buffering Windows
DWORD fdwMode, fdwOldMode;
 
 void disable_input_buffering()
@@ -1271,7 +1206,7 @@ 

12. Platform Specifics (Windows)

-Windows Includes +Windows Includes
#include <stdint.h> // uint16_t
 #include <stdio.h>  // FILE
 #include <signal.h> // SIGINT
@@ -1286,7 +1221,75 @@ 

12. Platform Specifics (Windows)

-

13. Running the VM

+

13. Final details

+ + +

To properly handle inputs in the terminal we need to adjust some settings. +The implementation varies for each platform and was defined above.

+ +
+ +Setup +
signal(SIGINT, handle_interrupt);
+disable_input_buffering();
+
+

Used by 1 2 3

+ + +

When the program is interrupted, we want to restore the terminal settings back to normal.

+ +
+ +Handle Interrupt +
void handle_interrupt(int signal)
+{
+    restore_input_buffering();
+    printf("\n");
+    exit(-2);
+}
+
+

Used by 1 2 3 4

+ + + + +
+ +Shutdown +
restore_input_buffering();
+
+

Used by 1 2 3

+ + +

All that we have written should have been added to the C file in the following order:

+ + + + + + +

14. Running the VM

You can now build and run the LC-3 VM!

@@ -1325,7 +1328,7 @@

Debugging

If the program doesn’t work correctly, it is likely because you programmed an instruction incorrectly. This can be tricky to debug. I recommend reading through the assembly source code of an LC-3 program while simultaneously using a debugger to step through the VM instructions one at a time. As you read the assembly, make sure the VM goes to the instruction that you expect it to. If a discrepancy occurs, you will then know which instruction caused the issue. Reread its specification and double check your code.

-

14. Alternate C++ Technique

+

15. Alternate C++ Technique

This section shows an advanced way of executing instructions that makes the code a whole lot smaller. This section is entirely optional.

@@ -1454,7 +1457,7 @@

14. Alternate C++ Technique

The rest of the C++ version uses the code we already wrote! The full source is here: unix, windows.

-

15. Contributions

+

16. Contributions

atul-g has contributed a handy reference card diff --git a/index.lit b/index.lit index a52006c..715c132 100644 --- a/index.lit +++ b/index.lit @@ -764,29 +764,6 @@ fflush(stdout); running = 0; --- -To properly handle trap inputs in the terminal we need to adjust some settings. -The implementation varies for each platform and will be shown later. - ---- Setup -signal(SIGINT, handle_interrupt); -disable_input_buffering(); ---- - -When the program is interrupted, we want to restore the terminal settings back to normal. - ---- Handle Interrupt -void handle_interrupt(int signal) -{ - restore_input_buffering(); - printf("\n"); - exit(-2); -} ---- - ---- Shutdown -restore_input_buffering(); ---- - ## Loading Programs We have mentioned a lot about loading and executing instructions from memory, but how do instructions get into memory in the first place? When an assembly program is converted to machine code, the result is a file containing an array of instructions and data. This can be loaded by just copying the contents right into an address in memory. @@ -884,29 +861,6 @@ uint16_t mem_read(uint16_t address) That completes the last component of the VM! Provided that you implemented the rest of the trap routines and instructions, you are almost ready to try it out! -All that we have written should have been added to the C file in the following order: - ---- /lc3.c += -@{Memory Mapped Registers} -@{TRAP Codes} - -@{Memory Storage} -@{Register Storage} - -@{Sign Extend} -@{Swap} -@{Update Flags} -@{Read Image File} -@{Read Image} -@{Check Key} -@{Memory Access} -@{Input Buffering} -@{Handle Interrupt} - -@{Main Loop} ---- - - ## Platform Specifics (Unix) This section contains some tedious details that are needed to access the keyboard and behave nicely. @@ -1009,6 +963,54 @@ void restore_input_buffering() HANDLE hStdin = INVALID_HANDLE_VALUE; --- +## Final details + +To properly handle inputs in the terminal we need to adjust some settings. +The implementation varies for each platform and was defined above. + +--- Setup +signal(SIGINT, handle_interrupt); +disable_input_buffering(); +--- + +When the program is interrupted, we want to restore the terminal settings back to normal. + +--- Handle Interrupt +void handle_interrupt(int signal) +{ + restore_input_buffering(); + printf("\n"); + exit(-2); +} +--- + +--- Shutdown +restore_input_buffering(); +--- + +All that we have written should have been added to the C file in the following order: + +--- /lc3.c += +@{Memory Mapped Registers} +@{TRAP Codes} + +@{Memory Storage} +@{Register Storage} + +@{Sign Extend} +@{Swap} +@{Update Flags} +@{Read Image File} +@{Read Image} +@{Check Key} +@{Memory Access} +@{Input Buffering} +@{Handle Interrupt} + +@{Main Loop} +--- + + --- /lc3-win.c --- noWeave @{Windows Includes} @@ -1035,7 +1037,6 @@ HANDLE hStdin = INVALID_HANDLE_VALUE; @{Main Loop} --- - ## Running the VM You can now build and run the LC-3 VM! From 421b81f7742b4b67b97999f7d7f6abe2cacb0020 Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Sat, 9 Jul 2022 18:34:36 -0600 Subject: [PATCH 03/10] refine explanation --- docs/index.html | 4 ++-- index.lit | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/index.html b/docs/index.html index 5397c8f..3e2b861 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1224,8 +1224,8 @@

12. Platform Specifics (Windows)

13. Final details

-

To properly handle inputs in the terminal we need to adjust some settings. -The implementation varies for each platform and was defined above.

+

To properly handle input to the terminal we need to adjust some buffering settings. +The implementation for these varies for each platform and should have been defined abo ve.

diff --git a/index.lit b/index.lit index 715c132..dd87381 100644 --- a/index.lit +++ b/index.lit @@ -965,8 +965,8 @@ HANDLE hStdin = INVALID_HANDLE_VALUE; ## Final details -To properly handle inputs in the terminal we need to adjust some settings. -The implementation varies for each platform and was defined above. +To properly handle input to the terminal we need to adjust some buffering settings. +The implementation for these varies for each platform and should have been defined abo ve. --- Setup signal(SIGINT, handle_interrupt); From c75cef349cdd560c00e0f8985633c0db6afd46e4 Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Sat, 9 Jul 2022 18:46:46 -0600 Subject: [PATCH 04/10] refactor headings --- docs/index.html | 298 ++++++++++++++++++++++++------------------------ index.lit | 42 +++---- 2 files changed, 169 insertions(+), 171 deletions(-) diff --git a/docs/index.html b/docs/index.html index 3e2b861..78e5061 100644 --- a/docs/index.html +++ b/docs/index.html @@ -24,7 +24,7 @@

Write your Own Virtual Machine

The final code is about 250 lines of C (unix, windows). All you need to know is how to read basic C or C++ and how to do binary arithmetic.

-
  1. What is a virtual machine?
  2. LC-3 Architecture
  3. Assembly Examples
  4. Executing Programs
  5. Implementing Instructions
  6. Instruction Cheat Sheet
  7. Trap Routines
  8. Trap Routine Cheat Sheet
  9. Loading Programs
  10. Memory Mapped Registers
  11. Platform Specifics (Unix)
  12. Platform Specifics (Windows)
  13. Final details
  14. Running the VM
  15. Alternate C++ Technique
  16. Contributions
+
  1. What is a virtual machine?
  2. LC-3 architecture
  3. Assembly examples
  4. Executing programs
  5. Implementing instructions
  6. Instruction cheat sheet
  7. Trap routines
  8. Trap routine cheat sheet
  9. Loading programs
  10. Memory mapped registers
  11. Platform specifics
  12. Running the VM
  13. Alternate C++ technique
  14. Contributions

Note: This tutorial is a literate program. @@ -55,7 +55,7 @@

1. What is a virtual machine?

Another example of this behavior is demonstrated by Ethereum smart contracts. Smart contracts are small programs which are executed by each validating node in the blockchain network. This requires the node operators to run programs on their machines that have been written by complete strangers, without any opportunity to scrutinize them beforehand. To prevent a contract from doing malicious things, they are run inside a VM that has no access to the file system, network, disc, etc. Ethereum is also a good application of the portability features that result when using a VM. Since Ethereum nodes can be run on many kinds of computers and operating systems, the use of a VM allows smart contracts to be written without any consideration of the many platforms they run on.

-

2. LC-3 Architecture

+

2. LC-3 architecture

Our VM will simulate a fictional computer called the LC-3. The LC-3 is popular for teaching university students how to program in assembly language. It has a simplified instruction set compared to x86, but contains all the main ideas used in modern CPUs.

@@ -68,7 +68,7 @@

Memory

-Memory Storage +Memory Storage
#define MEMORY_MAX (1 << 16)
 uint16_t memory[MEMORY_MAX];  /* 65536 locations */
 
@@ -88,7 +88,7 @@

Registers

-Registers +Registers
enum
 {
     R_R0 = 0,
@@ -111,13 +111,13 @@ 

Registers

-Register Storage +Register Storage
uint16_t reg[R_COUNT];
 

Used by 1 2 3 4

-

Instruction Set

+

Instruction set

An instruction is a command which tells the CPU to do some fundamental task, such as add two numbers. Instructions have both an opcode which indicates the kind of task to perform and a set of parameters which provide inputs to the task being performed.

@@ -127,7 +127,7 @@

Instruction Set

-Opcodes +Opcodes
enum
 {
     OP_BR = 0, /* branch */
@@ -153,7 +153,7 @@ 

Instruction Set

Note: The Intel x86 architecture has hundreds of instructions, while others such as ARM and LC-3 have very few. Small instruction sets are referred to as RISCs while larger ones are called CISCs. Larger instruction sets typically do not provide any fundamentally new possibilities, but they often make it more convenient to write assembly for. A single instruction in CISC might take the place of several in RISC. However, they tend to be more complex and expensive for engineers to design and manufacture. This and other tradeoffs cause the designs to come in and out of style.

-

Condition Flags

+

Condition flags

The R_COND register stores condition flags which provide information about the most recently executed calculation. This allows programs to check logical conditions such as if (x > 0) { ... }.

@@ -161,7 +161,7 @@

Condition Flags

-Condition Flags +Condition Flags
enum
 {
     FL_POS = 1 << 0, /* P */
@@ -179,25 +179,25 @@ 

Condition Flags

-

3. Assembly Examples

+

3. Assembly examples

Now let’s look at an LC-3 assembly program to get an idea of what the VM actually runs. You don’t need to know how to program assembly or understand everything that is going on. Just try to get a general idea of what is going on. Here is a simple “Hello World”:

-Hello World Assembly +Hello World Assembly
.ORIG x3000                        ; this is the address in memory where the program will be loaded
 LEA R0, HELLO_STR                  ; load the address of the HELLO_STR string into R0
 PUTs                               ; output the string pointed to by R0 to the console
@@ -224,7 +224,7 @@ 

3. Assembly Examples

-Loop Assembly +Loop Assembly
AND R0, R0, 0                      ; clear R0
 LOOP                               ; label at the top of our loop
 ADD R0, R0, 1                      ; add 1 to R0 and store back in R0
@@ -237,7 +237,7 @@ 

3. Assembly Examples

Note: Learning to write assembly is not necessary for this tutorial. However, if you are interested, you can write and assemble your own LC-3 programs using the LC-3 Tools.

-

4. Executing Programs

+

4. Executing programs

Once again, the previous examples are just to give you an idea of what the VM does. To write a VM, you don’t need to be fluent in assembly. As long as you follow the proper procedure for reading and executing instructions, any LC-3 program will run correctly, no matter how complicated it is. In theory, it could even run a web browser or an operating system like Linux!

@@ -265,11 +265,11 @@

Procedure

-Main Loop +Main Loop
int main(int argc, const char* argv[])
 {
-    @{Load Arguments}
-    @{Setup}
+    @{Load Arguments}
+    @{Setup}
 
     /* since exactly one condition flag should be set at any given time, set the Z flag */
     reg[R_COND] = FL_ZRO;
@@ -289,55 +289,55 @@ 

Procedure

switch (op) { case OP_ADD: - @{ADD} + @{ADD} break; case OP_AND: - @{AND} + @{AND} break; case OP_NOT: - @{NOT} + @{NOT} break; case OP_BR: - @{BR} + @{BR} break; case OP_JMP: - @{JMP} + @{JMP} break; case OP_JSR: - @{JSR} + @{JSR} break; case OP_LD: - @{LD} + @{LD} break; case OP_LDI: - @{LDI} + @{LDI} break; case OP_LDR: - @{LDR} + @{LDR} break; case OP_LEA: - @{LEA} + @{LEA} break; case OP_ST: - @{ST} + @{ST} break; case OP_STI: - @{STI} + @{STI} break; case OP_STR: - @{STR} + @{STR} break; case OP_TRAP: - @{TRAP} + @{TRAP} break; case OP_RES: case OP_RTI: default: - @{BAD OPCODE} + @{BAD OPCODE} break; } } - @{Shutdown} + @{Shutdown} }

Used by 1 2

@@ -348,7 +348,7 @@

Procedure

-Load Arguments +Load Arguments
if (argc < 2)
 {
     /* show usage string */
@@ -365,12 +365,12 @@ 

Procedure

} }
-

Used by 1 2 3

+

Used by 1 2 3

-

5. Implementing Instructions

+

5. Implementing instructions

Your task now is to fill in each opcode case with a correct implementation. This is easier than it sounds. A detailed specification for each instruction is included in the project documents. The specificiation for each translates pretty easily to several lines of codes. I will demonstrate how to implement two of them here. The code for the rest can be found in the next section.

@@ -387,7 +387,7 @@

ADD

-Add Register Assembly +Add Register Assembly
ADD R2 R0 R1 ; add the contents of R0 to R1 and store in R2.
 
@@ -402,7 +402,7 @@

ADD

-Add Immediate Assembly +Add Immediate Assembly
ADD R0 R0 1 ; add 1 to R0 and store back in R0
 
@@ -416,7 +416,7 @@

ADD

-Sign Extend +Sign Extend
uint16_t sign_extend(uint16_t x, int bit_count)
 {
     if ((x >> (bit_count - 1)) & 1) {
@@ -438,7 +438,7 @@ 

ADD

-Update Flags +Update Flags
void update_flags(uint16_t r)
 {
     if (reg[r] == 0)
@@ -462,7 +462,7 @@ 

ADD

-ADD +ADD
{
     /* destination register (DR) */
     uint16_t r0 = (instr >> 9) & 0x7;
@@ -485,7 +485,7 @@ 

ADD

update_flags(r0); }
-

Used by 1

+

Used by 1

This section contained a lot of information, so let’s summarize. @@ -515,7 +515,7 @@

LDI

-C LDI Sample +C LDI Sample
// the value of far_data is an address
 // of course far_data itself (the location in memory containing the address) has an address
 char* far_data = "apple";
@@ -542,7 +542,7 @@ 

LDI

-LDI +LDI
{
     /* destination register (DR) */
     uint16_t r0 = (instr >> 9) & 0x7;
@@ -553,14 +553,14 @@ 

LDI

update_flags(r0); }
-

Used by 1

+

Used by 1

As I said, this instruction shared a lot of the code and knowledge learned from ADD. You will find this is the case with the remaining instructions.

You now need to go back and implement the rest of the switch cases for the instructions. Follow the specification and use the code listed here to complete the others. The code for all instructions is listed at the end of the tutorial. Two of the opcodes specified before will not be used, they are OP_RTI and OP_RES. You can ignore these cases or throw an error if they are executed. After you are done, the bulk of your VM will be completed!

-

6. Instruction Cheat Sheet

+

6. Instruction cheat sheet

This section contains the full implementations of the remaining instructions if you get stuck.

@@ -571,10 +571,10 @@

RTI & RES

-BAD OPCODE +BAD OPCODE
abort();
 
-

Used by 1

+

Used by 1

Bitwise and

@@ -583,7 +583,7 @@

Bitwise and

-AND +AND
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t r1 = (instr >> 6) & 0x7;
@@ -602,7 +602,7 @@ 

Bitwise and

update_flags(r0); }
-

Used by 1

+

Used by 1

Bitwise not

@@ -611,7 +611,7 @@

Bitwise not

-NOT +NOT
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t r1 = (instr >> 6) & 0x7;
@@ -620,14 +620,14 @@ 

Bitwise not

update_flags(r0); }
-

Used by 1

+

Used by 1

Branch

-BR +BR
{
     uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
     uint16_t cond_flag = (instr >> 9) & 0x7;
@@ -637,7 +637,7 @@ 

Branch

} }
-

Used by 1

+

Used by 1

Jump

@@ -646,21 +646,21 @@

Jump

-JMP +JMP
{
     /* Also handles RET */
     uint16_t r1 = (instr >> 6) & 0x7;
     reg[R_PC] = reg[r1];
 }
 
-

Used by 1

+

Used by 1

-

Jump Register

+

Jump register

-JSR +JSR
{
     uint16_t long_flag = (instr >> 11) & 1;
     reg[R_R7] = reg[R_PC];
@@ -676,14 +676,14 @@ 

Jump Register

} }
-

Used by 1

+

Used by 1

Load

-LD +LD
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
@@ -691,14 +691,14 @@ 

Load

update_flags(r0); }
-

Used by 1

+

Used by 1

-

Load Register

+

Load register

-LDR +LDR
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t r1 = (instr >> 6) & 0x7;
@@ -707,14 +707,14 @@ 

Load Register

update_flags(r0); }
-

Used by 1

+

Used by 1

-

Load Effective Address

+

Load effective address

-LEA +LEA
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
@@ -722,42 +722,42 @@ 

Load Effective Address

update_flags(r0); }
-

Used by 1

+

Used by 1

Store

-ST +ST
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
     mem_write(reg[R_PC] + pc_offset, reg[r0]);
 }
 
-

Used by 1

+

Used by 1

-

Store Indirect

+

Store indirect

-STI +STI
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
     mem_write(mem_read(reg[R_PC] + pc_offset), reg[r0]);
 }
 
-

Used by 1

+

Used by 1

-

Store Register

+

Store register

-STR +STR
{
     uint16_t r0 = (instr >> 9) & 0x7;
     uint16_t r1 = (instr >> 6) & 0x7;
@@ -765,12 +765,12 @@ 

Store Register

mem_write(reg[r1] + offset, reg[r0]); }
-

Used by 1

+

Used by 1

-

7. Trap Routines

+

7. Trap routines

The LC-3 provides a few predefined routines for performing common tasks and interacting with I/O devices. For example, there are routines for getting input from the keyboard and for displaying strings to the console. These are called trap routines which you can think of as the operating system or API for the LC-3. Each trap routine is assigned a trap code which identifies it (similar to an opcode). To execute one, the TRAP instruction is called with the trap code of the desired routine.

@@ -781,7 +781,7 @@

7. Trap Routines

-TRAP Codes +TRAP Codes
enum
 {
     TRAP_GETC = 0x20,  /* get character from keyboard, not echoed onto the terminal */
@@ -816,32 +816,32 @@ 

7. Trap Routines

-TRAP +TRAP
reg[R_R7] = reg[R_PC];
 
 switch (instr & 0xFF)
 {
     case TRAP_GETC:
-        @{TRAP GETC}
+        @{TRAP GETC}
         break;
     case TRAP_OUT:
-        @{TRAP OUT}
+        @{TRAP OUT}
         break;
     case TRAP_PUTS:
-        @{TRAP PUTS}
+        @{TRAP PUTS}
         break;
     case TRAP_IN:
-        @{TRAP IN}
+        @{TRAP IN}
         break;
     case TRAP_PUTSP:
-        @{TRAP PUTSP}
+        @{TRAP PUTSP}
         break;
     case TRAP_HALT:
-        @{TRAP HALT}
+        @{TRAP HALT}
         break;
 }
 
-

Used by 1 2

+

Used by 1 2

As with instructions, I will show you how to implement a single trap routine and leave the rest to you.

@@ -861,7 +861,7 @@

PUTS

-TRAP PUTS +TRAP PUTS
{
     /* one char per word */
     uint16_t* c = memory + reg[R_R0];
@@ -873,12 +873,12 @@ 

PUTS

fflush(stdout); }
-

Used by 1

+

Used by 1

That’s all for this routine. The trap routines are pretty straightforward if you are familiar with C. Go back to the specification and implement the others now. As with the instructions, the full code can be found at the end of the tutorial.

-

8. Trap Routine Cheat Sheet

+

8. Trap routine cheat sheet

This section contains the full implementations of the remaining trap routines.

@@ -887,30 +887,30 @@

8. Trap Routine Cheat Sheet

-TRAP GETC +TRAP GETC
/* read a single ASCII char */
 reg[R_R0] = (uint16_t)getchar();
 update_flags(R_R0);
 
-

Used by 1

+

Used by 1

Output Character

-TRAP OUT +TRAP OUT
putc((char)reg[R_R0], stdout);
 fflush(stdout);
 
-

Used by 1

+

Used by 1

Prompt for Input Character

-TRAP IN +TRAP IN
{
     printf("Enter a character: ");
     char c = getchar();
@@ -920,14 +920,14 @@ 

8. Trap Routine Cheat Sheet

update_flags(R_R0); }
-

Used by 1

+

Used by 1

Output String

-TRAP PUTSP +TRAP PUTSP
{
     /* one char per byte (two bytes per word)
        here we need to swap back to
@@ -944,24 +944,24 @@ 

8. Trap Routine Cheat Sheet

fflush(stdout); }
-

Used by 1

+

Used by 1

Halt Program

-TRAP HALT +TRAP HALT
puts("HALT");
 fflush(stdout);
 running = 0;
 
-

Used by 1

+

Used by 1

-

9. Loading Programs

+

9. Loading programs

We have mentioned a lot about loading and executing instructions from memory, but how do instructions get into memory in the first place? When an assembly program is converted to machine code, the result is a file containing an array of instructions and data. This can be loaded by just copying the contents right into an address in memory.

@@ -972,7 +972,7 @@

9. Loading Programs

-Read Image File +Read Image File
void read_image_file(FILE* file)
 {
     /* the origin tells us where in memory to place the image */
@@ -1000,7 +1000,7 @@ 

9. Loading Programs

-Swap +Swap
uint16_t swap16(uint16_t x)
 {
     return (x << 8) | (x >> 8);
@@ -1015,7 +1015,7 @@ 

9. Loading Programs

-Read Image +Read Image
int read_image(const char* image_path)
 {
     FILE* file = fopen(image_path, "rb");
@@ -1030,7 +1030,7 @@ 

9. Loading Programs

-

10. Memory Mapped Registers

+

10. Memory mapped registers

Some special registers are not accessible from the normal register table. Instead, a special address is reserved for them in memory. To read and write to these registers, you just read and write to their memory location. These are called memory mapped registers. They are commonly used to interact with special hardware devices.

@@ -1041,7 +1041,7 @@

10. Memory Mapped Registers

-Memory Mapped Registers +Memory Mapped Registers
enum
 {
     MR_KBSR = 0xFE00, /* keyboard status */
@@ -1055,7 +1055,7 @@ 

10. Memory Mapped Registers

-Memory Access +Memory Access
void mem_write(uint16_t address, uint16_t val)
 {
     memory[address] = val;
@@ -1083,17 +1083,19 @@ 

10. Memory Mapped Registers

That completes the last component of the VM! Provided that you implemented the rest of the trap routines and instructions, you are almost ready to try it out!

-

11. Platform Specifics (Unix)

+

11. Platform specifics

This section contains some tedious details that are needed to access the keyboard and behave nicely. These are not insightful or relevant to learning about VMs. Feel free to copy paste!

+

Linux/macOS/UNIX

+

NOTE: Skip to the next section for the Windows versions of these functions.

-Check Key +Check Key
uint16_t check_key()
 {
     fd_set readfds;
@@ -1113,7 +1115,7 @@ 

11. Platform Specifics (Unix)

-Input Buffering +Input Buffering
struct termios original_tio;
 
 void disable_input_buffering()
@@ -1136,7 +1138,7 @@ 

11. Platform Specifics (Unix)

-Includes +Includes
#include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
@@ -1154,10 +1156,7 @@ 

11. Platform Specifics (Unix)

Used by 1 2

- - -

12. Platform Specifics (Windows)

- +

Windows

This section contains some tedious details that are needed to access the keyboard and behave nicely. These are not insightful or relevant to learning about VMs. Feel free to copy paste!

@@ -1166,7 +1165,7 @@

12. Platform Specifics (Windows)

-Check Key Windows +Check Key Windows
uint16_t check_key()
 {
     return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit();
@@ -1179,7 +1178,7 @@ 

12. Platform Specifics (Windows)

-Input Buffering Windows +Input Buffering Windows
DWORD fdwMode, fdwOldMode;
 
 void disable_input_buffering()
@@ -1206,7 +1205,7 @@ 

12. Platform Specifics (Windows)

-Windows Includes +Windows Includes
#include <stdint.h> // uint16_t
 #include <stdio.h>  // FILE
 #include <signal.h> // SIGINT
@@ -1219,28 +1218,25 @@ 

12. Platform Specifics (Windows)

Used by 1 2

- - -

13. Final details

- +

All platforms

To properly handle input to the terminal we need to adjust some buffering settings. The implementation for these varies for each platform and should have been defined abo ve.

-Setup +Setup
signal(SIGINT, handle_interrupt);
 disable_input_buffering();
 
-

Used by 1 2 3

+

Used by 1 2 3

When the program is interrupted, we want to restore the terminal settings back to normal.

-Handle Interrupt +Handle Interrupt
void handle_interrupt(int signal)
 {
     restore_input_buffering();
@@ -1255,10 +1251,10 @@ 

13. Final details

-Shutdown +Shutdown
restore_input_buffering();
 
-

Used by 1 2 3

+

Used by 1 2 3

All that we have written should have been added to the C file in the following order:

@@ -1266,30 +1262,30 @@

13. Final details

-

14. Running the VM

+

12. Running the VM

You can now build and run the LC-3 VM!

@@ -1328,7 +1324,7 @@

Debugging

If the program doesn’t work correctly, it is likely because you programmed an instruction incorrectly. This can be tricky to debug. I recommend reading through the assembly source code of an LC-3 program while simultaneously using a debugger to step through the VM instructions one at a time. As you read the assembly, make sure the VM goes to the instruction that you expect it to. If a discrepancy occurs, you will then know which instruction caused the issue. Reread its specification and double check your code.

-

15. Alternate C++ Technique

+

13. Alternate C++ technique

This section shows an advanced way of executing instructions that makes the code a whole lot smaller. This section is entirely optional.

@@ -1341,7 +1337,7 @@

15. Alternate C++ Technique

-Instruction C++ +Instruction C++
template <unsigned op>
 void ins(uint16_t instr)
 {
@@ -1427,7 +1423,7 @@ 

15. Alternate C++ Technique

if (0x0080 & opbit) { mem_write(base_plus_off, reg[r0]); } // STR if (0x8000 & opbit) // TRAP { - @{TRAP} + @{TRAP} } //if (0x0100 & opbit) { } // RTI if (0x4666 & opbit) { update_flags(r0); } @@ -1440,7 +1436,7 @@

15. Alternate C++ Technique

-Op Table +Op Table
static void (*op_table[16])(uint16_t) = {
     ins<0>, ins<1>, ins<2>, ins<3>,
     ins<4>, ins<5>, ins<6>, ins<7>,
@@ -1457,7 +1453,7 @@ 

15. Alternate C++ Technique

The rest of the C++ version uses the code we already wrote! The full source is here: unix, windows.

-

16. Contributions

+

14. Contributions

atul-g has contributed a handy reference card diff --git a/index.lit b/index.lit index dd87381..fe8de29 100644 --- a/index.lit +++ b/index.lit @@ -38,7 +38,7 @@ VMs are also useful for executing code in a secure or isolated way. One applicat Another example of this behavior is demonstrated by [Ethereum smart contracts](https://solidity.readthedocs.io/en/v0.4.24/introduction-to-smart-contracts.html). Smart contracts are small programs which are executed by each validating node in the blockchain network. This requires the node operators to run programs on their machines that have been written by complete strangers, without any opportunity to scrutinize them beforehand. To prevent a contract from doing malicious things, they are run inside a [VM](http://ethdocs.org/en/latest/introduction/what-is-ethereum.html#ethereum-virtual-machine) that has no access to the file system, network, disc, etc. Ethereum is also a good application of the portability features that result when using a VM. Since Ethereum nodes can be run on many kinds of computers and operating systems, the use of a VM allows smart contracts to be written without any consideration of the many platforms they run on. -## LC-3 Architecture +## LC-3 architecture Our VM will simulate a fictional computer called the [LC-3](https://en.wikipedia.org/wiki/Little_Computer_3). The LC-3 is popular for teaching university students how to program in assembly language. It has a simplified instruction set [compared to x86](http://ref.x86asm.net/coder64.html), but contains all the main ideas used in modern CPUs. @@ -87,7 +87,7 @@ Just like the memory, we will store the registers in an array: uint16_t reg[R_COUNT]; --- -### Instruction Set +### Instruction set An instruction is a command which tells the CPU to do some fundamental task, such as add two numbers. Instructions have both an **opcode** which indicates the kind of task to perform and a set of **parameters** which provide inputs to the task being performed. @@ -120,7 +120,7 @@ enum > **Note:** The Intel x86 architecture has hundreds of instructions, while others such as ARM and LC-3 have very few. Small instruction sets are referred to as [RISCs](https://en.wikipedia.org/wiki/Reduced_instruction_set_computer) while larger ones are called [CISCs](https://en.wikipedia.org/wiki/Complex_instruction_set_computer). Larger instruction sets typically do not provide any fundamentally new possibilities, but they [often make it more convenient](https://cs.stanford.edu/people/eroberts/courses/soco/projects/risc/risccisc/) to write assembly for. A single instruction in CISC might take the place of several in RISC. However, they tend to be more complex and expensive for engineers to design and manufacture. This and other tradeoffs cause the designs to come [in and out of style](https://cs.stackexchange.com/questions/269/why-would-anyone-want-cisc). -### Condition Flags +### Condition flags The `R_COND` register stores condition flags which provide information about the most recently executed calculation. This allows programs to check logical conditions such as `if (x > 0) { ... }`. @@ -146,7 +146,7 @@ We are finished setting up the hardware components of our VM! After adding stand @{Condition Flags} --- -## Assembly Examples +## Assembly examples Now let's look at an LC-3 assembly program to get an idea of what the VM actually runs. You don't need to know how to program assembly or understand everything that is going on. Just try to get a general idea of what is going on. Here is a simple "Hello World": @@ -184,7 +184,7 @@ BRn LOOP ; go back to LOOP if the result was negative > **Note:** Learning to write assembly is not necessary for this tutorial. However, if you are interested, you can write and assemble your own LC-3 programs using the [LC-3 Tools](http://highered.mheducation.com/sites/0072467509/student_view0/lc-3_simulator.html). -## Executing Programs +## Executing programs Once again, the previous examples are just to give you an idea of what the VM does. To write a VM, you don't need to be fluent in assembly. As long as you follow the proper procedure for reading and executing instructions, *any* LC-3 program will run correctly, no matter how complicated it is. In theory, it could even run a web browser or an operating system like Linux! @@ -304,7 +304,7 @@ for (int j = 1; j < argc; ++j) } --- -## Implementing Instructions +## Implementing instructions Your task now is to fill in each opcode case with a correct implementation. This is easier than it sounds. A detailed specification for each instruction is included in the [project documents](supplies/lc3-isa.pdf). The specificiation for each translates pretty easily to several lines of codes. I will demonstrate how to implement two of them here. The code for the rest can be found in the next section. @@ -466,7 +466,7 @@ As I said, this instruction shared a lot of the code and knowledge learned from You now need to go back and implement the rest of the switch cases for the instructions. Follow [the specification](supplies/lc3-isa.pdf) and use the code listed here to complete the others. The code for all instructions is listed at the end of the tutorial. Two of the opcodes specified before will not be used, they are `OP_RTI` and `OP_RES`. You can ignore these cases or throw an error if they are executed. After you are done, the bulk of your VM will be completed! -## Instruction Cheat Sheet +## Instruction cheat sheet This section contains the full implementations of the remaining instructions if you get stuck. @@ -537,7 +537,7 @@ abort(); } --- -### Jump Register +### Jump register --- JSR { uint16_t long_flag = (instr >> 11) & 1; @@ -565,7 +565,7 @@ abort(); } --- -### Load Register +### Load register --- LDR { uint16_t r0 = (instr >> 9) & 0x7; @@ -576,7 +576,7 @@ abort(); } --- -### Load Effective Address +### Load effective address --- LEA { uint16_t r0 = (instr >> 9) & 0x7; @@ -595,7 +595,7 @@ abort(); } --- -### Store Indirect +### Store indirect --- STI { uint16_t r0 = (instr >> 9) & 0x7; @@ -604,7 +604,7 @@ abort(); } --- -### Store Register +### Store register --- STR { uint16_t r0 = (instr >> 9) & 0x7; @@ -614,7 +614,7 @@ abort(); } --- -## Trap Routines +## Trap routines The LC-3 provides a few predefined routines for performing common tasks and interacting with I/O devices. For example, there are routines for getting input from the keyboard and for displaying strings to the console. These are called *trap routines* which you can think of as the operating system or API for the LC-3. Each trap routine is assigned a *trap code* which identifies it (similar to an opcode). To execute one, the `TRAP` instruction is called with the trap code of the desired routine. @@ -709,7 +709,7 @@ Notice that unlike C strings, characters are *not stored* in a single byte, but That's all for this routine. The trap routines are pretty straightforward if you are familiar with C. Go back to the specification and implement the others now. As with the instructions, the full code can be found at the end of the tutorial. -## Trap Routine Cheat Sheet +## Trap routine cheat sheet This section contains the full implementations of the remaining trap routines. @@ -764,7 +764,7 @@ fflush(stdout); running = 0; --- -## Loading Programs +## Loading programs We have mentioned a lot about loading and executing instructions from memory, but how do instructions get into memory in the first place? When an assembly program is converted to machine code, the result is a file containing an array of instructions and data. This can be loaded by just copying the contents right into an address in memory. @@ -817,7 +817,7 @@ int read_image(const char* image_path) } --- -## Memory Mapped Registers +## Memory mapped registers Some special registers are not accessible from the normal register table. Instead, a special address is reserved for them in memory. To read and write to these registers, you just read and write to their memory location. These are called **memory mapped registers**. They are commonly used to interact with special hardware devices. @@ -861,11 +861,13 @@ uint16_t mem_read(uint16_t address) That completes the last component of the VM! Provided that you implemented the rest of the trap routines and instructions, you are almost ready to try it out! -## Platform Specifics (Unix) +## Platform specifics This section contains some tedious details that are needed to access the keyboard and behave nicely. These are not insightful or relevant to learning about VMs. Feel free to copy paste! +### Linux/macOS/UNIX + NOTE: Skip to the next section for the **Windows** versions of these functions. --- Check Key @@ -917,7 +919,7 @@ void restore_input_buffering() #include --- -## Platform Specifics (Windows) +### Windows This section contains some tedious details that are needed to access the keyboard and behave nicely. These are not insightful or relevant to learning about VMs. Feel free to copy paste! @@ -963,7 +965,7 @@ void restore_input_buffering() HANDLE hStdin = INVALID_HANDLE_VALUE; --- -## Final details +### All platforms To properly handle input to the terminal we need to adjust some buffering settings. The implementation for these varies for each platform and should have been defined abo ve. @@ -1071,7 +1073,7 @@ You can now build and run the LC-3 VM! If the program doesn't work correctly, it is likely because you programmed an instruction incorrectly. This can be tricky to debug. I recommend reading through the assembly source code of an LC-3 program while simultaneously using a debugger to step through the VM instructions one at a time. As you read the assembly, make sure the VM goes to the instruction that you expect it to. If a discrepancy occurs, you will then know which instruction caused the issue. Reread its specification and double check your code. -## Alternate C++ Technique +## Alternate C++ technique This section shows an advanced way of executing instructions that makes the code a whole lot smaller. This section is entirely optional. From 3ee95b068e7dde6eabc1c8ab30c65eaecb2dd0f8 Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Thu, 14 Jul 2022 21:59:50 -0600 Subject: [PATCH 05/10] Update index.lit Co-authored-by: Ryan Pendleton --- index.lit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.lit b/index.lit index fe8de29..2d24022 100644 --- a/index.lit +++ b/index.lit @@ -967,7 +967,7 @@ HANDLE hStdin = INVALID_HANDLE_VALUE; ### All platforms -To properly handle input to the terminal we need to adjust some buffering settings. +To properly handle input to the terminal, we need to adjust some buffering settings. The implementation for these varies for each platform and should have been defined abo ve. --- Setup From d0c34d3e88c32f31763a87c11e1879018629f481 Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Thu, 14 Jul 2022 21:59:58 -0600 Subject: [PATCH 06/10] Update index.lit Co-authored-by: Ryan Pendleton --- index.lit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.lit b/index.lit index 2d24022..691c7df 100644 --- a/index.lit +++ b/index.lit @@ -968,7 +968,7 @@ HANDLE hStdin = INVALID_HANDLE_VALUE; ### All platforms To properly handle input to the terminal, we need to adjust some buffering settings. -The implementation for these varies for each platform and should have been defined abo ve. +The implementation for these varies for each platform and should have been defined above. --- Setup signal(SIGINT, handle_interrupt); From 420195e8af632f3026ce07e80e94438e18c7965e Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Thu, 14 Jul 2022 22:21:30 -0600 Subject: [PATCH 07/10] clarify platform specific code better --- docs/index.html | 52 ++++++++++++++++++++-------------------- docs/src/lc3-alt-win.cpp | 7 +++--- docs/src/lc3-alt.cpp | 4 +--- docs/src/lc3-win.c | 7 +++--- docs/src/lc3.c | 4 +--- index.lit | 28 ++++++++++++---------- 6 files changed, 51 insertions(+), 51 deletions(-) diff --git a/docs/index.html b/docs/index.html index 78e5061..49baf75 100644 --- a/docs/index.html +++ b/docs/index.html @@ -337,7 +337,7 @@

Procedure

break; } } - @{Shutdown} + @{Shutdown} }

Used by 1 2

@@ -1087,7 +1087,8 @@

11. Platform specifics

This section contains some tedious details that are needed to access the keyboard and behave nicely. -These are not insightful or relevant to learning about VMs. Feel free to copy paste!

+These are not insightful or relevant to learning about VMs. Feel free to copy paste! +These functions should be declared somewhere above your main function.

Linux/macOS/UNIX

@@ -1142,12 +1143,10 @@

Linux/macOS/UNIX

#include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
-#include <string.h>
 #include <signal.h>
-/* unix */
+/* unix only */
 #include <unistd.h>
 #include <fcntl.h>
-
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/termios.h>
@@ -1158,9 +1157,6 @@ 

Linux/macOS/UNIX

Windows

-

This section contains some tedious details that are needed to access the keyboard and behave nicely. -These are not insightful or relevant to learning about VMs. Feel free to copy paste!

-

NOTE: If you already included the Unix versions don’t add these!

@@ -1206,9 +1202,10 @@

Windows

Windows Includes -
#include <stdint.h> // uint16_t
-#include <stdio.h>  // FILE
-#include <signal.h> // SIGINT
+
#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <signal.h>
 /* windows only */
 #include <Windows.h>
 #include <conio.h>  // _kbhit
@@ -1220,8 +1217,10 @@ 

Windows

All platforms

-

To properly handle input to the terminal we need to adjust some buffering settings. -The implementation for these varies for each platform and should have been defined abo ve.

+

To properly handle input to the terminal, we need to adjust some buffering settings. +The implementation for these varies for each platform and should have been defined above.

+ +

We include this setup code at the beginning of the program (start of main).

@@ -1232,11 +1231,22 @@

All platforms

Used by 1 2 3

-

When the program is interrupted, we want to restore the terminal settings back to normal.

+

When the program is interrupted, we want to restore the terminal settings back to normal. +This should be done at the end of the program.

-Handle Interrupt +Shutdown +
restore_input_buffering();
+
+

Used by 1 2 3

+ + +

Settings should also be restored if we receive a signal to end the program.

+ +
+ +Handle Interrupt
void handle_interrupt(int signal)
 {
     restore_input_buffering();
@@ -1247,16 +1257,6 @@ 

All platforms

Used by 1 2 3 4

- - -
- -Shutdown -
restore_input_buffering();
-
-

Used by 1 2 3

- -

All that we have written should have been added to the C file in the following order:

diff --git a/docs/src/lc3-alt-win.cpp b/docs/src/lc3-alt-win.cpp index b93b00d..6b12acb 100644 --- a/docs/src/lc3-alt-win.cpp +++ b/docs/src/lc3-alt-win.cpp @@ -1,6 +1,7 @@ -#include // uint16_t -#include // FILE -#include // SIGINT +#include +#include +#include +#include /* windows only */ #include #include // _kbhit diff --git a/docs/src/lc3-alt.cpp b/docs/src/lc3-alt.cpp index 6841d14..44626c9 100644 --- a/docs/src/lc3-alt.cpp +++ b/docs/src/lc3-alt.cpp @@ -1,12 +1,10 @@ #include #include #include -#include #include -/* unix */ +/* unix only */ #include #include - #include #include #include diff --git a/docs/src/lc3-win.c b/docs/src/lc3-win.c index 30e9c6b..7d10841 100644 --- a/docs/src/lc3-win.c +++ b/docs/src/lc3-win.c @@ -1,6 +1,7 @@ -#include // uint16_t -#include // FILE -#include // SIGINT +#include +#include +#include +#include /* windows only */ #include #include // _kbhit diff --git a/docs/src/lc3.c b/docs/src/lc3.c index b9e7f9c..93bac01 100644 --- a/docs/src/lc3.c +++ b/docs/src/lc3.c @@ -1,12 +1,10 @@ #include #include #include -#include #include -/* unix */ +/* unix only */ #include #include - #include #include #include diff --git a/index.lit b/index.lit index 691c7df..842e530 100644 --- a/index.lit +++ b/index.lit @@ -865,6 +865,7 @@ That completes the last component of the VM! Provided that you implemented the r This section contains some tedious details that are needed to access the keyboard and behave nicely. These are not insightful or relevant to learning about VMs. Feel free to copy paste! +These functions should be declared somewhere above your main function. ### Linux/macOS/UNIX @@ -907,12 +908,10 @@ void restore_input_buffering() #include #include #include -#include #include -/* unix */ +/* unix only */ #include #include - #include #include #include @@ -921,9 +920,6 @@ void restore_input_buffering() ### Windows -This section contains some tedious details that are needed to access the keyboard and behave nicely. -These are not insightful or relevant to learning about VMs. Feel free to copy paste! - NOTE: If you already included the **Unix** versions don't add these! --- Check Key Windows @@ -955,9 +951,10 @@ void restore_input_buffering() --- --- Windows Includes -#include // uint16_t -#include // FILE -#include // SIGINT +#include +#include +#include +#include /* windows only */ #include #include // _kbhit @@ -970,12 +967,21 @@ HANDLE hStdin = INVALID_HANDLE_VALUE; To properly handle input to the terminal, we need to adjust some buffering settings. The implementation for these varies for each platform and should have been defined above. +We include this setup code at the beginning of the program (start of main). + --- Setup signal(SIGINT, handle_interrupt); disable_input_buffering(); --- When the program is interrupted, we want to restore the terminal settings back to normal. +This should be done at the end of the program. + +--- Shutdown +restore_input_buffering(); +--- + +Settings should also be restored if we receive a signal to end the program. --- Handle Interrupt void handle_interrupt(int signal) @@ -986,10 +992,6 @@ void handle_interrupt(int signal) } --- ---- Shutdown -restore_input_buffering(); ---- - All that we have written should have been added to the C file in the following order: --- /lc3.c += From d3588172e9ba7c466117c1e3c134c8bfd00f1edf Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Thu, 14 Jul 2022 22:30:22 -0600 Subject: [PATCH 08/10] combine key block with others --- docs/index.html | 113 ++++++++++++++++----------------------- docs/src/lc3-alt-win.cpp | 16 +++--- docs/src/lc3-alt.cpp | 25 ++++----- docs/src/lc3-win.c | 16 +++--- docs/src/lc3.c | 25 ++++----- index.lit | 49 +++++++---------- 6 files changed, 108 insertions(+), 136 deletions(-) diff --git a/docs/index.html b/docs/index.html index 49baf75..f564e38 100644 --- a/docs/index.html +++ b/docs/index.html @@ -72,7 +72,7 @@

Memory

#define MEMORY_MAX (1 << 16)
 uint16_t memory[MEMORY_MAX];  /* 65536 locations */
 
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Registers

@@ -114,7 +114,7 @@

Registers

Register Storage
uint16_t reg[R_COUNT];
 
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Instruction set

@@ -179,7 +179,7 @@

Condition flags

/lc3.c -
@{Includes}
+
@{Includes}
 
 @{Registers}
 @{Opcodes}
@@ -269,7 +269,7 @@ 

Procedure

int main(int argc, const char* argv[])
 {
     @{Load Arguments}
-    @{Setup}
+    @{Setup}
 
     /* since exactly one condition flag should be set at any given time, set the Z flag */
     reg[R_COND] = FL_ZRO;
@@ -337,10 +337,10 @@ 

Procedure

break; } } - @{Shutdown} + @{Shutdown} }
-

Used by 1 2

+

Used by 1 2

While we are at the main loop let’s handle command line input to make our program usable. @@ -425,7 +425,7 @@

ADD

return x; }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Note: If you are interested in exactly how negative numbers can be represented in binary, you can read about Two’s Complement. However, this is not essential. You can just copy the code above and use it whenever the specification says to sign extend numbers.

@@ -455,7 +455,7 @@

ADD

} }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Now we are ready to write the code for the ADD case:

@@ -792,7 +792,7 @@

7. Trap routines

TRAP_HALT = 0x25 /* halt the program */ };
-

Used by 1 2 3 4

+

Used by 1 2 3 4

You may be wondering why the trap codes are not included in the instructions. @@ -841,7 +841,7 @@

7. Trap routines

break; }
-

Used by 1 2

+

Used by 1 2

As with instructions, I will show you how to implement a single trap routine and leave the rest to you.

@@ -993,7 +993,7 @@

9. Loading programs

} }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Notice that swap16 is called on each loaded value. LC-3 programs are big-endian, but most modern computers are little-endian. So, we need to swap each uint16 that is loaded. (If you happen to be using an obscure computer, like an old PPC Mac, then do not swap.)

@@ -1006,7 +1006,7 @@

9. Loading programs

return (x << 8) | (x >> 8); }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

Note: Endianness refers to how bytes of an integer are interpreted. In little-endian, the first byte is the least significant digit, and in big-endian, it is reversed. As far as I know, the decision is mostly arbitrary. Different companies made different decisions, so now we are left with varying implementations. You do not need to know anything else about endianness for this project.

@@ -1025,7 +1025,7 @@

9. Loading programs

return 1; } -

Used by 1 2 3 4

+

Used by 1 2 3 4

@@ -1048,7 +1048,7 @@

10. Memory mapped registers

MR_KBDR = 0xFE02 /* keyboard data */ }; -

Used by 1 2 3 4

+

Used by 1 2 3 4

Memory mapped registers make memory access a bit more complicated. We can’t read and write to the memory array directly, but must instead call setter and getter functions. When memory is read from KBSR, the getter will check the keyboard and update both memory locations.

@@ -1078,7 +1078,7 @@

10. Memory mapped registers

return memory[address]; } -

Used by 1 2 3 4

+

Used by 1 2 3 4

That completes the last component of the VM! Provided that you implemented the rest of the trap routines and instructions, you are almost ready to try it out!

@@ -1088,7 +1088,7 @@

11. Platform specifics

This section contains some tedious details that are needed to access the keyboard and behave nicely. These are not insightful or relevant to learning about VMs. Feel free to copy paste! -These functions should be declared somewhere above your main function.

+These functions should be declared above your main function.

Linux/macOS/UNIX

@@ -1096,27 +1096,7 @@

Linux/macOS/UNIX

-Check Key -
uint16_t check_key()
-{
-    fd_set readfds;
-    FD_ZERO(&readfds);
-    FD_SET(STDIN_FILENO, &readfds);
-
-    struct timeval timeout;
-    timeout.tv_sec = 0;
-    timeout.tv_usec = 0;
-    return select(1, &readfds, NULL, NULL, &timeout) != 0;
-}
-
-

Used by 1 2

- - -

This is Unix specific code for setting up terminal input.

- -
- -Input Buffering +Input Buffering
struct termios original_tio;
 
 void disable_input_buffering()
@@ -1131,20 +1111,32 @@ 

Linux/macOS/UNIX

{ tcsetattr(STDIN_FILENO, TCSANOW, &original_tio); } + +uint16_t check_key() +{ + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(STDIN_FILENO, &readfds); + + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + return select(1, &readfds, NULL, NULL, &timeout) != 0; +}
-

Used by 1 2

+

Used by 1 2

-Includes +Includes
#include <stdio.h>
-#include <stdlib.h>
 #include <stdint.h>
 #include <signal.h>
 /* unix only */
+#include <stdlib.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <sys/time.h>
@@ -1161,22 +1153,15 @@ 

Windows

-Check Key Windows -
uint16_t check_key()
+Input Buffering Windows
+
HANDLE hStdin = INVALID_HANDLE_VALUE;
+uint16_t check_key()
 {
     return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit();
 }
-
-

Used by 1 2

- - -
- -Input Buffering Windows -
DWORD fdwMode, fdwOldMode;
-
+DWORD fdwMode, fdwOldMode;
 void disable_input_buffering()
 {
     hStdin = GetStdHandle(STD_INPUT_HANDLE);
@@ -1201,16 +1186,13 @@ 

Windows

-Windows Includes +Windows Includes
#include <stdio.h>
-#include <stdlib.h>
 #include <stdint.h>
 #include <signal.h>
 /* windows only */
 #include <Windows.h>
 #include <conio.h>  // _kbhit
-
-HANDLE hStdin = INVALID_HANDLE_VALUE;
 

Used by 1 2

@@ -1224,7 +1206,7 @@

All platforms

-Setup +Setup
signal(SIGINT, handle_interrupt);
 disable_input_buffering();
 
@@ -1236,7 +1218,7 @@

All platforms

-Shutdown +Shutdown
restore_input_buffering();
 

Used by 1 2 3

@@ -1246,7 +1228,7 @@

All platforms

-Handle Interrupt +Handle Interrupt
void handle_interrupt(int signal)
 {
     restore_input_buffering();
@@ -1254,14 +1236,14 @@ 

All platforms

exit(-2); }
-

Used by 1 2 3 4

+

Used by 1 2 3 4

-

All that we have written should have been added to the C file in the following order:

+

Everything we have written so far should have been added to the C file in the following order:

-/lc3.c += +/lc3.c +=
@{Memory Mapped Registers}
 @{TRAP Codes}
 
@@ -1273,10 +1255,9 @@ 

All platforms

@{Update Flags} @{Read Image File} @{Read Image} -@{Check Key} @{Memory Access} -@{Input Buffering} -@{Handle Interrupt} +@{Input Buffering} +@{Handle Interrupt} @{Main Loop}
@@ -1337,7 +1318,7 @@

13. Alternate C++ technique

-Instruction C++ +Instruction C++
template <unsigned op>
 void ins(uint16_t instr)
 {
@@ -1436,7 +1417,7 @@ 

13. Alternate C++ technique

-Op Table +Op Table
static void (*op_table[16])(uint16_t) = {
     ins<0>, ins<1>, ins<2>, ins<3>,
     ins<4>, ins<5>, ins<6>, ins<7>,
diff --git a/docs/src/lc3-alt-win.cpp b/docs/src/lc3-alt-win.cpp
index 6b12acb..a4cab3c 100644
--- a/docs/src/lc3-alt-win.cpp
+++ b/docs/src/lc3-alt-win.cpp
@@ -1,13 +1,10 @@
 #include 
-#include 
 #include 
 #include 
 /* windows only */
 #include 
 #include   // _kbhit
 
-HANDLE hStdin = INVALID_HANDLE_VALUE;
-
 enum
 {
     R_R0 = 0,
@@ -120,10 +117,6 @@ int read_image(const char* image_path)
     fclose(file);
     return 1;
 }
-uint16_t check_key()
-{
-    return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit();
-}
 void mem_write(uint16_t address, uint16_t val)
 {
     memory[address] = val;
@@ -145,8 +138,15 @@ uint16_t mem_read(uint16_t address)
     }
     return memory[address];
 }
-DWORD fdwMode, fdwOldMode;
 
+HANDLE hStdin = INVALID_HANDLE_VALUE;
+uint16_t check_key()
+{
+    return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit();
+}
+
+
+DWORD fdwMode, fdwOldMode;
 void disable_input_buffering()
 {
     hStdin = GetStdHandle(STD_INPUT_HANDLE);
diff --git a/docs/src/lc3-alt.cpp b/docs/src/lc3-alt.cpp
index 44626c9..4ce000e 100644
--- a/docs/src/lc3-alt.cpp
+++ b/docs/src/lc3-alt.cpp
@@ -1,8 +1,8 @@
 #include 
-#include 
 #include 
 #include 
 /* unix only */
+#include 
 #include 
 #include 
 #include 
@@ -122,17 +122,6 @@ int read_image(const char* image_path)
     fclose(file);
     return 1;
 }
-uint16_t check_key()
-{
-    fd_set readfds;
-    FD_ZERO(&readfds);
-    FD_SET(STDIN_FILENO, &readfds);
-
-    struct timeval timeout;
-    timeout.tv_sec = 0;
-    timeout.tv_usec = 0;
-    return select(1, &readfds, NULL, NULL, &timeout) != 0;
-}
 void mem_write(uint16_t address, uint16_t val)
 {
     memory[address] = val;
@@ -168,6 +157,18 @@ void restore_input_buffering()
 {
     tcsetattr(STDIN_FILENO, TCSANOW, &original_tio);
 }
+
+uint16_t check_key()
+{
+    fd_set readfds;
+    FD_ZERO(&readfds);
+    FD_SET(STDIN_FILENO, &readfds);
+
+    struct timeval timeout;
+    timeout.tv_sec = 0;
+    timeout.tv_usec = 0;
+    return select(1, &readfds, NULL, NULL, &timeout) != 0;
+}
 void handle_interrupt(int signal)
 {
     restore_input_buffering();
diff --git a/docs/src/lc3-win.c b/docs/src/lc3-win.c
index 7d10841..cfdb3cf 100644
--- a/docs/src/lc3-win.c
+++ b/docs/src/lc3-win.c
@@ -1,13 +1,10 @@
 #include 
-#include 
 #include 
 #include 
 /* windows only */
 #include 
 #include   // _kbhit
 
-HANDLE hStdin = INVALID_HANDLE_VALUE;
-
 enum
 {
     R_R0 = 0,
@@ -120,10 +117,6 @@ int read_image(const char* image_path)
     fclose(file);
     return 1;
 }
-uint16_t check_key()
-{
-    return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit();
-}
 void mem_write(uint16_t address, uint16_t val)
 {
     memory[address] = val;
@@ -145,8 +138,15 @@ uint16_t mem_read(uint16_t address)
     }
     return memory[address];
 }
-DWORD fdwMode, fdwOldMode;
 
+HANDLE hStdin = INVALID_HANDLE_VALUE;
+uint16_t check_key()
+{
+    return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit();
+}
+
+
+DWORD fdwMode, fdwOldMode;
 void disable_input_buffering()
 {
     hStdin = GetStdHandle(STD_INPUT_HANDLE);
diff --git a/docs/src/lc3.c b/docs/src/lc3.c
index 93bac01..2a679b7 100644
--- a/docs/src/lc3.c
+++ b/docs/src/lc3.c
@@ -1,8 +1,8 @@
 #include 
-#include 
 #include 
 #include 
 /* unix only */
+#include 
 #include 
 #include 
 #include 
@@ -121,17 +121,6 @@ int read_image(const char* image_path)
     fclose(file);
     return 1;
 }
-uint16_t check_key()
-{
-    fd_set readfds;
-    FD_ZERO(&readfds);
-    FD_SET(STDIN_FILENO, &readfds);
-
-    struct timeval timeout;
-    timeout.tv_sec = 0;
-    timeout.tv_usec = 0;
-    return select(1, &readfds, NULL, NULL, &timeout) != 0;
-}
 void mem_write(uint16_t address, uint16_t val)
 {
     memory[address] = val;
@@ -167,6 +156,18 @@ void restore_input_buffering()
 {
     tcsetattr(STDIN_FILENO, TCSANOW, &original_tio);
 }
+
+uint16_t check_key()
+{
+    fd_set readfds;
+    FD_ZERO(&readfds);
+    FD_SET(STDIN_FILENO, &readfds);
+
+    struct timeval timeout;
+    timeout.tv_sec = 0;
+    timeout.tv_usec = 0;
+    return select(1, &readfds, NULL, NULL, &timeout) != 0;
+}
 void handle_interrupt(int signal)
 {
     restore_input_buffering();
diff --git a/index.lit b/index.lit
index 842e530..fe13f5b 100644
--- a/index.lit
+++ b/index.lit
@@ -865,28 +865,12 @@ That completes the last component of the VM! Provided that you implemented the r
 
 This section contains some tedious details that are needed to access the keyboard and behave nicely.
 These are not insightful or relevant to learning about VMs. Feel free to copy paste!
-These functions should be declared somewhere above your main function.
+These functions should be declared above your main function.
 
 ### Linux/macOS/UNIX
 
 NOTE: Skip to the next section for the **Windows** versions of these functions.
 
---- Check Key
-uint16_t check_key()
-{
-    fd_set readfds;
-    FD_ZERO(&readfds);
-    FD_SET(STDIN_FILENO, &readfds);
-
-    struct timeval timeout;
-    timeout.tv_sec = 0;
-    timeout.tv_usec = 0;
-    return select(1, &readfds, NULL, NULL, &timeout) != 0;
-}
----
-
-This is Unix specific code for setting up terminal input.
-
 --- Input Buffering
 struct termios original_tio;
 
@@ -902,14 +886,26 @@ void restore_input_buffering()
 {
     tcsetattr(STDIN_FILENO, TCSANOW, &original_tio);
 }
+
+uint16_t check_key()
+{
+    fd_set readfds;
+    FD_ZERO(&readfds);
+    FD_SET(STDIN_FILENO, &readfds);
+
+    struct timeval timeout;
+    timeout.tv_sec = 0;
+    timeout.tv_usec = 0;
+    return select(1, &readfds, NULL, NULL, &timeout) != 0;
+}
 ---
 
 --- Includes
 #include 
-#include 
 #include 
 #include 
 /* unix only */
+#include 
 #include 
 #include 
 #include 
@@ -922,16 +918,16 @@ void restore_input_buffering()
 
 NOTE: If you already included the **Unix** versions don't add these!
 
---- Check Key Windows
+--- Input Buffering Windows
+
+HANDLE hStdin = INVALID_HANDLE_VALUE;
 uint16_t check_key()
 {
     return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit();
 }
----
 
---- Input Buffering Windows
-DWORD fdwMode, fdwOldMode;
 
+DWORD fdwMode, fdwOldMode;
 void disable_input_buffering()
 {
     hStdin = GetStdHandle(STD_INPUT_HANDLE);
@@ -952,14 +948,11 @@ void restore_input_buffering()
 
 --- Windows Includes
 #include 
-#include 
 #include 
 #include 
 /* windows only */
 #include 
 #include   // _kbhit
-
-HANDLE hStdin = INVALID_HANDLE_VALUE;
 ---
 
 ### All platforms
@@ -992,7 +985,7 @@ void handle_interrupt(int signal)
 }
 ---
 
-All that we have written should have been added to the C file in the following order:
+Everything we have written so far should have been added to the C file in the following order:
 
 --- /lc3.c +=
 @{Memory Mapped Registers}
@@ -1006,7 +999,6 @@ All that we have written should have been added to the C file in the following o
 @{Update Flags}
 @{Read Image File}
 @{Read Image}
-@{Check Key}
 @{Memory Access}
 @{Input Buffering}
 @{Handle Interrupt}
@@ -1033,7 +1025,6 @@ All that we have written should have been added to the C file in the following o
 @{Update Flags}
 @{Read Image File}
 @{Read Image}
-@{Check Key Windows}
 @{Memory Access}
 @{Input Buffering Windows}
 @{Handle Interrupt}
@@ -1211,7 +1202,6 @@ The full source is here: [unix](src/lc3-alt.cpp), [windows](src/lc3-alt-win.cpp)
 @{Update Flags}
 @{Read Image File}
 @{Read Image}
-@{Check Key}
 @{Memory Access}
 @{Input Buffering}
 @{Handle Interrupt}
@@ -1258,7 +1248,6 @@ int main(int argc, const char* argv[])
 @{Update Flags}
 @{Read Image File}
 @{Read Image}
-@{Check Key Windows}
 @{Memory Access}
 @{Input Buffering Windows}
 @{Handle Interrupt}

From 0f73ab040f914efff52f3675892d20db3193a4b7 Mon Sep 17 00:00:00 2001
From: Justin Meiners 
Date: Thu, 14 Jul 2022 22:33:18 -0600
Subject: [PATCH 09/10] minor rearrangement

---
 docs/index.html          | 10 +++++-----
 docs/src/lc3-alt-win.cpp | 11 +++++------
 docs/src/lc3-win.c       | 11 +++++------
 index.lit                | 11 +++++------
 4 files changed, 20 insertions(+), 23 deletions(-)

diff --git a/docs/index.html b/docs/index.html
index f564e38..5f79639 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1155,11 +1155,6 @@ 

Windows

Input Buffering Windows
HANDLE hStdin = INVALID_HANDLE_VALUE;
-uint16_t check_key()
-{
-    return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit();
-}
-
 
 DWORD fdwMode, fdwOldMode;
 void disable_input_buffering()
@@ -1178,6 +1173,11 @@ 

Windows

{ SetConsoleMode(hStdin, fdwOldMode); } + +uint16_t check_key() +{ + return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit(); +}

Used by 1 2

diff --git a/docs/src/lc3-alt-win.cpp b/docs/src/lc3-alt-win.cpp index a4cab3c..4d599b9 100644 --- a/docs/src/lc3-alt-win.cpp +++ b/docs/src/lc3-alt-win.cpp @@ -138,13 +138,7 @@ uint16_t mem_read(uint16_t address) } return memory[address]; } - HANDLE hStdin = INVALID_HANDLE_VALUE; -uint16_t check_key() -{ - return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit(); -} - DWORD fdwMode, fdwOldMode; void disable_input_buffering() @@ -163,6 +157,11 @@ void restore_input_buffering() { SetConsoleMode(hStdin, fdwOldMode); } + +uint16_t check_key() +{ + return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit(); +} void handle_interrupt(int signal) { restore_input_buffering(); diff --git a/docs/src/lc3-win.c b/docs/src/lc3-win.c index cfdb3cf..cb93a4c 100644 --- a/docs/src/lc3-win.c +++ b/docs/src/lc3-win.c @@ -138,13 +138,7 @@ uint16_t mem_read(uint16_t address) } return memory[address]; } - HANDLE hStdin = INVALID_HANDLE_VALUE; -uint16_t check_key() -{ - return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit(); -} - DWORD fdwMode, fdwOldMode; void disable_input_buffering() @@ -163,6 +157,11 @@ void restore_input_buffering() { SetConsoleMode(hStdin, fdwOldMode); } + +uint16_t check_key() +{ + return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit(); +} void handle_interrupt(int signal) { restore_input_buffering(); diff --git a/index.lit b/index.lit index fe13f5b..c9168de 100644 --- a/index.lit +++ b/index.lit @@ -919,13 +919,7 @@ uint16_t check_key() NOTE: If you already included the **Unix** versions don't add these! --- Input Buffering Windows - HANDLE hStdin = INVALID_HANDLE_VALUE; -uint16_t check_key() -{ - return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit(); -} - DWORD fdwMode, fdwOldMode; void disable_input_buffering() @@ -944,6 +938,11 @@ void restore_input_buffering() { SetConsoleMode(hStdin, fdwOldMode); } + +uint16_t check_key() +{ + return WaitForSingleObject(hStdin, 1000) == WAIT_OBJECT_0 && _kbhit(); +} --- --- Windows Includes From a19407d29807bb983850be84a78bf21f437ba787 Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Thu, 14 Jul 2022 22:38:07 -0600 Subject: [PATCH 10/10] fix space --- docs/index.html | 2 +- docs/src/lc3-alt-win.cpp | 2 +- docs/src/lc3-win.c | 2 +- index.lit | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/index.html b/docs/index.html index 5f79639..58fbbdf 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1155,8 +1155,8 @@

Windows

Input Buffering Windows
HANDLE hStdin = INVALID_HANDLE_VALUE;
-
 DWORD fdwMode, fdwOldMode;
+
 void disable_input_buffering()
 {
     hStdin = GetStdHandle(STD_INPUT_HANDLE);
diff --git a/docs/src/lc3-alt-win.cpp b/docs/src/lc3-alt-win.cpp
index 4d599b9..026be93 100644
--- a/docs/src/lc3-alt-win.cpp
+++ b/docs/src/lc3-alt-win.cpp
@@ -139,8 +139,8 @@ uint16_t mem_read(uint16_t address)
     return memory[address];
 }
 HANDLE hStdin = INVALID_HANDLE_VALUE;
-
 DWORD fdwMode, fdwOldMode;
+
 void disable_input_buffering()
 {
     hStdin = GetStdHandle(STD_INPUT_HANDLE);
diff --git a/docs/src/lc3-win.c b/docs/src/lc3-win.c
index cb93a4c..4e76bea 100644
--- a/docs/src/lc3-win.c
+++ b/docs/src/lc3-win.c
@@ -139,8 +139,8 @@ uint16_t mem_read(uint16_t address)
     return memory[address];
 }
 HANDLE hStdin = INVALID_HANDLE_VALUE;
-
 DWORD fdwMode, fdwOldMode;
+
 void disable_input_buffering()
 {
     hStdin = GetStdHandle(STD_INPUT_HANDLE);
diff --git a/index.lit b/index.lit
index c9168de..a90756c 100644
--- a/index.lit
+++ b/index.lit
@@ -920,8 +920,8 @@ NOTE: If you already included the **Unix** versions don't add these!
 
 --- Input Buffering Windows
 HANDLE hStdin = INVALID_HANDLE_VALUE;
-
 DWORD fdwMode, fdwOldMode;
+
 void disable_input_buffering()
 {
     hStdin = GetStdHandle(STD_INPUT_HANDLE);