Step one is to add disk and character (terminal) IO to your machine. This is done by introducing controllers that know how to interface to the actual hardware (the disk or the terminal). (The starter model we provide has these components, as well as additional logic required to make interfacing them to the main datapath easy.) Each controller defines a "protocol" - what inputs it needs to be given, and in what order, to do something. More details are given in a later part of this document, and even more in the SMOK documentation for the two devices, but the basic idea is that one of their input ports is used to give them commands, e.g., "write a character" or "read a block from the disk into memory."
Of course, it's not enough just to put the IO controllers into the hardware, you have to give the assembly language programmer a way to use them (that is, the ISA has to define some way to do IO). One could imagine adding instructions for this (e.g, "OutputACharacter $t0"), but we'll do something even simpler - we won't add any instructions at all, but instead use the existing load and store instructions as a way to do IO. How? We'll take over a piece of the address space, and any loads or stores with addresses in that piece will mean "read/write a controller, not RAM." The piece we'll use is everything in the range 0x40000000 to 0x4000003F. As you'll see, a store to location 0x40000000 is really writing to the character controller, and a load is reading from it. The assembly language programmer can therefore read and write characters by loading and storing from that location. The hardware can figure out it should route data to/from the character controller, and not memory, because the address starts with the 26-bits 01000000000000000000000000.
At the end of this step, a program running on your machine can do character and disk IO.
Step two is to implement (user) process address mapping. There are three new registers: the "memory base," the "memory length," and the "status" registers. The last of these contains a bit that if 0 means "when a memory address is generated (either for an instruction fetch or for the execution of a load/store instruction), add the memory base register to it before actually using it to reference memory." Additionally, check that the address before adding the memory base register is less than the value in the memory length register. If not, that's a programmer error. (There's not much the hardware can do in response to this error at this point, but there will be in the next step.) On the other hand, if that bit in the status register is 1, it means not to do this -- ignore the memory base and length registers and to do what you've been doing so far, just compute the address and give it to the memory interface or instruction fetch unit. Strangely enough, this bit is called the "privilege bit."
How does a programmer manipulate the memory base, memory length, and status registers? They're memory mapped as well (and so are read/written with load/store instructions.) Why would you want to use those registers? Well, until there's an operating system (and a loader) you really wouldn't. But, you can still implement and test it now, before moving to the next step. (The OS uses it so that all programs can be linked as though they were going to be loaded at address 0, but then actually be loaded at whatever address is the beginning of a sufficiently big block of currently unused ram.)
At the end of this step, a single .exe can be loaded anywhere in memory (i.e., not just location 0) and will run correctly without modification, so long as you set the memory base, length, and status registers correctly when you load it.
Step three is to implement exceptions and exception handling.
There are still more new registers to help with this.
(Yes, they're memory mapped.) They're described in a different section
of the main assignment page.
The basic idea, though, is that "when something unusual
happens" the hardware can decide to set the PC to a special address -
not PC+4, and not the instruction named by a branch or a jump. There is
a bit of operating system code (called the trap handler) at that address,
and it is prepared to deal with whatever special circumstance has arisen.
For the trap handler to do its job, quite a bit of new data path and control
is needed. Additionally, there are new instructions required
by this part: syscall
and rfe
.
At the end of this step, a program running on your machine can use the
syscall
instruction to cause transfer of control to a
trap handler, and the trap handler can rfe
to go back.
Step four (of this three step assignment) is to actually run the OS and see that it works. If the first three steps were done perfectly, it will. More likely, trying to run the OS will point out some miscommunication about what was needed in an earlier step, but that should be easy to correct.