When you're done, you'll have a processor and a mini-OS for it that is capable of running Cebollita programs.
So, don't despair, it's a lot easier than it probably will look.
Name | Address | Use |
Character Controller | 0x40000000 | When written, the data is fed into the character controller. When read, the result is the current output of character controller. |
Block Ctrl | 0x40000010 | When written, writes the block controller Ctrl input. When read, reads the controller's current output (Status port). |
Block Memory Address | 0x40000014 | Write-only. Writes the block controller's memory address input. |
Block Disk Address | 0x40000018 | Write-only. Writes the block controller's disk address input. |
Block Count | 0x4000001c | Write-only. Writes the block controller's block count input. |
Name | Address | Use |
Handler Address | 0x40000030 | Holds the address that should be loaded into the PC when an interrupt, exception, or trap occurs. |
Status Register | 0x40000034 | Bit 0 is the privilege bit. When on,
the machine operates in privileged mode. (When off, it does not.)
Bit 1 is the interrupt enable bit. When 1, interrupts (and exceptions, traps, or whatever other name you might use) can occur. When 0, they are not raised. |
EPC | 0x40000038 | PC of the instruction executing when the trap occurred. |
Cause | 0x4000003c | Read-only. An integer indicating the kind of trap that occurred. |
(Note that "read-only" refers to what can be done by software. The hardware can of course do most anything.)
The length register is used to constrain the portion of memory an application can touch. If the address it issues (before adding base) is greater than or equal to length, it is a memory addressing error. In this case, a fault is raised.
When the machine is operating in privileged mode, the base and length registers are ignored. (This allows the operating system full access to memory.)
Note that because all memory mapped resources are at addresses that will be larger than any application's base+length register settings, no application will be able to manipulate those resources itself. This is an aspect of enforcing security.
Name | Address | Use |
Memory Base Register | 0x40000020 | Offset to add to all memory addresses when machine is running in unprivileged mode. |
Memory Length Register | 0x40000024 | Largest legal address that can be requested by the program when the machine is running in unprivileged mode. |
Memory Size | 0x40000028 | Read-only. The size of physical memory, in bytes. |
syscall
, that forces a trap.
When the trap occurs, the hardware needs to do all of the following in the current
cycle:
rfe
, that returns from an interrupt
syscall
and rfe
do the desired things by writing tiny test programs and observing that the machine
jumps to the correct address and that the Status register is updated correctly.
(The formats of the new instructions are in the text on page A-78.)
The SMOK container provides a lot of support for these functions -- you should not have to understand the internals of that component to get this working. More information is provided at the end of this document.
Exception Cause ID | Meaning |
1 | Char device needs attention |
2 | Block device needs attention |
4 | Addressing violation |
8 | System call |
16 | Integer overflow |
Having done Part A, these should be relatively easy.
Build an OS image, including some test applications, and run them. The OS will kill any process that generates an unhandled exception. It should run processes that do not generate exceptions to completion.
Because we're getting perilously close to what a real machine and system is at this point, you do not simply use the SMOK memory component to magically read the OS disk image into itself. Instead, you use SMOK's boot capability, which requires you to attach a virtual disk (i.e., the OS image file you generated) to the Block Controller device inside the memory map container. Having done that, you can use the power on toolbar button to boot your machine. (See the documentation on SMOK's Block Controller for more information.)
When you boot your machine, the SMOK Block Controller will load the boot loader (whose source is bootloader.s) into memory at address 0. When that is done, your machine starts executing (so it should have its PC initialized to 0). The boot loader loads the rest of the OS into memory and then transfers control to it.
Once the OS has initialized itself it starts the first process, a shell. The shell is just a regular old user application. The OS transfers control to it, and it starts running.
This is a very primitive system -- it has no file system at all. (It has a disk, but it doesn't have any notion of files, just raw disk space.) All the shell can do for you is start other programs.
The shell sits in a loop. At the top of the loop it prints a prompt asking you for a disk block number. To identify the program you want to run, you have to give the disk block number at which it is located. The block number corresponding to each program on your disk is printed out when you create the disk image, so those are the numbers you need to supply.
So, what are you doing in this part? You're just making sure you can create a disk image and that the OS will run on the machine you've built. That is, you're just using tools and software that is provided, and verifying that your hardware implementation works. There is no need for you to implement any new hardware or write any software for this part.
In the hardware you should connect up the Character Controller in the memory mapping container to your exception control logic, so that an interrupt is raised when a new keyboard character becomes ready.
In the software, you should implement a new function for this handle this interrupt. The interrupt routine should read the character from the Character Controller and store it in some buffer it owns. It should also echo the character back to the console, so that the user sees s/he has typed something. Then the interrupt routine returns to the running program. (If the buffer overlows, well, too bad, now input data has been lost.)
You also need to modify the function that services the readString
system call.
It must fetch characters out of the buffer your interrupt handler has been filling.
If it manages to find an entire string in there, it's done -- it returns the result to the application.
If not, it has to go into a busy loop waiting for more user input.
The container is the hardware analogue of a subroutine in software. It has a relatively simple interface -- what it wants for input, and what it provides as output. All you need to do is attach to that interface correctly. The interface is described in a separate document. (Note that you do have to make a simple connection internally to the container - you have to connect your Memory component to the Mem input of the Block Controller. There is no way to avoid this, sorry.)
$ java util.Disasm > 34084000 ori $t0, $0, 16384 > 8d090000 lw $t1, $t0, 0 > [killed with ^C] $ java util.Disasm 34084000 ori $t0, $0, 16384
perl convertTrace converted.txt