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 MEMSETs.

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.

Without secure hardware, there is no secure software.