This page focuses primarily character IO. Once you understand it, you'll should have no problem understanding disk IO from the description of the block controller component in the SMOK documentation.
Connect the container to your machine so that it handles loads/stores to addresses 0x40000000-0x4000003F. Also make sure that you do NOT write to memory if the address is in that range. This will require adding just a few new components to the datapath. There are no changes required to control -- the new container has an output that tells you whether or not it thinks it's handling the address.
iolib.c
. You can complete this part of the assignment just
by implementing memory mapped devices, without needing to know how they work -
the sample code knows.
Looking at that code MAY help clarify things that aren't made clear by this
description.
There are two parts to I/O controllers, the I and the O. Both sides are a little different than what we're accustomed to. On the input side, there is no instruction that can cause a character to be typed - the user types one whenever s/he feels like. So, input is asynchronous with instruction execution; it happens when it happens. On the output side, output is slow, requiring many cycles to complete. Because of this, output as viewed by the software is demarcated by two events. The first is the start of an output operation. That is caused by the software telling the controller to perform an output, using a controller-specific command. The second event is output completion. This is signalled by the controller raising one of the bits of its output to indicate that it is done. While an output operation is going on, it is a software error to tell the controller to start another one (meaning the result is undefined).
To accomplish these tasks, the controller has a command set (more or less a set of instructions that it knows how to execute) that it accepts as its input. When the software wants the controller to start an output, it sends it a command to the character controller (by writing some data to it) indicating what it wants (e.g., "write a character" and the character).
The controller also has an output. The output indicates the current state of the controller, e.g., "ready to accept a new output command" and/or "a keyboard input character is available" (or neither).
Given this, it is the software's responsibility to use the controller correctly. (Note that the software using it correctly is the responsibility of the software implementer, not the ISA implementer. I'm explaining it here just to give you some context about how what you're implementing fits into the larger picture.) For output, the software must do this:
When the software wants to read a character, it must do this:
The details of how this work depend on the controller. We'll be using SMOK's Character Controller Device. Its documentation tell you what the controller's command set is, and what the controllers output bits mean.
We could create new opcodes for this: RFCC
(Read From Character
Controller) and WTCC
(Write To Character Controller). That
would work, but the downside is that we'd need a different pair of opcodes
for each controller attached to our system. Besides being cumbersome, that
would make it impossible to attach controllers that were designed after the
processor was built (since the processor wouldn't have the needed opcodes).
Instead of using new opcodes, we note that sending commands to the
controller is basically a store
operation: "Take the
contents of this register and send it to..." Similarly, reading
the controller's output is basically a load
: "Get ... and
put it in this register."
So, instead of special opcodes, we'll just use sw
and
lw
.
How can that work? How does the machine know we want to talk with
the character controller, rather than memory, on a sw
or
lw
? The answer is that we use a special address to indicate
that we really mean the controller, not memory. In particular, Cebollita uses
the address 0x40000000. A sw
to that address should take the
register contents and store it to the character controller (and not anywhere
in memory), and a lw
from that address puts the character
controller's output into a register.
To get this to work is relatively simple: you just need to add enough control
to notice that the instruction is a lw
or sw
and
that the address is 0x40000000. Under those conditions, you route the data
to/from the character controller, and (even if it's a sw
)
you do NOT cause the memory interface to do a write to ram.
(Note: The Cebollita ISA defines the meaning of loading or storing to just some of the addresses in the range 0x400000000-0x40000003F. Loads or stores to other addresses have undefined result.)
chartest.c
.
It calls printString()
and readString()
to
read/write from/to the terminal.
Compile and assemble it as always. After that, you'll need to link it with
iolib.o
and prologue-noos.o
.
Make sure to put the prologue first in the command
that links, otherwise the initial value of the PC in your SMOK model
(which is almost certainly 0) will not be pointing at the first instruction
that should be executed.
To test the disk controller, you can use
blocktest.c
. It needs to
be linked with iolib.o as well.
Additionally, to run it you need to connect a "virtual disk"
to the block controller in the memory map container
(right-click / Properties).
That's simply a file
that the block controller will read/write. Note that the file will be
altered by writes, so don't use anything valuable. (I copy the file
blocktest_diskfile_master.smokdisk to blocktest_diskfile.smokdisk, and
connect the latter to the block controller, but you can use any file
you want (that is at least something like 3KB in size).)
cebsim --sync ceb-final.smok mytestprogram.exe
Should you want to run Cebollita alone on the apps you build for
some reason, it requires a little
extra work. Because SMOK apps assume the hardware is doing IO, with no
interference by the OS, while Cebollita runs on top of an actual OS,
there is a bit of a mismatch. The shell script cebrun.sh
can be used to invoke Cebollita, taking the required steps to get the OS
out of the way:
$ ./cebrun.sh a.exe
If you're using CYGWIN, for that to work you must have environment variable CYGWIN set to "tty" before the Cygwin shell is launched. Did I mention BEFORE. You can't do it in your .bash_profile, for instance, you have to do it the raw Windows way.