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.