Identity-modifying instructions (privileged)
| Opcode | P/U | Category | Description |
NPCALL |
priv | identity modifying | call nonprivileged subroutine |
NPRIV |
priv | identity modifying | nonprivileged user |
PCALL |
priv | identity modifying | call privileged subroutine |
PEEK |
priv | identity modifying | peek |
POKE |
priv | identity modifying | poke |
PRIV |
priv | identity modifying | privileged user (superuser) |
SETUP |
priv | identity modifying | setup |
USER |
priv | identity modifying | user |
As mentioned earlier, the Dauug|36 concept of privilege is simply the presence of a privileged instruction to execute. A related concept is identity, or which running program an instruction affects. A Dauug|36 identity, loosely called a user because it numerically identifies an actual user (executing program), is an eight-bit differentiator applied to the address bits of:
- both copies of the register file
- return address stack
- page table
In the netlist, the user is specified in 8-bit register named ff u, and 1-bit flip-flops reguserflop and ptsuserflop can mask its content using AND gates (make the user appear to be user 0, which we call the superuser) on the way to the three destinations. Three electrical situations can result:
ptsuserflop |
reguserflop |
Outcome |
| 0 | 0 | superuser’s registers, page table, & stack |
| 0 | 1 | (electrically impossible to reach this state) |
| 1 | 0 | superuser’s registers, but user’s page table & stack |
| 1 | 1 | user’s registers, page table, & stack |
Of the eight identity-modifying instructions, half do nothing but change the values of these ten bits. These instructions are NPRIV, PRIV, SETUP, and USER. The other four instructions handle situations where the user and superuser swap roles during register transfers or subroutine calls. These instructions are NPCALL, PCALL, PEEK, and POKE.
The following table describes the state of the two single-bit flip-flops after each instruction described on this page:
| Instruction | ptsuserflop |
reguserflop |
Comment |
NPCALL |
1 | 1 | NPRIV mode |
NPRIV |
1 | 1 | NPRIV mode |
PCALL |
0 | 0 | PRIV mode |
PEEK |
0 | 0 | PRIV mode |
POKE |
1 | 1 | NPRIV mode |
PRIV |
0 | 0 | PRIV mode |
SETUP |
1 | 0 | SETUP mode |
USER |
unchanged | unchanged | mode unchanged |
NPCALL Call nonprivileged subroutine
| Syntax |
npcall subr |
| No registers used |
| No flags changed |
NPCALL is a variant of CALL where after the return address and flags are pushed on the stack, ptsuserflop and reguserflop are both set to 1, causing further code to run in user mode as opposed to superuser mode.
NPCALL is used to move the CPU’s attention from the operating system to a user program. Here is how that works:
1. Because NPCALL is being executed by the operating system, the user program is already frozen, and its flags and next instruction address are ready at the top of its stack.
2. Even though the purpose of NPCALL is to switch to a user program, the code at subr is actually part of the operating system instead. It’s just one instruction: REVERT.
3. NPCALL does three things, the first of which is save the return address (of the instruction immediately after NPCALL) on the superuser’s stack.
4. Second, NPCALL changes the instruction pointer to point to subr, which should be the location of a REVERT instruction within the operating system’s code.
5. Third, NPCALL switches the system to user mode. The page table, stack, and registers now all belong to the user program.
4. Now the REVERT in the operating system’s code is executed, except the user’s stack is now in effect, so the flags and instruction pointer recovered are that of the suspended user program.
5. The next instruction executed is a continuation of the user’s program.
6. At this point, the frozen operating system has its flags and return address from NPCALL on top of its stack. These will be restored later by instruction decoder hijacking when either the multitasking timer expires or the user program executes a YIELD instruction.
NPRIV Nonprivileged user
| Syntax |
npriv |
| No registers used |
| No flags changed |
NPRIV sets the one-bit flip-flops ptsuserflop and reguserflop to both output 1, forcing executing code to use the (nonprivileged) user’s registers, return address stack, and page table. This instruction is the opposite of PRIV.
The multitasking timer is enabled by reguserflop and doesn’t have a separate control, so going NPRIV starts the multitasking countdown from whatever its configured initial setpoint is. Partial countdowns are not saved, so any NPRIV restarts the clock at the beginning. See also TIMER under Program initialization.
Simulator note: 11 July 2023
NPRIV has been observed crashing the electrical simulation with the message:
glitch on D1 pin(s) a11
This apparently results from a multitasking timer bit not being initialized to high or low, but instead being left as uncertain. This crash was worked around by initializing the timer prior to going NPRIV. For instance:
unsigned timer.setting
timer.setting = 10_0000_0000_0000_0000`b
disable.timer:
timer timer.setting
jump >= disable.timer
Although this is a bug in that the simulation crashes, it’s not a high-priority one to fix, because code that makes this bug surface already places the CPU at the mercy of an unpredictable timer. So correcting the simulator would still leave buggy software in simulation.
PCALL Call privileged subroutine
| Syntax |
pcall subr |
| No registers used |
| No flags changed |
PCALL is a candidate for removal from the architecture.
PCALL is a variant of CALL subr where after the return address and flags are pushed on the stack, ptsuserflop and reguserflop are both set to 0, causing further code to run in superuser mode as opposed to user mode.
PCALL was written at the same time as NPCALL, but PCALL does not have an identified use as of 19 June 2023. Because the opposite process of what NPCALL does is not done by PCALL, but is done by hijacking the instruction decoder.
The HIJACK Pseudo instruction is almost exactly a PCALL.
PEEK Peek
| Syntax |
su_reg = peek u_reg |
| Register | Signedness |
| All | ignored |
| 1 opcode only |
| Flag | Set if and only if |
N |
bit 35 of the result is set |
Z |
all result bits are zero |
T |
flag does not change |
R |
flag does not change |
PEEK fetches the contents of register u_reg into the arithmetic logic unit (ALU), forces the CPU into PRIV mode regardless of its preceding mode, then writes the result to register su_reg. The superuser’s flags are modified as if the destination is an unsigned register. No other user’s flags are modified.
PEEK provides a mechanism for the superuser to read the contents of a user register. Ordinarily, registers belonging to different users cannot mingle, due to the 8-bit user differentiator’s contribution to the register file’s address bits. PEEK overrides this limitation by clearing the single-bit flip-flop reguserflop while the ALU is actively copying a register. For consistency, ptsuserflop is also cleared.
Ideally PEEK would keep the system in superuser (PRIV) mode, but there is no electrical means to do this. The second choice would be to have PEEK temporarily enter NPRIV mode and return to PRIV mode upon completion, but there isn’t enough time in the instruction cycle to do this either. So what actually happens is:
- The CPU must already be in
NPRIVmode whenPEEKis executed. PEEKwill leave the CPU inPRIVmode, which is as we would want it.
Be warned that the multitasking timer will be active while the system is NPRIV. This won’t affect most operating systems, but an uncharacteristically short timer setting in combination with an uncharacteristically long set of instructions while in NPRIV mode could produce a surprising result. See POKE for an example of how to use NPRIV before PEEK.
When using PEEK and POKE, keep in mind that the user and superuser need to coordinate numbering of any user registers that exchange data. The KEEP assembler keyword can be used to force consistent register numbering.
POKE Poke
| Syntax |
poke u_reg = su_reg |
| Register | Signedness |
| All | ignored |
| 1 opcode only |
| No flags changed |
POKE fetches the contents of register su_reg into the arithmetic logic unit (ALU), forces the CPU into NPRIV mode regardless of its preceding mode, then writes the result to register u_reg. No flags are modified.
POKE provides a mechanism for the superuser to set the contents of a user register. Ordinarily, registers belonging to different users cannot mingle, due to the 8-bit user differentiator’s contribution to the register file’s address bits. POKE overrides this limitation by setting the single-bit flip-flop reguserflop while the ALU is actively copying a register. For consistency, ptsuserflop is also set.
Ideally POKE would keep the system in superuser (PRIV) mode, but there is no electrical means to do this. The second choice would be to have POKE temporarily enter NPRIV mode and return to PRIV mode upon completion, but there isn’t enough time in the instruction cycle to do this either. So what actually happens is:
- The CPU must be in
PRIVorSETUPmode whenPOKEis executed. POKEwill leave the CPU inNPRIVmode as an unwanted side-effect.
Things will go very wrong if you forget to restore PRIV mode after using POKE, because (i) the multitasking timer will have started, (ii) your program will have the wrong page table, (iii) the wrong stack, and (iv) the wrong registers. The silver lining is that because PEEK leaves the system in PRIV mode, you can “collapse” mode changes if data is being exchanged in two directions. In the code:
; Here is an example of how to increment `u_reg`, ; which the superuser cannot directly do. npriv su_reg = peek u_reg priv ; can't have NPRIV mode su_reg = su_reg +1 poke u_reg = su_reg priv
the first PRIV is extraneous, because the PEEK already took us there. The remaining NPRIV and PRIV are necessary, however. The code should be one instruction shorter like this:
; Here is an example of how to increment `u_reg`, ; which the superuser cannot directly do. npriv ; PEEK requires CPU in NPRIV mode su_reg = peek u_reg ; PEEK leaves CPU in PRIV mode su_reg = su_reg +1 poke u_reg = su_reg ; POKE leaves CPU in NPRIV mode priv
On the other hand, for one-directional transfers of data into multiple registers, you need to restore the correct mode at the start of each transfer like this:
; This example zeros three user registers from within a superuser program. poke u_reg1 = 0 priv ; POKE gave up the CPU's PRIV mode poke u_reg2 = 0 priv ; POKE gave up the CPU's PRIV mode poke u_reg3 = 0 priv ; POKE gave up the CPU's PRIV mode
When using PEEK and POKE, keep in mind that the user and superuser need to coordinate numbering of any user registers that exchange data. The KEEP assembler keyword can be used to force consistent register numbering.
PRIV Privileged user
| Syntax |
priv |
| No registers used |
| No flags changed |
PRIV sets the one-bit flip-flops ptsuserflop and reguserflop to both output 0, forcing code to execute with the superuser’s registers, return address stack, and page table. This instruction is the opposite of NPRIV.
The multitasking timer is enabled by reguserflop and doesn’t have a separate control, so the superuser is not subject to preemption. You could hack around that by:
user 0 npriv
which gives you a superuser that can be interrupted by the multitasking timer. Control would be transferred from the superuser to itself. Maybe even successfully, but it hasn’t been tried yet.
SETUP Setup
| Syntax |
setup |
| No registers used |
| No flags changed |
SETUP is a halfway state between NPRIV and PRIV, where the one-bit flip-flop ptsuserflop outputs 1, enabling access to the (nonprivileged) user’s call stack and page table, but reguserflop outputs 0, preserving the running superuser program’s access to its own registers.
Were it not for having a SETUP instruction, a superuser would go NPRIV to set up a user’s page table. But without its own registers, the program would have forgotten what it wanted to do with the page table. So it would go PRIV to try to remember, but then the user’s page table wouldn’t be accessible. It would be a hot mess.
SETUP is also used to initially set the instruction pointer for a user program that hasn’t run at all yet. This is achieved via some shenanigans that temporarily inserts a function call at the instruction immediately prior to the user program’s start address, and then switches call stacks while the function is called. You only need to understand the whole story if you’re writing an operating system, but there are examples under Preemptive multitasking and Cooperative multitasking to help you.
The multitasking timer is enabled by reguserflop and doesn’t have a separate control, so SETUP mode is not subject to preemption.
USER User
| Syntax |
user uid |
| Register | Signedness |
| All | ignored |
| 1 opcode only |
| No flags changed |
USER sets the 8-bit user differentiator within ff u, thereby associating a running program with one of the 256 independent return address stacks, 256 independent page tables, and 256 independent sets of 512 registers. Bits 8–35 of uid are ignored.
Although USER is the principal component of selecting which program is running, it does not modify that program’s instruction pointer or flags. These are modified using the REVERT instruction to switch from operating system to user code, and by control decoder hijacking initiated by a YIELD instruction or the multitasking timer to switch from user code to the operating system.
USER may or may not take immediate effect, depending on the condition of the one-bit flip-flops ptsuserflop and reguserflop. The condition of these flip-flops depend on every other instruction on this page—but not on USER.