The Dauug House Wright State University logo
Dauug|36 minicomputer documentation


In Dauug|36 assembly language, a scope is what some languages call a subroutine or function. I decided that “scope” was a more accurate term for this architecture, because the only thing the assembler enforces about scopes is register and label namespacing.

Two example scopes

Suppose we’d like a subroutine to convert from Centigrade to Fahrenheit, and another to convert from Fahrenheit to Centigrade. To keep this example simple, we’ll assume that approximate results are sufficient. (We’re using integer arithmetic, shifts used for division round toward negative infinity, and we’re not trying to squeeze out all the precision we might.) Here’s how we may write these:

Scope to convert Centigrade to Fahrenheit

    signed C_in F_out               ; input and output registers
    keep C_in F_out
    signed t u                      ; temporary registers

    t = C_in asr 434343_434343`o    ; t = C_in / 2
    u = t asr 434343_434343`o       ; u = t / 2
    t = t + u                       ; t = 0.75 * C_in
    u = t asr 404040_404040`o       ; u = t / 16
    t = t + u                       ; t = 0.797 * C_in
    t = C_in + t                    ; t = 1.797 * C_in
    F_out = t + 32                  ; F_out = 1.797 * C_in + 32

The first thing to note is that scopes are declared using two colons after their name, so C_to_F:: introduces a scope named C_to_F. The C_to_F scope continues until a new scope is declared. Scopes must be contiguous, so we can’t abandon the C_to_F scope and return to it later. Scopes do not nest.

The registers declared, C_in and F_out, are accessible within the C_to_F scope by just using their names in the ordinary way. But in this scope, C_in and F_out are intended as input and output registers for a subroutine to convert Centigrade to Fahrenheit, so C_in will need to be written before the scope is CALLed, and F_out and will need to be read after the scope RETURNs. We accomplish this in two stages:

  1. We use the keep keyword to make the registers accessible outside the scope.
  2. Outside the scope, we prefix register names with scope names, i.e., C_to_F::C_in.

So to call the C_to_F scope from another scope, we might write something like:

C_to_F::C_in = temp (Centigrade)
call C_to_F
temp (Fahrenheit) = C_to_F::F_out

In assembler syntax, (Centigrade) and (Fahrenheit) are comments, because they are parenthesized.

The right arithmetic shifts are tricky to read, because Dauug|36 are always specified in terms of a left rotation. Using 43`o and 40`o in every tribble of the right operand indicates a right shift of 1 and 4 bits respectively.

Next, we look at the opposite conversion from Fahrenheit to Centigrade.

Scope to convert Fahrenheit to Centigrade

    signed F_in C_out               ; input and output registers
    keep F_in C_out
    signed t u                      ; temporary registers

    C_out = F_in - 32               ; C_out = F_in - 32
    u = C_out asr 414141_414141`o   ; u = (F_in - 32) / 8
    u = C_out - u                   ; u = (F_in - 32) * 7 / 8
    t = u asr 363636_363636`o       ; t = (F_in - 32) * 7 / 512
    t = t + u                       ; t = (F_in - 32) * 455 / 512
    t = t asr 404040_404040`o       ; t = (F_in - 32) * 455 / 8192
    u = C_out asr 434343_434343`o   ; u = (F_in - 32) / 2
    C_out = t + u                   ; C_out = (F_in - 32) * 4551 / 8192
    return                          ; C_out = 0.555542 * (F_in - 32)

Here, the tribbles 43`o, 41`o, 40`o, and 36`o cause right shifts of 1, 3, 4, and 6 bits.

As mentioned, these conversions are approximate. When temperatures between −100 °F and 100 °F are converted to °C and back to °F, the output will be 0 to 4 °F lower than the input. Temperatures between −100 °C and 100 °C, when converted to °F and back to °C, will see the output 0 to 2 °C lower than the input.

More about scopes

Two registers that aren’t in the same scope are different registers, even if they have the same name. The same is true of labels (JUMP destinations), so when deciding what to name things, you only need to consider conflicts that are in the current scope.

Because scopes begin at their :: declaration and continue until changed, I needed to decide what happens prior to the first scope declaration in a program. I decided this is also a scope, and that its name is main. Programs start running at the first instruction in their main scope. It’s not legal to declare scope main—you can’t write


because the assembler does that internally for you on “line 0” of your program.

Because scopes are zero-cost abstractions that create no instructions on their own, I couldn’t call them subroutines, because:

  1. They might not contain any code at all.
  2. They don’t require CALL to enter.
  3. They don’t require RETURN or REVERT to exit.
  4. They don’t initialize any registers or check if you initialized them.

I also couldn’t call scopes functions for the above reasons, not to mention that they aren’t guaranteed to have inputs or outputs.

“Rolling” in and out of scopes

It’s possible to drift in and out of scopes as the instruction pointer increments. There is no inherent requirement for CALL to reach the top of a scope, and the assembler will not require a RETURN or REVERT to appear at the end of a scope. This can lead to bugs if the programmer isn’t paying attention, or can be put to good use. One use for “rolling” into or out of a scope is that a subroutine with multiple entry points can be written.

As an example, suppose you’re writing a privileged routine to set the multitasking timer’s LFSR setpoint. In most applications, the setpoint would be 1010111111110111`b, because it sets the timer for its longest possible duration. So you’d like to have that as a default value, but you’d also like to support an alternative where the caller can use any setpoint. One approach is to abut two scopes like this:

init.multitasking.timer:: = 1010111111110111`b
    unsigned              ; value being shifted to ff tims
    unsigned setpoint               ; eventual setting for ff tims
    keep setpoint = 10_0000000000000000`b = | setpoint
    jump >= loop

The scope exposes local register setpoint as an input, allowing you to set ff tims to any value you want. For example, you may choose to have the timer expire after 20 instructions. You would call this as = 1010111111110111`b

On the other hand, if you want to use the timer default of 65,535 instructions, you would instead

call init.multitasking.timer

without the .to suffix, and without specifying setpoint yourself. The called scope would fill in the default value for you, then “roll into” to do the work. There is only one RETURN at the end, and it covers both scopes.

Useless scopes

The assembler does not require that scopes have any contents. Here is an example of a scope with no contents, called colour, followed by scope color.

    unsigned red green blue
    keep red green blue
    ; etc.

Although your British friends can

call colour

and be happy, they cannot access registers as

colour::green = 255

because green is in scope color. Scope colour doesn’t have registers.

Register-only scopes

It can be advantageous to have scopes that contain only registers, such as for global variables or to group registers for some purpose. For instance:

    unsigned debug.level default.units
    signed timezone.seconds
    keep debug.level default.units timezone.seconds

Elsewhere in the program, prefix these registers’ names with g:: to use them.


Scope g doesn’t initialize these registers. You may wish to have it do so:

    unsigned debug.level default.units
    signed timezone.seconds
    keep debug.level default.units timezone.seconds

    debug.level = 0
    timezone.seconds = ~14_400
    default.units = 86_400

Your program would need to call g to preset these values.

Marc W. Abel
Computer Science and Engineering
College of Engineering and Computer Science
Without secure hardware, there is no secure software.