Simulation instructions
Opcode | P/U | Category | Description |
CLOCK |
user | simulation | get date and time |
CLOSE |
user | simulation | close simulator file handle |
DEC |
user | simulation | write unsigned integer in decimal |
GETC |
user | simulation | read 8 bits from file |
GETW |
user | simulation | read 36 bits from file |
HALT |
priv | simulation | halt |
HEX |
user | simulation | write integer in hexadecimal |
LENC |
user | simulation | get length of file in bytes |
LENW |
user | simulation | get length of file in words |
MEMSET |
priv | simulation | fill block of memory |
NSLEEP |
priv | simulation | pause for simulated nanoseconds |
OCT |
user | simulation | write integer in octal |
OPENR |
user | simulation | open paravirtualized file for reading |
OPENM |
user | simulation | open multiplexed tty for writing |
OPENW |
user | simulation | open paravirtualized file for writing |
PUTC |
user | simulation | write 8 bits to file |
PUTW |
user | simulation | write 36 bits to file |
SDEC |
user | simulation | write signed integer in decimal |
SETEOF |
user | simulation | set end-of-file indication word |
SETIDL |
user | simulation | set nothing-to-read indication word |
TET |
user | simulation | write integer in tetrasexagesimal |
UNLINK |
user | simulation | delete paravirtualized file |
Simulation opcodes are implemented by the electrical simulation (in netsim/comps/trap.c
) for diagnostic and development purposes. Real-life Dauug|36 systems won’t support any of these opcodes, but instead treat all of them as no-ops.
A few of these opcodes are treated as privileged by Osmin because user programs can easily harm other programs or system integrity by using them.
Most of the simulation opcodes implement a paravirtualized filesystem. This filesystem has no concept of directories or privileges; instead, filenames are just 36-bit integers that map into the host filesystem via their tetrasexagesimal representations. So the assembly language filename dauug`t
becomes file‑0dauug
within the host filesystem. This means that “user” programs within an Osmin simulation have full access to each other’s files. That’s ok for testing, but it won’t apply to real-life computers.
No CPU flags change
The simulation opcodes are implemented by intercepting control decoder reads, and then directly accessing the code RAM and registers. This electrically looks like a no-op, and one consequence is that simulation opcodes cannot modify any CPU flags. So there will be places where assembly language syntax may look like you can test a flag, but you can’t. For example:
signed fd fd = openr MyFile`t ; will return all 1s if open fails jump < noSuchFile
needs an additional instruction to update flags, such as
signed fd fd = openr MyFile`t fd = fd + 0 jump < noSuchFile
Many programs probably won’t bother having fd
be a signed register, in which case they would
unsigned fd fd = openr MyFile`t ; all 1s if open fails fd = fd | fd ; bitwise OR copies bit 35 to N flag jump < NoSuchFile
Some syntax is quirky
Because Dauug|36 assembly language has no reserved words, statements must adhere to some rigid patterns. This adherence can be subtly misleading. For example, in
putc fd = 32
one would think fd
is a destination register because it’s left of the =
sign. Actually, putc
does not modify any register, so fd
and 32
are putc
’s left and right operands. The above statement can be read to essentially mean “the byte being output to stream fd
is 32.”
For example code
Any .a
file under the netsim
directory that contains openm
, openr
, or openw
is a good candidate for study.
CLOCK
Get date and time
Syntax |
now = clock tz |
Register | Signedness |
now |
signed |
tz |
signed |
1 opcode only |
No flags changed |
CLOCK
obtains the number of seconds since the Epoch from the host operating system by calling time(NULL)
. To this integer, it adds the signed value tz
and returns the result in now
.
Because Dauug|36 has 36-bit words, all time between 882 C.E. and 3058 C.E. are representable, plus a bit outside this range. In other words, this architecture has a “year 3059 problem.”
The assembler does not enforce signedness for this opcode.
CLOSE
Close simulator file handle
Syntax |
close fd |
Register | Signedness |
fd | ignored |
1 opcode only |
No flags changed |
CLOSE
closes the file or multiplexed tty associated with an open handle. No registers are changed. No indication is made in the event of an error.
DEC
Write unsigned integer in decimal
Syntax |
DEC fd = num |
Register | Signedness |
fd | ignored |
num | unsigned |
2 opcodes (see also SDEC ) |
No flags changed |
DEC
writes the unsigned right operand num
in decimal to open file handle fd
. No registers are changed. No indication is made in the event of an error.
The DEC
and SDEC
opcodes both use DEC
as their mnemonic. The assembler will determine which opcode to produce from the signedness of num
.
The number output opcodes DEC
, HEX
, OCT
, and SDEC
do not output spaces, separators, or leading zeros. In contrast, TET
outputs six base-64 digits and preserves leading zeros.
GETC
Read 8 bits from file
Syntax |
c = getc fd |
Register | Signedness |
c | ignored |
fd | ignored |
1 opcode only |
No flags changed |
GETC
reads one byte from open file handle fd
. Ordinarily, the result will be in the range 0 through 255 and placed in c
.
Simulation reads are performed with nonblocking Linux system calls. If there is no data to read, a 36-bit “idle” value is returned. See also SETIDL
.
If the read does not complete due to end-of-file, a 36-bit “eof” value is returned. See also SETEOF
.
If the read does not complete due to an error, the all-ones value is returned. Most likely, the error will be that fd
is not an open file handle.
GETW
Read 36 bits from file
Syntax |
c = getw fd |
Register | Signedness |
c | ignored |
fd | ignored |
1 opcode only |
No flags changed |
GETW
reads five bytes from open file handle fd
as a 40-bit number in big-endian order (most-significant byte first). The top four bits are discarded, and the 36 least significant bits are placed in c
.
Simulation reads are performed with nonblocking Linux system calls. If GETW
would block before five bytes are read, a 36-bit “idle” value is returned. The input stream is positioned as if the GETW
did not occur. See also SETIDL
.
If GETW
would encounter an end-of-file before five bytes are read, a 36-bit “eof” value is returned. The input stream is positioned as if the GETW
did not occur. See also SETEOF
.
HALT
Halt
Syntax |
halt |
No registers used |
No flags changed |
The HALT
instruction causes the simulator to stop. The active .ns
test script will continue at the line following the run
that started the simulator.
The simplest use case for HALT
is that a program has terminated. Another use case is that the .ns
script can check the value most recently written to the register file to verify that an assembly language program is executing correctly. An @
SRAM tweak is used for this check (see netsim/comps/c_sram.c
). After the check, the script can resume the program using a subsequent run
directive.
HEX
Write unsigned integer in hexadecimal
Syntax |
HEX fd = num |
Register | Signedness |
fd | ignored |
num | ignored |
1 opcode only |
No flags changed |
HEX
writes the unsigned right operand num
in hexadecimal to open file handle fd
. No registers are changed. No indication is made in the event of an error.
The number output opcodes DEC
, HEX
, OCT
, and SDEC
do not output spaces, separators, or leading zeros. In contrast, TET
outputs six base-64 digits and preserves leading zeros.
LENC
Get length of file in bytes
Syntax |
len = lenc fname |
Register | Signedness |
c | signed |
fname | unsigned |
1 opcode only |
No flags changed |
LENC
takes tetrasexagesimal filename fname
(a 36-bit register with six packed characters) and checks to see if a file with that name exists in the paravirtualized filesystem. If it does, the file’s size in bytes is returned in len
. If the file does not exist or there is an error, −1 is returned.
LENC
works on file names, not on open file handles. It does not matter whether the file is open or not.
The assembler does not enforce signedness for this opcode.
LENW
Get length of file in words
Syntax |
len = lenw fname |
Register | Signedness |
c | signed |
fname | unsigned |
1 opcode only |
No flags changed |
LENW
takes tetrasexagesimal filename fname
(a 36-bit register with six packed characters) and checks to see if a file with that name exists in the paravirtualized filesystem. If it does, the file’s size in words is returned in len
. If the file does not exist or there is an error, −1 is returned.
The number of words in a file is considered to be the number of bytes, divided by 5. Any fractional word left over does not count towards the file’s size.
LENW
works on file names, not on open file handles. It does not matter whether the file is open or not.
The assembler does not enforce signedness for this opcode.
MEMSET
Fill block of memory
Syntax |
memset |
No registers used |
No flags changed |
Osmin, like most operating systems, often fill expansive blocks of memory with zeros. Because the electrical simulation may only run a dozen or two instructions per second, these fills take a lot of time. This wouldn’t be a problem on a physical computer.
MEMSET
lets Osmin hand off large memory fills to the simulator, which can do the fills lickety-split. It works with several RAMs—both register files, the page table, code memory, and data memory. The SRAMs that participate must all use the memset
tweak to opt in; any SRAMs without that tweak are immune to MEMSET
s.
MEMSET
alters the next write operation that will occur on an opted-in SRAM. On that future write, all locations between the previous address written and the new address being written are filled with whatever contents the instruction involved would have written at the new address.
Example to fill code memory addresses 1024–2047 with zeros:
wcm 1024 = 0 memset wcm 2047 = 0
Example to fill page 1 of virtual memory with all ones:
sto 4096 = ~1 memset sto 8191 = ~1
The MEMSET
instruction should be used twice in immediate succession if the intent is to write to a bank of registers. Otherwise, only the left or right copy (whichever electrically happens first) will be MEMSET
, which is probably not what you want.
Example to zero whatever registers happen to be between alpha
and omega
:
alpha = 0 memset memset omega = 0
NSLEEP
Pause for simulated nanoseconds
Syntax |
nsleep duration |
Register | Signedness |
duration | unsigned |
1 opcode only |
No flags changed |
NSLEEP
causes the simulation to “freeze” the CPU for the given number of wall-clock nanoseconds. This is more energy-efficient on the host CPU than an idle loop for tests that need to wait, such as an ongoing program that shows the time of day. The longest sleep that can fit in a 36-bit word is about 68.7 seconds.
Osmin disallows NSLEEP
in user programs because they could freeze the system.
The assembler does not enforce signedness for this opcode, so NSLEEP ~1
will sleep for about 68.7 seconds.
OCT
Write unsigned integer in octal
Syntax |
OCT fd = num |
Register | Signedness |
fd | ignored |
num | ignored |
1 opcode only |
No flags changed |
OCT
writes the unsigned right operand num
in octal to open file handle fd
. No registers are changed. No indication is made in the event of an error.
The number output opcodes DEC
, HEX
, OCT
, and SDEC
do not output spaces, separators, or leading zeros. In contrast, TET
outputs six base-64 digits and preserves leading zeros.
OPENM
Open multiplexed tty for writing
Syntax |
fd = OPENM ttyname |
Register | Signedness |
fd | signed |
ttyname | unsigned |
1 opcode only |
No flags changed |
Osmin tests call for several open terminals to display output from concurrently running programs and the kernel. Netsim provides two Python scripts mux
and rx
to easily configure and manage these terminals. The mux
script should run in its own terminal and connects it all together. Invoke it as:
$ ./mux
For each terminal you want, also run an instance of rx
in its own window. Each invocation takes a tetrasexagesimal tty name of up to six digits. For instance:
$ ./rx Jo ; base 64 is case-sensitive $ ./rx walter
OPENM
connects netsim to these scripts by opening a multiplexed terminal ttyname
and saving its file handle to fd
. Only writing is supported; there is no way to read from a multiplexed terminal as of August 2025. (You can use OPENR
via a symlink to write interactive programs.) In assembly language:
unsigned j.fd w.fd j.fd = openm Jo`t w.fd = openm walter`t putc j.fd = 74 ; write capital J to the Jo tty putc w.fd = 119 ; write lowercase w to the walter tty close j.fd close w.fd
The assembler does not enforce signedness for this opcode.
OPENR
Open paravirtualized file for reading
Syntax |
fd = openr fname |
Register | Signedness |
fd | signed |
fname | unsigned |
1 opcode only |
No flags changed |
OPENR
takes tetrasexagesimal filename fname
(a 36-bit register with six packed characters) and checks to see if a file with that name exists in the paravirtualized filesystem. If it does, the file is opened for reading and its handle is written to fd. If the file does not exist or there is an error, −1 is returned.
After OPENR
returns a non-negative file descriptor fd
, the opcodes you can use with fd
are CLOSE
, GETC
, GETW
, SETEOF
, and SETIDL
.
Paravirtualized files all live in a single directory that is determined by an iodir
directive in the .ns
script. Within that directory, their names have the format file-123abc
, where the last six characters are a tetrasexagesimal name. (To review, characters are 0–9, a–z, A–Z, apostrophe, and period.) For example, a paravirtualized filesystem might have this hierarchy:
io/ io/file-000pts io/file-000api io/file-0clock io/file-0color io/file-00echo io/file-0groot io/file-0user1 io/file-bootMe io/file-unlink
The .ns
script to access this filesystem would include the line iodir io
, assuming netsim is run from the directory that contains io
.
To read from a Linux terminal, have a file within this namespace on the host that symlinks to the device you want. For example, you might
ln -s /dev/pts/0 io/file-000pts
The assembler does not enforce signedness for the OPENR opcode.
OPENW
Open paravirtualized file for writing
Syntax |
fd = openw fname |
Register | Signedness |
fd | signed |
fname | unsigned |
1 opcode only |
No flags changed |
OPENW
takes tetrasexagesimal filename fname
(a 36-bit register with six packed characters) and opens it for writing. If the file already exists, the original contents are preserved and appended to. If the file does not exist, it will be created. If you want to overwrite a file with a brand new one, UNLINK
the filename before you OPENW
.
OPENW
will return a handle fd
for subsequent operations with the file. If the open fails for some reason, fd
will be −1. The most likely reason for failure is missing an iodir
directive in the .ns
script to say where the paravirtualized filesystem lives.
After OPENW
returns a non-negative file descriptor fd
, the opcodes you can use with fd
are CLOSE
, DEC
, HEX
, OCT
, PUTC
, PUTW
, SDEC
(the mnemonic for SDEC
is DEC
), and TET
.
If you want OPENW
to write to a terminal somewhere, use a symbolic link in the file tree (see also OPENR
). Interactive programs will usually use OPENR
and OPENW
on the same filename, but return two different file descriptors. The program netsim/echo.a
in the August 2025 tarball has a working example of this.
The assembler does not enforce signedness for the OPENW opcode.
PUTC
Write 8 bits to file
Syntax |
putc fd = byte |
Register | Signedness |
All | ignored |
1 opcode only |
No flags changed |
PUTC
writes the eight least-significant bits of register byte
to open file handle fd
. PUTC
is the natural complement of GETC
. Exactly one byte is output to the host filesystem. No indication is given if there is an error. The most likely error is that fd
is not open for writing.
The assembler does not enforce signedness for this opcode.
PUTW
Write 36 bits to file
Syntax |
c = a + b |
Register | Signedness |
All | ignored |
1 opcode only |
No flags changed |
PUTW
writes 36 bits to the host filesystem as a sequence of five bytes. PUTW
is the natural complement of GETW
, so the order is big-endian with four leading zero bits:
0000zyxw vutsrqpo nmlkjihg fedcba98 76543210 most significance least first output order last
No indication is given if there is an error. The most likely error is that fd
is not open for writing.
The assembler does not enforce signedness for this opcode.
SDEC
Write signed integer in decimal
Syntax |
DEC fd = num |
Register | Signedness |
fd | ignored |
num | signed |
2 opcodes (see also DEC ) |
No flags changed |
SDEC
writes the signed right operand num
in decimal to open file handle fd
. No registers are changed. No indication is made in the event of an error.
The DEC
and SDEC
opcodes both use DEC
as their mnemonic. The assembler will determine which opcode to produce from the signedness of num
.
The number output opcodes DEC
, HEX
, OCT
, and SDEC
do not output spaces, separators, or leading zeros. In contrast, TET
outputs six base-64 digits and preserves leading zeros.
SETEOF
Set end-of-file indication word
Syntax |
seteof fd = word |
Register | Signedness |
All | ignored |
1 opcode only |
No flags changed |
By default, GETC
and GETW
return all ones (either −1 or 68719476735) if a read past the end of a file is attempted. The SETEOF
instruction lets you change this default for any open file handle fd
to any 36-bit value word
.
If SETEOF
changes the end-of-file indicator word to something other than −1, the indicator word for read errors will still be −1. Supporting a configurable error-indicating word is not a priority right now.
SETIDL
Set nothing-to-read indication word
Syntax |
setidl fd = word |
Register | Signedness |
All | ignored |
1 opcode only |
No flags changed |
By default, GETC
and GETW
return almost all ones (either −2 or 68719476734) if a read operation would block. This would happen, for example, for an interactive program that is waiting for a user to type something. The SETIDL
instruction lets you change this default for any open file handle fd
to any 36-bit value word
.
TET
Write integer in tetrasexagesimal
Syntax |
tet fd = num |
Register | Signedness |
All | ignored |
1 opcode only |
No flags changed |
TET
writes the unsigned right operand num
in base 64 to open file handle fd
. The output will be exactly six digits (selected from 0–9, a–z, A–Z, apostrophe, period). Leading zeros are not suppressed. No registers are changed. No indication is made in the event of an error.
UNLINK
Delete paravirtualized file
Syntax |
unlink fname |
Register | Signedness |
fname | unsigned |
1 opcode only |
No flags changed |
UNLINK
takes tetrasexagesimal filename fname
(a 36-bit register with six packed characters) and checks to see if a file with that name exists in the paravirtualized filesystem. If it does, the file is deleted. If the file does not exist, nothing happens and no error is indicated.
UNLINK
works on file names, not on open file handles. It does not matter whether the file is open or not. (Note that if you UNLINK
an open file on a Linux host, the file doesn’t go away until all programs have closed it.)
The assembler does not enforce signedness for this opcode.