The 32-bit MIPS is the simplest "real" 32-bit processor for which development tools are readily available; furthermore, the MIPS architecture is already widely used for teaching in various contexts. This makes it a natural choice for System/161.
The specific dialect of MIPS processor found in System/161, which for a lack of a better term we'll refer to as MIPS-161, is essentially a cache-coherent r3000, or MIPS-I. It has a few features borrowed from later models, and a couple of its own.
There are two basic versions of the MIPS-161: the System/161 1.x version and the System/161 2.x version. Distinguishing these on the fly is discussed below.
In user mode, the MIPS-161 behaves the same as any other 32-bit MIPS. All user instructions are fully interlocked and there are no pipeline hazards. All MIPS-I instructions are supported. MIPS-II and higher instructions are not supported, except (in System/161 2.0 and up) for the LL and SC instructions used for multiprocessor synchronization, and the SYNC memory barrier instruction. Please consult your favorite MIPS reference for further details.
In kernel mode, the MIPS-161 is mostly a MIPS-I, with a few differences and a few extensions. For completeness, the following sections define the complete kernel mode interface.
The WAIT instruction has been borrowed from MIPS-II. This operation puts the processor into a low-power state and suspends execution until some external event occurs, such as an interrupt. Since the exact behavior of WAIT is not clearly specified anywhere I could find, the MIPS-161 behavior is as follows:
The TLBR, TLBWR, TLBWI, and TLBP instructions are described in the MMU section below. The RFE instruction is described in the trap handling section, also below. The MFC0 and MTC0 instructions for accessing supervisor mode registers are described in the next two sections.
The System/161 2.x MIPS-161 implements the MIPS-32 "select" feature for coprocessor registers: each coprocessor has up to 8 banks of 32 registers each. The "select" number identifies the register bank. Most registers of most coprocessors are in select 0. The System/161 1.x processor ignores the "select" field, so all banks will appear to mirror select 0.
Coprocessor registers are accessed with the MFC0..3 and MTC0..3 family of instructions, to access coprocessors 0 through 3, as follows:
mfc0 $4, $12, 0loads the contents of coprocessor 0 (supervisor) register 12 select 0 (STATUS) into general-purpose register 4 (a0), and
mtc0 $4, $12, 0does the reverse. If the select number is 0 (the common case) it may be omitted.
The MIPS-161 has 11 supervisor registers in coprocessor 0. These are:
When an exception occurs, the following things happen:
The exception handler addresses are:
To return from an exception, one executes the following sequence:
jr k0 rfewhere the k0 register has been loaded with the desired exception return address, either the value previously retrieved from the EPC register or some other address chosen by software. The RFE instruction is not a jump; it occurs in the delay slot of a jump. It shifts the six bottom bits of the status register right by two, undoing the shift done at exception entry time. This returns the processor to whatever interrupt state and mode (user/kernel) it was in when the exception occurred.
Because there are three pairs of state bits, the processor can take two nested exceptions without losing state, if one is careful. This is to facilitate the fast-path TLB refill handler. See below.
The two soft interrupt lines can be activated by writing to the CAUSE register.
The exception codes:
The MMU is the MIPS-I MMU, with a 64-entry fully associative TLB where each entry maps one 4K virtual page to one 4K physical page. The paired pages setup of later MIPS processors is not present, and there is no support for superpages.
The processor's virtual address space is divided into four segments:
There are four MMU-related instructions:
The processor is built to support a fast-path TLB refill handler, which is invoked via the UTLB exception vector (see above). The idea is that the OS maintains page tables in virtual memory using the kseg2 region (see above) and loads the base address of the page table into the PTBASE field of the CONTEXT register. Each page table entry is a 4-byte quantity suitable for loading directly into the TLBLO register; 1024 of these fit on a 4K page, so each page table page maps 4MB and it takes 512 pages, or 2MB of virtual space, to map the whole 2GB user address space. (Since these are placed in virtual memory, only the page table pages that are used need be materialized.) With this setup, the UTLB exception handler can then read the CONTEXT register and use the resulting value to load directly from the page table. If this fails because that section of the page table is not materialized, a second (non-UTLB) exception occurs. Careful register usage and the three-deep nesting of the bottom part of the STATUS register allows the general-purpose exception handler to recover from this condition and proceed as desired. On success, the UTLB handler can then unconditionally write the PTE it got into the TLB. If the V (valid) bit is not set, on return from the UTLB handler another exception will occur; however, because a matching (though not valid) TLB entry exists, this will not be a UTLB exception, and the general exception handler will get control and can schedule pagein or whatever.
There are a number of possible other ways to use the UTLB handler, of course. One simple way is to just have it jump to the general-purpose exception handler.
As noted above, the V (valid) bit does not prevent a TLB entry from being "matching". A TLB entry is matching if both of the following are true:
One must never load the TLB in such a fashion that two (or more) entries can match the same virtual address. If this happens, the processor shuts down and is unrecoverable except by hard reset. Since there is no way to prevent entries from matching, one should clear the TLB by loading each entry with a distinct VPAGE, and use VPAGEs from the kseg0 or kseg1 regions that will never be presented to the MMU for translation. To reset the TLB at startup, since it is not cleared by processor reset, one should use a second, potentially larger, set of distinct VPAGEs and check that each is not already present before loading it.
There is no way to tell if a TLB entry has been used, or how recently it has been used. Nor is there a direct way to tell if a TLB entry has been used for writing. The D ("dirty") bit can be used for this purpose with software support, as follows:
The MMU exceptions are as follows:
The MIPS-I has a remarkably painful cache and cache control architecture. While the MIPS-161 exhibits the same cache control bits in the STATUS register, it is in fact cache-coherent and there is no need to flush, examine, or otherwise touch the cache subsystem. In fact, doing any of these things in the MIPS-I fashion will result in undefined behavior.
Since normal MIPS processors have split instruction and data caches, and future System/161 releases may include more cache handling, it is recommended that all necessary flushes of the instruction cache be included and stubbed out.
Even with cache coherence, or when using uncached memory regions, processors that support out-of-execution may require so-called memory barrier instructions to ensure that memory accesses occur on the external bus (and become visible to other processors or devices) in the same order they are issued in machine code.
In the System/161 2.x processor, the MIPS-II SYNC instruction can be used to wait until all pending load and store operations are complete. This guarantees that all memory updates before the SYNC become visible before any memory updates after the SYNC. All SYNC instructions executed on all cores and processors within a system occur in a single well defined global order.
Each MIPS-161 processor has only one core on the die. However, as noted elsewhere System/161 supports up to 32 processors on the mainboard. There is little software-visible difference between 32 single-core processors on one mainboard and 32 cores in one processor; most of what effects exist are cache-related and not modeled by System/161 in any event.
On CPU reset execution begins from the Reset vector defined above. The processor starts out in an almost completely undefined state. The cache is in an undefined state (except on the MIPS-161 this does not matter...), the TLB is in an undefined state, and the contents of the general and kernel-mode registers are all undefined, except as follows:
In System/161, the boot ROM takes care of these issues and loads a kernel as described in the LAMEbus documentation. However, the state guaranteed by the boot ROM is only slightly more flexible: the boot ROM guarantees that the cache is in a workable state, and it provides a stack and an argument string in the a0 register. The TLB is still in an undefined state and the contents of other general and kernel-mode registers and register fields are still undefined.
Distinguishing the MIPS-161 from an arbitrarily chosen MIPS-I is likely to be problematic: according to the lore, the PRID register values for MIPS-I processors could not reliably be used to distinguish processor types even in real deployed hardware. The recommended method is to arrange to know that you have a MIPS-161 because you are running on System/161; then the scheme below can be used to identify which version you have. If this is not feasible, you're on your own. It might be possible to detect the MIPS-161 by enabling certain cache control bits and ascertaining that they have no effect.
The System/161 1.x processor can be distinguished from the System/161 2.x processor by fetching the value from the PRID register. The top half of the PRID register is 0 (because the MIPS-161 is not a MIPS-32 or MIPS-64) and the bottom half gives the version:
The processors in pre-2.0 versions of System/161 prior to 1.99.07 also report 0x3ff in the PRID register. These nonetheless support LL/SC and the on-chip timer (not documented herein yet, alas) but do not support SYNC, coprocessor register selects, or the CFEAT/IFEAT registers. The best way to detect this case is to check the LAMEbus mainboard version; if the multiprocessor mainboard is present, the processor supports LL/SC and the on-chip timer. (If the uniprocessor mainboard is present, this support might still be present, but can only be detected by direct probing, which is not recommended.) Other than this, no guarantees are made about mainboard versions and features vs. CPU versions and features. Mainboard and CPU properties can be selected independently in sys161.conf.
Some ancient prerelease versions of System/161 (0.95 and earlier) reported 0xbeef in the PRID register. These versions are now long gone and can be ignored. The documentation also for a long time described the PRID value for the System/161 1.x processor as 0xbeef; this was incorrect.
CPU features beyond those found by default in the System/161 2.x processor should be probed by checking the CFEAT and IFEAT registers, after confirming that a System/161 2.x processor is in fact present. See the descriptions of these feature bits above. (So far, there are none.)