Zyelios CPU 10 Handbook

Black Phoenix

Русская документация доступна здесь

The Zyelios CPU is a virtual processor. It was created so the Wiremod players could include it in their contraptions, automating their contraptions and learning the basics of low-level programming, assembly programming and, with introduction of the HL-ZASM compiler, C programming.

This processor is very similar to the kind of processors all modern complex devices use. Its closest real-world analog is the x86 architecture, but it has several major distinctions, the most of which is the use of floating-point numbers instead of integers.

It should be noted that the processor is a complex machine, and just like a real vehicle it will require some practice to master. It should be understood, however, that despite large number of possible uses of the processor it’s based on a very simple set of logic rules. Only the complex combination of these basic logic rules creates all the behaviour of the processor.

This handbook should serve as a concise documentation that covers all of the ZCPU features and quirks you might encounter during your programming. Just like a car manual for a real car, this book covers all of the controls and features of the ZCPU. This handbook assumes the reader has some basic knowledge of real-world processors.

The processor was invented as a logical step up from basic analog computers. First processors were tasked with solving mathematical equations, performing simulations, and processing large arrays of statistic data.

The program that the processor must execute is split into instructions. Each instruction is a simple operation, which somehow alters processor state, or state of any external devices. For example:

MOV R0,100 //Move 100 to R0
ADD R0,R1 //Add R0 with R1

The extra parameters specified by a programmer to alter the execution of a specific instruction by the processor are called operands. All the instructions in ZCPU have zero, one or two operands. The first operand is usually the destanation operand, and the second one is the source operand.

If there is some result after execution the instruction, it is written to the first operand:

ADD R0,200; //R0 = R0 + 200

A register is a variable inside the processor. They are divided into general purpose registers and the special (internal) registers. Programmer may use general purpose registers as temporary or intermediate storage for any sort of numeric data:

MOV R6,100;
MOV R7,R6;

The special registers are used to alter the processor execution mode.

The processor may be attached to external devices and external memory via data bus. A data bus is a special type of device which allows to connect several other devices together. There are two data buses in ZCPU: the MemBus (memory bus), and the IOBus (input/output bus). Any memory cell on these buses and in the processors internal memory can be accessed by an address - an integer value. There is no principial difference between how the two buses operate, although it is possible to use memory cells located on the IOBus in an easy to understand way - by addressing them as ports:

MOV R0,PORT0 //Read cell 0 via I/O bus
MOV [500],R0 //Write to cell 500 via memory bus

There are several memory models provided by the ZCPU - these specify how much internal memory the ZCPU has (it may either have some memory and ROM, like a microcontroller, or not use any internal memory at all, similarly to common processors).

There are 40 general purpose registers in the ZCPU. They are divided into the main and the extended set. The registers from the main set are called EAX, EBX, ECX, EDX, ESI, EDI, ESP and EBP (named similarly to the x86 architecture registers). The registers from the extended set are called R0, R1, ..., R31.

You can use any of these for any arithmetics purposes (that means they only hold numerical values), apart from the ESP register, which is reserved as the stack pointer (see: ?? for more information on the processor stack). When programming using C syntax the EBP register is also used for storing stack frame pointer, and must not be altered to avoid program crash.

Every register is a single 64-bit floating point value. It can contain any value between −103000 and 103000, but the precision in digits is limited to 48 bits. This is a simplification that differs ZCPU from any other real-world processor.

Registers are a lot faster than using memory. In the version 10 of the processor 32 more general purpose registers were added to speed up the program execution.

When the CPU is reset, all registers (except for ESP) will be set to 0. If the current ZCPU memory model includes RAM space, then ESP will be reset to the CPU internal RAM size minus one (e.g. 65535 for 64k memory model).

The Zyelios CPU has a special register called the instruction pointer (IP). IP points to the currently executed instruction and is incremented every time a new instruction is read.

Instructions can be either variable sized or fixed-sized, depending on the local execution mode (see: ??), so the IP may increment in varying steps. It is possible to set IP explictly by using any of the branching opcodes (see: ??)

The following is the internal execution flow for fetching and executing an instruction:

  1. Fetch instruction number (so the processor would know what instruction it is going to execute, and how much operands will the instruction have)
  2. If the instruction has operands, then fetch the RM byte (see rmbyte). If the instruction has no operands and the next byte is zero it will be skipped (this behaviour assures that the processor is backwards compatible).
  3. Processor decodes instruction number and fetches segment prefix bytes (if required, this provides support for segment prefixes. see: ?? for more information on the instruction format).
  4. If RM byte requires an immediate value to follow, it will be fetched too.
  5. The processor executes the instruction microcode, which somehow alters the processor state (but does not touch the values which are explictly stated in the operation).
  6. All values that must be written are written back to registers, memory, etc.

For the purpose of optimization, one or more of these steps might be cached by the processor and omitted from actual execution. See page ?? for more information on this behaviour.

The processor will run a specific amount of cycles per second. The amount of cycles required for executing each instruction may vary, and it is counted by the TMR register. Most instructions are only 1 cycle long, although internal caching or memory access may consume additional cycles.

see: ?? more information on the execution process.

When a certain exceptional event occurs (external signal, error in arithmetics, memory error, etc), the ZCPU will generate an interrupt. Interrupts are special events which will temporarily "interrupt" or change flow of the program and branch to execute the appropriate interrupt handler code. This means that whenever the ZCPU encounters an interrupt it will remember its current state, store it on the processor stack (see: ??) and change IP to the first opcode of the interrupt handler.

In extended mode the processor stores an interrupt table - a table which holds pointers to all interrupt handlers. If extended mode is not enabled, any interrupts called will stop the processor execution.

After the interrupt handler has finished running, it can restore the ZCPU state and the previously executed code will continue executing from where it was interrupted.

For example, if this was the stack state before an interrupt call, and current value of IP is 157:

..........
65304: ...
65306: 181
65305: -94

ESP = 65304

then the stack state will be altered to this after an interrupt call (and IP will be set to interrupt handler entrypoint):

..........
65304: ...
65306: 181
65305: -94
65304: 0   CS
65303: 157 IP

ESP = 65302

Interrupts are very similar to normal calls (see: ??), but instead of using a memory pointer an interrupt number is given. Also unlike the regular calls a special return instruction IRET must be used in place of RET. Any interrupt can be called using the INT instruction. For example:

interrupt_handler:
  ....
iret;

There are two types of interrupts: internal interrupts, and external interrupts. The external interrupts will push the entire ZCPU state (along with the return data internal interrupt would push) to stack before entering the interrupt handler. They can only be triggered via external interrupt pin, or by using the EXTINT instruction. The external interrupts require EXTRET instruction instead of the IRET to restore the processor state.

There are several static interrupts. These interrupts are executed when certain exceptional conditions happen internally. All interrupt numbers less than 32 are reserved by the processor. In addition, interrupts 0 and 1 have special meanings and are handled internally. The user program can only use the interrupts 32..255. see: ?? for more information on the error interrupts.

The following table describes all of the reserved interrupt numbers:


NumberDescription
0Reset
1Halt
2End of program execution
3Division by zero
4Unknown opcode
5Internal processor error
6Stack error (overflow/underflow)
7Memory read/write fault
8MemBus fault
9Write access violation (page protection)
10Port read/write fault
11Page acccess violation (page protection)
12Read access violation (page protection)
13General processor fault
14Execute access violation (page protection)
15Address space violation
17Frame instruction limit (GPU only)
23String read error (GPU only)
28Page read access trap
29Page write access trap
30Page access trap
31Debug trap
 

The reset interrupt (0) will reset the processor state, and restart code execution from the top of memory. The halt interrupt (1) will stop execution until the processor is reset externally. The int 1 and int 0 instructions will work as long as the interrupts are not handled by the software (as long as they are disabled in the interrupt table, or interrupt table is not used):

 INT 1; //Stop execution
 
 INT 0; //Reset the processor

If extended mode is not enabled, all the interrupts will work similarly to the halt interrupt (int 1) when triggered. The error code will be set on the error output.

Interrupt handling can be disabled using the IF flag. It is not directly writeable, but it can be changed using the special instructions. When setting the flag, the value will only become updated after the next instruction. For example:

STI;         //Set interrupt flag
MOV EAX,123; //IF = 0
ADD EAX,123; //IF = 1 (flag updated)
CLI;         //Clear interrupt flag
SUB EAX,123; //IF = 0 (flag clear on this instruction)
RET;         //IF = 0 

This assures that for example in this situation the interrupt will not occur between the RET and the STI:

ContextSwitch:
  CLI;
  
  ... thread switcher code ...
  
  STI;  //An interrupt will never occur between this
EXTRET; //STI instruction and the NMIRET

For information on using the interrupt table, consult the advanced interrupts chapter (see: ??).

There are 8 segment registers in the ZCPU. They are CS, SS, DS, ES, GS, FS, KS and LS. They are used by the ZCPU to support certain memory models. Additionally any of the 40 general purpose registers can be used as segment registers.

Every segment register can contain a 48-bit integer. This value can be used for specifying an offset for a pointer to memory. The ZCPU always uses segments when referencing memory: it translates the local address (the memory address explictly written by the user, it’s important to remember that any variable or label has a certain memory address) into the absolute address (the physical address, which serves as a command to direct ZCPU towards where the real data is located).

The formula used for the address translation is:

AbsoluteAddress = LocalAddress + SegmentOffset

The user can specify which register to use for offsets by prefixing the operand with the segment name followed by a semicolon. If a segment is not specified, DS is used.

Both segment and general purpose registers can be used for segment prefixing. It is not possible to use a constant value for segment prefix. Here is an example of various ZASM syntax for reading memory:

MOV EAX,#EBX      //Address: DS+EBX
MOV EAX,ES:#EBX   //Address: ES+EBX
MOV EAX,[EBX]     //Address: DS+EBX
MOV EAX,[ES:EBX]  //Address: ES+EBX
MOV EAX,[ES+EBX]  //Address: ES+EBX

MOV EAX,EBX:ECX   //Address: EBX+ECX
MOV EAX,[EBX+ECX] //Address: EBX+ECX
MOV EAX,[EBX:100] //Address: EBX+100
MOV EAX,[100:EBX] //This is not valid

With some clever programming the segment prefixes can be used for quick array access, typically by using the prefix as the array pointer and the base as the index into the array:

MOV ES,ArrayStart
MOV EAX,ES:#0 //EAX = 10
MOV EBX,ES:#2 //EBX = 30
MOV ECX,ES:#1 //ECX = 50

MOV EAX,0
MOV EBX,EAX:#ArrayStart //EBX = 10
INC EAX
MOV EBX,EAX:#ArrayStart //EBX = 50
INC EAX
MOV EBX,EAX:#ArrayStart //EBX = 30

ArrayStart: db 10,50,30

Some of the segment registers are used by the processor for specific tasks, as per the following table:


RegisterNameDescription
CSCode segmentProcessor fetches code from this segment
SSData segmentDefault segment for data
DSStack segmentProcessor stack is located in this segment
ESExtra segmentUser segment
GSG segmentUser segment
FSF segmentUser segment
KSKey segmentUser segment
LSLibrary segmentUser segment
 

All segment registers other than CS can be directly set by using the MOV operation. The only way to modify the CS is to execute CALLF or JMPF instructions (see: ?? for more information):

//CS = 0
//IP = 928

JMPF 500,100;

//CS = 100
//IP = 500

Attempting to set CS directly will trigger interrupt 13:1 (general processor fault). For example:

MOV DS,100
MOV ES,KS
MOV CS,1000 //Will generate interrupt 13:1

After CPU reset all segment registers will be initialized to 0.

The ZCPU is capable of working in several different memory modes. Linear addressing mode is the default mode, and it is the mode in use when the CPU is reset.

Different modes require different use of registers and provide different execution features. Each mode will usually require extended processor mode to be active, enabling some advanced memory protection features such as page permissions and memory mapping.

It is important to be aware of how memory access actually works. When a memory request occurs (read or write) these are the actions that will be undertaken by the CPU:

  1. Check BusLock register. If this register is set to 1 the request will fail.
  2. Validate the address. The address must be a 48-bit integer value.
  3. Fetch page that corresponds to the requested address.
  4. If EF flag is set check if current runlevel is less or equal to target page runlevel and check if it’s possible to read/write on the page.
  5. If page has Mapped flag set then change the address accordingly.
  6. Perform the I/O operation.

This is the default address mode when all segment registers are initialized to 0. This mode is available after the initial processor startup. Since all registers are set to zero, in this mode the code, data and stack are all located in same address space.

No segment prefixes are required in this mode. For example you can access the processor stack without specifying the SS prefix:

MOV EAX,#0  //EAX will be equal to 14 (MOV opcode no)
MOV #ESP,100 //Same as PUSH 100
DEC ESP
POP EAX     //EAX will be equal to 100

This is the most common addressing mode when segment registers are used differently. For example, the code, data and stack might all be located in different areas of memory. This has certain benefits:

Example:

MOV DS,1000 //Set first data block
MOV SS,2000 //Set first stack block
CALLF 0,500 //Call the routine (CS will be set to 500, IP to 0)

MOV DS,3000 //Set second data block
MOV SS,4000 //Set second stack block
CALLF 0,500 //Call the same routine, but now
            //working on different data block

This will run the same routine (located at physical offset 500) on two different sets of data. Thus the same subprogram can be reused for different sets of variables.

Mapped memory mode uses the memory mapping features of the processor to reroute memory addresses in order to create the appearance of a single continuous address space for the user program. This mode allows user programs to dynamically allocate blocks of data, which can themselves be physically located in different areas of memory. This also allows use of dynamic libraries, where single library loaded once can be used in many programs while remaining in a single place in physical memory.

see: ?? for more information.

The Zyelios CPU has a built-in hardware processor stack. Stack operation is controlled by the current stack pointer register (ESP), the stack size register (ESZ) and the stack segment register (SS) Stack data is located in physical RAM.

ESP points to the next free value on the stack. The stack grows down.

PUSH and POP are used to push or pop values to or from the stack, respectively. Stack overflow or underflow is indicated by the interrupt 6:ESP (stack error). The interrupt parameter will be set to the value of ESP. For example:

MOV SS,5000   //Stack starts at offset 5000
MOV ESP,2999  //Stack is 3000 bytes in size
              //Next free offset in segment is 2999
CPUSET 9,3000 //Set ESZ register

PUSH 200
PUSH 100
POP EAX //EAX = 100
POP EBX //EBX = 200

//PUSH X is same as the following (but with error checks):
MOV SS:#ESP,X
DEC ESP

//POP Y is same as the following (but with error checks):
INC ESP
MOV Y,SS:#ESP

Additionally, the RSTACK and SSTACK instructions allow the user to read or write an arbitrary position on the stack. These instructions may also trigger stack underflow or overflow interrupts. The following example illustrates the use of RSTACK and SSTACK:

RSTACK X,Y //X = MEMORY[SS+Y]
SSTACK X,Y //MEMORY[SS+X] = Y

RSTACK EAX,ESP:1 //Read stack top
RSTACK EAX,ESP:2 //Read value under stack top

PUSH 100 //
PUSH 200 //Value under top value
PUSH 300 //Value on top

SSTACK ESP:2,123 //Set value under stack top
POP EAX //EAX = 300
POP EBX //EBX = 123
POP ECX //ECX = 100

The Zyelios CPU supports various kinds of branching: conditional or unconditional, absolute or relative. The instruction pointer (IP) points to the currently executing instruction. All branching instructions modify IP; some also modify CS (see: ??).

The simpliest type of branching is absolute unconditional. To perform an unconditional jump, JMP or JMPF are used (the latter also modifies CS).

Program subroutines can be called by using CALL or CALLF. This will save current the instruction pointer (and CS when CALLF is used) to the processor stack. The instruction pointer (and code segment if required) can be restored from the stack by using RET or RETF accordingly.

The following example illustrates creating routines and jumping:

JMPF MAIN,CODE_SEGMENT //Syntax is JMPF IP,CS

......

MAIN: //A label
  CALL SUBROUTINE
  JMP EXIT

SUBROUTINE:
  CALL SUBROUTINE2
RET //Exit subroutine

SUBROUTINE2: //Called inside SUBROUTINE
  ... do something ...
RET
......

EXIT:

It is possible to perform a relative jump. To do this, JMPR (jump relative) is used. This instruction adds or subtracts IP by a certain amount instead of giving a specific number. For example example:

JMPR +10 //Jump 10 bytes forward
JMPR -10 //Jump 10 bytes backward

JMPR LABEL-__PTR__ //Jump to label
                   //__PTR__ label always points
                   //to current write pointer

......

LABEL:

Conditional branching allows program flow to be changed depending on certain conditions. CMP is used to compare two values, and instructions from the following table react accordingly:


InstructionOperationDescription
JNEX <> YJump if not equal
JNZX - Y <> 0Jump if not zero
JGX > YJump if greater than
JNLENOT X <= YJump if not less or equal
JGEX >= YJump if greater or equal
JNLNOT X < YJump if not less than
JLX < YJump if less than
JNGENOT X >= YJump if not greater or equal
JLEX <= YJump if less or equal
JNGNOT X > YJump if not greater than
JEX = YJump if equal
JZX - Y = 0Jump if zero
CNEX <> YCall if not equal
CNZX - Y <> 0Call if not zero
CGX > YCall if greater than
CNLENOT X <= YCall if not less or equal
CGEX >= YCall if greater or equal
CNLNOT X < YCall if not less than
CLX < YCall if less than
CNGENOT X >= YCall if not greater or equal
CLEX <= YCall if less or equal
CNGNOT X > YCall if not greater than
CEX = YCall if equal
CZX - Y = 0Call if zero
JNERX <> YJump relative if not equal
JNZRX - Y <> 0Jump relative if not zero
JGRX > YJump relative if greater than
JNLERNOT X <= YJump relative if not less or equal
JGERX >= YJump relative if greater or equal
JNLRNOT X < YJump relative if not less than
JLRX < YJump relative if less than
JNGERNOT X >= YJump relative if not greater or equal
JLERX <= YJump relative if less or equal
JNGRNOT X > YJump relative if not greater than
JERX = YJump relative if equal
JZRX - Y = 0Jump relative if zero
 

There are other instruction which perform branch testing, such as BIT, which tests specific bits of a given value. For example:

CMP EAX,EBX
JG  LABEL1 //Jump if EAX >  EBX
JLE LABEL2 //Jump if EAX <= EBX
JE  LABEL3 //Jump if EAX  = EBX
CL  LABEL4 //Call if EAX <  EBX
CGE LABEL5 //Call if EAX >= EBX

BIT EAX,4 //Test 5th bit of EAX
JZ  LABEL1 //Jump if 5th bit is 0
JNZ LABEL1 //Jump if 5th bit is 1

There are several error codes that might be generated during program execution. Each error condition generates an interrupt.

If processor is not in extended mode, code execution will halt when the error is generated. Upon encountering an error, the processor will halt and output the error code to ERROR processor output. It will also emit a secondary error code in the fraction part of the ERROR processor output. If the processor is in extended mode, the corresponding interrupt will be called instead.

For example, a typical output might be 7.65536, which would indicate a memory read/write fault at address 65536.

The error code will be reset to zero when the CPU is reset. The following are error codes are defined by the current version of the processor:


CodeDescription
02End of program execution
03Division by zero
04Unknown opcode
05Internal processor error
06Stack error (overflow/underflow)
07Memory read/write fault
08MemBus fault
09Write access violation (page protection)
10Port read/write fault
11Page acccess violation (page protection)
12Read access violation (page protection)
13General processor fault
14Execute access violation (page protection)
15Address space violation
 

Error message: STOP detected
Occurs when: STOP/OPCODE 0 is executed
Cause: Abnomal program end
Result: None


Error message: Invalid opcode
Occurs when: Any of the branching instructions is executed (JMP, CALL, etc)
Cause: Jumping by offset that does not point to valid ZCPU instruction
Result: None


Error message: Unable to divide by zero
Occurs when: Second operand for DIV opcode is zero
Cause: User error
Result: LADD = 1


Error message: Unable to perform inversion on a zero
Occurs when: Calling FINV opcode with zero operand
Cause: User error
Result: LADD = 2


Error message: Unknown opcode detected in the instruction stream
Occurs when: Next executed instruction is not one of the recognized instructions
Cause: Invalid branching operation is performed (processor attempts to execute data)
Result: LADD = OPCODE NUMBER


Error message: Unable to execute instruction (microcode error)
Occurs when: An internal error has ocurred while executing microcode
Cause: Encountering a bug in processor
Result: LADD = 01


Error message: Unknown internal error
Occurs when: Never
Cause: None
Result: LADD = 02


Error message: Read error while fetching the instruction
Occurs when: Instruction decoder was not able to fetch all of the instruction bytes
Cause: JMP or CALL to a memory location outside of physical/logical range
Result: LADD = 12


Error message: Could not fetch an immediate byte for operand 1
Occurs when: Instruction decoder was not able to fetch an immediate byte for the first operand
Cause: JMP or CALL to a memory location outside of physical/logical range
Result: LADD = 22


Error message: Could not fetch an immediate byte for operand 2
Occurs when: Instruction decoder was not able to fetch an immediate byte for the second operand
Cause: JMP or CALL to a memory location outside of physical/logical range
Result: LADD = 32


Error message: Invalid RM parameter for operand 1
Occurs when: Never
Cause: None
Result: LADD = 42


Error message: Invalid RM parameter for operand 2
Occurs when: Never
Cause: None
Result: LADD = 52


Error message: Target operand not writeable
Occurs when: Never
Cause: None
Result: LADD = 62


Error message: Source operand not writeable (XCHG instruction only)
Occurs when: Never
Cause: None
Result: LADD = 72


Error message: Unknown opcode
Occurs when: Never
Cause: None
Result: LADD = 82


Error message: Stack overflow error
Occurs when: PUSH is executed
Cause: ESP register value becomes negative
Result: LADD = 0


Error message: Stack underflow error
Occurs when: POP is executed
Cause: ESP register is greater than ESZ
Result: LADD = ESZ


Error message: Stack read error
Occurs when: POP is executed
Cause: Unable to read value from memory
Result: LADD = ESP


Error message: Stack out of bounds error
Occurs when: RSTACK or SSTACK is executed
Cause: Requested value out of stack bounds
Result: LADD equals to requested index on stack


Error message: Read error: address does not exist
Occurs when: Processor attempts to read a value from outside of the internal RAM
Cause: No device is attached to the MemBus
Result: LADD equals to faulty address in memory


Error message: Read error: unable to read memory location
Occurs when: Processor attempts to read a value from outside of the internal RAM
Cause: A failure has occured while attempting to read a value (value out of the device address range)
Result: LADD equals to faulty address in memory


Error message: Write error: address does not exist
Occurs when: Processor attempts to write a value to outside of the internal RAM
Cause: No device is attached to the MemBus
Result: LADD equals to faulty address in memory


Error message: Write error: unable to write to memory location
Occurs when: Processor attempts to write a value to outside of the internal RAM
Cause: A failure has occured while attempting to write a value (value out of the device address range)
Result: LADD equals to faulty address in memory


Error message: MemBus device error
Occurs when: Processor attempts to read a value from outside of the internal RAM
Cause: Device currently attached to MemBus does not support hispeed interface
Result: LADD equals to faulty address in memory


Error message: IOBus device error
Occurs when: Processor attempts to read a value from a port
Cause: Device currently attached to IOBus does not support hispeed interface
Result: LADD = -PORT_NUMBER


Error message: Access violation
Occurs when: Attempting to write a value
Cause: EF flag set to 1, no write permission on referenced page, and referenced by the address has a smaller runlevel than the current one.
Result: LADD equals to faulty address in memory


Error message: Read error: unable to read a port
Occurs when: Processor attempts to read a value from a port
Cause: A failure has occured while attempting to read a value (value out of he device address range)
Result: LADD equals to faulty address in memory


Error message: Write error: unable to write to a port
Occurs when: Processor attempts to write a value to a port
Cause: A failure has occured while attempting to write a value (value out of the device address range)
Result: LADD equals to faulty address in memory


Error message: Unable to set page readonly
Occurs when: SPG opcode is executed
Cause: Current runlevel greater than runlevel of the refernced page
Result: LADD points to referenced page


Error message: Unable to set page read- and writeable
Occurs when: CPG opcode is executed
Cause: Current runlevel greater than runlevel of the refernced page
Result: LADD points to referenced page


Error message: Unable to set page flag
Occurs when: SPP opcode is executed
Cause: Current runlevel greater than runlevel of the refernced page
Result: LADD points to referenced page


Error message: Unable to clear page flag
Occurs when: CPP opcode is executed
Cause: Current runlevel greater than runlevel of the refernced page
Result: LADD points to referenced page


Error message: Unable to set page runlevel
Occurs when: SRL opcode is executed
Cause: Current runlevel greater than runlevel of the refernced page
Result: LADD points to referenced page


Error message: Unable to set page mapping
Occurs when: SMAP opcode is executed
Cause: Current runlevel greater than runlevel of the refernced page
Result: LADD points to referenced page


Error message: Access violation
Occurs when: Attempting to read a value
Cause: EF flag set to 1, no read permission on referenced page, and referenced by the address has a smaller runlevel than the current one.
Result: LADD equals to faulty address in memory


Error message: Referenced page outside ROM
Occurs when: ERPG opcode executed
Cause: Page referenced by the instruction could not be erased, because it lies outside the processor ROM
Result: LADD = 0


Error message: Referenced page outside ROM
Occurs when: WRPG opcode executed
Cause: Page referenced by the instruction could not be written, because it lies outside the processor ROM
Result: LADD = 0


Error message: Referenced page outside ROM
Occurs when: RDPG opcode executed
Cause: Page referenced by the instruction could not be read, because it lies outside the processor ROM
Result: LADD = 0


Error message: Code segment write violation
Occurs when: CS value is being used as a target operand
Cause: An attempt is being made to set a read-only segment register. Use CALLF or JMPF instructions to modify the CS register
Result: LADD = 1


Error message: Unable to read interrupt table
Occurs when: Could not fetch entry from interrupt table
Cause: Invalid interrupt table location set with LIDTR
Result: LADD = 2


Error message: Invalid interrupt number
Occurs when: Interrupt outside of the acceptable boundary (0..255) is called
Cause: Misuse of the INT or the EXTINT instructions
Result: LADD = 3


Error message: Unprivileged external interrupt call
Occurs when: External interrupt was called while the appropriate flag was not set in the interrupt table
Cause: 6th bit not set for the external interrupt in the interrupt table, and the external interrupt is called
Result: LADD = 4


Error message: Incompatible mode
Occurs when: Interrupt call in protected (compatibility) mode with less than 512 bytes of RAM
Cause: Not enough RAM to contain a complete interrupt table
Result: LADD = 5


Error message: Unable to push return data
Occurs when: Interrupt handler was unable to push return data onto the processor stack
Cause: Interrupt was called with no stack space available
Result: LADD = 6


Error message: Unable to call an interrupt
Occurs when: Interrupt is called without sufficient rights
Cause: Interrupt is called without sufficient rights to complete the jump
Result: LADD = 7


Error message: Unable to read page table
Occurs when: Page table is not located in valid memory range
Cause: Page table is not located in valid memory range
Result: LADD = 8


Error message: Attempting to execute a privileged instruction
Occurs when: Could not execute a privileged instruction due to runlevel
Cause: Calling one of the following instructios without sufficient rights: RD, WD, SPG, CPG, STI, CLI, STP, CLP, STEF, CLEF, EXTINT, ERPG, WRPG, RDPG, LIDTR, EXTRET, IDLE, STD2, STM, CLM, CPUGET, CPUSET, CPP, SPP, SRL, GRL, SMAP, GMAP
Result: LADD = Opcode number


Error message: Access violation
Occurs when: Execution is attempted on a memory page that does not allow execution
Cause: Invalid branching into the protected memory areas
Result: LADD = Page number


Error message: Access violation
Occurs when: Execution is attempted on a memory page that does not allow execution
Cause: Code execution has entered page that does not allow execution
Result: LADD = Page number


Error message: Address space violation
Occurs when: Memory is accessed by an invalid address
Cause: Address is not a 48-bit signed integer
Result: LADD = Referenced invalid address


This section describes instruction execution in complete detail. The processor will fetch instructions sequentially, decode them, and then execute.

During execution the XEIP register points to the instruction start address in global memory space, while the IP register is incremented as the instruction bytes are fetched in sequence.

The execution will also increment TMR register by amount of cycles that have been required to complete this instruction. The CODEBYTES register is incremented each time next byte is fetched by the instruction decoder (so the CODEBYTES register counts the total size of executable code executed so far).

//TMR = 170

MOV EAX,10; //1-cycle instructions
ADD EAX,EBX;

//TMR = 172

The instruction decoder will set CPAGE register to number of the current page (the page that fetched instruction is located on), and PPAGE to number of the page previous instruction was located on. Current page is determined by the location of the first fetched byte of the currently executed instruction.

When CPAGE and PPAGE values mismatch the permission check logic is triggered. Peforming a jump of any sort will run a different check logic, and will reset both CPAGE and PPAGE to page the target jump offset is located on.

The instructions can be either fixed-sized or variable-sized, see: ??.

This is the Pseudocode for the instruction decoding procedure:

// Calculate absolute execution address and set current page
XEIP = IP + CS
SetCurrentPage(floor(XEIP/128))

// Do not allow execution if we are not on kernel
// page, or not calling from kernel page
if (PCAP == 1) and (CurrentPage.Execute == 0) and 
   (PreviousPage.RunLevel <> 0) then
  Interrupt(14,CPAGE)
end

// Reset interrupts flags
INTR = 0
if NIF <> undefined then
  IF = NIF
  NIF = undefined
end

// Fetch instruction and RM byte
Opcode = Fetch()
RM = 0
isFixedSize = false

// Check if it is a fixed-size instruction
if ((Opcode >= 2000) and (Opcode < 4000)) or
   ((Opcode >= 12000) and (Opcode < 14000)) then
  Opcode = Opcode - 2000
  isFixedSize = true
end

// Fetch RM if required
if (OperandCount > 0) or
   (Precompile_Peek() == 0) or 
   (isFixedSize) then
  RM = Fetch()
end

// If failed to fetch opcode/RM then report an error
if INTR == 1 then
  IF = 1
  Interrupt(5,12)
end

// Check opcode runlevel
if (PCAP == 1) and (CurrentPage.Runlevel > RunLevel[Opcode]) then
  Interrupt(13,Opcode)
end

// Decode RM byte
dRM2 = floor(RM / 10000)
dRM1 = RM - dRM2*10000

// Default segment offsets
Segment1 = -4
Segment2 = -4

// Decode segment offsets
if Opcode > 1000 then
  if Opcode > 10000 then
    Segment2 = Fetch()

    Opcode = Opcode-10000
    if Opcode > 1000 then
      Segment1 = Fetch()
      
      Opcode = Opcode-1000
      Segment1 <> Segment 2
    else
      if isFixedSize then
        Fetch()
      end
    end
  else
    Segment1 = Fetch()
    Opcode = Opcode-1000
    if isFixedSize then
      Fetch()
    end
  end
elseif isFixedSize then
  Fetch()
  Fetch()
end

// If failed to fetch segment prefix then report an error
if INTR == 1 then
  Interrupt(5,12)
end

// Check if opcode is invalid
if opcode is not valid then
  Interrupt(4,Opcode)
else
  // Fetch immediate values if required
  if isFixedSize then
    OperandByte1 = Fetch()
    if INTR == 1 then 
      Interrupt(5,22)
    end
    OperandByte2 = Fetch()
    if INTR == 1 then 
      Interrupt(5,32)
    end
  else
    if OperandCount > 0 then
      if NeedFetchByte1 then
        OperandByte1 = Fetch()
        // If failed to read the byte, report an error
        if INTR == 1 then 
          Interrupt(5,22)
        end
      end
      if OperandCount > 1 then
        if NeedFetchByte2 then
          OperandByte2 = Precompile_Fetch() or 0
          // If failed to read the byte, report an error
          if INTR == 1 then
            Interrupt(5,32)
          end
        end
      end
    end
  end
  
  // Execute instruction
  Execute()

  // Write back the values
  if OperandCount > 0 then
    WriteBack(1)
    if OperandCount > 1 then
      WriteBack(2)
    end
  end
end

// Advance timers and counters
CODEBYTES = CODEBYTES + Instruction_Size
TMR = TMR + Instrucion_Cycles
TIMER = TIMER + TimerDT

// Set this page as previous (if it is executable)
XEIP = IP + CS
SetPreviousPage(floor(XEIP/128))  

The Zyelios CPU divides the entire accessable memory space into pages. Every page is 128 bytes in size. Pages are numbered sequentially; This means that addresses 0, 1, ... 127 belong to page 0, addresses 128 .. 255 belong to page 1, 256 .. 383 belong to page 2 and so on.

Every page can have a separate permission mask (read permission, write permission, execute permission) and runlevel.

Runlevel is a number that can be used to divide code into different permission levels, with higher levels reflecting kernel-mode code and lower levels reflecting user-level code. Each runlevel is a number between 0 and 255 inclusive. Higher permission levels are those that are closer to 0. Lower permission levels are those that are closer to 255. Code executing from a page with a higher permission level can read or write pages of a lower runlevel, but the opposite cannot occur.

SRL 1,12 //Set page 1 runlevel to 12
//Page 1 is addresses 128..255

SPP 5,0 //Set page 5 to be readable
CPP 6,1 //Make page 6 non-writeable
CPP 7,2 //Prevent code execution on page 7

Runlevel 0 is a special runlevel which will ignore all permission settings. This runlevel is also capable of running several privileged instructions such as CPUSET. All others runlevels follow page permissions.

When a page is marked non-readable, it can only be read from a page with a higher runlevel. When a page is marked non-writeable it can only be written from a page with smaller runlevel. When a page is marked non-executable, then only code from runlevel 0 can branch into this page.

Every page can also be remapped to any other page in memory. This means that every time the processor accessess target page, the data will be read from a different physical address. Any page can be remapped, even if the page already points to valid memory.

MOV #130,1234 //Set cell 2 of page 1 to 1234
SMAP 0,1 //Remap page 0 to page 1
MOV EAX,#2 //Read cell 2 of page 0 (but really read from page 1)
//EAX is now set to 1234

Page mapping and permissions settings are stored in the page table. The page table is active when the processor has the extended memory mode enabled (MF set to 1). If MF is set to 0 the page table will be stored internally; if it is set to 1 the page table will be stored in RAM. The actual permission checks are being made only when EF flag is set to 1, and the processor is running in the extended mode.

The PTBL internal register is an absolute pointer to the table start, and PTBE holds the number of entires in the table. It is possible to switch page tables during execution.

Every page table entry is 2 bytes in size. The first entry (entry number 0) is the default page. All other pages in the table correspond to memory pages.

If the address being accessed is not covered by any page listed in the page table, it will use the permissions set on the default page entry. You cannot perform memory mapping on pages which do not have entry in page table.

The first byte of every entry holds the runlevel and permission flags. The second byte holds index of page this page should be remapped to. Note that permission flags in the page table entry are inverted; 1 means the permission is restricted and 0 means that it is not.

Example on how to setup a page table:

PageTable:
  alloc 513*2 //512 page entries + default for 64KB of RAM
  
  .....
  
//Setup page table
CPUSET 37,PageTable //PTBL
CPUSET 38,512 //PTBE

//Enable extended memory mode
STM

//Indirect table manipulation
SMAP 0,1 //Map page 0 to 1
SPP 5,0 //Set page 5 to be readable

//Direct table manipulation
MOV ESI,PageTable; //Table offset
MOV ESI:#0,0xE0; //Set default page permissions to not allow anything
MOV ESI:#2,0; //Disable page 0
MOV ESI:#5,10; //Remap page 1 to page 10

The layout of the first byte of each entry is as follows:


BitDescription
00Is page disabled? Set to 1 to disable this page
01Is page remapped? Set to 1 to remap this page
02Page must generate external interrupt 30 (page trap) upon access
03Page trap overrides read/write (generates external interrupts 28, 29)
04Reserved
05Read permissions (0: allowed, 1: disabled)
06Write permissions (0: allowed, 1: disabled)
07Execute permissions (0: allowed, 1: disabled)
08..15Runlevel
 

Disabled pages will cause a memory fault when a read is attempted to any address in its range (as if page wasn’t in the address space). Remapped pages will use the second byte in the entry as the index for the physical page this page must be remapped to.

It is possible to trap page accesses by setting bits 2 and/or 3. This will cause any access to this page to generate external interrupt 28 (read), 29 (write), or 30 (access) with the page number being accessed as the parameter.

If bit 2 and is set, the external interrupt 30 will be generated on memory access attempt. If bit 3 is also set then the external interrupts 28 or 29 will be generated instead of the interrupt 30, and it will be possible to override the result of the memory read/write access (see: ?? for more info).

There are special rules when handling interrupts. Permissions to call the interrupts are defined by runlevel of the interrupt table. This means you can restrict the user program from calling certain interrupts, but the user program would be able to call lower runlevel code using other (non-restricted) interrupts. You can imagine it as if you were CALLing your interrupt routine from the interrupt table (and not from user program). This is the way you can protect your kernel code from reading/writing/executing while retaining the ability to run the code (via calling interrupts).

STEF //Enable extended mode
LIDTR 2048 //Interrupt table at pages 16-23

SRL 16,0 //Set runlevel of interrupts 0..31 to 0
SRL 17,1 //Set runlevel of interrupts 32..63 to 1
SRL 18,2 //Set runlevel of interrupts 64..95 to 2
SRL 19,3 //Set runlevel of interrupts 96..127 to 3

Current page when executing the instructions is determined by the CPAGE register. The actual current page is determined by the location of the first fetched byte of the currently executed instruction. The paging system performs permissions checks whenever PPAGE (page the previously executed instruction was located on) and CPAGE register values mismatch (which corresponds to crossing the page boundary).

Peforming a jump of any sort will run permission check logic, and will reset both CPAGE and PPAGE to page the target jump offset is located on.

It is only possible to cross page boundary or perform a jump if execute flag of the next (target) page is set to 1, or if it is set to 0 and previous page has runlevel 0.

All external access to the ZCPU inside memory will trigger the corresponding read/write permission checks. The runlevel for the external access is equal to the value of the XTRL register (external runlevel). By default the external memory access operations have a runlevel of 0.

To sum it up, these are all the permission and logic checks performed by the paging system:

The Zyelios CPU can be configured to have built-in ROM and RAM. This allows you to store some program code right on the processor chip. The contents of ROM will be written into RAM every time the processor is reset. The ROM can be read from and written to in software.

There are three opcodes to work with the internal ROM: ERPG (erase ROM page), WRPG (write ROM page), RDPG (read ROM page).

See example of use:

ERPG 4 //Erase some data from ROM
WRPG 4 //Write this block of data to ROM
       //After CPU reset it will be still preserved
RDPG 4 //Restore this page from ROM

ORG 512 //Put on page 4
SOME_AREA:
 ... some data ...

The ZCPU can work with integer values of variable bit width. It supports 8, 16, 32, and 48 bit integers, and has a complete instruction set to work with them.

There are bitwise logic instructions which work on all bits of the integer number. They are BAND, BOR, BXOR, BSHL, BSHR, BNOT. For example:

MOV  EAX,105 //1101001
BAND EAX,24  //0011000
//EAX = 8      0001000

BOR EAX,67   //1000011
//EAX = 75   //1001011

BXOR EAX,15  //0001111
//EAX = 68   //1000100

BSHL EAX,2
//EAX = 272  //100010000

BSHR EAX,4
//EAX = 17   //0010001

BNOT EAX
//EAX = -18  //111111111111111111101110

Also there are additional operations that work on separate bits of the number. They are BIT, SBIT, TBIT, CBIT.

The BIT operation tests whether specific bit of the number is set. It is possible to check result of this comparsion by using a conditional jump:

MOV EAX,105
BIT EAX,0
JNZ LABEL //Jump succeeds (bit is not zero)

BIT EAX,1
JNZ LABEL //Jump fails (bit is zero)

The SBIT and CBIT clear and set the specific bit of the number. TBIT instruction will toggle that bit:

MOV EAX,105 //1101001
SBIT EAX,1
//EAX = 107   1101011

CBIT EAX,6
//EAX = 43    0101011

TBIT EAX,0
//EAX = 42    0101010

Certain instructions in the ZCPU support using the BLOCK instruction before them to specify a memory block the instruction must be executed on. For example it is possible to set permissions of an entire memory block at once:

BLOCK 1024,8192 //A 8KB block at offset 1024
SRL 0,4 //Set runlevel of all pages in this block to 4

The first operand of the BLOCK instruction is the memory offset the block must start from (must be aligned on page boundary for the paging instructions), and the second operand is the block size (the size must be divisable by 128 if this is a block for the page permission instructions).

The BLOCK instruction sets the two internal registers BlockStart, BlockSize. After the instruction that supports memory blocks has executed, it will reset block size back to zero.

The block size must be non-zero for the instruction to work on that block.

It is possible that an interrupt will occur between the BLOCK instruction and the target instruction. It is advised against using BLOCK instruction inside an interrupt handler!

The following instructions support this psuedo-prefix: SPP, CPP, SRL, SMAP.

There are built-in instructions in the ZCPU for quickly moving and working with large arrays of data in an easy way.

It’s possible to copy one area of memory to another using the MCOPY instruction:

mov ESI,source_data;
mov EDI,dest_data;
mcopy 5; //Copy 5 bytes

string source_data,"Apple";
string dest_data,"A quick fox";

After running this code "dest_data" string will be equal to "Appleck fox".

Two blocks of memory can be swapped using the MXCHG instruction:

mov ESI,source_data;
mov EDI,dest_data;
mxchg 5; //Swap 5 bytes

string source_data,"Apple";
string dest_data,"A quick fox";

After running this code "dest_data" string will be equal to "Appleck fox", and "source_data" will be equal to "A qui".

And it’s also possible to shift all values in block of data:

mov ESI,source_data;

mshift 13,2; //Shift 13 bytes by 2 cells to the right
//source_data = "quick foxA "

mshift 13,-4; //Shift 13 bytes by 4 cells to the left
//source_data = "oxA quick f"

string source_data,"A quick fox";

The offset by which data is shifted must be less or equal to amount of bytes that are being shifted. But it is possible to specify offsets larger than the data size, and that will reverse the direction of shift, and will not wrap the data around.

All of these instructions can work on up to 8192 bytes in a memory block.

The ZCPU processor has two extra instructions to provide stack frame support. Stack frame allows to prevent stack corruption by induvidual subroutines, and it also provides an easy way to create local variables on stack, and pass values to the subroutine via stack.

ENTER X instruction is same as the following code:

Push(EBP)
EBP = ESP + 1
ESP = ESP - X

It will set EBP register to stack frame base (value of stack pointer at function entrypoint), and add some empty space on stack for the local variables.

LEAVE instruction corresponds to the following code:

ESP = EBP - 1
EBP = Pop()

It will restore the stack frame base of the previous function (if it exists), and restore stack.

Interrupts work in a way similar to processor subroutines, but they can also be triggered if an error occurs while executing an instruction. In this case the instruction execution will not be completed, and the processor will attempt to handle this interrupt.

Interrupt handling is only available in extended mode of the processor. If interrupt occurs with extended mode disabled the processor will halt its execution and report error code to the external output:

lidtr interrupt_table; //Load interrupt table
stef; //Enable extended mode

int 40; //Handle interrupt 40
mov #0.123,10; //Handle interrupt 15 (address space violation)

clef; //Disable extended mode
int 40; //Will halt processor execution

The processor determines which subroutine it must call by checking the interrupt descriptor table/interrupt table. It’s a table located somewhere in the processor memory which holds addresses to all interrupt handlers, and extra flags that alter the way the interrupt is handled.

The pointer to this table must be specified using the LIDTR instruction. The interrupt table usually has 256 entries for 256 possible interrupts, but it may have less entries if NIDT (interrupt table entry count) register is changed. If processor attempts to handle an interrupt which is not in the table, it will skip it:

cpuset 52,32; //Only have 32 entries
lidtr interrupt_table; //Load interrupt table
stef; //Enable extended mode

int 31; //Will be handled
int 32; //Will be skipped

Each entry in the interrupt table has the following format:


ByteDescription
0Interrupt handler IP
1Interrupt handler CS (if required, see flags)
2Reserved (must be 0)
3Flags
 

There are the following flags that can be set:


BitDescription
3CMPR register will be set to 1 if an interrupt has occured
41 if interrupt should not set CS
5Interrupt enabled (active)
6This interrupt is an external interrupt
0Interrupt handler IP

If bit 3 is set, the interrupt will set CMPR register to 1. This can be used for making simple exception handlers:

If bit 4 is set, the interrupt will not set the code segment (it sets it by default). The interrupt will only be called if bit 5 is set, if it’s clear then it will be ignored (unless it’s the interrupt 0 or 1, if so, then they will act as if called without extended mode enabled).

This interrupt can only be called as an external interrupt if bit 6 is set. Otherwise it will generate interrupt 13 (general processor fault) instead.

There are special rules for using the interrupt table if paging is used. see: ?? for more information on how permissions will work if that’s the case.

The interrupt handler is fairly complex. The Pseudocode for the interrupt handler is listed below:

// Interrupt is active, lock the bus to prevent any further read/write
INTR = 1
BusLock = 1
  
// Set registers
LINT = interruptNo
LADD = interruptParameter or XEIP

// Output the error externally
SignalError(interruptNo,LADD)
  
if IF == 1 then
  if EF == 1 then // Extended mode
    // Boundary check
    if (interruptNo < 0) or (interruptNo > 255) then
      if not cascadeInterrupt then Interrupt(13,3) end
    end

    // Check against boundaries of the interrupt table
    if interruptNo > NIDT-1 then
      if interruptNo == 0 then Reset = 1 end
      if interruptNo == 1 then Clk = 0 end
    end

    // Calculate absolute offset in the interrupt table
    interruptOffset = IDTR + interruptNo*4

    // Disable bus lock, set the current page for read operations
    BusLock = 0
    SetCurrentPage(interruptOffset)

    IF = 0
    INTR = 0
    IP    = ReadCell(interruptOffset+0)
    CS    = ReadCell(interruptOffset+1)
            ReadCell(interruptOffset+2)
    FLAGS = ReadCell(interruptOffset+3)
    IF = 1
    
    if INTR == 1 then
      if not cascadeInterrupt then Interrupt(13,2) end
    else
      INTR = 1
    end
    
    // Set previous page to trigger same logic as if
    //  CALL-ing from a privilegied page
    SetCurrentPage(XEIP)
    SetPrevPage(interruptOffset)
    BusLock = 1

    if isExternal and (FLAGS[6] <> 1) then
      if not cascadeInterrupt then Interrupt(13,4) end
    end
    
    if FLAGS[5] == 1 then
      // Push return data
      BusLock = 0
      IF = 0
      INTR = 0
      Push(IP)
      Push(CS)
      IF = 1
      
      if INTR == 1 then
        if not cascadeInterrupt then Interrupt(13,6) end
      else
        INTR = 1
      end
      BusLock = 1

      // Perform a short or a long jump
      IF = 0
      INTR = 0
      if FLAGS[4] == 0
      then Jump(IP,CS)
      else Jump(IP)
      end
      IF = 1
      
      if INTR == 1 then
        if not cascadeInterrupt then Interrupt(13,7) end
      else
        INTR = 1
      end
      
      // Set CMPR
      if FLAGS[3] == 1 then
        CMPR = 1
      end
    else
      if interruptNo == 0 then
        Reset()
      end
      if interruptNo == 1 then
        Clk = 0
      end
      if FLAGS[3] == 1 then
        CMPR = 1
      end
    end
  end

  if (EF == 0) then // Normal mode
    if (interruptNo < 0) or 
       (interruptNo > 255) or 
       (interruptNo > NIDT-1) then
      // Interrupt not handled
      Exit()
    end
    if interruptNo == 0 then Reset = 1 end
    if interruptNo ~= 31 then Clk = 0 end
  end
end
  
// Unlock the bus
BusLock = 0

(no chapter)

(no chapter)

The ZCPU has an internal timer which can be used for precise time measurements during the code execution. The timer can also be used for triggering interrupts at precise time intervals.

The TIMER instruction can be used to fetch the internal timer value in seconds:

TIMER EAX
//EAX is now equal to amount of seconds since CPU startup

It is possible to configure the timer to trigger external interrupt after certain amount of seconds, or a certain amount of cycles has passed by configuring one of the special registers.

TimerMode register controls the timer mode. If it’s set to 0 the timer will be disabled. If TimerMode is set to 1 the timer will use TMR register as counter source, and if TimerMode is set to 2 the timer will use the TIMER register as the source.

TimerRate sets the number of cycles, or the number of seconds that must pass before the timer will trigger. TimerPrevTime register stores value of the TMR or the TIMER register when timer fired the last time.

TimerAddress is the number of the interrupt that will be called when the timer fires. The interrupt call will be an external interrupt call.

Changing value of the TimerMode register will reset the TimerPrevTime register.

This is an example of how to setup timer:

CPUSET 65,90; //Trigger once per 90 cycles
CPUSET 67,40; //Trigger external interrupt #40

CPUSET 64,1;  //Enable timer to count cycles

It could also be initialized for an interval in seconds:

CPUSET 65,1.5; //Trigger once per 1.5 seconds
CPUSET 67,40;  //Trigger external interrupt #40

CPUSET 64,2;   //Enable timer to count seconds

If precision in intervals between timer firing is required, the timer can be reset in the interrupt caller (if required):

ExternalInterrupt:
  CLI; //Disable interrupts
  
  .....
  
  CPUGET EAX,29; //Read cycles counter
  ADD EAX,4;     //Account for 4 'missing' cycles
  CPUSET 66,EAX; //Write last timer fire time
  
  STI; //Enable interrupts
EXTRET;

Or alternatively it’s possible to reset it by swapping the timer mode:

ExternalInterrupt:
  CLI; //Disable interrupts
  
  .....
  
  CPUSET 64,1; //Restart timer   
  STI; //Enable interrupts
EXTRET;

(using VMODE,VADD,etc)

(Using hardware debug mode)

The Zyelios CPU will cache executed microcode for faster execution. This increases code execution speed, but some penalties apply. The processor will decode instructions the first time they are encountered, and all subsequent executions will use the cached execution information.

The Zyelios CPU will cache executed microcode for faster execution. The processor will decode instructions the first time they are encountered, and all subsequent executions will use the cached execution information. The cache will provide very little benefit for code that is executed infrequently, but code that is executed frequently will run much faster.

The penalties caused by caching system mostly concern the flow of execution and the internal optimizations the processor makes:

  1. While executing a single cached block of microcode the processor uses fast-access cached registers. This means that register values are not changed until the block finishes executing.
  2. The processor only caches instructions the first time they are decoded. It will invalidate cache if the processor itself writes or reads to the memory, but it does not invalidate cache if any other device writes to an external source.
  3. Cached microcode blocks contain up to 8 instructions each. They will end prematurely on unconditional branches and jumps.
  4. All reading and writing operations might be delayed by several instructions until they actually happen. There are only very specific ways to make sure your I/O operation really happens.

Each instruction in the ZCPU begins with a single byte which identifies the instruction to be executed. If the instruction has operands, then a RM byte (register/memory selector byte) follows. This extra byte specifies what kind of operands are being passed to the instruction.

Instruction number may also encode information about use of segment prefix, or about the local execution mode (see: ??). The instruction number can fall into one of the following ranges:

The instruction may be followed by several extra bytes that specify segment prefix, constant values, etc. The constant values are always the last bytes in the instruction data. Here’s an example of a wide variety of different instructions:

STEF            48
INC EAX         20, 1
MOV EAX,10      14, 1, 10
ADD EAX,ESP     10, 70001
DIV EBX,ES:ECX  10013, 290002, 4
ADD R0,#R2      10, 20822048
MOV #100,#500   14, 250025, 100, 500
MOV EAX:#50,GS  1014, 130025, 9, 50

The RM byte consists of two parts - the RM selector for the first and the second operand: RM = RM1 + 10000 · RM2.

For example RM bytes from the example in the previous chapter could be decoded like this:

                RM        RM1   RM2
STEF            n/a       n/a   n/a
INC EAX         1         1     n/a
MOV EAX,10      1         1     0
ADD EAX,ESP     70001     1     7
DIV EBX,ES:ECX  290002    2     29
ADD R0,#R2      20822048  2048  2082
MOV #100,#500   250025    25    25
MOV EAX:#50,GS  130025    25    13

These RM selectors are supported right now:


SelectorOperandSource/target
0123Constant value
1EAXRegister EAX
2EBXRegister EBX
3ECXRegister ECX
4EDXRegister EDX
5ESIRegister ESI
6EDIRegister EDI
7ESPRegister ESP
8EBPRegister EBP
9CSRegister CS
10SSRegister SS
11DSRegister DS
12ESRegister ES
13GSRegister GS
14FSRegister FS
15KSRegister KS
16LSRegister LS
17#EAX, ES:#EAXMemory cell EAX + segment
18#EBX, ES:#EBXMemory cell EBX + segment
19#ECX, ES:#ECXMemory cell ECX + segment
20#EDX, ES:#EDXMemory cell EDX + segment
21#ESI, ES:#ESIMemory cell ESI + segment
22#EDI, ES:#EDIMemory cell EDI + segment
23#ESP, ES:#ESPMemory cell ESP + segment
24#EBP, ES:#EBPMemory cell EBP + segment
25#123, ES:#123Memory cell by constant value
26ES:EAXRegister EAX + segment
27ES:EBXRegister EBX + segment
28ES:ECXRegister ECX + segment
29ES:EDXRegister EDX + segment
30ES:ESIRegister ESI + segment
31ES:EDIRegister EDI + segment
32ES:ESPRegister ESP + segment
33ES:EBPRegister EBP + segment
34No syntaxMemory cell EAX + constant
35No syntaxMemory cell EBX + constant
36No syntaxMemory cell ECX + constant
37No syntaxMemory cell EDX + constant
38No syntaxMemory cell ESI + constant
39No syntaxMemory cell EDI + constant
40No syntaxMemory cell ESP + constant
41No syntaxMemory cell EBP + constant
42No syntaxRegister EAX + constant
43No syntaxRegister EBX + constant
44No syntaxRegister ECX + constant
45No syntaxRegister EDX + constant
46No syntaxRegister ESI + constant
47No syntaxRegister EDI + constant
48No syntaxRegister ESP + constant
49No syntaxRegister EBP + constant
50ES:123Constant value plus segment
1000PORT0Port 0
1001PORT1Port 1
............
2023PORT1023Port 1023
2048R0Extended register R0
............
2079R31Extended register R31
2080#R0, ES:#R0Memory cell R0 + segment
............
2111#R31, ES:#R31Memory cell R31 + segment
2112ES:R0Extended register R0 + segment
............
2143ES:R31Extended register R31 + segment
2144No syntaxMemory cell R0 + constant
............
2175No syntaxMemory cell R31 + constant
2176No syntaxExtended register R0 + constant
............
2207No syntaxExtended register R31 + constant
 

Segment offset is specified by a byte that follows the RM byte. For example, here is how the same instruction is encoded with different segment prefixes:

MOV EAX,EBX        14,20001
MOV LS:EAX,EBX     1014,20027,8
MOV EAX,LS:EBX     10014,280001,8
MOV LS:EAX,LS:EBX  11014,280027,8,8
MOV R0:EAX,EBX     1014,20027,17
MOV EAX,R0:EBX     10014,280001,17
MOV R0:EAX,R0:EBX  11014,280027,17,17

There can be the following segment prefixes. The negative-value prefixes are obsolete now, and are only preserved for backwards compatibility:

ValueRegisterValueRegister
-02CS01CS
-03SS02SS
-04DS03DS
-05ES04ES
-06GS05GS
-07FS06FS
-08KS07KS
-09LS08LS
-10EAX09EAX
-11EBX10EBX
-12ECX11ECX
-13EDX12EDX
-14ESI13ESI
-15EDI14EDI
-16ESP15ESP
-17EBP16EBP
17R0  
......  
47R32  

The Zyelios CPU supports two machine code formats: fixed-length and variable-length. The CPU automatically determines which type is in use in order to retain binary compatibility. Several simple rules are employed for this detection.

Example of instructions in the default mode:

STEF            48
INC EAX         20, 1
MOV EAX,10      14, 1, 10
ADD EAX,ESP     10, 70001
DIV EBX,ES:ECX  10013, 290002, 4
ADD R0,#R2      10, 20822048
MOV #100,#500   14, 250025, 100, 500
MOV EAX:#50,GS  1014, 130025, 9, 50

Example of instructions encoded in fixed-size mode:

STEF            2048,0,-4,-4,0,0
INC EAX         2020,1,-4,-4,0,0
MOV EAX,10      2014,1,-4,-4,0,10
ADD EAX,ESP     2010,70001,-4,-4,0,0
DIV EBX,ES:ECX  12013,290002,-4,4,0,0
ADD R0,#R2      2010,20822048,-4,-4,0,0
MOV #100,#500   2014,250025,-4,-4,100,500
MOV EAX:#50,GS  3014,130025,9,-4,50,0

Same instructions in the ZCPU compatibility mode:

STEF            48, 0
INC EAX         20, 1
MOV EAX,10      14, 1, 10
ADD EAX,ESP     10, 70001
DIV EBX,ES:ECX  10013, 290002, 4
ADD R0,#R2      10, 20822048
MOV #100,#500   14, 250025, 100, 500
MOV EAX:#50,GS  1014, 130025, 9, 50

The processor has several internal registers which are used to store the internal processor state or control advanced processor features. It is possible to read or write most of these registers using CPUSET and CPUGET.

For example:

CPUGET EAX,24 //Read register 24 into EAX 
              //24 is the interrupt descriptor table pointer
CPUSET 9,EBX //Set register 9 (stack size) to EBX

CPUGET EAX,1000 //Invalid register will set EAX to 0

The registers XEIP, CPAGE, PPAGE, SerialNo, CODEBYTES, TimerDT, RAMSize are read-only - it is not possible to change their value by any means.

Changing value of the IP or the CS register is possible, and will be handled as a far jump.

See the section 2 for description of how some of the registers are used.


MnemonicNumberDescription
IP00Instruction pointer
EAX01General purpose register A
EBX02General purpose register B
ECX03General purpose register C
EDX04General purpose register D
ESI05Source index
EDI06Destanation index
ESP07Stack pointer
EBP08Base pointer
ESZ09Stack size
CS16Code segment
SS17Stack segment
DS18Data segment
ES19Extra segment
GS20User segment
FS21User segment
KS22Key segment
LS23Library segment
IDTR24Interrupt descriptor table pointer
CMPR25Comparsion result register
XEIP26Pointer to start of currently executed instruction
LADD27Current interrupt code
LINT28Current interrupt number
TMR29Instruction/cycle counter
TIMER30Internal precise timer
CPAGE31Current page number
IF32Interrupts enabled flag
PF33Protected mode flag
EF34Extended mode flag
NIF35Next cycle interrupt enabled flag state
MF36Extended memory mapping flag
PTBL37Page table offset
PTBE38Page table number of entries
PCAP39Processor paging system capability
RQCAP40Processor delayed memory request capability
PPAGE41Previous page ID
MEMRQ42Type of the memory request
RAMSize43Amount of internal memory
External44External I/O operation
BusLock45Is bus locked for read/write
Idle46Should CPU skip some cycles
INTR47Handling an interrupt
SerialNo48Processor serial number
CODEBYTES49Amount of bytes executed so far
BPREC50Binary precision level
IPREC51Integer precision level
NIDT52Number of interrupt descriptor table entries
BlockStart53Start offset of the block
BlockSize54Block size
VMODE55Vector mode (2: 2D, 3: 3D)
XTRL56Runlevel for external memory access
HaltPort57Halt until this port changes value
HWDEBUG58Hardware debug mode active
DBGSTATE59Hardware debug mode state
DBGADDR60Hardware debug mode address/parameter
CRL61Current runlevel
TimerDT62Current timer discrete step
MEMADDR63Address reqested by the memory operation
TimerMode64Timer mode (off, instructions, seconds)
TimerRate65Timer rate
TimerPrevTime66Previous timer fire time
TimerAddress67Number of external interrupt to call when timer fires
 

This section describes the primary set of opcodes available in the ZCPU. Not all of these are present in ZGPU or ZSPU though. The following primary set instructions are not available in those architectures: SPG, CPG, HALT, IRET, STI, CLI, STEF, CLEF, EXTINT, ERPG, WRPG, RDPG, LIDTR, EXTRET, STM, CLM, SPP, CPP, SRL, GRL, SMAP, GMAP.

Mnemonic: STOP
Encoding: 000




Triggers interrupt #2 (equivalent to INT 2). Will stop the processor execution if extended mode is not enabled.

Used by the processor to detect end of program/invalid jump error, because this instruction is usually the result of invalid jump. Acts as NOP if interrupts are disabled.

Pseudocode:

Interrupt(2,0)

Mnemonic: JNE/JNZ X
Encoding: 001 RM [Segment1] [Constant1]




Jumps to the specified address if in previous comparsion two values were not equal to each other.

Can also be used to check if result of the previous bit operation was not zero:

BIT EAX,2
JNZ LABEL

This code will jump to a label if the second bit is set to 1.

May trigger error 14 (execution access violation) if instruction execution moves into a restricted memory area when extended mode is enabled. see: ?? for information about memory protection and paging.

Pseudocode:

if CMPR <> 0 then
  Jump(X)
end

Mnemonic: JMP X
Encoding: 002 RM [Segment1] [Constant1]




Unconditional jump to the specified address.

May trigger error 14 (execution access violation) if instruction execution moves into a restricted memory area when extended mode is enabled. see: ?? for information about memory protection and paging.

Pseudocode:

Jump(X)

Mnemonic: JG/JNLE X
Encoding: 003 RM [Segment1] [Constant1]




Jumps to target address if the previous comparsion resulted in greater, or not less or equal result.

May trigger error 14 (execution access violation) if instruction execution moves into a restricted memory area when extended mode is enabled. see: ?? for information about memory protection and paging.

Pseudocode:

if CMPR > 0 then
  Jump(X)
end

Mnemonic: JGE/JNL X
Encoding: 004 RM [Segment1] [Constant1]




Jumps to target address if the previous comparsion resulted in greater or equal, or not less result.

May trigger error 14 (execution access violation) if instruction execution moves into a restricted memory area when extended mode is enabled. see: ?? for information about memory protection and paging.

Pseudocode:

if CMPR >= 0 then
  Jump(X)
end

Mnemonic: JL/JNGE X
Encoding: 005 RM [Segment1] [Constant1]




Jumps to target address if the previous comparsion resulted in less, or not greater or equal result.

May trigger error 14 (execution access violation) if instruction execution moves into a restricted memory area when extended mode is enabled. see: ?? for information about memory protection and paging.

Pseudocode:

if CMPR < 0 then
  Jump(X)
end

Mnemonic: JLE/JNG X
Encoding: 006 RM [Segment1] [Constant1]




Jumps to target address if the previous comparsion resulted in less or equal, or not greater result.

May trigger error 14 (execution access violation) if instruction execution moves into a restricted memory area when extended mode is enabled. see: ?? for information about memory protection and paging.

Pseudocode:

if CMPR <= 0 then
  Jump(X)
end

Mnemonic: JE/JZ X
Encoding: 007 RM [Segment1] [Constant1]




Jumps to the specified address if in previous comparsion two values were equal to each other.

Can also be used to check if result of the previous bit operation was zero:

BIT EAX,2
JZ LABEL

This code will jump to a label if the second bit is set to 0.

May trigger error 14 (execution access violation) if instruction execution moves into a restricted memory area when extended mode is enabled. see: ?? for information about memory protection and paging.

Pseudocode:

if CMPR <> 0 then
  Jump(X)
end

Mnemonic: CPUID X
Encoding: 008 RM [Segment1] [Constant1]




The CPUID instruction will write various information about the processor itself into the EAX register. This can be used to check for the processor capabilities.

The first parameter passed into the instruction defines what kind of the information is required:


OperandDescription
0Processor version (current version: 10.00, reported as 1000)
1Amount of internal RAM
2Processor type
3Amount of internal ROM
 

The result will be written into the EAX register. Processor type can be one of the following:


EAXDescription
0ZCPU
1ZGPU beta version
2ZSPU
3ZGPU
 

Pseudocode:

EAX = CPUID[X]

Mnemonic: PUSH X
Encoding: 009 RM [Segment1] [Constant1]




Pushes a value to the processor stack (see: ?? for more information on the processor stack). Will check for interrupt overflow by comparing the new stack pointer to zero.

Uses the ESP register as the stack pointer, and the ESZ register as the stack size pointer.

Example of use:

PUSH 10
PUSH 20

POP EAX //EAX is now 20

May trigger error 6 (stack overflow/underflow) if the stack pointer goes outside the allowable limits.

May trigger error 7 (memory read/write fault) if the instruction was not able to perform a memory write/read operation due to an error.

Pseudocode:

MEMORY[ESP+SS] = X
ESP = ESP - 1

if ESP < 0 then
  ESP = 0
  Interrupt(6,ESP)
end

Mnemonic: ADD X,Y
Encoding: 010 RM [Segment1] [Segment2] [Constant1] [Constant2]




Adds two values together, and writes the result to the first operand.

Pseudocode:

X = X + Y

Mnemonic: SUB X,Y
Encoding: 011 RM [Segment1] [Segment2] [Constant1] [Constant2]




Subtracts second operand from the first one, and writes the result to the first operand.

Pseudocode:

X = X - Y

Mnemonic: MUL X,Y
Encoding: 012 RM [Segment1] [Segment2] [Constant1] [Constant2]




Muliplies two values together, and writes the result to the first operand.

Pseudocode:

X = X * Y

Mnemonic: DIV X,Y
Encoding: 013 RM [Segment1] [Segment2] [Constant1] [Constant2]




Divides the first operand by the second operand, and writes the result to the first operand. Checks for the division by zero error.

May trigger error 3 (division by zero) if the second operand was equal to zero.

Will not trigger division by zero error if interrupt flag register IF is set to 0.

Pseudocode:

if Y <> 0 then
  X = X / Y
else
  Interrupt(3,0)
end

Mnemonic: MOV X,Y
Encoding: 014 RM [Segment1] [Segment2] [Constant1] [Constant2]




Copies the contents of the second operand to the first operand.

Pseudocode:

X = Y

Mnemonic: CMP X,Y
Encoding: 015 RM [Segment1] [Segment2] [Constant1] [Constant2]




Compares the two operands together, and remembers the result of this comparsion.

This instruction is used in conjunction with the conditional branching instructions (see ??), for example:

CMP EAX,EBX
JG  LABEL1 //Jump if EAX >  EBX
JLE LABEL2 //Jump if EAX <= EBX
JE  LABEL3 //Jump if EAX  = EBX

Pseudocode:

CMPR = X - Y

Mnemonic: MIN X,Y
Encoding: 018 RM [Segment1] [Segment2] [Constant1] [Constant2]




Finds the smaller of the two values, and writes it to the first operand. For example:

MOV EAX,100
MOV EBX,200
MIN EBX,EAX //Sets EBX to 100

Pseudocode:

if X > Y then
  X = Y
end

Mnemonic: MAX X,Y
Encoding: 019 RM [Segment1] [Segment2] [Constant1] [Constant2]




Finds the bigger of the two values, and writes it to the first operand. For example:

MOV EAX,100
MOV EBX,200
MAX EAX,EBX //Sets EAX to 200

Pseudocode:

if X < Y then
  X = Y
end

Mnemonic: INC X
Encoding: 020 RM [Segment1] [Constant1]




Increments the target operand by one. For example:

MOV EAX,100
INC EAX //EAX is now 101

Pseudocode:

X = X + 1

Mnemonic: DEC X
Encoding: 021 RM [Segment1] [Constant1]




Decrements the target operand by one. For example:

MOV EAX,100
DEC EAX //EAX is now 99

Pseudocode:

X = X - 1

Mnemonic: NEG X
Encoding: 022 RM [Segment1] [Constant1]




Negates the target operand (changes its sign). For example:

MOV EAX,123
NEG EAX //EAX is now -123

MOV EBX,0
NEG EBX //EBX is now -0, signed zero

Pseudocode:

X = -X

Mnemonic: RAND X
Encoding: 023 RM [Segment1] [Constant1]




Generates a random value between 0.0 and 1.0. The random seed is automatically generated by the processor hardware.

Range inclusive of 0.0 and 1.0.

Pseudocode:

X = RANDOM(0.0,1.0)

Mnemonic: LOOP X
Encoding: 024 RM [Segment1] [Constant1]




Performs a conditional jump to a certain label as long as ECX does not equal zero. Is usually used for creating loops in programs:

MOV ECX,100;
LABEL:
  <...>
LOOP ECX; //Repeats 100 times

Will only terminate when ECX is equal to zero.

Pseudocode:

if ECX <> 0 then
  ECX = ECX - 1
  IP = X
end

Mnemonic: LOOPA X
Encoding: 025 RM [Segment1] [Constant1]




Performs a conditional jump to a certain label as long as EAX does not equal zero. Is usually used for creating loops in programs:

MOV EAX,100;
LABEL:
  <...>
LOOP EAX; //Repeats 100 times

Will only terminate when EAX is equal to zero. Similar to the LOOP instruction.

Pseudocode:

if EAX <> 0 then
  EAX = EAX - 1
  IP = X
end

Mnemonic: LOOPB X
Encoding: 026 RM [Segment1] [Constant1]




Performs a conditional jump to a certain label as long as EBX does not equal zero. Is usually used for creating loops in programs:

MOV EBX,100;
LABEL:
  <...>
LOOP EBX; //Repeats 100 times

Will only terminate when EBX is equal to zero. Similar to the LOOP instruction.

Pseudocode:

if EBX <> 0 then
  EBX = EBX - 1
  IP = X
end

Mnemonic: LOOPD X
Encoding: 027 RM [Segment1] [Constant1]




Performs a conditional jump to a certain label as long as EDX does not equal zero. Is usually used for creating loops in programs:

MOV EDX,100;
LABEL:
  <...>
LOOP EDX; //Repeats 100 times

Will only terminate when EDX is equal to zero. Similar to the LOOP instruction.

Pseudocode:

if EDX <> 0 then
  EDX = EDX - 1
  IP = X
end

Mnemonic: SPG X
Encoding: 028 RM [Segment1] [Constant1]




Makes the specific page read-only. It will clear the write flag of the page specified by the operand, and also set the read flag of the page specified by the operand. see: ?? for more information on the paging system.

Example of use:

SPG 1 //Set addresses 128..255 read-only
SPG 2 //Set addresses 256..511 read-only

CPG 1 //Make the 128..255 range writeable again

May trigger error 11 (page acccess violation) if the instruction attempts to access a page, and the target page runlevel is greater than the current page runlevel.

This instruction is privileged, meaning it can only be executed from the code page which has runlevel of 0.

In case of an error it will pass the target page number as an interrupt parameter.

Pseudocode:

if CurrentPage.Runlevel < Page[X].Runlevel then
  Page[X].Read = 1
  Page[X].Write = 0
else
  Interrupt(11,X)
end

Mnemonic: CPG X
Encoding: 029 RM [Segment1] [Constant1]




Makes the specific page readable and writeable. It will set the write flag of the page specified by the operand, and also set the read flag of the page specified by the operand. see: ?? for more information on the paging system.

Example of use:

SPG 1 //Set addresses 128..255 read-only
SPG 2 //Set addresses 256..511 read-only

CPG 1 //Make the 128..255 range writeable again

May trigger error 11 (page acccess violation) if the instruction attempts to access a page, and the target page runlevel is greater than the current page runlevel.

This instruction is privileged, meaning it can only be executed from the code page which has runlevel of 0.

In case of an error it will pass the target page number as an interrupt parameter.

Pseudocode:

if CurrentPage.Runlevel < Page[X].Runlevel then
  Page[X].Read = 1
  Page[X].Write = 1
else
  Interrupt(11,X)
end

Mnemonic: POP X
Encoding: 030 RM [Segment1] [Constant1]




Pops a value off the processor stack (see: ?? for more information on the processor stack). Will check for underflow by comparing the new stack pointer to the stack size register.

Uses the ESP register as the stack pointer, and the ESZ register as the stack size pointer.

Example of use:

PUSH 10
PUSH 20

POP EAX //EAX is now 20

May trigger error 6 (stack overflow/underflow) if the stack pointer goes outside the allowable limits.

Pseudocode:

ESP = ESP + 1
if ESP > ESZ then
  ESP = ESZ
  Interrupt(6,ESP)
end

Mnemonic: CALL X
Encoding: 031 RM [Segment1] [Constant1]




Calls a subroutine of the program. The subroutine will return to this point of execution by executing the RET instruction.

This instruction pushes the current instruction pointer to stack, and will restore it upon executing the RET instruction. Any damage to the stack data might cause subroutine to fail to return to the original calling point.

The opcode may trigger stack-related errors.

For example:

CALL SUBROUTINE0;
CALL SUBROUTINE1;

SUBROUTINE0:
  <...>
RET

SUBROUTINE1:
  <...>
  CALL SUBROUTINE0;
RET

May trigger error 6 (stack overflow/underflow) if the stack pointer goes outside the allowable limits.

Pseudocode:

Push(IP)
if NoInterrupts then
  IP = X
end

Mnemonic: BNOT X
Encoding: 032 RM [Segment1] [Constant1]




Toggles all bits in the number. The number of bits affected by the operation depend on the current setting of the BPREC register (binary precision).

For example:

CPUSET 50,8 //Set 8-bit precision

MOV EAX,1
BNOT EAX //EAX is now 254

Pseudocode:

X = NOT X

Mnemonic: FINT X
Encoding: 033 RM [Segment1] [Constant1]




Rounds down the value. Will round down to the lower integer:

MOV EAX,1.9
FINT EAX //EAX = 1.0

MOV EAX,4.21
FINT EAX //EAX = 4.0

MOV EAX,1520.101
FINT EAX //EAX = 1520.0

Pseudocode:

X = FLOOR(X)

Mnemonic: FRND X
Encoding: 034 RM [Segment1] [Constant1]




Rounds the value to the nearest integer:

MOV EAX,1.9
FRND EAX //EAX = 2.0

MOV EAX,4.21
FRND EAX //EAX = 4.0

MOV EAX,1520.101
FRND EAX //EAX = 1520.0

Pseudocode:

X = ROUND(X)

Mnemonic: FFRAC X
Encoding: 035 RM [Segment1] [Constant1]




Returns the fractional value of the operand:

MOV EAX,1.9
FRND EAX //EAX = 0.9

MOV EAX,4.21
FRND EAX //EAX = 0.21

MOV EAX,1520.101
FRND EAX //EAX = 0.101

Pseudocode:

X = FRAC(X)

Mnemonic: FINV X
Encoding: 036 RM [Segment1] [Constant1]




Finds the inverse of the operand. Checks for the division by zero error.

May trigger error 3 (division by zero) if the second operand was equal to zero.

Will not trigger division by zero error if interrupt flag register IF is set to 0.

Pseudocode:

if X <> 0 then
  X = 1 / X
else
  Interrupt(3,1)
end

Mnemonic: FSHL X
Encoding: 038 RM [Segment1] [Constant1]




Performs an arithmetic shift-left by multiplying the input number by two. The result might be a floating-point value:

MOV EAX,100
FSHR EAX //EAX = 200

MOV EAX,8
FSHR EAX //EAX = 16

MOV EAX,4.2
FSHR EAX //EAX = 8.2

Pseudocode:

X = X * 2

Mnemonic: FSHR X
Encoding: 039 RM [Segment1] [Constant1]




Performs an arithmetic shift-right by dividing the input number by two. The result might be a floating-point value:

MOV EAX,100
FSHR EAX //EAX = 50

MOV EAX,8
FSHR EAX //EAX = 4

MOV EAX,4.2
FSHR EAX //EAX = 2.1

Pseudocode:

X = X / 2

Mnemonic: RET
Encoding: 040




Returns from a subroutine previous called by CALL instruction. This instruction will pop a value off the stack, and use it as a return address.

It can be used for creating subroutines:

CALL SUBROUTINE0;
CALL SUBROUTINE1;

SUBROUTINE0:
  <...>
RET

SUBROUTINE1:
  <...>
  CALL SUBROUTINE0;
RET

May trigger error 6 (stack overflow/underflow) if the stack pointer goes outside the allowable limits.

Pseudocode:

IP = POP()

Mnemonic: IRET
Encoding: 041




Returns from an interrupt call. This instruction is very similar to the RET instruction, except it restores both the code segment CS and the instruction pointer IP. It will pop both values off the stack.

The code segment that will be restored correspond to the code segment that the execution was in when the interrupt occured. It is not affected by the IF flag (interrupt flag).

For example, a typical interrupt body would be:

INTERRUPT_HANDLER:
  <...>
IRET;

Pseudocode:

if EF = 0 then
  IP = Pop()
end
if EF = 1 then
  CS = Pop()
  IP = Pop()
end

Mnemonic: STI
Encoding: 042




Set interrupt flag IF to 1 after the next instruction. This will enable triggering the interrupts (without the IF flag interrupts are ignored).

The delay of one instruction is so it would be possible to combine STI instruction with an IRET or EXTRET instruction to provide an atomic interrupt handler (preventing any other interrupts from happening during this interrupt). It’s most efficiently used to prevent external interrupts from happening.

For example:

INTERRUPT_HANDLER:
  CLI;
  <...>
  STI;
EXTRET;

This instruction is privileged, meaning it can only be executed from the code page which has runlevel of 0.

Pseudocode:

NextIF = 1

Mnemonic: CLI
Encoding: 043




Clears the interrupt flag IF. This will prevent any interrupts from being triggered, and the errors raised by the processor will be ignored. The interrupts can be turned back on using the STI instruction.

This instruction is privileged, meaning it can only be executed from the code page which has runlevel of 0.

Pseudocode:

IF = 0

Mnemonic: RETF
Encoding: 047




Performs a return from a far subroutine call via the CALLF instruction. Works similarly to the IRET instruction, but does not check for the extended mode.

Will set code segment and instruction pointer to values popped off the stack.

Pseudocode:

CS = Pop()
IP = Pop()

Mnemonic: STEF
Encoding: 048




Enable the extended mode of the processor. This mode will enable the interrupt table support, and permission checks for the paging system.

The paging system is independant of the extended mode, and can function with or without the extended mode enabled.

It’s possible to disable the extended mode by using the CLEF instruction.

This instruction is privileged, meaning it can only be executed from the code page which has runlevel of 0.

Pseudocode:

EF = 1

Mnemonic: CLEF
Encoding: 049




Disables the extended mode which was enabled by the STEF instruction. This will disable interrupt table, and disable the permission checks in the paging system.

The paging system is independant of the extended mode, and can function with or without the extended mode enabled.

This instruction is privileged, meaning it can only be executed from the code page which has runlevel of 0.

Pseudocode:

EF = 0

Mnemonic: AND X,Y
Encoding: 050 RM [Segment1] [Segment2] [Constant1] [Constant2]




Performs a logical AND operation on the two operands, and writes the result back to the first operand. The result will be 1 if both operands are greater or equal to 1.

Pseudocode:

X = X AND Y

Mnemonic: OR X,Y
Encoding: 051 RM [Segment1] [Segment2] [Constant1] [Constant2]




Performs a logical OR operation on the two operands, and writes the result back to the first operand. The result will be 1 if either of the operands is greater or equal to 1.

Pseudocode:

X = X OR Y

Mnemonic: XOR X,Y
Encoding: 052 RM [Segment1] [Segment2] [Constant1] [Constant2]




Performs a logical XOR operation on the two operands, and writes the result back to the first operand. The result will be 1 if just one of operands is greater or equal to 1 (but not two of them at once).

Pseudocode:

X = X XOR Y

Mnemonic: FSIN X,Y
Encoding: 053 RM [Segment1] [Segment2] [Constant1] [Constant2]




Finds sine of the second operand, and writes it into the first operand.

Pseudocode:

X = Sin(Y)

Mnemonic: FCOS X,Y
Encoding: 054 RM [Segment1] [Segment2] [Constant1] [Constant2]




Finds cosine of the second operand, and writes it into the first operand.

Pseudocode:

X = Cos(Y)

Mnemonic: FTAN X,Y
Encoding: 055 RM [Segment1] [Segment2] [Constant1] [Constant2]




Finds tangent of the second operand, and writes it into the first operand.

Pseudocode:

X = Tan(Y)

Mnemonic: FASIN X,Y
Encoding: 056 RM [Segment1] [Segment2] [Constant1] [Constant2]




Finds arcsine of the second operand, and writes it into the first operand.

Pseudocode:

X = ArcSin(Y)

Mnemonic: FACOS X,Y
Encoding: 057 RM [Segment1] [Segment2] [Constant1] [Constant2]




Finds arccosine of the second operand, and writes it into the first operand.

Pseudocode:

X = ArcCos(Y)

Mnemonic: FATAN X,Y
Encoding: 058 RM [Segment1] [Segment2] [Constant1] [Constant2]




Finds arctangent of the second operand, and writes it into the first operand.

Pseudocode:

X = ArcTan(Y)

Mnemonic: MOD X,Y
Encoding: 059 RM [Segment1] [Segment2] [Constant1] [Constant2]




Finds a remainder after the first operand was divided by the second operand, and returns the result to the first operand. If the input is a floating-point value, then it will return the value of Xn * Y, where n is the quotient of X / Y, rounded toward zero to an integer.

Pseudocode:

X = X FMOD Y

Mnemonic: BIT X,Y
Encoding: 060 RM [Segment1] [Segment2] [Constant1] [Constant2]




Tests whether specific bit is set in the given number, and writes the result to the CMPR register. It’s possible to check the result of this operation using the conditional branching opcodes (see:s ??, ??):

BIT EAX,4 //Test 5th bit of EAX
JZ  LABEL1 //Jump if 5th bit is 0
JNZ LABEL1 //Jump if 5th bit is 1

Pseudocode:

CMPR = Yth bit of X

Mnemonic: SBIT X,Y
Encoding: 061 RM [Segment1] [Segment2] [Constant1] [Constant2]




Sets specific bit of the first operand (see: ??):

MOV EAX,105 //1101001
SBIT EAX,1
//EAX = 107   1101011

Pseudocode:

Yth bit of X = 1

Mnemonic: CBIT X,Y
Encoding: 062 RM [Segment1] [Segment2] [Constant1] [Constant2]




Clears specific bit of the first operand (see: ??):

MOV EAX,107 //1101011
CBIT EAX,6
//EAX = 43    0101011

Pseudocode:

Yth bit of X = 0

Mnemonic: TBIT X,Y
Encoding: 063 RM [Segment1] [Segment2] [Constant1] [Constant2]




Toggles specific bit of the first operand (see: ??):

MOV EAX,43 //0101011
TBIT EAX,0
//EAX = 42   0101010

Pseudocode:

Yth bit of X = 1 - Yth bit of X

Mnemonic: BAND X,Y
Encoding: 064 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: BOR X,Y
Encoding: 065 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: BXOR X,Y
Encoding: 066 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: BSHL X,Y
Encoding: 067 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: BSHR X,Y
Encoding: 068 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: JMPF X,Y
Encoding: 069 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: EXTINT X
Encoding: 070 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CNE X
Encoding: 071 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CNZ X
Encoding: 071 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CG X
Encoding: 073 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CNLE X
Encoding: 073 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CGE X
Encoding: 074 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CNL X
Encoding: 074 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CL X
Encoding: 075 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CNGE X
Encoding: 075 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CLE X
Encoding: 076 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CNG X
Encoding: 076 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CE X
Encoding: 077 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CZ X
Encoding: 077 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: MCOPY X
Encoding: 078 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: MCOPY X
Encoding: 078 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: MXCHG X
Encoding: 079 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: MXCHG X
Encoding: 079 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: FPWR X,Y
Encoding: 080 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: XCHG X,Y
Encoding: 081 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: FLN X,Y
Encoding: 082 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: FLOG10 X,Y
Encoding: 083 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: IN X,Y
Encoding: 084 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: OUT X,Y
Encoding: 085 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: FABS X,Y
Encoding: 086 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: FSGN X,Y
Encoding: 087 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: FEXP X,Y
Encoding: 088 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: CALLF X,Y
Encoding: 089 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: FPI X
Encoding: 090 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: FE X
Encoding: 091 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: INT X
Encoding: 092 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: TPG X
Encoding: 093 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: FCEIL X
Encoding: 094 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: ERPG X
Encoding: 095 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: WRPG X
Encoding: 096 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: RDPG X
Encoding: 097 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: TIMER X
Encoding: 098 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: LIDTR X
Encoding: 099 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: JNER X
Encoding: 101 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: JNZR X
Encoding: 101 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: JMPR X
Encoding: 102 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: JGR X
Encoding: 103 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: JNLER X
Encoding: 103 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: JGER X
Encoding: 104 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: JNLR X
Encoding: 104 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: JLR X
Encoding: 105 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: JNGER X
Encoding: 105 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: JLER X
Encoding: 106 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: JNGR X
Encoding: 106 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: JER X
Encoding: 107 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: JZR X
Encoding: 107 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: LNEG X
Encoding: 108 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: EXTRET
Encoding: 110




Pseudocode:

Mnemonic: IDLE
Encoding: 111




Pseudocode:

Mnemonic: NOP
Encoding: 112




Pseudocode:

Mnemonic: PUSHA
Encoding: 114




Pseudocode:

Mnemonic: POPA
Encoding: 115




Pseudocode:

Mnemonic: STD2
Encoding: 116




Pseudocode:

Mnemonic: LEAVE
Encoding: 117




Pseudocode:

Mnemonic: STM
Encoding: 118




Pseudocode:

Mnemonic: CLM
Encoding: 119




Pseudocode:

Mnemonic: CPUGET X,Y
Encoding: 120 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: CPUSET X,Y
Encoding: 121 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: SPP X,Y
Encoding: 122 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: CPP X,Y
Encoding: 123 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: SRL X,Y
Encoding: 124 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: GRL X,Y
Encoding: 125 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: LEA X,Y
Encoding: 126 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: BLOCK X,Y
Encoding: 127 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: CMPAND X,Y
Encoding: 128 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: CMPOR X,Y
Encoding: 129 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: MSHIFT X,Y
Encoding: 130 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: SMAP X,Y
Encoding: 131 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: GMAP X,Y
Encoding: 132 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: RSTACK X,Y
Encoding: 133 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: SSTACK X,Y
Encoding: 134 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: ENTER X
Encoding: 135 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: VADD X,Y
Encoding: 250 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: VSUB X,Y
Encoding: 251 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: VMUL X,Y
Encoding: 252 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: VDOT X,Y
Encoding: 253 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: VCROSS X,Y
Encoding: 254 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: VMOV X,Y
Encoding: 255 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: VNORM X,Y
Encoding: 256 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: VCOLORNORM X,Y
Encoding: 257 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: LOOPXY X,Y
Encoding: 259 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

IF EDX > 0 THEN
IP = X
IF ECX > 0 THEN
ECX = ECX - 1
ELSE
EDX = EDX - 1
ECX = Y
END
END

Mnemonic: MADD X,Y
Encoding: 260 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: MSUB X,Y
Encoding: 261 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: MMUL X,Y
Encoding: 262 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: MROTATE X,Y
Encoding: 263 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: MSCALE X,Y
Encoding: 264 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: MPERSPECTIVE X,Y
Encoding: 265 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: MTRANSLATE X,Y
Encoding: 266 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: MLOOKAT X,Y
Encoding: 267 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: MMOV X,Y
Encoding: 268 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: VLEN X,Y
Encoding: 269 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: MIDENT X
Encoding: 270 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: VMODE X
Encoding: 273 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: VDIV X,Y
Encoding: 295 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: VTRANSFORM X,Y
Encoding: 296 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

HL-ZASM is a high-level assembler compiler and a programming language. It provides support for all the basic C high-level structures, while retaining compatibility with ZASM2 assembly code.

There are certain characters and keywords that are reserved in the HL-ZASM compiler, and cannot be used as, for example, variable names. A variable name/function name/identifier can be any alphanumeric character, underscore (_). In some cases the dot (.) symbol can be part of a label name.

The following keywords are reserved: GOTO, FOR, IF, ELSE, WHILE, DO, SWITCH, CASE, CONST, RETURN, BREAK, CONTINUE, EXPORT, FORWARD, DB, ALLOC, SCALAR, VECTOR1F, VECTOR2F, UV, VECTOR3F, VECTOR4F, COLOR, VEC1F, VEC2F, VEC3F, VEC4F, MATRIX, STRING, DB, DEFINE, CODE, DATA, ORG, OFFSET, VOID, FLOAT, CHAR, INT48, VECTOR, PRESERVE, ZAP.

The exact meaning and use of these keywords is detailed in the following chapters.

HL-ZASM supports the following common assembly syntax (Intel-like):

mov eax,123; //Moving constant into register
mov eax,ebx; //Moving register into register

add eax,123; //2-operand instruction
jmp 123; //1-operand instruction on constant
jmp eax; //1-operand instruction on register

The two operands always follow the instruction. The first one is always the destanation operand, and the second one is the source operand.

It is possible to use both the traditional ZASM memory access symbol (#) or the more common square brackets ([]). Both are considered valid.

The CPU also supports segment prefixes to offset memory reads (see: ?? for more information on segments). It is possible to use any general-purpose or segment register as a segment prefix to any common operand.

HL-ZASM allows for an ’inverted’ syntax, which is when a segment prefix follows the operand itself (for example ’es:eax’ instead of ’eax:es’).

When using common syntax for reading a value from memory, a segment prefix must be included inside the brackets. It is possible to use the plus sign (+) instead of a colon (:), but the common memory access syntax must be used.

It is also possible to use a constant value as a segment prefix for a register via inverted syntax support. It is currently not possible to access a memory cell using a constant offset by a constant, but this will be fixed later.

The following syntax for using memory access and segments is supported:

//ZASM2-style reading from memory
mov eax,#100;
mov eax,#eax;
mov eax,es:#100;
mov eax,es:#eax;
mov eax,eax:#100;
//mov eax,eax:#es; //Invalid: cannot use segment register as pointer

//Common-style reading from memory
mov eax,[100];
mov eax,[eax];
mov eax,[es:eax];
mov eax,[es+eax];
//mov eax,[eax:es]; //Invalid: cannot use segment register as pointer
//mov eax,[eax+es]; //Invalid: cannot use segment register as pointer

//Using constants with segments
mov eax,es:100;
mov eax,eax:100;
mov eax,100:es;
mov eax,100:eax;
//mov eax,100:200; //Invalid

//Using registers with segments
mov eax,ebx:ecx;
mov eax,es:ebx;
mov eax,ebx:es; //Invalid, but translated into the valid 'es:ebx'
//mov eax,fs:es; //Invalid, as no valid syntax is possible

It is possible to use constant expressions, hexadecimal values and label values in HL-ZASM:

//Constant integers
mov eax,255;   //EAX = 255
mov eax,0255;  //EAX = 255
mov eax,0x1FF; //EAX = 511

//Negative integers
mov eax,-255;   //EAX = -255
mov eax,-0x1FF; //EAX = -511

//Floating point numbers and expressions
mov eax,1e4;         //EAX = 10000
mov eax,1.53e-3;     //EAX = 0.00153
mov eax,1.284;       //EAX = 1.248
mov eax,2.592+3.583; //EAX = 6.175
mov eax,1e2+15;      //EAX = 115
//mov eax,1e-5+0.034;//Does not parse (bug)
mov eax,0xFF+100;    //EAX = 355

//Complex expressions
define labelname,512;
mov eax,10+15;         //EAX = 25
mov eax,10*(15+17*24); //EAX = 4230
mov eax,10+labelname*53-10*(17-labelname); //EAX = 32096

Labels are used to branch to specific portions of code. Each label name is actually a number with a name assigned to it. That means that variable names and label names all have valid constant values, and can be used in expressions as parameters for various macros:

labelname:
  jmp labelname;
  mov eax,labelname+3;

labelName: //case sensitive
  jmp labelName;

The define macro allow a label name to be assigned to any constant expression:

define defVar1,120;
define defVar2,50+defVar1;
//define defVar3,labelname2*10; //Does not parse, bug

labelname2:
  mov eax,defVar1; //120
  mov eax,defVar2; //170
//mov eax,defVar3; //170

The db macro is used to write specific values to memory, bypassing any preprocessing. They will be written into the code segment:

db 100; //Outputs 100
db 0xFF; //Ouputs 255
db labelname2+10;
db 'This is a string',0;
db 15,28,595,'string',35,29;

The alloc macro allocates space and allows it to be accessed by name, if one is given. It will be located at the current position in memory (if data and code are located in the same segment), or in a separate data segment, depending on the compiler preferences. The first parameter must either be a valid ident or a constant expression that does not start with an ident:

alloc var1,100,72; //alloc label,size,value, same as 'var1: db 72,72,72,...'
alloc var2,100;    //alloc label,value, same as 'var2: db 100'
alloc var3;        //alloc label, same as 'var3: db 0'
alloc 120;         //alloc size, same as 'db 0,0,0,0,0....'

define allocsize,120;
alloc 0+allocsize; //must not start with an ident name

There are also special macros for defining vector variables and string variables. The basic syntax is VECTOR_MACRO NAME,DEFAULT_VALUE. These are the examples of all available vector macros:

scalar name,...; //For example "scalar x,10"
vector1f name,...;
vector2f name,...;
vector3f name,...;
vector4f name,...;
vec1f name,...;
vec2f name,...;
vec3f name,...;
vec4f name,...;
uv name,...; 
color name,...; 
matrix name; //No default initializer

It’s possible to get pointer to each of the members of the vector variable:

vector3f vec1;
mov #vec1.x,10;
mov #vec1.y,20;
*(vec1.z) = 30;

uv texcoord1;
mov #texcoord1.u,10;
mov #texcoord1.v,20;

color col1;
mov #col1.r,10;
mov #col1.g,20;
mov #col1.b,30;
mov #col1.a,40;

Matrix and vector variables can be used along with the vector extension of the ZCPU:

matrix m1;
matrix m2;
vector4f rot,0,0,1,45;

mident m1; //Load identity matrix
mrotate m2,rot; //Load rotation matrix

mmul m1,m2; //Multiply two matrices

There’s also an additional macro which is not directly connected to creating variables, but it allows to change the current write pointer of the program (the location in memory at which program is being written). The macro is called ORG:

//Write pointer 0
alloc 64;
//Write pointer is now 64
alloc 1;
//Write pointer is now 65

ORG 1000;
//Write pointer is now 1000

This macro can be used for slightly more advanced management of the code generation.

There are also two special helper macros available that can be used to simplify variable declaration in the simple program: the DATA and the CODE macros. They are used like this:

DATA; //Data section
  alloc var1;
  alloc var2;
  ......
  subroutine1:
    ....
  ret
  ....
CODE; //Main code section
  call subroutine1;
  call subroutine2;
  ......

These macros are expanded into the following code:

//DATA
jmp _code;

//CODE
_code:

HL-ZASM has a built-in expression generator. It is possible to generate complex expressions that can involve function calls, registers, variables in a similar way as they are generated in the C code.

Here are the several examples of various expressions:

EAX = 0 //Same as "mov eax,0"
EAX += EBX //Same as "add EAX,EBX"

main()
print("text",999)
R17 = solve("sol1",50,R5)
R2 = R0 + R1*128
c = *ptr++;

R0 = (noise(x-1, y) + noise(x+1, y) + noise(x, y-1) + noise(x, y+1)) /  8
Offset = 65536+32+MAX_DEVICES+udhBusOffset[busIndex*2]

Extra care must be taken while using the expression generator, since it will use the 6 general purpose registers (EAX .. EDI) as temporary storage for evaulating the expressions.

There is support for parsing constant expressions, which are reduced to a single constant value during compile time. It is possible to use the following syntax features in the expression parser:


SyntaxDescription
-X, +XSpecify value sign, or negate the value
&globalvarPointer to a global variable (always constant)
&stackvarPointer to a stack variable (always dynamic)
&globalvar[..]Pointer to an element of a global array
&stackvar[..]Pointer to an element of a stack-based array
1234Constant value
"string"Pointer to a constant string, can be only used inside functions
’c’Single character
pointervarConstant pointer (or a label)
EAXRegister
func(...)Function call
var[...]Array access
*varRead variable by pointer
expr + exprAddition
expr - exprSubtraction
expr * exprProduct
expr / exprDivision
expr ^^exprRaising to power
expr &exprBinary AND
expr |exprBinary OR
expr ^exprBinary XOR
expr &&exprLogic AND
expr ||exprLogic OR
expr = exprAssign (returns value of left side AFTER assigning)
expr > exprGreater than
expr >= exprGreater or equal than
expr == exprEqual
expr <= exprLess or equal than
expr < exprLess than
expr++Increment (returns value BEFORE incrementing)
expr--Decrement (returns value BEFORE decrementing)
++exprIncrement (returns value AFTER incrementing)
--exprDecrement (returns value AFTER decrementing)
 

Expression generator supports expressions inside opcodes as well.

It’s possible to declare variables in HL-ZASM the same way they are declared in the C programming language. It must be noted, however, that there is a difference between variables declared with this way, and variables declared using ZASM2 macros. It will be detailed a bit more on this problem further down.

Variables may be declared either in global space (data segment), or local space (stack segment/stack frame). The variables declared in the global space are created at compile-time, while the stack-based variables are allocated runtime.

Take note that variable declarations and variable definitions are two different things. Variables are declared as shown below, but they can be defined using ZASM2 macros. Consider the following code:

float x;
mov x,10;  //Set variable X to 10
mov #x,10; //Same as '*(x) = 10'

scalar y;
mov y,10; //Does nothing, same as 'mov 123,10'
mov #y,10; //Sets variable Y to 10

There are three types supported by the compiler right now: float (64-bit floating point variable), char (64-bit floating point character code), void (undeclared type/no assigned behaviour). Unlike the usual compilers, all three types work the same way, and are essentially the same thing:

float x;
float y,z;
char* w;
char** u; //Pointer to a pointer

//In all of these examples 'a' is a pointer to a character
char * a;
char* a;
char *a;
char b,*a;

It’s possible to use * symbol to create pointers to variables. There is no difference on whether there are any whitespaces between the asterisk and the variable name (the asterisk belongs to the variable name).

Arrays of constant size may be declared as variables. If array is located in global scope, all members will usually default to zero, although this is might not be the case if program is dynamically loaded by the OS. If the array is declared in local scope, the contents of it may be undeclared. :

float arr[256];

arr[100] = 123;
R0 = arr[R1];

There is no way to declare 2-dimensional arrays right now, but there will be support for that feature at some point.

It’s possible to use initializers for the arrays and the variables. It’s only possible to use constant expressions as initializers for the global scope variables and arrays (there is no limit on what expressions may be used in the stack-based mode):

//Global scope
float x = 10;
float garr1[4] = { 0, 5, 7, 2 };
float garr2[8] = { 1, 2 }; //Missing entries filled with zeroes
float garr3[8] = { 1, 2, 0, 0, 0, 0, 0, 0 }; //Same as garr2

//Local scope
void func(float x,y) {
  float z = x + y * 128;
  float larr1[4] = { x, y, 0, 0 };
  float larr2[16] = { x, 0, y }; //Missing entries filled with zeroes
}

There is also support for creating variables, which are stored in the processors registers. To do this their type must be prepended with the register keyword. Local variables in registers work much faster than the stack-based local variables, but they cannot be an array type (they can be a pointer though):

void strfunc(char *str) {
  register char *ptr = str;  
  *ptr++ = 'A';
}

There is a limit on how much local register variables there can be.

It’s possible to use C-style function declaration syntax, although only one style is currently supported:

return_type function_name(param_type1 name1, name2, param_type2 name3) {
  ....
  code
  ....
}

Parameters are defined the same way the variables are defined, see: ??. It’s possible to use constant-size arrays as variables into function, but there is no way to actually pass them into the function right now. It’s also possible to use sizeless array as a parameter (see examples).

Examples of declaring functions:

void main() { .. }
float func1() { .. }
float func2(float x, y, float z) { .. }

char strcmp(char* str1, char* str2) { .. }
float strlen(char str[]) { .. } //'char str[]' is same as 'char* str'

//no way to actually pass an array like this:
void dowork(char str[256]) { .. } 

There are two kinds of function declarations - normal declarations, and forward declarations. Normal function declarations can be anywhere in the code, and there’s no restriction on using them from any other part of the program. Forward function declarations must always precede the function use (or they must be declared beforehand without the function body).

The forward function declarations allow to perform strict arguments check, and they allow overriding the function to have different parameter lists while having the same name. A function can be forward-declared by using the FORWARD keyword before the function type:

//Forward declarations before use
forward void func(); //First function
forward void func(float x); //Second function
forward void func(float x, *y);

func(); //Calls first function
func(10); //Calls second function

func2(); //COMPILE ERROR: function 'func2' is not declared

//Actual function bodies
forward void func() { //First function
  ...
}
forward void func(float x) { //Second function
  ...
}
forward void func(float x, *y) {
  ...
}
forward void func2() {
  ...
}

Unlike the forward function declarations, the normal functions can be used from anywhere in the code, but they do not provide strict argument type checks:

void func1() { .. }

func1();
func2(); //Works even thought it's not yet declared

void func2() { .. }

There’s also an additional keyword that can be used when defining functions - EXPORT. If this keyword is used, the function name will be preserved in the generated library (see: ?? for more informations on how to generate libraries). The compiler will also add a declaration for this function automatically in the generated library file:

export void func1() { .. }
void func2() { .. } //Function name will be mangled in the resulting file

HL-ZASM uses the cdecl calling convention. The function result is passed via the EAX register. If the function is not forward-declared, or if it has variable argument count, then the ECX register must be set to the parameter count:

R0 = func(a,b,c);

//Same as:
push c;
push b;
push a;
mov ecx,3; //Optional if forward-declared
call func;
add esp,3; //Must clean up stack
mov r0,eax; //Return result

main();

//Same as:
mov ecx,0;
call func;

The function will modify EAX, ECX, EBP registers (along with the ESP register), and may modify all other registers unless they are marked as preserved (see ??).

The HL-ZASM uses the ZCPU stack frame management instructions (ENTER, LEAVE) for creating a stack frame for the function. It will pass number of local variables into the ENTER instruction, so it will create a stack frame with pre-allocated space for local variables.

The function would generate such code:

void func(float x,y) {
  float z,w;
  ...
}

//Generates:
func:
  enter 2;
    ....
  leave;
  ret

All the access to variables on stack is done via the RSTACK, SSTACK instructions. The compiler will use EBP:X as the stack offset, where EBP is the stack frame base register, and X is the offset of the target value on stack. For example:

void func(float x,y) {
  float z,w;
}

//These values would be laying on stack
//[ 3] Y
//[ 2] X
//[ 1] Return address
//[ 0] Saved value of EBP (at function call)
//[-1] Z
//[-1] Z

//Therefore this would be valid:
rstack R0,EBP:2  //R0 = X
rstack R1,EBP:3  //R1 = Y
rstack R2,EBP:-1 //R2 = Z
rstack R3,EBP:-2 //R3 = W

Therefore it’s possible to generate a stack trace using the following code:

void stack_trace() {
  char* returnAddress,savedEBP;
  
  R0 = EBP; //'Current' EBP
  while ((R0 > 0) && (R0 < 65535)) {
    rstack returnAddress,R0:1;
    rstack savedEBP,R0:0;    
    
    add_to_trace(returnAddress);    
    R0 = savedEBP;
  }
}

If the current program makes use of any ZCPU registers, it must mark them as preserved so the expression generator does not use those registers for purpose of calculating expressions.

The compiler will give out warning when unpreserved registers are being used. Right now only EAX-EDI registers must be marked as preserved (since only those are used for expression generator):

void func(float x,y) {
  preserve EAX, EBX;
  
  //Expression generator will never change EAX or EBX registers
  EAX = 123;
  EBX = x*10 + y;
}

The HL-ZASM compiler supports common C control structures, although right now the support is limited.

The conditional branching can be done via the if construct. The else clause, and the else if are supported. It’s possible to branch into a single expression, or into an entire block of code too:

//Can use blocks
if (expression) {
  ....
} else if (expression) {
  ....
} else {
  ....
}

//Can avoid using blocks:
if (expression) expression;
if (expression) expression1 else expression2;

HL-ZASM supports for loops. The syntax is:

for (initializer; condition; step) { ... }

where initializer is the expression that will be executed to setup the loop, condition is the condition that is tested on each step, and step is the expression executed after each step. For example:

float x;
for (x = 0; x < 128; x++) { .. } //Loop for X from 0 to 127
for (x = 128; x > 0; x--) { .. } //Loop for X from 128 to 1
for (;;) { .. } //Infinite loop

It’s possible to use the while loop (but no support for do - while loops yet):

while (expression) { ... }
while ((x < y) && (x > 0)) { ... }
while (1) { ... } //Infinite loop

The break keyword can be used to end the currently executed loop, for example:

while(1) {
  if (condition) {
    break;
  }
  ....
}

It’s possible to use the continue keyword to go on to the next step in the loop. For example:

float x;
for (x = 0; x < 128; x++) {
  if (x == 50) { //Skip iteration 50
    continue;
  }
}

It’s also possible to define labels, and then jump to those labels by defining them the same way they are defined in ZASM2, and using GOTO:

....
  goto label1; //Jumps to label1
....
  label1:

When using the GOTO it’s also possible to pass a complex expression into it:

goto function_entrypoints[10];

The HL-ZASM preprocessor supports C-style preprocessor macros. Preprocessor macros are always last on the current line (it is not possible to write two preprocessor macros on same line).

By default programs compiled with HL-ZASM have no attached runtime library, and would require rewriting all the basic routines. It is possible to link to a runtime library of a choice though. The default runtime library is called ZCRT, and it allows to boot up the ZCPU without any additional software.

The CRT library can be picked with the following preprocessor macro. It must be located in the first line of code, before any other code is generated, otherwise it will not work correctly (macro is case-insensitive):

#pragma CRT ZCRT

This macro will add CRT folder as one of the search paths, and include the main CRT file:

#pragma SearchPath lib\zcrt\
#include <zcrt\main.txt>

This makes it possible to use the default libraries that belong to that runtime library. see: ?? for more information on the ZCRT library.

It is possible to use the C style definition preprocessor macros (it is not possible to define preprocessor functions yet though). It supports the #define, #ifdef, #elseif, #else, #endif and the #undef macros:

#define DEF1
#define DEF2 1234

#ifdef DEF1
  func(DEF2) //same as func(1234)
  ...
#elseif DEF2
  ....
#else
  ...
#endif

#undef DEF1

The preprocessor supports including external files using the #include macro:

#include "filename"
#include <filename>

The #include "filename" macro will include file from the current working directory. This is the same directory the main (first) compiled source file is located in. The other version of this macro includes file relative to the base directory (CPUChip).

If file is not found, it will also be searched on one of the search paths.

The preprocessor also supports the ZASM2 file include syntax:

##include## filename
same as
#include <filename>

There are several special compiler commands available through the #pragma macro.

The #pragma set macro allows user to modify settings of the compiler. Example of the syntax (everything is case-sensitive):

#pragma set OutputResolveListing true

There are the following settings available:


NameDefaultDescription
CurrentLanguageHLZASMCurrent compiler language. Can be HLZASM or ZASM2
CurrentPlatformCPUTarget platform. Defines the feature set, cannot be modified
MagicValue-700500The magic value is used in place of an erroneous constant value
OptimizeLevel0Optimizer level. 0 is none, 1 is low, 2 is high. Not supported right now.
OutputCodeTreefalseOutput code tree
OutputResolveListingfalseOutput code listing for resolve stage
OutputFinalListingfalseOutput code listing for final stage
OutputTokenListingfalseOutput tokenized sourcecode
OutputBinaryListingfalseOutput final binary dump as listing
OutputDebugListingfalseOutput the debug data as listing
OutputToFilefalseOutput listings to files instead of to the console
OutputOffsetsInListingtrueOutput binary offsets in listings
OutputLabelsInListingtrueOutput label names in final listing
GenerateCommentstrueGenerate extra comments in output listing
FixedSizeOutputfalseOutput fixed-size instructions (can be toggled at any time)
SeparateDataSegmentfalsePuts all variables into separate data segment Not supported right now.
GenerateLibraryfalseGenerate a precompiled library. see: ??
AlwaysEnterLeavefalseAlways generate the enter/leave blocks in functions
NoUnreferencedLeavestrueDo not compile functions and variables which are not used by the program
 

The #pragma language macro can be used in place of setting the language via changing the compiler variables:

#pragma language zasm
#pragma set CurrentLanguage ZASM2

The #pragma crt macro can be used to attach a C runtime library. see: ?? for more information.

The #pragma cpuname macro is used to assign a specific name to the target processor:

#pragma CPUName ACPI Power Controller

There are several preprocessor definitions and special labels available for use by the programmer:


BitDescription
__PTR__Current write pointer
__LINE__Current line number
__FILE__Current file name (a string)
__DATE_YEAR__Current year (at compile time)
__DATE_MONTH__Current month (at compile time)
__DATE_DAY__Current day (at compile time)
__DATE_HOUR__Current hour (at compile time)
__DATE_MINUTE__Current minute (at compile time)
__DATE_SECOND__Current second (at compile time)
__PROGRAMSIZE__Total size of the program in bytes
programsizeTotal size of the program in bytes (ZASM2 compatibility macro)
 

no chapter

no chapter

no chapter but lots of incredible fun

Previously unknown label, variable, or function call was never declared in the current scope.

Variable or function with this name was already defined previously in the scope. Error message will point to initial definition.

Expression generator expects identifier to follow (variable/function name, etc).

There was something unexpected in the source code at that position. Can also indicate invalid expression syntax.

Unable to get pointer of the given identifier.

The ZCPU does not support the given instruction operand. Can indicate that segment prefix is used for segment register access, or for port access.

Given opcode is not supported by the current architecture.

It’s only possible to define arrays which have constant size. There is no support for declaring variable-sized arrays.

There is no support for having complex expressions as variable initializers right now.

Compiler does not support preserving registers in global scope right now.

A constant value is expected, and it must not rely on values of any unknown variables or labels.

Invalid syntax is being used for some specific language structure.

This error indicates one of the following things:

Invalid runtime library name/library is not found.

File was not found in the specified folder, and it was not found on one of the search paths.

Internal compiler error (is not an error that can be worked around).

ZGPU is a vector graphics processor, which runs a certain set of instructions to display an image on screen. It has two basic modes of operation - frame-based mode, in which the processor core will execute a certain subprogram each time the new frame must be drawn to screen, and asynchonous mode, which allows to run ZGPU instructions without being tied to screen refreshes.

Even thought ZGPU works with vector graphics the final result is being rasterized into one of two buffers. These two buffers are called the front buffer, and the texture buffer. Both buffers are 512x512 pixels in size, and can be used by the programmer. There is support for a natively vertex mode for drawing graphics, but it has certain limitations.

By default the ZGPU runs the frame-based mode, which makes use of the front buffer to store data, which will then

be displayed on screen. When running in asynchonous mode the graphics are drawn to one of the two buffers, and are then being output to screen.

Most of the features in the ZGPU are controlled via internal registers (see ?? for a complete list of all the registers). They are located in the register memory, which starts at addesses 63488 and ends at 65535.

The memory area between addresses 65536 and 131071 (inclusive) is reserved as an additional external memory bus. It is very slow, but it allows GPU to communicate with other devices. If several GPU’s are running at the same time, the access to these memory areas will be concurrent, and so additional synchronization is required to prevent race conditions or collisions of any sort.

The simpliest program GPU can execute is the following (it makes use of the frame-based mode of execution):

dtest; //Output test pattern to screen
dexit; //Finish execution

It’s possible to setup asynchonous rendering instead of frame-based rendering:

//Setup entrypoints
dentrypoint 0,DrawThread; 
dentrypoint 4,AsyncThread;

//Disable hardware clear so drawing thread does not wipe
//the image on screen
mov #regHWClear,0;
//Set asynchronous thread frequency (speed)
mov #regAsyncFreq,200000;
//Run the thread
mov #regAsyncClk,1; 
dexit;

DrawThread: //Do nothing
dexit;

AsyncThread:
  dbegin;
    dtest;
  dend;
  
  dvsync; //Add frame synchonization
jmp AsyncThread;

ZGPU makes use of the vector extension (see: ??) which allows it to work with matrices and vectors:

//Generate rotation, translation matrices
mrotate mRotateMatrix,vRotate;
mtranslate mTranslateMatrix,vTranslate;

//Create model matrix
mmov mModelMatrix,mRotateMatrix;
mmul mModelMatrix,mTranslateMatrix;

The ZGPU supports 2D and 3D graphics , which must be drawn as polygons:

dvxdata_2f polydata,4; //4 vertices
dvxdata_3f cubedata,12; //12 triangles

....

polydata:
db  0,  0;
db 10,  0;
db 10, 10;
db  0, 10;

cubedata:
db -1,-1,-1; //Triangle 1
db  1,-1,-1;
db  1, 1,-1;

db -1,-1,-1; //Triangle 2
db  1, 1,-1;
db -1, 1,-1;

...

Polygons can be drawn in both normal mode, and indexed mode. They can also be drawn solid-colored, textured, or wireframe:

//Load array of vertices
mov #regVertexArray,cube_varray; 
dvxdata_3f cube_idxarray,12; //Draw all faces
dvxdata_3f_wf cube_idxarray,6; //Draw faces 1-3 as wireframe

//Load array of vertices with texture coords
mov #regVertexArray,cube_varray_tex; 
dvxdata_3f_tex cube_idxarray,12; //Draw all faces, textured

cube_varray:
  db -1,-1,-1; //0
  db -1,-1, 1; //1
  db -1, 1,-1; //2
  db -1, 1, 1; //3
  db  1,-1,-1; //4
  db  1,-1, 1; //5
  db  1, 1,-1; //6
  db  1, 1, 1; //7
  
cube_idx:
  db 0,4,6;  db 0,6,2; //Face 1
  db 5,1,7;  db 1,3,7; //Face 2
  db 4,0,5;  db 0,1,5; //Face 3
  db 2,6,7;  db 3,2,7; //Face 4
  db 0,2,3;  db 1,0,3; //Face 5
  db 6,4,7;  db 4,5,7; //Face 6

It supports vertex buffer, which serves as temporary storage for 2D/3D data before it’s rendered on screen. This allows to provide depth-sorting within the buffer, and other features:

//Enable vertex buffer features
denable 0; //Vertex buffer
denable 1; //ZSorting
denable 2; //Lighting

//Add commands to vertex buffer
dcolor cube_color;
dvxdata_3f cube_data,12;

//Flush vertex buffer
dvxflush;

//Disable vertex buffer and its features
ddisable 0;

There is support for texturing using both custom textures, and textures available externally:

mov #regVertexMode,1; //Enable vertex mode
mov #regTexSize,128; //Texture size
denable 5; //Enable custom texture mapping
dcolor white; //Set color to white

dtexture 2; //Pick texture #2
drectwh rect_pos,rect_size;

...
ddisable 5; //Disable custom texture mapping

dxtexture texture_name; //Pick texture
drectwh rect_pos,rect_size;

....
string texture_name,"brick/brickfloor001a";

ZGPU supports various 2D transformations to move shapes on screen:

dmove target_pos; //move to position
drotatescale 1.23,2; //Rotate by 1.23 radians, and scale up twice
drect rect_pos1,rect_pos2; //Draw rectangle around 0,0 point

....
vector2f target_pos,256,256; //Screen center

vector2f rect_pos1,-50,-50; //Two endpoints for rectangle
vector2f rect_pos2, 50, 50;

There is also support for performing similar transformations on the textures, independantly of the previous transformations (rotation is performed around texture centerpoint usually):

denable 5; //Enable custom texturing
mov #regTexRotation,1.23; //Rotate texture by 1.23 radians
mov #regTexOffsetV,0.2; //Offset V coordinates by 0.2
dvxtexpoly horizon_polygon,4;

The basic graphics output in GPU makes use of the few control instructions (such as DCOLOR, which changes the current drawing color), and the few drawing instructions (for example DRECT, DLINE, etc).

The basic graphics output only requires use of the frame-based drawing mode. The GPU will clear the screen to black each frame, and set the current color to black too. To draw something the color must first be set to wanted color, and then some drawing instructions must be executed:

dcolor white;
drect rect_point1,rect_point2;
dexit; //Program must be correctly terminated

//Compiler macros for data:
color white,255,255,255;

vec2f rect_point1,50,50;
vec2f rect_point2,100,150;

These are all the basic drawing instruction that can be used:


InstructionDescription
DRECTDraw a rectangle between two endpoints
DRECTWHDraw a rectangle at some point (first operand), with some size (second operand)
DORECTSimilar to DRECT, but draws a rectangle outline
DORECTWHSimilar to DRECTWH, but draws a rectangle outline
DCIRCLEDraw a circle at some point (first operand), with some radius (second operand)
DLINEDraws a line between two points. Width specified with the DSETWIDTH instruction
DVXPOLYDraw a custom polygon
 

It’s possible to specify quality at which the circle is drawn:

dcolor white;
mov #regCircleQuality,8; //8 vertices in the circle
dcircle pos,256; //Draw a circle in middle of the screen,
                 //and covering the entire screen
dexit;

//Compiler macros for data:
color white,255,255,255;
vec2f pos,256,256;

It’s also possible to draw 2D polygons (each polygon may have up to 128 vertices in it):

dcolor white;
dvxpoly polygon_data,4;
dexit;

//Compiler macros for data:
color white,255,255,255;

polygon_data: //Polygon for a distorted rectangle
  db 50,50;
  db 190,50;
  db 120,190;
  db 50,120;

It’s possible to use all of these instructions to draw textured data (see: ??)

Asynchonous thread runs in parallel to the main frame-based rendering thread, but it is not synchronized to frame boundaries (while the normal frame-based mode will restart execution each time it must render a new frame). It’s possible to use both at the same time, or use just one of two.

Asynchonous thread is not active by default, but it can be started up using the following code:

//Setup entrypoints
dentrypoint 0,DrawThread; 
dentrypoint 4,AsyncThread;

//Set asynchronous thread frequency (speed)
mov #regAsyncFreq,200000;
//Run the thread
mov #regAsyncClk,1; 
dexit;

DrawThread: //Do nothing
dexit;

AsyncThread:
  ...
jmp AsyncThread;

Asychronous thread frequency may be set up to 1,200,000. If asynchonous thread encounters an error, and there is no specified error handler, it will simply shut down (and reset AsyncClk register back to 0).

It’s possible to perform rendering in asynchonous thread in two ways. There are built-in opcodes which allow to draw to texture buffer, and then copy that image back into the front buffer. They require the hardware clear feature to be disabled though:

mov #regHWClear,0;
....

AsyncThread:
  dbegin; //Start drawing
    ... //Drawing code of any length
  dend; //Copy the image to front buffer
  
  dvsync; //If rendering is too fast, it can be synchronized with frame
          //generation, making it less resource intensive
jmp AsyncThread;

It’s also possible to manually switch buffers for drawing. see: ?? for more information on that.

If any error is encountered during the GPU execution, it will be handled in one of the possible ways:

Entrypoint for error handler in the frame-based mode is 3, and entrypoint for the asynchonous thread error handler is 5. The error code will be passed in the LINT internal register, and the error parameter is passed in the LADD register. Here’s an example of how to setup an error handler in both threads:

//Setup entrypoints
dentrypoint 0,DrawThread;
dentrypoint 3,DrawError;
dentrypoint 4,AsyncThread;
dentrypoint 5,AsyncError;

....

DrawError:
  cpuget R0,28; //Read error parameter
  cpuget R1,27; //Read error code
  ....
dexit;

AsyncError: //Similar to DrawError
  cpuget R0,28; //Read error parameter
  cpuget R1,27; //Read error code
  ....
Stop: dvsync; jmp Stop; //Stop with infinite loop

GPU provides several coordinate transformations. This allows programmer to control how the screen coordinates, which are generated by the drawing instructions, are mapped to screen coordiantes. The GPU native screen size is always 512x512 pixels (size of the rasterizer buffer/front buffer).

Coordinate transformation pipe (routine) can be selected using the DCPIPE opcode:

dcpipe 2; //Select transformation pipe 2

These coordinate transformation pipes are supported:


IndexDescription
0Coordinates are unchanged
1Screen height/width specified by Width and Height registers
2Coordinates must be in 0..1 range
3Coordinates must be in -1..1 range
4All drawing is offset so point (0,0) is the screen center
 

Before the coordinates are mapped to the screen ones the GPU also performs additional transformations based on the values of several register. This allows for scaling, rotating, offseting the result of drawing instructions:

dmove offset; //Move by vector 'offset'
drotatescale 1.23,2; //Scale up twice, rotate by 1.23 radians
drect ...; //Draw something

drotatescale 0,1; //Reset rotation, scale
dmove 0; //Reset offset

Rotation is clockwise, argument is specified in radians. It’s possible to scale on each axis separately, see the list of internal registers at page ??.

The GPU can also perform transformations on separate vertices which are being drawn via the drawing instructions. This is usually used to provide 3D graphics support.

no chapter

no chapter

no chapter

no chapter

no chapter paramlist

no chapter

no chapter

no chapter

no chapter

no chapter

no chapter DDFRAME, DDTERRAIN

The internal registers of the ZGPU are mapped to the memory, and are available as memory locations. They can be read and written to at any time, and they control various aspects of the ZGPU operation.

All of these registers are available in the HL-ZASM compiler by prepending reg prefix to the registers name.

Memory offsets 63488..64511 are mapped to the IOBus (external ports). The memory offsets 65536..131071 are mapped to the MemBus, allowing for access to external devices from the GPU. There is support for both reading and writing this memory, although at very low speed.


NameAddressDescription
Clk65535Current GPU power state (if set to 0, the GPU will be shut down)
Reset65534Reset the GPU state
HWClear65533Enables or disables the hardware clear (front buffer filling to black)
VertexMode65532Enables or disables the vertex mode (raw vertex output instead of rasterizing)
Halt65531Halts the current GPU execution, and preserves image in the front buffer
RAMReset65530Clears the GPU RAM
AsyncReset65529Reset the asynchonous thread state
AsyncClk65528Asynchronous thread execution state
AsyncFreq65527Asynchronous thread frequency
Index65526GPU index, can be between 0 and 31
HScale65525Horizontal image scale (for rasterized output)
VScale65524Vertical image scale (for rasterized output)
HWScale65523Hardware image scale
Rotation65522Rotation of the rasterized image. 0 for 0 deg, 1 for 90 deg, 2 for 180 deg, 3 for 270 deg
TexSize65521Subtexture size
TexDataPtr65520Pointer to texture data for load by the GPU
TexDataSz65519Size of the texture data for load by the GPU
RasterQ65518Rasterizer quality
TexBuffer65517Buffer used for the texturing (0: front buffer, 1: texture buffer)
Width65515Screen width (resolution)
Height65514Screen height (resolution)
Ratio65513Current screen ratio (physical)
ParamList65512Pointer to list of parameters for the DWRITEFMT instruction, or 0 if unused
CursorX65505X coordinate of the cursor (0..1)
CursorY65504Y coordinate of the cursor (0..1)
Cursor65503Should the cursor be drawn on screen
CursorButtons65502State of the cursor buttons
BrightnessW65495Total screen brightness
BrightnessR65494R component brightness
BrightnessG65493G component brightness
BrightnessB65492B component brightness
ContrastW65491Total screen contrast
ContrastR65490R component contrast
ContrastG65489G component contrast
ContrastB65488B component contrast
CircleQuality65485Circle output quality (number of vertices). Can be between 3 and 128
OffsetX65484X offset for screen coordinates of all drawn graphics
OffsetY65483Y offset for screen coordinates of all drawn graphics
Rotation65482Rotation in radians for screen coordinates of all drawn graphics
Scale65481Scale (1 is normal scale) for screen coordinates of all drawn graphics
CenterX65480X coordinate of centerpoint of rotation (see Rotation register)
CenterY65479Y coordinate of centerpoint of rotation (see Rotation register)
CircleStart65478Circle start angle (in radians)
CircleEnd65477Circle end angle (in radians)
LineWidth65476Line width
ScaleX65475X component of the scale for screen coordinates of all drawn graphics
ScaleY65474Y component of the scale for screen coordinates of all drawn graphics
FontHalign65473Font horizontal align mode
ZOffset65472Extra Z offset for all coordinates passed into vertex pipe
FontValign65471Font vertical align mode
CullDistance65470Culling distance
CullMode65469Face culling mode (0: front, 1: back)
LightMode65468Lighting mode (0: two-side, 1: front, -1: back)
VertexArray65467Pointer to array of vertices for indexed rendering
TexRotation65466Texture rotation in radians
TexScale65465Texture scale (1 is normal)
TexCenterU65464U component of centerpoint of texture rotation
TexCenterV65463V component of centerpoint of texture rotation
TexOffsetU65462U offset for the texture output
TexOffsetV65461V offset for the texture output
 

Mnemonic: DTEST
Encoding: 200




This opcode generates a test pattern image on the GPU screen, somewhat similar to the PAL TV test pattern. It ignores any coordinate or texture transformations, and ignores all previous color commands/settings.

The rightmost left black bar will be left transparent.

Pseudocode:

W = ScreenWidth
H = ScreenHeight
    
for bar=0,6 do
  SetColor(TEST_PATTERN_COLOR[bar])
  Rectangle(W*0.125*bar,0,W*0.125,H*0.80)
end
  
for gray=0,7 do
  SetColor(31*gray,31*gray,31*gray,255)
  Rectangle(W*0.125*gray,H*0.80,W*0.125,H*0.20)
end

Mnemonic: DEXIT/DVSYNC
Encoding: 201




Finishes drawing the current frame (used only in the frame-based mode). This must be the last instruction in any program that makes use of the frame-based mode.

The execution may be terminated before this instruction if amount of cycles spent drawing the current frame exceeds total limit

It’s implementation is exactly same as that of the IDLE instruction (while in the frame-based mode).

Pseudocode:

INTR = 1

Mnemonic: DCLR
Encoding: 202




Clears screen/current buffer by filling it with black background.

Pseudocode:

SetColor(0,0,0,0)
Rectangle(0,0,ScreenWidth,ScreenHeight)

Mnemonic: DCLRTEX
Encoding: 203




Clears screen/current buffer by filling it with a texture.

If vertex texturing is enabled, it will use the texture specified by the DTEXTURE opcode. Otherwise it will use the texture specified by the DXTEXTURE opcode.

If no texture was defined, it will fill buffer with solid black color.

Pseudocode:

BindState()
SetColor(0,0,0,255)
Rectangle(0,0,ScreenWidth,ScreenHeight)

Mnemonic: DVXFLUSH
Encoding: 204




Draws all the pending polygons in the vertex buffer to screen, and clears the buffer.

This instruction is used with vertex buffer enabled. It will perform Z-sorting, clipping, etc, and draw the output to screen.

Pseudocode:

FlushBuffer()

Mnemonic: DVXCLEAR
Encoding: 205




Clears any pending polygons from the vertex buffer.

Pseudocode:

ClearBuffer()

Mnemonic: DSETBUF_VX
Encoding: 206




Sets the current drawing target to the raw vertex output. This opcode can only be used when vertex mode is active, and it is the default target for the vertex mode.

Pseudocode:

SetRendertarget(2)

Mnemonic: DSETBUF_SPR
Encoding: 207




Sets current drawing target to the texture buffer. Also known as DBACKBUF.

Pseudocode:

SetRenderTarget(1)

Mnemonic: DSETBUF_FBO
Encoding: 208




Sets current drawing target to the front/main buffer. Also known as DFRONTBUF.

Pseudocode:

SetRenderTarget(0)

Mnemonic: DSWAP
Encoding: 209




Copies contents of the texture buffer into the front buffer.

Pseudocode:

Copy(RenderTarget(1),RenderTarget(0))

Mnemonic: DVXPIPE X
Encoding: 210 RM [Segment1] [Constant1]




Selects the current vertex pipe/vertex transformation mode. This controls the transformation that brings world-space coordinates into screen coordinates. X can be one of the following pipes:

Pseudocode:

VertexPipe = X

Mnemonic: DCPIPE X
Encoding: 211 RM [Segment1] [Constant1]




Selects the current coordinate pipe/coordinate transformation mode. This controls the transformation that converts the screen coordinates into true coordinates, which are correctly mapped to the buffer. X can be one of the following pipes:

Pseudocode:

CoordinatePipe = X

Mnemonic: DENABLE X
Encoding: 212 RM [Segment1] [Constant1]




Enables one of the internal GPU modes/switches. X can be one of the following:

For example:

//Prepare 3D drawing
mov #regCullingDistance,4.0; //Setup culling distance
denable 0; //Vertex buffer
denable 1; //ZSorting
denable 2; //Lighting
denable 3; //Face culling
denable 4; //Distance-based culling

Pseudocode:

MODE_SWITCH[X] = 1

Mnemonic: DDISABLE X
Encoding: 213 RM [Segment1] [Constant1]




Disables one of the internal GPU modes/switches. X can be one of the following:

For example:

//Finish 3D drawing
ddisable 0; //Vertex buffer
ddisable 1; //ZSorting
ddisable 2; //Lighting
ddisable 3; //Face culling
ddisable 4; //Distance-based culling

Pseudocode:

MODE_SWITCH[X] = 0

Mnemonic: DCLRSCR X
Encoding: 214 RM [Segment1] [Constant1]




Clears screen with the specified color.

Pseudocode:

SetColor(X)
Rectangle(0,0,ScreenWidth,ScreenHeight)

Mnemonic: DCOLOR X
Encoding: 215 RM [Segment1] [Constant1]




Sets the current drawing color.

If the vertex buffer is enabled, it will change the color of all the following polygons in the buffer (until the next DCOLOR command, or end of buffer).

Pseudocode:

SetColor(X)

Mnemonic: DTEXTURE X
Encoding: 216 RM [Segment1] [Constant1]




Sets a texture out of one of the internal buffers. The buffer texture data is taken from is specified by the TexBuffer register.

By default it will take the entire buffer as the texture, but it is possible to specify smaller subtextures of the buffer. For example, it’s possible to use four 256x256 textures, or 16 128x128 textures. If it is done so, the X parameter will specify which subtexture must be used.

Example:

mov #regTexBuffer,0; //Select front buffer
mov #regTexSize,128; //128x128 subtextures
dtexture 2; //Bind subtexture #2

Pseudocode:

SetBufferTexture(X)

Mnemonic: DSETFONT X
Encoding: 217 RM [Segment1] [Constant1]




Set the current font for all operations which output text. There are 8 fonts available:

Pseudocode:

Font = X

Mnemonic: DSETSIZE X
Encoding: 218 RM [Segment1] [Constant1]




Set the current font size for all operations which output text. Size can be any integer value between 4 and 200.

Pseudocode:

Mnemonic: DMOVE X
Encoding: 219 RM [Segment1] [Constant1]




Set the offset for 2D drawing. This instruction will offset all screen coordinates of next rendering instructions by the given vector.

X must be a pointer to vector, or it can be 0. If X is equal to zero then offset will be removed.

Pseudocode:

Registers[OffsetX] = X.x
Registers[OffsetY] = X.y

Mnemonic: DVXDATA_2F X,Y
Encoding: 220 RM [Segment1] [Segment2] [Constant1] [Constant2]




Draw a single 2D polygon with up to 128 vertices. Also known as DVXPOLY.

If vertex array mode is not used, X points to an array of polygon vertex coordinates, and Y specifies the total count of vertices.

In vertex array mode, X points to an array of indexes into the vertex array, and Y specifies the total count of vertices.

Pseudocode:

VDATA = Registers[VertexArray]
for IDX=1,MIN(128,Y) do
  if VDATA > 0 then
    VIDX = ReadCell(X+IDX-1)
    VD[IDX] = {
      x = ReadCell(VDATA+VIDX*2+0),
      y = ReadCell(VDATA+VIDX*2+1)}
  else
    VD[IDX] = {
      x = ReadCell(X+(IDX-1)*2+0),
      y = ReadCell(X+(IDX-1)*2+1)}
  end
  ComputeTextureUV(VD[IDX],VD[IDX].x/512,VD[IDX].y/512)
end
DrawToBuffer(VD)

Mnemonic: DVXDATA_2F_TEX X,Y
Encoding: 221 RM [Segment1] [Segment2] [Constant1] [Constant2]




Draw a single textured 2D polygon with up to 128 vertices. Also known as DVXTEXPOLY.

If vertex array mode is not used, X points to an array of polygon vertex coordinates and texture coordinates, and Y specifies the total count of vertices.

In vertex array mode, X points to an array of indexes into the vertex array, and Y specifies the total count of vertices.

Pseudocode:

VDATA = Registers[VertexArray]
for IDX=1,MIN(128,Y) do
  if VDATA > 0 then
    VIDX = ReadCell(X+IDX-1)
    VD[IDX] = {
      x = ReadCell(VDATA+VIDX*4+0),
      y = ReadCell(VDATA+VIDX*4+1)}
    ComputeTextureUV(VD[IDX],
      ReadCell(VDATA+VIDX*4+2),
      ReadCell(VDATA+VIDX*4+3))
  else
    VD[IDX] = {
      x = ReadCell(X+(IDX-1)*4+0),
      y = ReadCell(X+(IDX-1)*4+1)}      
    ComputeTextureUV(VD[IDX],
      ReadCell(X+(IDX-1)*4+2),
      ReadCell(X+(IDX-1)*4+3))
  end
end
DrawToBuffer(VD)

Mnemonic: DVXDATA_3F X,Y
Encoding: 222 RM [Segment1] [Segment2] [Constant1] [Constant2]




Draw a single 3D polygon with up to 128 triangles.

If vertex array mode is not used, X points to an array of triangle vertex coordinates, and Y specifies the total count of triangles.

In vertex array mode, X points to an array of indexes into the vertex array, and Y specifies the total count of triangles.

Pseudocode:

VDATA = Registers[VertexArray]
for IDX=1,MIN(128,Y) do
  if VDATA > 0 then
    VIDX1 = ReadCell(X+(IDX-1)*3+0)
    VIDX2 = ReadCell(X+(IDX-1)*3+1)
    VIDX3 = ReadCell(X+(IDX-1)*3+2)
    VD[1] = {
      x = ReadCell(VDATA+VIDX1*3+0),
      y = ReadCell(VDATA+VIDX1*3+1),
      z = ReadCell(VDATA+VIDX1*3+2)}
    VD[2] = {
      x = ReadCell(VDATA+VIDX2*3+0),
      y = ReadCell(VDATA+VIDX2*3+1),
      z = ReadCell(VDATA+VIDX2*3+2)}
    VD[3] = {
      x = ReadCell(VDATA+VIDX3*3+0),
      y = ReadCell(VDATA+VIDX3*3+1),
      z = ReadCell(VDATA+VIDX3*3+2)}
  else
    VD[1] = {
      x = ReadCell(X+(IDX-1)*9+0),
      y = ReadCell(X+(IDX-1)*9+1),
      z = ReadCell(X+(IDX-1)*9+2)}
    VD[2] = {
      x = ReadCell(X+(IDX-1)*9+3),
      y = ReadCell(X+(IDX-1)*9+4),
      z = ReadCell(X+(IDX-1)*9+5)}
    VD[3] = {
      x = ReadCell(X+(IDX-1)*9+6),
      y = ReadCell(X+(IDX-1)*9+7),
      z = ReadCell(X+(IDX-1)*9+8)}
  end
    
  ComputeTextureUV(VD[1],0,0)
  ComputeTextureUV(VD[2],1,0)
  ComputeTextureUV(VD[3],1,1)
   
  DrawToBuffer(VD)
end

Mnemonic: DVXDATA_3F_TEX X,Y
Encoding: 223 RM [Segment1] [Segment2] [Constant1] [Constant2]




Draw a single textured 3D polygon with up to 128 triangles.

If vertex array mode is not used, X points to an array of triangle vertex coordinates and texture coordinates, and Y specifies the total count of triangles.

In vertex array mode, X points to an array of indexes into the vertex array, and Y specifies the total count of triangles.

Pseudocode:

VDATA = Registers[VertexArray]
for IDX=1,MIN(128,Y) do
    if VDATA > 0 then
      $L VIDX1 = ReadCell(X+(IDX-1)*3+0)
      $L VIDX2 = ReadCell(X+(IDX-1)*3+1)
      $L VIDX3 = ReadCell(X+(IDX-1)*3+2)
      VD[1] = {
        x = ReadCell(VDATA+VIDX1*5+0),
        y = ReadCell(VDATA+VIDX1*5+1),
        z = ReadCell(VDATA+VIDX1*5+2),
      }
      VD[2] = {
        x = ReadCell(VDATA+VIDX2*5+0),
        y = ReadCell(VDATA+VIDX2*5+1),
        z = ReadCell(VDATA+VIDX2*5+2),
      }
      VD[3] = {
        x = ReadCell(VDATA+VIDX3*5+0),
        y = ReadCell(VDATA+VIDX3*5+1),
        z = ReadCell(VDATA+VIDX3*5+2),
      }
      
      ComputeTextureUV(VD[1],
        ReadCell(VDATA+VIDX1*5+3),
        ReadCell(VDATA+VIDX1*5+4))
      ComputeTextureUV(VD[2],
        ReadCell(VDATA+VIDX2*5+3),
        ReadCell(VDATA+VIDX2*5+4))
      ComputeTextureUV(VD[3],
        ReadCell(VDATA+VIDX3*5+3),
        ReadCell(VDATA+VIDX3*5+4))
    else
      VD[1] = {
        x = ReadCell(X+(IDX-1)*15+0),
        y = ReadCell(X+(IDX-1)*15+1),
        z = ReadCell(X+(IDX-1)*15+2),
      }
      VD[2] = {
        x = ReadCell(X+(IDX-1)*15+5),
        y = ReadCell(X+(IDX-1)*15+6),
        z = ReadCell(X+(IDX-1)*15+7),
      }
      VD[3] = {
        x = ReadCell(X+(IDX-1)*15+10),
        y = ReadCell(X+(IDX-1)*15+11),
        z = ReadCell(X+(IDX-1)*15+12),
      }
      
      ComputeTextureUV(VD[1],
        ReadCell(X+(IDX-1)*15+ 3),
        ReadCell(X+(IDX-1)*15+ 4))
      ComputeTextureUV(VD[2],
        ReadCell(X+(IDX-1)*15+ 8),
        ReadCell(X+(IDX-1)*15+ 9))
      ComputeTextureUV(VD[3],
        ReadCell(X+(IDX-1)*15+13),
        ReadCell(X+(IDX-1)*15+14))
    end
    
    self:Dyn_EmitInterruptCheck()
    DrawToBuffer(VD)
  end

Mnemonic: DVXDATA_3F_WF X,Y
Encoding: 224 RM [Segment1] [Segment2] [Constant1] [Constant2]




Draw a single wireframe 3D polygon with up to 128 triangles.

If vertex array mode is not used, X points to an array of triangle vertex coordinates and texture coordinates, and Y specifies the total count of triangles.

In vertex array mode, X points to an array of indexes into the vertex array, and Y specifies the total count of triangles.

Pseudocode:

VDATA = Registers[VertexArray]
for IDX=1,MIN(128,Y) do
  if VDATA > 0 then
    VIDX1 = ReadCell(X+(IDX-1)*3+0)
    VIDX2 = ReadCell(X+(IDX-1)*3+1)
    VIDX3 = ReadCell(X+(IDX-1)*3+2)
    VD[1] = {
      x = ReadCell(VDATA+VIDX1*3+0),
      y = ReadCell(VDATA+VIDX1*3+1),
      z = ReadCell(VDATA+VIDX1*3+2)}
    VD[2] = {
      x = ReadCell(VDATA+VIDX2*3+0),
      y = ReadCell(VDATA+VIDX2*3+1),
      z = ReadCell(VDATA+VIDX2*3+2)}
    VD[3] = {
      x = ReadCell(VDATA+VIDX3*3+0),
      y = ReadCell(VDATA+VIDX3*3+1),
      z = ReadCell(VDATA+VIDX3*3+2)}
  else
    VD[1] = {
      x = ReadCell(X+(IDX-1)*9+0),
      y = ReadCell(X+(IDX-1)*9+1),
      z = ReadCell(X+(IDX-1)*9+2)}
    VD[2] = {
      x = ReadCell(X+(IDX-1)*9+3),
      y = ReadCell(X+(IDX-1)*9+4),
      z = ReadCell(X+(IDX-1)*9+5)}
    VD[3] = {
      x = ReadCell(X+(IDX-1)*9+6),
      y = ReadCell(X+(IDX-1)*9+7),
      z = ReadCell(X+(IDX-1)*9+8)}
  end
    
  ComputeTextureUV(VD[1],0,0)
  ComputeTextureUV(VD[2],1,0)
  ComputeTextureUV(VD[3],1,1)
   
  DrawToBuffer(VD,WIREFRAME)
end

Mnemonic: DRECT X,Y
Encoding: 225 RM [Segment1] [Segment2] [Constant1] [Constant2]




Draws a single rectangle. X is a pointer to vector which specifies the top-left vertex, and Y is a pointer to vector which specifies the bottom-right vertex.

Pseudocode:

  VD[1] = {
    x = ReadCell(X+0),
    y = ReadCell(X+1)}
  VD[2] = {
    x = ReadCell(Y+0),
    y = ReadCell(X+1)}
  VD[3] = {
    x = ReadCell(Y+0),
    y = ReadCell(Y+1)}
  VD[4] = {
    x = ReadCell(X+0),
    y = ReadCell(Y+1)}
  
  ComputeTextureUV(VD[1],0,0)
  ComputeTextureUV(VD[2],1,0)
  ComputeTextureUV(VD[3],1,1)
  ComputeTextureUV(VD[4],0,1)

  DrawToBuffer(VD)

Mnemonic: DCIRCLE X,Y
Encoding: 226 RM [Segment1] [Segment2] [Constant1] [Constant2]




Draws a circle or a sector with specific radius and angles.

Pseudocode:

  R = Y
  SIDES = clamp(ReadCell(65485),3,64)
  START = ReadCell(65478)
  END = ReadCell(65477)
  STEP = (END-START)/SIDES
  VEC = ReadVector2f(X)
  
  for IDX=1,SIDES do
    VD[1] = {
      x = VEC.x + R*sin(START+STEP*(IDX+0)),
      y = VEC.y + R*cos(START+STEP*(IDX+0))}
    VD[2] = {
      x = VEC.x,
      y = VEC.y}
    VD[3] = {
      x = VEC.x + R*sin(START+STEP*(IDX+1)),
      y = VEC.y + R*cos(START+STEP*(IDX+1))}
    
    ComputeTextureUV(VD[1],0,0)
    ComputeTextureUV(VD[2],1,0)
    ComputeTextureUV(VD[3],1,1)
    
    DrawToBuffer(VD)
  end

Mnemonic: DLINE X,Y
Encoding: 227 RM [Segment1] [Segment2] [Constant1] [Constant2]




Draws a line between two points specified by the vectors X and Y.

Pseudocode:

  DrawLine(ReadVector2f($1),ReadVector2f($2))

Mnemonic: DRECTWH X,Y
Encoding: 228 RM [Segment1] [Segment2] [Constant1] [Constant2]




Draws a single rectangle. X is a pointer to vector which specifies the top-left corner coordinates, and Y is a pointer to vector which specifies the Rectangle size.

Pseudocode:

  VD[1] = {
    x = ReadCell(X+0),
    y = ReadCell(X+1)}
  VD[2] = {
    x = ReadCell(X+0)+ReadCell(Y+0),
    y = ReadCell(X+1)}
  VD[3] = {
    x = ReadCell(X+0)+ReadCell(Y+0),
    y = ReadCell(X+1)+ReadCell(Y+1)}
  VD[4] = {
    x = ReadCell(X+0),
    y = ReadCell(X+1)+ReadCell(Y+1)}
  
  ComputeTextureUV(VD[1],0,0)
  ComputeTextureUV(VD[2],1,0)
  ComputeTextureUV(VD[3],1,1)
  ComputeTextureUV(VD[4],0,1)

  DrawToBuffer(VD)

Mnemonic: DORECT X,Y
Encoding: 229 RM [Segment1] [Segment2] [Constant1] [Constant2]




Draws an outline of a rectangle. X is a pointer to vector which specifies the top-left vertex, and Y is a pointer to vector which specifies the bottom-right vertex.

The line width can be specified with the DSETWIDTH instruction.

Pseudocode:

  VD[1] = {
    x = ReadCell(X+0),
    y = ReadCell(X+1)}
  VD[2] = {
    x = ReadCell(Y+0),
    y = ReadCell(X+1)}
  VD[3] = {
    x = ReadCell(Y+0),
    y = ReadCell(Y+1)}
  VD[4] = {
    x = ReadCell(X+0),
    y = ReadCell(Y+1)}

  DrawLine(VD[1],VD[2])
  DrawLine(VD[2],VD[3])
  DrawLine(VD[3],VD[4])
  DrawLine(VD[4],VD[1])

Mnemonic: DTRANSFORM2F X,Y
Encoding: 230 RM [Segment1] [Segment2] [Constant1] [Constant2]




Transforms a 2D vector using the projection and the modelview matrices.

Pseudocode:

Pseudocode:

Mnemonic: DTRANSFORM3F X,Y
Encoding: 231 RM [Segment1] [Segment2] [Constant1] [Constant2]




Transforms a 3D vector using the projection and the modelview matrices.

Pseudocode:

Mnemonic: DSCRSIZE X,Y
Encoding: 232 RM [Segment1] [Segment2] [Constant1] [Constant2]




Sets the current screen size.

Pseudocode:

Registers[Width] = X
Registers[Height] = Y

Mnemonic: DROTATESCALE X,Y
Encoding: 233 RM [Segment1] [Segment2] [Constant1] [Constant2]




Rotates and scales coordinates of all following graphics instructions. The default centerpoint of rotation is (0,0) which can be changed using the CenterX and the CenterY registers.

Pseudocode:

Registers[Rotation] = X
Registers[Scale] = Y

Mnemonic: DORECTWH X,Y
Encoding: 234 RM [Segment1] [Segment2] [Constant1] [Constant2]




Draws an outline of a rectangle. X is a pointer to vector which specifies the top-left corner coordinates, and Y is a pointer to vector which specifies the Rectangle size.

The line width can be specified with the DSETWIDTH instruction.

Pseudocode:

  VD[1] = {
    x = ReadCell(X+0),
    y = ReadCell(X+1)}
  VD[2] = {
    x = ReadCell(X+0)+ReadCell(Y+0),
    y = ReadCell(X+1)}
  VD[3] = {
    x = ReadCell(X+0)+ReadCell(Y+0),
    y = ReadCell(X+1)+ReadCell(Y+1)}
  VD[4] = {
    x = ReadCell(X+0),
    y = ReadCell(X+1)+ReadCell(Y+1)}
  
  DrawLine(VD[1],VD[2])
  DrawLine(VD[2],VD[3])
  DrawLine(VD[3],VD[4])
  DrawLine(VD[4],VD[1])

Mnemonic: DCULLMODE X,Y
Encoding: 235 RM [Segment1] [Segment2] [Constant1] [Constant2]




Sets the current culling mode and lighting mode.

X sets the culling mode:

Y sets the lighting mode:

Pseudocode:

Register[CullMode] = X
Register[LightMode] = X

Mnemonic: DPIXEL X,Y
Encoding: 238 RM [Segment1] [Segment2] [Constant1] [Constant2]




Outputs a single pixel to screen. X is a pointer to vector which specifies coordinates on screen (can be non-integer, which will cause anti-aliasing effect), and Y is a pointer to color of the pixel.

Pseudocode:

SetPixel(X,Y)

Mnemonic: DWRITE X,Y
Encoding: 240 RM [Segment1] [Segment2] [Constant1] [Constant2]




Writes a null-terminated string to screen. X is a pointer to vector that specifies the position of string on screen, and Y is the pointer to the first character of the string.

Pseudocode:

TEXT = VM:ReadString(Y)
FontWrite(X,TEXT)

Mnemonic: DWRITEI X,Y
Encoding: 241 RM [Segment1] [Segment2] [Constant1] [Constant2]




Writes a integer value to screen. X is a pointer to vector that specifies the position of string on screen, and Y is the value that must be drawn on screen.

Pseudocode:

FontWrite(X,Integer(Y))

Mnemonic: DWRITEF X,Y
Encoding: 242 RM [Segment1] [Segment2] [Constant1] [Constant2]




Writes a floating-point value to screen. X is a pointer to vector that specifies the position of string on screen, and Y is the value that must be drawn on screen.

Pseudocode:

FontWrite(X,Y)

Mnemonic: DENTRYPOINT X,Y
Encoding: 243 RM [Segment1] [Segment2] [Constant1] [Constant2]




Sets one of the GPU entrypoints. Each entrypoint corresponds to a specific function, there are the following entrypoints available right now:

Pseudocode:

Mnemonic: DSETLIGHT X,Y
Encoding: 244 RM [Segment1] [Segment2] [Constant1] [Constant2]




Sets parameters of one of the 8 lights supported by the GPU. X is the light index (0..7), and Y points to the following data structure:

LightData:
  vector4f position,<x>,<y>,<z>,0;
  vector4f color,<r>,<g>,<b>,<brightness>;

Light brightness is usually set to 1, but can vary.

Pseudocode:

if (X < 0) or (X > 7) then
  Interrupt(19,0)
else
  Lights[X] = {
    Position = ReadVector4f(Y+0),
    Color    = ReadVector4f(Y+4)}
end

Mnemonic: DGETLIGHT X,Y
Encoding: 245 RM [Segment1] [Segment2] [Constant1] [Constant2]




Reads light data for one of the 8 lights supported by the GPU. X is the light index (0..7), and Y points to the following data structure, which will be filled with light data:

LightData:
  vector4f position,<x>,<y>,<z>,0;
  vector4f color,<r>,<g>,<b>,<brightness>;

Pseudocode:

N/A

Mnemonic: DWRITEFMT X,Y
Encoding: 246 RM [Segment1] [Segment2] [Constant1] [Constant2]




Writes a formatted string to screen. X points to vector that specifies the position of string on screen, and Y is the string that must be drawn on screen.

Variables used in the string format must follow the string data. If ParamList register is set, then variables used in the string format start at that offset.

Pseudocode:

N/A

Mnemonic: DWRITEFIX X,Y
Encoding: 247 RM [Segment1] [Segment2] [Constant1] [Constant2]




Writes a fixed-point value to screen. X is a pointer to vector that specifies the position of string on screen, and Y is the value that must be drawn on screen.

Pseudocode:

N/A

Mnemonic: DTEXTWIDTH X,Y
Encoding: 248 RM [Segment1] [Segment2] [Constant1] [Constant2]




Returns the width of string using the current font, and writes it to X.

Pseudocode:

Mnemonic: DTEXTHEIGHT X,Y
Encoding: 249 RM [Segment1] [Segment2] [Constant1] [Constant2]




Returns the height of string using the current font, and writes it to X.

Pseudocode:

Mnemonic: MLOADPROJ X
Encoding: 271 RM [Segment1] [Constant1]




Loads given matrix into the GPU projection matrix. X points to the matrix.

Pseudocode:

ProjectionMatrix = ReadMatrix(X)

Mnemonic: MREAD X
Encoding: 272 RM [Segment1] [Constant1]




Reads GPU model matrix. X points to the matrix into which model matrix will be written.

Pseudocode:

WriteMatrix(X,ModelMatrix)

Mnemonic: DT X
Encoding: 274 RM [Segment1] [Constant1]




Returns time passed since last frame (works only in frame-based mode).

Pseudocode:

X = TimerDT

Mnemonic: DSHADE X
Encoding: 276 RM [Segment1] [Constant1]




Shades the current color by specific amount. X is the shading value. A value between 0 and 1 will make the color darker, a value of 1 will not change the current color, while value higher than 1 will make color brighter.

There is no normalization, so values outside of 0..1 range might generate weird colors.

Pseudocode:

Color.x = Color.x*X
Color.y = Color.y*X
Color.z = Color.z*X
SetColor(Color)

Mnemonic: DSETWIDTH X
Encoding: 277 RM [Segment1] [Constant1]




Sets line width.

Pseudocode:

Register[LineWidth] = X

Mnemonic: MLOAD X
Encoding: 278 RM [Segment1] [Constant1]




Loads given matrix into the GPU model matrix. X points to the matrix.

Pseudocode:

ModelMatrix = ReadMatrix(X)

Mnemonic: DSHADENORM X
Encoding: 279 RM [Segment1] [Constant1]




Shades the current color by specific amount. X is the shading value. A value between 0 and 1 will make the color darker, a value of 1 will not change the current color, while value higher than 1 will make color brighter.

The resulting color is normalized, so it’s possible to use values outside of the 0..1 range.

Pseudocode:

Color.x = Clamp(Color.x*X,0,255)
Color.y = Clamp(Color.y*X,0,255)
Color.z = Clamp(Color.z*X,0,255)
SetColor(Color)

Mnemonic: DDFRAME X
Encoding: 280 RM [Segment1] [Constant1]




Draws a framed rectangle. X points to the following data structure:

FrameData:
  vector2f position,<x>,<y>;
  vector2f size,<w>,<h>;
  vector4f info,<shadow>,<highlight>,<face>,<border size>;

The info entry stores pointers to colors that must be used in rendering.

Pseudocode:

V1 = ReadVector2f(X+0)
V2 = ReadVector2f(X+2)
V3 = ReadVector4f(X+4)
  
CSHADOW    = ReadVector3f(V3.x)
CHIGHLIGHT = ReadVector3f(V3.y)
CFACE      = ReadVector3f(V3.z)

VD1[1] = {
  x = V3.w + V1.x,
  y = V3.w + V1.y}
VD1[2] = {
  x = V3.w + V1.x + V2.x,
  y = V3.w + V1.y}
VD1[3] = {
  x = V3.w + V1.x + V2.x,
  y = V3.w + V1.y + V2.y}
VD1[4] = {
  x = V3.w + V1.x,
  y = V3.w + V1.y + V2.y}

VD2[1] = {
  x = -V3.w + V1.x,
  y = --V3.w + V1.y}
VD2[2] = {
  x = -V3.w + V1.x + V2.x,
  y = -V3.w + V1.y}
VD2[3] = {
  x = -V3.w + V1.x + V2.x,
  y = -V3.w + V1.y + V2.y}
VD2[4] = {
  x = -V3.w + V1.x,
  y = -V3.w + V1.y + V2.y}

VD3[1] = {
  x = V1.x,
  y = V1.y}
VD3[2] = {
  x = V1.x + V2.x,
  y = V1.y}
VD3[3] = {
  x = V1.x + V2.x,
  y = V1.y + V2.y}
VD3[4] = {
  x = V1.x,
  y = V1.y + V2.y}

ComputeTextureUV(VD1[1],0,0)
ComputeTextureUV(VD1[2],1,0)
ComputeTextureUV(VD1[3],1,1)
ComputeTextureUV(VD1[4],0,1)
  
ComputeTextureUV(VD2[1],0,0)
ComputeTextureUV(VD2[2],1,0)
ComputeTextureUV(VD2[3],1,1)
ComputeTextureUV(VD2[4],0,1)
  
ComputeTextureUV(VD3[1],0,0)
ComputeTextureUV(VD3[2],1,0)
ComputeTextureUV(VD3[3],1,1)
ComputeTextureUV(VD3[4],0,1)

SetColor(CSHADOW)
DrawToBuffer(VD1)
SetColor(CHIGHLIGHT)
DrawToBuffer(VD2)
SetColor(CFACE)
DrawToBuffer(VD3)

Mnemonic: DRASTER X
Encoding: 283 RM [Segment1] [Constant1]




Set raster quality.

Pseudocode:

Registers[RasterQ] = X

Mnemonic: DDTERRAIN X
Encoding: 284 RM [Segment1] [Constant1]




Draw 3D terrain.

Pseudocode:

W = ReadCell(X+0)
H = ReadCell(X+1)
R = clamp(floor(ReadCell(X+2)),0,16)
U = ReadCell(X+3)
V = ReadCell(X+4)

MinX = clamp(floor(W/2 + U - R),1,W-1)
MinY = clamp(floor(H/2 + V - R),1,H-1)
MaxX = clamp(floor(W/2 + U + R),1,W-1)
MaxY = clamp(floor(H/2 + V + R),1,H-1)

for X=MinX,MaxX do
  for Y=MinY,MaxY do
    XPOS = X - W/2 - U - 0.5
    YPOS = Y - H/2 - U - 0.5

    if (X > 0) and (X <= W-1) and (Y > 0) and (Y <= H-1) and (XPOS^2+YPOS^2 <= R^2) then
      Z1 = ReadCell(X+16+(Y-1)*W+(X-1)
      Z2 = ReadCell(X+16+(Y-1)*W+(X-0)
      Z3 = ReadCell(X+16+(Y-0)*W+(X-0)
      Z4 = ReadCell(X+16+(Y-0)*W+(X-1)
      
      VD[1] = { x = XPOS,   y = YPOS,   y = Z1 }
      VD[2] = { x = XPOS+1, y = YPOS,   y = Z2 }
      VD[3] = { x = XPOS+1, y = YPOS+1, y = Z3}

      ComputeTextureUV(VD[1],0,0)
      ComputeTextureUV(VD[2],1,0)
      ComputeTextureUV(VD[3],1,1)
      DrawToBuffer(VD)
        
      VD[1] = { x = XPOS,   y = YPOS,   y = Z1}
      VD[2] = { x = XPOS,   y = YPOS+1, y = Z4}
      VD[3] = { x = XPOS+1, y = YPOS+1, y = Z3}

      ComputeTextureUV(VD[1],0,0)
      ComputeTextureUV(VD[2],0,1)
      ComputeTextureUV(VD[3],1,1)
      DrawToBuffer(VD)
    end
  end
end

Mnemonic: DMULDT X,Y
Encoding: 294 RM [Segment1] [Segment2] [Constant1] [Constant2]




Multiplies Y by time-step and writes it into X. Used in frame-based mode to provide smooth animations.

Pseudocode:

X = Y * TimerDT

Mnemonic: DBEGIN
Encoding: 298




Starts asynchonous drawing. Used only in asynchronous thread.

Pseudocode:

SetRenderTarget(1)

Mnemonic: DEND
Encoding: 299




Ends asynchonous drawing, and outputs the drawn image to screen.

Pseudocode:

FlushBuffer()
Copy(1,0)
SetRenderTarget(2)

Mnemonic: DXTEXTURE X
Encoding: 303 RM [Segment1] [Constant1]




Binds a predefined texture. X points to the string that contains texture name. If X is equal to 0, texture will be unbound.

Pseudocode:

if X > 0 then
  NAME = VM:ReadString(X)
  SetTexture(NAME)
else
  SetTexture(0)
end

ZSPU is a programmable sequencer and sound synthesizer. It works with waveforms and channels. Each waveform is an unique sound, there can be up to 128 different waveforms loaded, while each channel corresponds to one of the output channels. It’s possible to play just one waveform through each channel, although it’s possible to use same waveform for many channels.

Each channel has separate pitch, volume settings, and supports hardware ADSR/LFO.

no chapter

no chapter

Mnemonic: CHRESET X
Encoding: 320 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CHSTART X
Encoding: 321 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: CHSTOP X
Encoding: 322 RM [Segment1] [Constant1]




Pseudocode:

Mnemonic: WSET X,Y
Encoding: 330 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: CHWAVE X,Y
Encoding: 331 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: CHLOOP X,Y
Encoding: 332 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: CHVOLUME X,Y
Encoding: 333 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: CHPITCH X,Y
Encoding: 334 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: CHMODT X,Y
Encoding: 335 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: CHMODA X,Y
Encoding: 336 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: CHMODF X,Y
Encoding: 337 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: CHADSR X,Y
Encoding: 338 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:

Mnemonic: WLEN X,Y
Encoding: 339 RM [Segment1] [Segment2] [Constant1] [Constant2]




Pseudocode:


This document was translated from LATEX by HEVEA.