User documentation

Version 0.39

1. Introduction

Computer MITS Altair 8800 was named after a planet in one of the first episodes of Star Trek series. Having Intel 8080 CPU inside, with 256 bytes of memory, no display and keyboard is this computer, when comparing to the present era, absolutely ridiculous. His author, Ed Roberts, called the invention "personal computer", which is now very common term. As Wikipedia states:

The Altair is widely recognized as the spark that ignited the microcomputer revolution.
— Wikipedia
Altair 8800
MITS Altair8800 with LSI ADM-3A terminal and floppy drive

Altair 8800 is one of the oldest commercially available computers overall. Ed Roberts (founder and CEO of MITS corporation) was selling these machines by mail directly from the factory.

Various enthusiasts understood the power of Altair and started to develop software and hardware for the computer. Those people saw a freedom in Altair - some kind of a release from batch tasks ran on mainframe systems, maintained by elite. The phenomenon of a computer which could be put on the kitchen table allowed to make enormous money by two smart university students. In 1975, Paul Allen and Bill Gates wrote a trimmed version of BASIC programming language, called Altair BASIC, which pushed them directly to foundation of Microsoft corporation.

Basic configuration of MITS Altair 8800 was:

Processor

Intel 8080 or 8080a

Speed

2 MHz

RAM

from 256 bytes to 64 kB

ROM

optional; usually EPROM Intel 1702 with 256 bytes. [1]

Storage

optional; paper tapes, cassette tapes or 5.25" or 8" floppy disks (MITS 88-DISK)

Extensions

at first 16 slots, later 18 slots

Bus

famous S-100

Video

none

I/O

optional; parallel or serial board (MITS 88-SIO)

Original software

Altair DOS, CP/M, Altair BASIC

2. Altair8800 for emuStudio

In emuStudio, there exist two variants of the computer. These variants varies only with the used CPU. There is available either Intel 8080 emulator, or Zilog Z80 emulator. Lots of behavior and some devices was inspired by simh emulator.

Abstract schema for emuStudio (Intel 8080):

Abstract schema of MITS Altair8800 (with Intel 8080)

Abstract schema for emuStudio (Zilog Z80):

Abstract schema of MITS Altair8800 (with Zilog Z80)

The following plug-ins are used in the schemas. Each plug-in is described in further sections.

Compiler

as-8080

Compiler

as-z80

Operating memory (RAM)

standard-mem

CPU

8080-cpu

Another CPU

z80-cpu

Device

88-sio

Device

88-disk

Device

simhPseudo-z80

Device

adm3A-terminal

3. Assembler as-8080

Assembler for Intel 8080 CPU in emuStudio is very similar to Intel assembler, but has some little differences. Features include:

  • full instructions support

  • macro support (unlimited nesting)

  • include other files support

  • data definition

  • relative addressing using labels

  • literals and expressions in various radixes (bin, dec, hex, oct)

  • output is in Intel HEX format

The features are very similar to those in as-z80 assembler.

3.1. Installation and run

The assembler is provided as part of emuStudio. It is not deployed as individual package. It can be found in compilers/ directory with name as-8080.jar.

The assembler can be run from command line, with command:

java -jar as-8080.jar [--output output_file.hex] [source_file.asm]

To query for more information, run it with command:

java -jar as-8080.jar --help

3.2. Lexical symbols

The assembler does not differentiate between upper and lower case (it is case-insensitive). The token/symbol types are as follows:

Type Description

Comments

semi-colon (;) with text after it until the end of the line

Keywords

instruction names; preprocessor directives (org, equ, set, macro, endm, include, if, endif); data definitions (db, dw, ds); CPU registers

Identifiers

([a-zA-Z_\?@])[a-zA-Z_\?@0-9]* except keywords

Labels

Constants

strings or integers

Operators

+, -, *, /, =, mod, and, or, not, xor, shl, shr

3.2.1. Constants

Numeric constants can be only integers, encoded with one of several number radixes. The possible formats are written using regexes:

  • binary numbers: [0-1]+[bB]

  • decimal numbers: [0-9]+[dD]?

  • octal numbers: [0-7]+[oOqQ]

  • hexadecimal numbers: [0-9][0-9a-fA-F]*[hH]

Characters or strings must be enclosed in single-quotes, e,g,: MVI E, '*'

3.2.2. Identifiers

Identifiers must fit to the following regex: ([a-zA-Z_\?@])[a-zA-Z_\?@0-9]*. It means, that it has to start with a letter a-z (or A-Z) or the at-sign (@). Then, it can be followed by letters, at-sign, or numbers.

However, they must not equal to any keyword.

3.3. Instructions syntax

The program is basically a sequence of instructions. The instructions are separated by a new line. The instruction have optional and mandatory parts, e.g.:

LABEL: CODE OPERANDS ; COMMENT

LABEL

Optional

Identifier of the memory position, followed by a colon (:). It can be used as forward or backward reference in instructions which expect memory address (or 16 bit number).

CODE

Mandatory

Instruction name.

OPERANDS

It depends

If applicable, a comma-separated (,) operands of the instruction.

COMMENT

Optional

semi-colonm (;) followed by any text until the end of the line.

Fields CODE and OPERANDS must be separated by at least one space. For example:

HERE:   MVI C, 0  ; Put 0 into C register
        DB 3Ah    ; Data constant of size 1 byte
LOOP:   JMP LOOP  ; Infinite loop

Labels are optional. Instructions and pseudo-instructions and register names are reserved for assembler and cannot be used as labels. Also, there cannot be more definitions of the same label.

Operands must be separated with comma (,). There exist several operand types, which represent so-called "address modes". Allowed address modes depend on the instruction. The possibilities are:

  • Implicit addressing: instructions do not have operands. They are implicit.

  • Register addressing: operands are registers. 8-bit general-purpose register names are: A, B, C, D, E, H, L. Stack pointer is defined as SP, and program status word (used by push / pop instructions) as PSW. When register pairs should be used in 16-bit instructions, the same register names are used. For example, DCX D which decrements pair DE.

  • Register indirect addressing: for the memory value specified by address in HL pair is used special register called M, for example: MOV A, M.

  • Immediate addressing: operand is the 8-bit constant. It can be also one character, enclosed in single-quotes.

  • Direct addressing: operand is either 8-bit or 16-bit constant, which is understood as the memory location (address). For example: SHLD 1234h.

  • Modified page zero: operand is 3-bit value (0-7). It represents a "index", which is multiplied by constant 8, resulting in final memory address. Used in RST instruction.

Immediate data or addresses can be defined in various ways:

  • Integer constant

  • Integer constant as a result of evaluation of some expression (e.g. 2 SHL 4, or 2 + 2)

  • Current address - denoted by special variable $. For example, instruction JMP $+6 denotes a jump by 6-bytes further from the current address.

  • Character constants, enclosed in single-quotes (e.g. MVI A, '*')

  • Labels. For example: JMP THERE will jump to the label THERE.

  • Variables. For example:

    VALUE SET 'A'
    MVI A, VALUE

3.4. Expressions

An expression is a combination of the data constants and operators. Expressions are evaluated in compile-time. Given any two expressions, they must not be defined in circular way.

Expressions can be used anywhere a constant is expected.

There exist several operators, such as:

+

Addition. Example: DB 2 + 2; evaluates to DB 4

-

Subtraction. Example: DW $ - 2; evaluates to the current compilation address minus 2.

*

Multiply.

/

Integer division.

=

Comparison for equality. Returns 1 if operands equal, 0 otherwise. Example: DB 2 = 2; evaluates to DB 1.

mod

Remainder after integer division. Example DB 4 mod 3; evaluates to DB 1.

and

Logical and.

or

Logical or.

xor

Logical xor.

not

Logical not.

shl

Shift left by 1 bit. Example: DB 1 SHL 3; evaluates to DB 8

shr

Shift right by 1 bit.

Operator priorities are as follows:

Priority Operator Type

7

or, xor

Binary

1

( )

Unary

2

*, /, mod, shl, shr

Binary

3

+, -

Unary and binary

4

=

Binary

5

not

Unary

6

and

Binary

All operators work with its arguments as if they were 16-bit. Their results are always 16-bit numbers. If there is expected 8-bit number, the result is automatically "cut" using operation result AND 0FFh. This may be unwanted behavior and might lead to bugs, but it is often useful so the programmer must ensure the correctness.

3.5. Defining data

Data can be defined using special pseudoinstructions. These accept constants. Negative integers are using two’s complement.

The following table describes all possible data definition pseudoinstructions:

DB [expression]

Define byte. The [expression] must be of size 1 byte. Using this pseudoinstruction, a string can be defined, enclosed in single quotes. For example: DB 'Hello, world!' is equal to DB 'H', DB 'e', etc. on separate lines.

DW [expression]

Define word. The [expression] must be max. of size 2 bytes. Data are stored using little endian.

DS [expression]

Define storage. The [expression] represents number of bytes which should be "reserved". The reserved space will not be modified in memory. It is similar to "skipping" particular number of bytes.

3.5.1. Examples:

HERE:  DB 0A3H          ; A3
W0RD1: DB 5*2, 2FH-0AH  ; 0A25
W0RD2: DB 5ABCH SHR 8   ; 5A
STR:   DB 'STRINGSpl'   ; 535452494E472031
MINUS: DB -03H          ; FD
ADD1: dw COMP          ; 1C3B  (assume COMP is 3B1CH)
ADD2: dw FILL          ; B43E (assume FILL is 3EB4H)
ADD3: dw 3C01H, 3CAEH  ; 013CAE3C

3.6. Including other source files

It is both useful and good practice to write modular programs. According to the DRY principle the repetitive parts of the program should be refactored out into functions or modules. Functionally similar groups of these functions or modules can be put into a library, reusable in other programs.

The pseudoinstruction include exists for the purpose of including already written source code into the current program. The pseudoinstruction is defined as follows:

INCLUDE '[filename]'

where [filename] is a relative or absolute path to the file which will be included, enclosed in single-quotes. The file can include other files, but there must not be defined circular includes (compiler will complain).

The current compilation address (denoted by $ variable) after the include will be updated about the binary size of the included file.

The namespace of the current program and the included file is shared. It means that labels or variables with the same name in the current program and the included file are prohibited. Include file "sees" everything in the current program as it was its part.

Example:

Let a.asm contains:

mvi b, 80h

Let b.asm contains:

include 'a.asm'

Then compiling b.asm will result in:

06 80     ; mvi b, 80h

3.7. Origin address (ORG)

Syntax: ORG [expression]

Sets the value to the $ variable. It means that from now on, the following instructions will be placed at the address given by the [expression]. Effectively, it is the same as using DS pseudo-instruction, but instead of defining number of skipped bytes, we define concrete memory location (address).

The following two code snippets are equal:

Address Block 1 Block 2 Opcode

2C10

NEXT: XRA A

NEXT: XRA A

AF

2C00

MOV A,C

MOV A,C

79

2C01

JMP NEXT

JMP NEXT

C3 10 2C

2C04

DS 12

ORG $+12

3.8. Equate (EQU)

Syntax: [identifier] EQU [expression]

Define a constant. The [identifier] is a mandatory name of the constant. Please see the Identifiers section for more details.

[expression] is the 16-bit expression.

The pseudo-instruction will define a constant - assign a name to given expression. The name of the constant then can be used anywhere where the constant is expected and the compiler will replace it with the expression.

It is not possible to redefine a constant.

3.9. Using variables

Syntax: [identifier] SET [expression]

Define or re-define a variable. The [identifier] is a mandatory name of the constant. Please see the Identifiers section for more details.

[expression] is the 16-bit expression.

The pseudo-instruction will define a variable - assign a name to given expression. Then, the name of the variable can be used anywhere where the constant is expected.

It is possible to redefine a variable, which effectively means to reassign new expression to the same name and forgetting the old one. The reassignment is aware of locality, i.e. before it the old value will be used, after it the new value will be used.

3.10. Conditional assembly

Syntax:

if [expression]
    i n s t r u c t i o n s
endif

At first, the compiler evaluates the [expression]. If the result is 0, instructions between if and endif will be ignored. Otherwise they will be included in the source code.

3.11. Defining and using macros

Syntax:

[identifier] macro [operands]
    i n s t r u c t i o n s
endm

The [identifier] is a mandatory name of the macro. Please see the Identifiers section for more details.

The [operands] part is a list of identifiers, separated by commas (,). Inside the macro, operands act as constants. If the macro does not use any operands, this part can be omitted.

The namespace of the operand identifiers is macro-local, ie. the operand names will not be visible outside the macro. Also, the operand names can hide variables, labels or constants defined in the outer scope.

The macros can be understood as "templates" which will be expanded in the place where they are "called". The call syntax is as follows:

[macro name] [arguments]

where [macro name] is the macro name as defined above. Then, [arguments] are comma-separated expressions, in the order as the original operands are defined. The number of arguments must be the same as number of macro operands.

The macro can be defined anywhere in the program, even in some included file. Also, it does not matter in which place is called - above or below the macro definition.

Examples:

SHV MACRO
LOOP: RRC      ; Right rotate with carry
      ANI 7FH  ; Clear MSB of accumulator
      DCR D    ; Decrement rotation counter - register D
      JNZ LOOP ; Jump to next rotation
ENDM

The macro SHV can be used as follows:

LDA TEMP
MVI D,3  ; 3 rotations
SHV
STA TEMP

Or another definition:

SHV MACRO AMT
      MVI D,AMT   ; Number of rotations
LOOP: RRC
      ANI 7FH
      DCR D
      JNZ LOOP
ENDM

And usage:

LDA TEMP
SHV 5

Which has the same effect as the previous example.

4. Assembler as-z80

The assembler syntax is inspired by as-8080 assembler, and by instruction set described here. The assembler supports the following features:

  • full instructions support

  • macro support (unlimited nesting)

  • include other files support

  • conditional assembly

  • data definition

  • relative addressing using labels

  • literals and expressions in various radixes (bin, dec, hex, oct)

  • output is in Intel HEX format

4.1. Installation and run

The assembler is provided as part of emuStudio. It is not deployed as individual package. It can be found in compilers/ directory with name as-z80.jar.

The assembler can be run from command line, with command:

java -jar as-z80.jar [--output output_file.hex] [source_file.asm]

To query for more information, run it with command:

java -jar as-z80.jar --help

4.2. Lexical symbols

The assembler does not differentiate between upper and lower case (it is case-insensitive). The token/symbol types are as follows:

Type Description

Comments

semi-colon (;) with text after it until the end of the line

Keywords

instruction names; preprocessor directives (org, equ, var, macro, endm, include, if, endif); data definitions (db, dw, ds); CPU registers

Identifiers

([a-zA-Z_\?@])[a-zA-Z_\?@0-9]* except keywords

Labels

Constants

strings or integers

Operators

+, -, *, /, =, %, &, |, !, ~, <<, >>, >, <, >=, '⇐`

4.2.1. Constants

Numeric constants can be only integers, encoded with one of several number radixes. The possible formats are written using regexes:

  • binary numbers: [0-1]+[bB]

  • decimal numbers: [0-9]+[dD]?

  • octal numbers: [0-7]+[oOqQ]

  • hexadecimal numbers: [0-9][0-9a-fA-F]*[hH]

Characters or strings must be enclosed in double-quotes, e,g,: LD E, "*"

4.2.2. Identifiers

Identifiers must fit to the following regex: ([a-zA-Z_\?@])[a-zA-Z_\?@0-9]*. It means, that it has to start with a letter a-z (or A-Z) or the at-sign (@). Then, it can be followed by letters, at-sign, or numbers.

However, they must not equal to any keyword.

4.3. Instructions syntax

The program is basically a sequence of instructions. The instructions are separated by a new line. The instruction have optional and mandatory parts, e.g.:

LABEL: CODE OPERANDS ; COMMENT

LABEL

Optional

Identifier of the memory position, followed by a colon (:). It can be used as forward or backward reference in instructions which expect memory address (or 16 bit number).

CODE

Mandatory

Instruction name.

OPERANDS

It depends

If applicable, a comma-separated (,) operands of the instruction.

COMMENT

Optional

semi-colonm (;) followed by any text until the end of the line.

Fields CODE and OPERANDS must be separated by at least one space. For example:

HERE:   LD C, 0  ; Put 0 into C register
        DB 3Ah   ; Data constant of size 1 byte
LOOP:   JP LOOP  ; Infinite loop

Labels are optional. Instructions and pseudo-instructions and register names are reserved for assembler and cannot be used as labels. Also, there cannot be more definitions of the same label.

Operands must be separated with comma (,). There exist several operand types, which represent so-called "address modes". Allowed address modes depend on the instruction. The possibilities are:

  • Implicit addressing: instructions do not have operands. They are implicit.

  • Register addressing: operands are registers. 8-bit general-purpose register names are: A, B, C, D, E, H, L. Register pairs have names: BC, DE, HL. Stack pointer is defined as SP, and program status word (used by push / pop instructions) as AF. Another 16-bit registers are defined as IX, IY.

  • Register indirect addressing: for example, loading a memory value at address in HL pair: LD A, (HL).

  • Immediate addressing: operand is the 8-bit constant. It can be also one character, enclosed in double-quotes.

  • Direct addressing: operand is either 8-bit or 16-bit constant, which is understood as the memory location (address). For example: LD (1234h), HL.

Immediate data or addresses can be defined in various ways:

  • Integer constant

  • Integer constant as a result of evaluation of some expression (e.g. 2 << 4, or 2 + 2)

  • Current address - denoted by special variable $. For example, instruction JP $+6 denotes a jump by 6-bytes further from the current address.

  • Character constants, enclosed in double-quotes (e.g. LD A, "*")

  • Labels. For example: JP THERE will jump to the label THERE.

  • Variables. For example:

    VALUE VAR 'A'
    LD A, VALUE

4.4. Expressions

An expression is a combination of the data constants and operators. Expressions are evaluated in compile-time. Given any two expressions, they must not be defined in circular way.

Expressions can be used anywhere a constant is expected.

There exist several operators, such as:

+

Addition. Example: DB 2 + 2; evaluates to DB 4

-

Subtraction. Example: DW $ - 2; evaluates to the current compilation address minus 2.

*

Multiply.

/

Integer division.

=

Comparison for equality. Returns 1 if operands equal, 0 otherwise. Example: DB 2 = 2; evaluates to DB 1.

%

Remainder after integer division. Example DB 4 mod 3; evaluates to DB 1.

&

Logical and.

|

Logical or.

~

Logical xor.

!

Logical not.

<<

Shift left by 1 bit. Example: DB 1 SHL 3; evaluates to DB 8

>>

Shift right by 1 bit.

>

Greater than. Example: DB 3 > 2; evaluates to DB 1

<

Less than.

>=

Greater or equal than.

Less or equal than.

Operator priorities are as follows:

Priority Operator Type

7

|, ~

Binary

1

( )

Unary

2

*, /, %, <<, >>, >, <, >=,

Binary

3

+, -

Unary and binary

4

=

Binary

5

!

Unary

6

&

Binary

All operators work with its arguments as if they were 16-bit. Their results are always 16-bit numbers. If there is expected 8-bit number, the result is automatically "cut" using operation result AND 0FFh. This may be unwanted behavior and might lead to bugs, but it is often useful so the programmer must ensure the correctness.

4.5. Defining data

Data can be defined using special pseudoinstructions. These accept constants. Negative integers are using two’s complement.

The following table describes all possible data definition pseudoinstructions:

DB [expression]

Define byte. The [expression] must be of size 1 byte. Using this pseudoinstruction, a string can be defined, enclosed in single quotes. For example: DB 'Hello, world!' is equal to DB 'H', DB 'e', etc. on separate lines.

DW [expression]

Define word. The [expression] must be max. of size 2 bytes. Data are stored using little endian.

DS [expression]

Define storage. The [expression] represents number of bytes which should be "reserved". The reserved space will not be modified in memory. It is similar to "skipping" particular number of bytes.

4.5.1. Examples:

HERE:  DB 0A3H          ; A3
W0RD1: DB 5*2, 2FH-0AH  ; 0A25
W0RD2: DB 5ABCH SHR 8   ; 5A
STR:   DB "STRINGSpl"   ; 535452494E472031
MINUS: DB -03H          ; FD
ADD1: dw COMP          ; 1C3B  (assume COMP is 3B1CH)
ADD2: dw FILL          ; B43E (assume FILL is 3EB4H)
ADD3: dw 3C01H, 3CAEH  ; 013CAE3C

4.6. Including other source files

It is both useful and good practice to write modular programs. According to the DRY principle the repetitive parts of the program should be refactored out into functions or modules. Functionally similar groups of these functions or modules can be put into a library, reusable in other programs.

The pseudoinstruction include exists for the purpose of including already written source code into the current program. The pseudoinstruction is defined as follows:

INCLUDE "[filename]"

where [filename] is a relative or absolute path to the file which will be included, enclosed in double-quotes. The file can include other files, but there must not be defined circular includes (compiler will complain).

The current compilation address (denoted by $ variable) after the include will be updated about the binary size of the included file.

The namespace of the current program and the included file is shared. It means that labels or variables with the same name in the current program and the included file are prohibited. Include file "sees" everything in the current program as it was its part.

Example:

Let a.asm contains:

ld b, 80h

Let b.asm contains:

include "a.asm"

Then compiling b.asm will result in:

06 80     ; ld b, 80h

4.7. Origin address (ORG)

Syntax: ORG [expression]

Sets the value to the $ variable. It means that from now on, the following instructions will be placed at the address given by the [expression]. Effectively, it is the same as using DS pseudo-instruction, but instead of defining number of skipped bytes, we define concrete memory location (address).

The following two code snippets are equal:

Address Block 1 Block 2 Opcode

2C10

NEXT: XOR A

NEXT: XOR A

AF

2C00

LD A,C

LD A,C

79

2C01

JP NEXT

JP NEXT

C3 10 2C

2C04

DS 12

ORG $+12

4.8. Equate (EQU)

Syntax: [identifier] EQU [expression]

Define a constant. The [identifier] is a mandatory name of the constant. Please see the Identifiers section for more details.

[expression] is the 16-bit expression.

The pseudo-instruction will define a constant - assign a name to given expression. The name of the constant then can be used anywhere where the constant is expected and the compiler will replace it with the expression.

It is not possible to redefine a constant.

4.9. Using variables

Syntax: [identifier] VAR [expression]

Define or re-define a variable. The [identifier] is a mandatory name of the constant. Please see the Identifiers section for more details.

[expression] is the 16-bit expression.

The pseudo-instruction will define a variable - assign a name to given expression. Then, the name of the variable can be used anywhere where the constant is expected.

It is possible to redefine a variable, which effectively means to reassign new expression to the same name and forgetting the old one. The reassignment is aware of locality, i.e. before it the old value will be used, after it the new value will be used.

4.10. Conditional assembly

Syntax:

if [expression]
    i n s t r u c t i o n s
endif

At first, the compiler evaluates the [expression]. If the result is 0, instructions between if and endif will be ignored. Otherwise they will be included in the source code.

4.11. Defining and using macros

Syntax:

[identifier] macro [operands]
    i n s t r u c t i o n s
endm

The [identifier] is a mandatory name of the macro. Please see the Identifiers section for more details.

The [operands] part is a list of identifiers, separated by commas (,). Inside the macro, operands act as constants. If the macro does not use any operands, this part can be omitted.

The namespace of the operand identifiers is macro-local, ie. the operand names will not be visible outside the macro. Also, the operand names can hide variables, labels or constants defined in the outer scope.

The macros can be understood as "templates" which will be expanded in the place where they are "called". The call syntax is as follows:

[macro name] [arguments]

where [macro name] is the macro name as defined above. Then, [arguments] are comma-separated expressions, in the order as the original operands are defined. The number of arguments must be the same as number of macro operands.

The macro can be defined anywhere in the program, even in some included file. Also, it does not matter in which place is called - above or below the macro definition.

Examples:

SHV MACRO
LOOP: RRCA        ; Right rotate with carry
      AND 7FH     ; Clear MSB of accumulator
      DEC D       ; Decrement rotation counter - register D
      JP NZ, LOOP ; Jump to next rotation
ENDM

The macro SHV can be used as follows:

LD A, (TEMP)
LD D,3  ; 3 rotations
SHV
LD (TEMP), A

Or another definition:

SHV MACRO AMT
      LD D,AMT   ; Number of rotations
LOOP: RRCA
      AND 7FH
      DEC D
      JP NZ, LOOP
ENDM

And usage:

LD A, (TEMP)
SHV 5
LD (TEMP), A

Which has the same effect as the previous example.

5. CPU: Intel 8080 emulator

Altair 8800 originally came with processor Intel 8080. It is 8-bit microprocessor, from 1974. Initial clock frequency was 2 MHZ. This processor was one of the first general-purpose and widespread processors, used not only in calculators but also in first personal computers. One of the key roles why this CPU become so popular is that Gary Killdall targeted his CP/M to this CPU; and CP/M was de facto a "standard" in personal computers those days.

Main features of the emulator include:

  • Threaded-dispatch combined with interpretation as emulation technique,

  • Correct real timing of instructions,

  • Ability to set clock frequency manually at run-time,

  • Emulation of all instructions including interrupts,

  • Disassembler implementation,

  • Ability to "dump" instruction history to console at run-time,

  • Support of breakpoints,

  • Ability of communication with up to 256 I/O devices,

  • Status window shows all registers, flags, and run-time frequency.

6. Dumping executed instructions

The CPU offers a quite unique feature, which is the ability to dump executed instructions as a sequence to the console. When enabled, then each executed instruction - together with content of flags and registers values after the execution are printed. This feature might be extremely useful in two cases:

  1. Reverse engineering of some unknown software

  2. It allows to build tools for automatic checking of register values during the emulation, when performing automatic emulation.

In order to enable this feature, please see the section Configuration file.

For example, let’s take one of the examples which computes a reverse text:

Example program for reversing text in 8080 assembly
; Print reversed text

org 1000

dcx sp       ; stack initialization (0FFFFh)

lxi h,text1
call putstr  ; print text1
lxi d,input  ; address for string input
call getline ; read from keyboard

lxi b,input

mvi d,0      ; chars counter

char_loop:
ldax b
inx b        ; bc = bc+1
cpi 10       ; end of input?
jz char_end
cpi 13
jz char_end

inr d        ; d =d+1
jmp char_loop
char_end:

dcx b        ;  bc = bc-1
dcx b

call newline

char2_loop:
ldax b
call putchar

dcx b

dcr d
jz char2_end

jmp char2_loop
char2_end:


hlt

include 'include\getchar.inc'
include 'include\getline.inc'
include 'include\putstr.inc'
include 'include\putchar.inc'
include 'include\newline.inc'

text1: db 'Reversed text ...',10,13,'Enter text: ',0
text2: db 10,13,'Reversed: ',0
input: ds 30

When the program is being run, and the dump instructions feature is turned on, on console you can see the following output:

0000 | PC=03e8 |       dcx SP |        3B  || regs=00 00 00 00 00 00 00 00  | flags=      | SP=ffff | PC=03e9
0001 | PC=03e9 |  lxi HL, 485 |  21 85 04  || regs=00 00 00 00 04 85 00 00  | flags=      | SP=ffff | PC=03ec
0001 | PC=03ec |     call 46D |  CD 6D 04  || regs=00 00 00 00 04 85 00 00  | flags=      | SP=fffd | PC=046d
0002 | PC=046d |     mov A, M |        7E  || regs=00 00 00 00 04 85 00 52  | flags=      | SP=fffd | PC=046e
0002 | PC=046e |       inx HL |        23  || regs=00 00 00 00 04 86 00 52  | flags=      | SP=fffd | PC=046f
0003 | PC=046f |        cpi 0 |     FE 00  || regs=00 00 00 00 04 86 00 52  | flags=      | SP=fffd | PC=0471
0004 | PC=0471 |           rz |        C8  || regs=00 00 00 00 04 86 00 52  | flags=      | SP=fffd | PC=0472
0005 | PC=0472 |       out 11 |     D3 11  || regs=00 00 00 00 04 86 00 52  | flags=      | SP=fffd | PC=0474
0006 | PC=0474 |      jmp 46D |  C3 6D 04  || regs=00 00 00 00 04 86 00 52  | flags=      | SP=fffd | PC=046d
0006 | PC=046d |     mov A, M |        7E  || regs=00 00 00 00 04 86 00 65  | flags=      | SP=fffd | PC=046e
0024 | Block from 0474 to 03EF; count=184
0024 | PC=03ef |  lxi DE, 4B2 |  11 B2 04  || regs=00 00 04 b2 04 a5 00 00  | flags= Z P  | SP=ffff | PC=03f2
0025 | PC=03f2 |     call 428 |  CD 28 04  || regs=00 00 04 b2 04 a5 00 00  | flags= Z P  | SP=fffd | PC=0428
0025 | PC=0428 |     mvi C, 0 |     0E 00  || regs=00 00 04 b2 04 a5 00 00  | flags= Z P  | SP=fffd | PC=042a
0025 | PC=042a |        in 10 |     DB 10  || regs=00 00 04 b2 04 a5 00 00  | flags= Z P  | SP=fffd | PC=042c
0026 | PC=042c |        ani 1 |     E6 01  || regs=00 00 04 b2 04 a5 00 00  | flags= Z P  | SP=fffd | PC=042e
0026 | PC=042e |       jz 42A |  CA 2A 04  || regs=00 00 04 b2 04 a5 00 00  | flags= Z P  | SP=fffd | PC=042a
0027 | PC=042a |        in 10 |     DB 10  || regs=00 00 04 b2 04 a5 00 00  | flags= Z P  | SP=fffd | PC=042c
1548 | Block from 042E to 0431; count=181125
1548 | PC=0431 |        in 11 |     DB 11  || regs=00 00 04 b2 04 a5 00 68  | flags=      | SP=fffd | PC=0433
1548 | PC=0433 |        cpi D |     FE 0D  || regs=00 00 04 b2 04 a5 00 68  | flags=      | SP=fffd | PC=0435
1548 | PC=0435 |       jz 461 |  CA 61 04  || regs=00 00 04 b2 04 a5 00 68  | flags=      | SP=fffd | PC=0438
1548 | PC=0438 |        cpi A |     FE 0A  || regs=00 00 04 b2 04 a5 00 68  | flags=      | SP=fffd | PC=043a
1549 | PC=043a |       jz 461 |  CA 61 04  || regs=00 00 04 b2 04 a5 00 68  | flags=      | SP=fffd | PC=043d
1549 | PC=043d |        cpi 8 |     FE 08  || regs=00 00 04 b2 04 a5 00 68  | flags=  AP  | SP=fffd | PC=043f
1549 | PC=043f |      jnz 459 |  C2 59 04  || regs=00 00 04 b2 04 a5 00 68  | flags=  AP  | SP=fffd | PC=0459
1549 | PC=0459 |       out 11 |     D3 11  || regs=00 00 04 b2 04 a5 00 68  | flags=  AP  | SP=fffd | PC=045b
1549 | PC=045b |      stax DE |        12  || regs=00 00 04 b2 04 a5 00 68  | flags=  AP  | SP=fffd | PC=045c
1549 | PC=045c |       inx DE |        13  || regs=00 00 04 b3 04 a5 00 68  | flags=  AP  | SP=fffd | PC=045d
1549 | PC=045d |        inr C |        0C  || regs=00 01 04 b3 04 a5 00 68  | flags=      | SP=fffd | PC=045e
1549 | PC=045e |      jmp 42A |  C3 2A 04  || regs=00 01 04 b3 04 a5 00 68  | flags=      | SP=fffd | PC=042a
1550 | PC=042a |        in 10 |     DB 10  || regs=00 01 04 b3 04 a5 00 00  | flags=      | SP=fffd | PC=042c
2940 | Block from 045E to 0461; count=267777
2940 | PC=0461 |     mvi A, A |     3E 0A  || regs=00 05 04 b7 04 a5 00 0a  | flags= ZAP  | SP=fffd | PC=0463
2940 | PC=0463 |      stax DE |        12  || regs=00 05 04 b7 04 a5 00 0a  | flags= ZAP  | SP=fffd | PC=0464
2940 | PC=0464 |       inx DE |        13  || regs=00 05 04 b8 04 a5 00 0a  | flags= ZAP  | SP=fffd | PC=0465
2940 | PC=0465 |     mvi A, D |     3E 0D  || regs=00 05 04 b8 04 a5 00 0d  | flags= ZAP  | SP=fffd | PC=0467
2940 | PC=0467 |      stax DE |        12  || regs=00 05 04 b8 04 a5 00 0d  | flags= ZAP  | SP=fffd | PC=0468
2940 | PC=0468 |       inx DE |        13  || regs=00 05 04 b9 04 a5 00 0d  | flags= ZAP  | SP=fffd | PC=0469
2940 | PC=0469 |     mvi A, 0 |     3E 00  || regs=00 05 04 b9 04 a5 00 00  | flags= ZAP  | SP=fffd | PC=046b
2941 | PC=046b |      stax DE |        12  || regs=00 05 04 b9 04 a5 00 00  | flags= ZAP  | SP=fffd | PC=046c
2941 | PC=046c |          ret |        C9  || regs=00 05 04 b9 04 a5 00 00  | flags= ZAP  | SP=ffff | PC=03f5
2941 | PC=03f5 |  lxi BC, 4B2 |  01 B2 04  || regs=04 b2 04 b9 04 a5 00 00  | flags= ZAP  | SP=ffff | PC=03f8
2941 | PC=03f8 |     mvi D, 0 |     16 00  || regs=04 b2 00 b9 04 a5 00 00  | flags= ZAP  | SP=ffff | PC=03fa
2941 | PC=03fa |      ldax BC |        0A  || regs=04 b2 00 b9 04 a5 00 68  | flags= ZAP  | SP=ffff | PC=03fb
2941 | PC=03fb |       inx BC |        03  || regs=04 b3 00 b9 04 a5 00 68  | flags= ZAP  | SP=ffff | PC=03fc
2941 | PC=03fc |        cpi A |     FE 0A  || regs=04 b3 00 b9 04 a5 00 68  | flags=      | SP=ffff | PC=03fe
2941 | PC=03fe |       jz 40A |  CA 0A 04  || regs=04 b3 00 b9 04 a5 00 68  | flags=      | SP=ffff | PC=0401
2942 | PC=0401 |        cpi D |     FE 0D  || regs=04 b3 00 b9 04 a5 00 68  | flags=      | SP=ffff | PC=0403
2942 | PC=0403 |       jz 40A |  CA 0A 04  || regs=04 b3 00 b9 04 a5 00 68  | flags=      | SP=ffff | PC=0406
2942 | PC=0406 |        inr D |        14  || regs=04 b3 01 b9 04 a5 00 68  | flags=      | SP=ffff | PC=0407
2942 | PC=0407 |      jmp 3FA |  C3 FA 03  || regs=04 b3 01 b9 04 a5 00 68  | flags=      | SP=ffff | PC=03fa
2942 | PC=03fa |      ldax BC |        0A  || regs=04 b3 01 b9 04 a5 00 65  | flags=      | SP=ffff | PC=03fb
2942 | Block from 0407 to 040A; count=36
2942 | PC=040a |       dcx BC |        0B  || regs=04 b7 05 b9 04 a5 00 0a  | flags= ZAP  | SP=ffff | PC=040b
2943 | PC=040b |       dcx BC |        0B  || regs=04 b6 05 b9 04 a5 00 0a  | flags= ZAP  | SP=ffff | PC=040c
2943 | PC=040c |     call 47A |  CD 7A 04  || regs=04 b6 05 b9 04 a5 00 0a  | flags= ZAP  | SP=fffd | PC=047a
2943 | PC=047a |     mvi A, A |     3E 0A  || regs=04 b6 05 b9 04 a5 00 0a  | flags= ZAP  | SP=fffd | PC=047c
2943 | PC=047c |     call 477 |  CD 77 04  || regs=04 b6 05 b9 04 a5 00 0a  | flags= ZAP  | SP=fffb | PC=0477
2943 | PC=0477 |       out 11 |     D3 11  || regs=04 b6 05 b9 04 a5 00 0a  | flags= ZAP  | SP=fffb | PC=0479
2943 | PC=0479 |          ret |        C9  || regs=04 b6 05 b9 04 a5 00 0a  | flags= ZAP  | SP=fffd | PC=047f
2943 | PC=047f |     mvi A, D |     3E 0D  || regs=04 b6 05 b9 04 a5 00 0d  | flags= ZAP  | SP=fffd | PC=0481
2943 | PC=0481 |     call 477 |  CD 77 04  || regs=04 b6 05 b9 04 a5 00 0d  | flags= ZAP  | SP=fffb | PC=0477
2943 | PC=0477 |       out 11 |     D3 11  || regs=04 b6 05 b9 04 a5 00 0d  | flags= ZAP  | SP=fffb | PC=0479
2943 | Block from 0481 to 0484; count=2
2943 | PC=0484 |          ret |        C9  || regs=04 b6 05 b9 04 a5 00 0d  | flags= ZAP  | SP=ffff | PC=040f
2944 | PC=040f |      ldax BC |        0A  || regs=04 b6 05 b9 04 a5 00 6f  | flags= ZAP  | SP=ffff | PC=0410
2944 | PC=0410 |     call 477 |  CD 77 04  || regs=04 b6 05 b9 04 a5 00 6f  | flags= ZAP  | SP=fffd | PC=0477
2944 | PC=0477 |       out 11 |     D3 11  || regs=04 b6 05 b9 04 a5 00 6f  | flags= ZAP  | SP=fffd | PC=0479
2944 | Block from 0410 to 0413; count=2
2944 | PC=0413 |       dcx BC |        0B  || regs=04 b5 05 b9 04 a5 00 6f  | flags= ZAP  | SP=ffff | PC=0414
2944 | PC=0414 |        dcr D |        15  || regs=04 b5 04 b9 04 a5 00 6f  | flags=  A   | SP=ffff | PC=0415
2944 | PC=0415 |       jz 41B |  CA 1B 04  || regs=04 b5 04 b9 04 a5 00 6f  | flags=  A   | SP=ffff | PC=0418
2944 | PC=0418 |      jmp 40F |  C3 0F 04  || regs=04 b5 04 b9 04 a5 00 6f  | flags=  A   | SP=ffff | PC=040f
2944 | PC=040f |      ldax BC |        0A  || regs=04 b5 04 b9 04 a5 00 6c  | flags=  A   | SP=ffff | PC=0410
2945 | Block from 0418 to 041B; count=31
2945 | PC=041b |          hlt |        76  || regs=04 b1 00 b9 04 a5 00 68  | flags= ZAP  | SP=ffff | PC=041c

The dump format consists of lines, each line represents one instruction execution. The line is separated by | chars, splitting it into so-called sections. Sections before the sequence || represent state before instruction execution, and sections after it represent the state after instruction execution. Particular sections are described in the following table.

Column Description

8

Program counter after instruction execution

1

Timestamp from program start (seconds)

2

Program counter before instruction execution

3

Disassembled instruction

4

Instruction opcodes

Now follows the state after instruction execution

5

Register values (B,C,D,E,H,L, reserved (always 0), A)

6

Flags

7

Stack pointer register (SP)

6.1. Configuration file

Configuration file of virtual computers contain also settings of all the used plug-ins, including CPUs. Please read the section "Accessing settings of plug-ins" in the user documentation of Main module to see how the settings can be accessed.

The following table shows all the possible settings of Intel 8080 CPU plug-in:

Table 1. Settings of Intel 8080 CPU emulator plug-in
Name Default value Valid values Description

printCodeUseCache

false

true / false

If printCode is set to true, then a cache will be used which remembers already visited blocks of code so the instruction dump will not be bloated with infinite loops

printCode

false

true / false

Whether the emulator should print executed instructions, and its internal state to console (dump)

7. CPU: Zilog Z80 emulator

It was possible to upgrade your Altair 8800 computer with a "better" 8-bit processor Zilog Z80. The processor was probably the most used 8-bit processor in 80’s. It was backward compatible with 8080, and brought many enhancements. It was originally targeted for embedded systems, but very soon it become very popular and used for all kinds of computers - including desktop computers, arcade games, etc. Today the CPU is still used in some MP3 players, see e.g. https://en.wikipedia.org/wiki/S1_MP3_player.

Main features of the emulator include:

  • Interpretation as emulation technique,

  • Correct real timing of instructions,

  • Ability to set clock frequency manually at run-time,

  • Emulation of all instructions including interrupts,

  • Disassembler implementation,

  • Ability to "dump" instruction history to console at run-time,

  • Support of breakpoints,

  • Ability of communication with up to 256 I/O devices,

  • Status window shows all registers, flags, and run-time frequency.

7.1. Dumping executed instructions

The CPU offers a quite unique feature, which is the ability to dump executed instructions as a sequence to the console. When enabled, then each executed instruction - together with content of flags and registers values after the execution are printed. This feature might be extremely useful in two cases:

  1. Reverse engineering of some unknown software

  2. It allows to build tools for automatic checking of register values during the emulation, when performing automatic emulation.

In order to enable this feature, please see the section Configuration file.

For example, let’s take one of the examples which computes a reverse text:

Example program for reversing text in Z80 assembly
; Print reversed text

org 1000

dec sp       ; stack initialization (0FFFFh)

ld hl,text1
call putstr  ; print text1
ld de,input  ; address for string input
call getline ; read from keyboard

ld bc,input

ld d,0      ; chars counter

char_loop:
ld a, (bc)
inc bc        ; bc = bc+1
cp 10       ; end of input?
jp z, char_end
cp 13
jp z, char_end

inc d        ; d =d+1
jp char_loop
char_end:

dec bc        ;  bc = bc-1
dec bc

call newline

char2_loop:
ld a, (bc)
call putchar

dec bc

dec d
jp z, char2_end

jp char2_loop
char2_end:

halt

include "include\getchar.inc"
include "include\getline.inc"
include "include\putstr.inc"
include "include\putchar.inc"
include "include\newline.inc"

text1: db "Reversed text ...",10,13,"Enter text: ",0
text2: db 10,13,"Reversed: ",0
input: ds 30

When the program is being run, and the dump instructions feature is turned on, on console you can see the following output:

0000 | PC=03e8 |          dec SP |        3B  || regs=00 00 00 00 00 00 00 00  IX=0000 IY=0000 IFF=0 I=00 R=01 | flags=       | SP=ffff | PC=03e9
0001 | PC=03e9 |      ld HL, 485 |  21 85 04  || regs=00 00 00 00 04 85 00 00  IX=0000 IY=0000 IFF=0 I=00 R=02 | flags=       | SP=ffff | PC=03ec
0002 | PC=03ec |        call 46D |  CD 6D 04  || regs=00 00 00 00 04 85 00 00  IX=0000 IY=0000 IFF=0 I=00 R=03 | flags=       | SP=fffd | PC=046d
0002 | PC=046d |      ld A, (HL) |        7E  || regs=00 00 00 00 04 85 00 52  IX=0000 IY=0000 IFF=0 I=00 R=04 | flags=       | SP=fffd | PC=046e
0003 | PC=046e |          inc HL |        23  || regs=00 00 00 00 04 86 00 52  IX=0000 IY=0000 IFF=0 I=00 R=05 | flags=       | SP=fffd | PC=046f
0003 | PC=046f |            cp 0 |     FE 00  || regs=00 00 00 00 04 86 00 52  IX=0000 IY=0000 IFF=0 I=00 R=06 | flags=    N  | SP=fffd | PC=0471
0007 | PC=0471 |           ret Z |        C8  || regs=00 00 00 00 04 86 00 52  IX=0000 IY=0000 IFF=0 I=00 R=07 | flags=    N  | SP=fffd | PC=0472
0008 | PC=0472 |      out (11),A |     D3 11  || regs=00 00 00 00 04 86 00 52  IX=0000 IY=0000 IFF=0 I=00 R=08 | flags=    N  | SP=fffd | PC=0474
0008 | PC=0474 |          jp 46D |  C3 6D 04  || regs=00 00 00 00 04 86 00 52  IX=0000 IY=0000 IFF=0 I=00 R=09 | flags=    N  | SP=fffd | PC=046d
0009 | PC=046d |      ld A, (HL) |        7E  || regs=00 00 00 00 04 86 00 65  IX=0000 IY=0000 IFF=0 I=00 R=0a | flags=    N  | SP=fffd | PC=046e
0025 | Block from 0474 to 03EF; count=184
0025 | PC=03ef |      ld DE, 4B2 |  11 B2 04  || regs=00 00 04 b2 04 a5 00 00  IX=0000 IY=0000 IFF=0 I=00 R=42 | flags= Z  N  | SP=ffff | PC=03f2
0025 | PC=03f2 |        call 428 |  CD 28 04  || regs=00 00 04 b2 04 a5 00 00  IX=0000 IY=0000 IFF=0 I=00 R=43 | flags= Z  N  | SP=fffd | PC=0428
0025 | PC=0428 |         ld C, 0 |     0E 00  || regs=00 00 04 b2 04 a5 00 00  IX=0000 IY=0000 IFF=0 I=00 R=44 | flags= Z  N  | SP=fffd | PC=042a
0026 | PC=042a |        in A, 10 |     DB 10  || regs=00 00 04 b2 04 a5 00 02  IX=0000 IY=0000 IFF=0 I=00 R=45 | flags= Z  N  | SP=fffd | PC=042c
0026 | PC=042c |           and 1 |     E6 01  || regs=00 00 04 b2 04 a5 00 00  IX=0000 IY=0000 IFF=0 I=00 R=46 | flags= ZHP   | SP=fffd | PC=042e
0026 | PC=042e |       jp Z, 42A |  CA 2A 04  || regs=00 00 04 b2 04 a5 00 00  IX=0000 IY=0000 IFF=0 I=00 R=47 | flags= ZHP   | SP=fffd | PC=042a
0027 | PC=042a |        in A, 10 |     DB 10  || regs=00 00 04 b2 04 a5 00 02  IX=0000 IY=0000 IFF=0 I=00 R=48 | flags= ZHP   | SP=fffd | PC=042c
6323 | Block from 042E to 0431; count=1048716
6323 | PC=0431 |        in A, 11 |     DB 11  || regs=00 00 04 b2 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=54 | flags=  H    | SP=fffd | PC=0433
6323 | PC=0433 |            cp D |     FE 0D  || regs=00 00 04 b2 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=55 | flags=    N  | SP=fffd | PC=0435
6324 | PC=0435 |       jp Z, 461 |  CA 61 04  || regs=00 00 04 b2 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=56 | flags=    N  | SP=fffd | PC=0438
6324 | PC=0438 |            cp A |     FE 0A  || regs=00 00 04 b2 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=57 | flags=    N  | SP=fffd | PC=043a
6324 | PC=043a |       jp Z, 461 |  CA 61 04  || regs=00 00 04 b2 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=58 | flags=    N  | SP=fffd | PC=043d
6324 | PC=043d |            cp 8 |     FE 08  || regs=00 00 04 b2 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=59 | flags=    N  | SP=fffd | PC=043f
6324 | PC=043f |      jp NZ, 459 |  C2 59 04  || regs=00 00 04 b2 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=5a | flags=    N  | SP=fffd | PC=0459
6324 | PC=0459 |      out (11),A |     D3 11  || regs=00 00 04 b2 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=5b | flags=    N  | SP=fffd | PC=045b
6324 | PC=045b |      ld (DE), A |        12  || regs=00 00 04 b2 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=5c | flags=    N  | SP=fffd | PC=045c
6324 | PC=045c |          inc DE |        13  || regs=00 00 04 b3 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=5d | flags=    N  | SP=fffd | PC=045d
6325 | PC=045d |           inc C |        0C  || regs=00 01 04 b3 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=5e | flags=       | SP=fffd | PC=045e
6325 | PC=045e |          jp 42A |  C3 2A 04  || regs=00 01 04 b3 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=5f | flags=       | SP=fffd | PC=042a
6325 | PC=042a |        in A, 10 |     DB 10  || regs=00 01 04 b3 04 a5 00 02  IX=0000 IY=0000 IFF=0 I=00 R=60 | flags=       | SP=fffd | PC=042c
8683 | Block from 045E to 0461; count=440826
8683 | PC=0461 |         ld A, A |     3E 0A  || regs=00 04 04 b6 04 a5 00 0a  IX=0000 IY=0000 IFF=0 I=00 R=5a | flags= ZH N  | SP=fffd | PC=0463
8683 | PC=0463 |      ld (DE), A |        12  || regs=00 04 04 b6 04 a5 00 0a  IX=0000 IY=0000 IFF=0 I=00 R=5b | flags= ZH N  | SP=fffd | PC=0464
8683 | PC=0464 |          inc DE |        13  || regs=00 04 04 b7 04 a5 00 0a  IX=0000 IY=0000 IFF=0 I=00 R=5c | flags= ZH N  | SP=fffd | PC=0465
8683 | PC=0465 |         ld A, D |     3E 0D  || regs=00 04 04 b7 04 a5 00 0d  IX=0000 IY=0000 IFF=0 I=00 R=5d | flags= ZH N  | SP=fffd | PC=0467
8683 | PC=0467 |      ld (DE), A |        12  || regs=00 04 04 b7 04 a5 00 0d  IX=0000 IY=0000 IFF=0 I=00 R=5e | flags= ZH N  | SP=fffd | PC=0468
8684 | PC=0468 |          inc DE |        13  || regs=00 04 04 b8 04 a5 00 0d  IX=0000 IY=0000 IFF=0 I=00 R=5f | flags= ZH N  | SP=fffd | PC=0469
8684 | PC=0469 |         ld A, 0 |     3E 00  || regs=00 04 04 b8 04 a5 00 00  IX=0000 IY=0000 IFF=0 I=00 R=60 | flags= ZH N  | SP=fffd | PC=046b
8684 | PC=046b |      ld (DE), A |        12  || regs=00 04 04 b8 04 a5 00 00  IX=0000 IY=0000 IFF=0 I=00 R=61 | flags= ZH N  | SP=fffd | PC=046c
8684 | PC=046c |             ret |        C9  || regs=00 04 04 b8 04 a5 00 00  IX=0000 IY=0000 IFF=0 I=00 R=62 | flags= ZH N  | SP=ffff | PC=03f5
8684 | PC=03f5 |      ld BC, 4B2 |  01 B2 04  || regs=04 b2 04 b8 04 a5 00 00  IX=0000 IY=0000 IFF=0 I=00 R=63 | flags= ZH N  | SP=ffff | PC=03f8
8684 | PC=03f8 |         ld D, 0 |     16 00  || regs=04 b2 00 b8 04 a5 00 00  IX=0000 IY=0000 IFF=0 I=00 R=64 | flags= ZH N  | SP=ffff | PC=03fa
8684 | PC=03fa |      ld A, (BC) |        0A  || regs=04 b2 00 b8 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=65 | flags= ZH N  | SP=ffff | PC=03fb
8684 | PC=03fb |          inc BC |        03  || regs=04 b3 00 b8 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=66 | flags= ZH N  | SP=ffff | PC=03fc
8684 | PC=03fc |            cp A |     FE 0A  || regs=04 b3 00 b8 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=67 | flags=    N  | SP=ffff | PC=03fe
8684 | PC=03fe |       jp Z, 40A |  CA 0A 04  || regs=04 b3 00 b8 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=68 | flags=    N  | SP=ffff | PC=0401
8684 | PC=0401 |            cp D |     FE 0D  || regs=04 b3 00 b8 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=69 | flags=    N  | SP=ffff | PC=0403
8685 | PC=0403 |       jp Z, 40A |  CA 0A 04  || regs=04 b3 00 b8 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=6a | flags=    N  | SP=ffff | PC=0406
8685 | PC=0406 |           inc D |        14  || regs=04 b3 01 b8 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=6b | flags=       | SP=ffff | PC=0407
8685 | PC=0407 |          jp 3FA |  C3 FA 03  || regs=04 b3 01 b8 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=6c | flags=       | SP=ffff | PC=03fa
8685 | PC=03fa |      ld A, (BC) |        0A  || regs=04 b3 01 b8 04 a5 00 68  IX=0000 IY=0000 IFF=0 I=00 R=6d | flags=       | SP=ffff | PC=03fb
8685 | Block from 0407 to 040A; count=28
8685 | PC=040a |          dec BC |        0B  || regs=04 b6 04 b8 04 a5 00 0a  IX=0000 IY=0000 IFF=0 I=00 R=09 | flags= ZH N  | SP=ffff | PC=040b
8685 | PC=040b |          dec BC |        0B  || regs=04 b5 04 b8 04 a5 00 0a  IX=0000 IY=0000 IFF=0 I=00 R=0a | flags= ZH N  | SP=ffff | PC=040c
8685 | PC=040c |        call 47A |  CD 7A 04  || regs=04 b5 04 b8 04 a5 00 0a  IX=0000 IY=0000 IFF=0 I=00 R=0b | flags= ZH N  | SP=fffd | PC=047a
8685 | PC=047a |         ld A, A |     3E 0A  || regs=04 b5 04 b8 04 a5 00 0a  IX=0000 IY=0000 IFF=0 I=00 R=0c | flags= ZH N  | SP=fffd | PC=047c
8686 | PC=047c |        call 477 |  CD 77 04  || regs=04 b5 04 b8 04 a5 00 0a  IX=0000 IY=0000 IFF=0 I=00 R=0d | flags= ZH N  | SP=fffb | PC=0477
8686 | PC=0477 |      out (11),A |     D3 11  || regs=04 b5 04 b8 04 a5 00 0a  IX=0000 IY=0000 IFF=0 I=00 R=0e | flags= ZH N  | SP=fffb | PC=0479
8686 | PC=0479 |             ret |        C9  || regs=04 b5 04 b8 04 a5 00 0a  IX=0000 IY=0000 IFF=0 I=00 R=0f | flags= ZH N  | SP=fffd | PC=047f
8686 | PC=047f |         ld A, D |     3E 0D  || regs=04 b5 04 b8 04 a5 00 0d  IX=0000 IY=0000 IFF=0 I=00 R=10 | flags= ZH N  | SP=fffd | PC=0481
8686 | PC=0481 |        call 477 |  CD 77 04  || regs=04 b5 04 b8 04 a5 00 0d  IX=0000 IY=0000 IFF=0 I=00 R=11 | flags= ZH N  | SP=fffb | PC=0477
8686 | PC=0477 |      out (11),A |     D3 11  || regs=04 b5 04 b8 04 a5 00 0d  IX=0000 IY=0000 IFF=0 I=00 R=12 | flags= ZH N  | SP=fffb | PC=0479
8686 | Block from 0481 to 0484; count=2
8686 | PC=0484 |             ret |        C9  || regs=04 b5 04 b8 04 a5 00 0d  IX=0000 IY=0000 IFF=0 I=00 R=14 | flags= ZH N  | SP=ffff | PC=040f
8686 | PC=040f |      ld A, (BC) |        0A  || regs=04 b5 04 b8 04 a5 00 6a  IX=0000 IY=0000 IFF=0 I=00 R=15 | flags= ZH N  | SP=ffff | PC=0410
8686 | PC=0410 |        call 477 |  CD 77 04  || regs=04 b5 04 b8 04 a5 00 6a  IX=0000 IY=0000 IFF=0 I=00 R=16 | flags= ZH N  | SP=fffd | PC=0477
8686 | PC=0477 |      out (11),A |     D3 11  || regs=04 b5 04 b8 04 a5 00 6a  IX=0000 IY=0000 IFF=0 I=00 R=17 | flags= ZH N  | SP=fffd | PC=0479
8686 | Block from 0410 to 0413; count=2
8686 | PC=0413 |          dec BC |        0B  || regs=04 b4 04 b8 04 a5 00 6a  IX=0000 IY=0000 IFF=0 I=00 R=19 | flags= ZH N  | SP=ffff | PC=0414
8687 | PC=0414 |           dec D |        15  || regs=04 b4 03 b8 04 a5 00 6a  IX=0000 IY=0000 IFF=0 I=00 R=1a | flags=  H N  | SP=ffff | PC=0415
8687 | PC=0415 |       jp Z, 41B |  CA 1B 04  || regs=04 b4 03 b8 04 a5 00 6a  IX=0000 IY=0000 IFF=0 I=00 R=1b | flags=  H N  | SP=ffff | PC=0418
8687 | PC=0418 |          jp 40F |  C3 0F 04  || regs=04 b4 03 b8 04 a5 00 6a  IX=0000 IY=0000 IFF=0 I=00 R=1c | flags=  H N  | SP=ffff | PC=040f
8687 | PC=040f |      ld A, (BC) |        0A  || regs=04 b4 03 b8 04 a5 00 6f  IX=0000 IY=0000 IFF=0 I=00 R=1d | flags=  H N  | SP=ffff | PC=0410
8687 | Block from 0418 to 041B; count=23
8687 | PC=041b |            halt |        76  || regs=04 b1 00 b8 04 a5 00 61  IX=0000 IY=0000 IFF=0 I=00 R=34 | flags= ZH N  | SP=ffff | PC=041c

The dump format consists of lines, each line represents one instruction execution. The line is separated by | chars, splitting it into so-called sections. Sections before the sequence || represent state before instruction execution, and sections after it represent the state after instruction execution. Particular sections are described in the following table.

Column Description

8

Program counter after instruction execution

1

Timestamp from program start (seconds)

2

Program counter before instruction execution

3

Disassembled instruction

4

Instruction opcodes

Now follows the state after instruction execution

5

Register values (B, C, D, E, H, L, reserved (always 0), A)

6

Flags

7

Stack pointer register (SP)

7.2. Configuration file

Configuration file of virtual computers contain also settings of all the used plug-ins, including CPUs. Please read the section "Accessing settings of plug-ins" in the user documentation of Main module to see how the settings can be accessed.

The following table shows all the possible settings of Zilog Z80 CPU plug-in:

Table 2. Settings of Zilog Z80 CPU emulator plug-in
Name Default value Valid values Description

printCodeUseCache

false

true / false

If printCode is set to true, then a cache will be used which remembers already visited blocks of code so the instruction dump will not be bloated with infinite loops

printCode

false

true / false

Whether the emulator should print executed instructions, and its internal state to console (dump)

8. Operating memory standard-mem

This plug-in emulates an operating memory, in a quite broad meaning. It can be used for any virtual computer which can benefit from the following basic properties:

  • A memory cell has size of 1 byte (8 bits)

  • Memory cells are linearly ordered (sequential)

  • Each memory cell is accessible by unique address, representing the index of the memory cell, if the memory is imagined as an array of cells

Besides, the memory supports these additional features:

  • Setting up ROM (read only memory) ranges

  • Changing memory size; by default it is 64kB

  • Support of bank switching

There are also some "interactive" features:

  • Manual loading/saving memory images in either binary or Intel HEX format

  • Ability to automatically load a memory image at startup

  • Intuitive control using keystrokes, and nice visual presentation of the data

This operating memory is (for now) used only in 8-bit emulator of MITS Altair8800. However it is possible to develop an emulator which can benefit from it.

8.1. GUI overview

To open the memory GUI (graphical user interface), click at the right-most icon in the debug toolbar, on the Emulator panel. The window is shown, as in the following image:

Standard operating memory
  • A: Open a memory image. Current memory content, if it does not interfere with the loaded data will be kept.

  • B: Dump (save) whole memory content into a file.

  • C: Go to address. The address can be either in decimal, hexadecimal (prefix 0x) or octal (prefix 0) format.

  • D: Find a sequence. A dialog shows up where user can find either a plain text or sequence of bytes in the memory.

  • E: Erases all memory content.

  • F: Shows memory settings

  • G: By double-clicking on the memory cell it is possible to edit it and change its value. The value format is the same as the input to "go to address" dialog (see C).

  • H: Page of the memory view. The whole memory cannot be shown in single window, because it can be quite large (64 kB by default), so it was divided into pages.

  • I: If the memory has set up memory banks, it is possible to change the view to different bank. Switching in here has no effect on the emulator and on the active bank.

  • J: Displays the data of the selected cell in various forms.

Generally, it is possible to move around the cells using keystrokes (arrows). If user presses some other letter/number key, a small text field appears allowing to edit the current value. When editing is finished, user can press ENTER key to confirm it, or ESC key to discard the editing.

8.2. Memory settings

Settings window can be opened by clicking on "settings" icon in the main GUI window:

Memory settings
  • A: Settings for memory bank-switching

  • B: Settings for ROM areas

  • C: If checked, settings for ROM areas will be saved to the configuration file

  • D: List of memory images which will be loaded at startup

  • E: The button will apply the settings

ROM areas and memory bank-switching is explained in the following sections.

8.2.1. ROM areas

Some "controllers" - used as embedded devices - usually logically organize memory into areas, some of which are read only, which usually contains the firmware, and some are rewritable. Physically, these memories are wired to specific addresses, so the programmer can access them.

Standard operating memory plug-in emulates this behavior. It allows to define ROM areas which represent read only memory. There can be set up multiple ROM areas, and they can overlap. Effectively it means that memory cells in ROM area cannot be changed from software running on the emulator. All writes to the memory will be ignored.

Manually, as a user it is possible to change the values, but only by loading new memory image. Editing the value will not work.

If a ROM range is defined, it is possible to remove only a part of it, effectively splitting the range and correcting their boundaries. For example, if there is defined a ROM range from 0x0A - 0x64 (see the image above), then it is possible to remove a range e.g. 0x32 - 0x46, which is the part of defined ROM area. Then, the original ROM area is split into two parts - first will be a range from 0x0A - 0x31, and the second from 0x47 - 0x64.

8.2.2. Memory bank-switching

This technique was invented as a workaround for a problem when the address space of a processor was smaller than memory size. In order to overcome this issue, memory was logically split into many regions of size equal to the processor address space. These regions are called "banks".

Physically, banks could refer to the same memory, but they could be also different memories (e.g. external cartridges), and the bank-switching involved switching the active memory.

Selecting a bank from a programming perspective was usually done by writing some code to some I/O port using some I/O instruction of a CPU. But it can be implemented in various ways, e.g. some memory addresses can be used for selecting a bank.

Also, it was very common that some part of the address space still kept some common memory part which was never switched out. This part is called a "common" part. In emuStudio, common part starts with the Common address (as it can be seen in the Settings dialog image above) and ends till the rest of the CPU address space (or memory end).

To summarize, let’s consider an example. If a CPU is 8-bit, it means it has address space of size 2^8 - i.e. it can access memory from address 0 to (2^8 - 1). If the memory was larger, CPU just doesn’t allow to access higher memory cells. So memory bank-switching is coming for the rescue. If the memory has 2 MB, we require 2^log2(2MB) = 2^21 addresses. So, if we won’t have any common address space, we require ceil(21 / 8) = 3 banks:

  • bank 0: maps from 0 - (2^8 - 1)

  • bank 1: maps from 2^8 - (2^16 - 1)

  • bank 2: maps from 2^16 - (2^21 - 1)

8.3. Configuration file

Configuration file of virtual computers contain also settings of all the used plug-ins, including devices. Please read the section "Accessing settings of plug-ins" in the user documentation of Main module to see how the settings can be accessed.

The following table shows all the possible settings of Standard operating memory plug-in:

Table 3. Settings of Standard operating memory
Name Default value Valid values Description

imageAddress(i)

N/A

>= 0 and < mem size

The i-th memory image load address

banksCount

0

>= 0

Number of memory banks

commonBoundary

0

>= 0 and < mem size

Address from which the banks are shared

memorySize

65536

> 0

Memory size in bytes

ROMfrom(i)

N/A

>= 0 and < mem size

Start of the i-th ROM area

ROMto(i)

N/A

>= ROMfrom(i) and < mem size

End of the i-th ROM area

imageName(i)

N/A

file path

The i-th memory image file name. [2]

8.4. Using memory in custom computers

This section is for developers of emulators. If you do not plan to create custom virtual computers, you can safely skip this section. In order to get started with developing plug-ins for emuStudio, please read tutorial "Developing emuStudio Plugins".

As it was mentioned in the earlier sections, the Standard operating memory plug-in can be used in other computers, too. Besides standard operations which are provided by emulib.plugins.memory.MemoryContext interface, it provides custom context API, enabling to use more features - e.g. bank-switching.

You can obtain the context during the initialize() method of plug-in main class. The context is named net.sf.emustudio.memory.standard.StandardMemoryContext:

...

public void initialize(SettingsManager settings) {
    ...

    StandardMemoryContext mem = contextPool.getMemoryContext(pluginID, StandardMemoryContext.class);
    ...
}

The memory context has the following content:

package net.sf.emustudio.memory.standard;

import emulib.annotations.ContextType;
import emulib.plugins.memory.MemoryContext;

import java.util.List;

/**
 * Extended memory context.
 *
 * Supports bank switching, ROM ranges, and loading HEX/BIN files.
 */
@ContextType(id = "Standard memory")
public interface StandardMemoryContext extends MemoryContext<Short> {

    /**
     * This interface represents a range of addresses in the memory.
     */
    interface AddressRange {
        int getStartAddress();
        int getStopAddress();
    }

    /**
     * Determine whether specified memory position is read-only.
     *
     * @param address memory position
     * @return true if the memory position is read only, false otherwise
     */
    boolean isROM(int address);

    /**
     * Get list of ranges of read-only addresses.
     *
     * @return list of ROM memory addresses
     */
    List<? extends AddressRange> getROMRanges();

    /**
     * Set specified memory range as RAM (Random Access Memory).
     *
     * @param range address range
     */
    void setRAM(AddressRange range);

    /**
     * Set specified memory range as ROM (Read Only Memory).
     *
     * @param range address range
     */
    void setROM(AddressRange range);

    /**
     * Get number of available memory banks.
     *
     * @return count of memory banks
     */
    int getBanksCount();

    /**
     * Get index of the selected memory bank.
     *
     * @return index of active (selected) memory bank
     */
    short getSelectedBank();

    /**
     * Select (set as active) a memory bank.
     *
     * @param bankIndex index (number) of a bank which should be selected
     */
    void selectBank(short bankIndex);

    /**
     * Return an address in the memory which represents a boundary from which
     * the memory banks have the same content. Before this address all banks
     * can have different content.
     *
     * @return common boundary address
     */
    int getCommonBoundary();

    /**
     * Loads a HEX file into the memory.
     *
     * @param filename file name
     * @param bank bank index
     * @return true if the file was loaded successfully, false otherwise
     */
    boolean loadHex(String filename, int bank);

    /**
     * Loads a binary file into the memory.
     *
     * @param filename file name
     * @param address location in the memory
     * @param bank bank index
     * @return true if the file was loaded successfully, false otherwise
     */
    boolean loadBin(String filename, int address, int bank);

}

9. Disk controller 88-disk

Altair Disk offered the advantage of fixed memory including relatively fast access to data. Data were transferred with speed 250 Kb/s (The plug-in does not emulate this). Disk was connected with disk controller (or board), and the data were transferred in serial fashion, bit after bit.

Disk controller, on the other hand communicated with CPU. It transformed these serial data into 8-bit words which were stored/read by CPU into/from operating memory.

MITS 88-DISK offered to connect up to 16 disk devices (one can be seen in the front image in the Introduction section).

Original manual can be downloaded at this link.

9.1. Features

The plug-in emulates basic functionality of the whole disk system for Altair 8800 computer. It is not only disk controller, but also the disk drive.

The features include:

  • allows to mount up to 16 disk images

  • CPU ports can be set manually

  • interrupts are not implemented

  • images can be saved for automatic mount at startup

  • GUI

9.2. Mounting disk images

In order to mount DISK images to the device, please go to the Settings window [3]:

Settings window of 88-DISK
  • A: Select drive (A - P)

  • B: Choosing the image file

  • C: Set sectors count and sector length for the current drive [4].

  • D: Set default values for sector count and sector length for the current drive.

  • E: Mount/unmount the image file onto/from the selected drive. Mount operation: If there is any disk mounted already, the new image will be re-mounted.

  • F: Check box for saving the settings into the computer configuration file. If checked, the settings will be loaded after start.

9.3. CPU Ports settings

MITS 88-DISK board communicates with CPU using its ports. There are three ports overall, each for different function. For more information, see section Programming. By default, the ports used by 88-DISK are:

  • port 1: 0x08

  • port 2: 0x09

  • port 3: 0x0A

These numbers can be changed in the Settings window, tab "CPU Ports":

Setting CPU ports

9.4. Configuration file

Configuration file of virtual computers contain also settings of all the used plug-ins, including devices. Please read the section "Accessing settings of plug-ins" in the user documentation of Main module to see how the settings can be accessed.

The following table shows all the possible settings of MITS 88-DISK plug-in:

Table 4. Settings of MITS 88-DISK
Name Default value Valid values Description

image15

N/A

Path to existing file

File name to mount on disk P (15)

port1CPU

0x08

> 0 and < 256

Number of Port 1

port2CPU

0x09

> 0 and < 256

Number of Port 2

port3CPU

0x0A

> 0 and < 256

Number of Port 3

sectorsPerTrack

32

> 0

Count of sectors in a disk image

sectorLength

137

> 0

Size of one sector in bytes

image0

N/A

Path to existing file

File name to mount on disk A (0)

…​

…​

…​

…​

9.5. Programming

Data are written onto or read from disk in a serial fashion. The position in the floppy disk is uniquely set by the track number, sector number and the offset in the sector. It is rudimentary to know how many tracks are available, so as how many sectors per track and the sector size.

In Altair8800, drive Pertec FD400 used 8" diskettes. Each had 77 tracks. The track had 32 sectors with 137 bytes long. The capacity was therefore 77 * 32 * 137 = 337568 B = 330 kB. Software used less capacity, because 9 bytes from each sector were used for the integrity checksum.

9.5.1. Setting the position

Track number and sector number can be set only incrementally, not directly. Setting the offset within the sector is more challenging.

After track and sector were set, programmer must "poll" the status port which tells him when the disk position is set to the beginning of the sector. Then, programmer must read data until he gets to the position where he wanted.

9.5.2. CPU Ports

The controller communicates with CPU using three I/O ports at addresses (by default) 0x08, 0x09 and 0x0A. The following table shows the CPU ports and how they are used.

Table 5. Summary of CPU ports usage
Port Address Input Output

3

0x0A

Read data

Write data

1

0x08

Disk and controller status

Select disk

2

0x09

Get number of sector

Disk settings

Now, detailed description of the ports follow. Bits are ordered in a byte as follows:

D7 D6 D5 D4 D3 D2 D1 D0

where D7 is the most significant bit, and D0 the least significant bit.

Port 1 (default address: 0x08)

WRITE:

Selects and enables one of 16 disk devices. By selecting a drive, all further operations will be performed on that drive. If the disk has not mounted any disk image, all further operations will be ignored. The previously selected device will be disabled.

  • D7 : if the value is 1, disable the drive. If the value is 0, select and enable the drive.

  • D6 D5 D4 : unused bits

  • D3 D2 D1 D0: index of the drive to be selected. From 0-15.

READ:

Read disk status of the selected drive.

  • D7 : New read data available. Indicates if there is at least 1 byte available for reading from Port 3 (value=0). It will be reset after data are read (value=1). If the value is 1, data read from Port 3 will be invalid or no new data is available.

  • D6 : Track 0. Indicates if the head is positioned at track 0 (value=0).

  • D5 : Interrupt Enabled. Indicates if interrupts are used (value=0). The plug-in does not support interrupts, therefore the value will be always 1.

  • D4 D3 : Unused bits; they are always 0.

  • D2 : Head Status. Indicates the correctness of the head setting. If the value is 0, reading sector number from Port 2 will be valid.

  • D1 : Move head. Indicates if the movement of the disk head is allowed. If the value is 1, all track number changes will be ignored.

  • D0 : Enter new write data. Indicates if the device is ready for writing data. If the value is 1, all written data will be ignored.

Initial values of the bits are: 11100111.

Port 2 (default address: 0x09)

WRITE:

Control the disk head, and other settings if a disk drive is selected.

  • D7 : Write Enable. Initializes write sequence (enables writing to the disk; value=1). The plug-in sets the sector number to 0 and also value 0 to bit D0 of Port 1 (Enter new write data) [5].

  • D6 : Head Current Switch. On real disks the bit should be set to 1 when a program is writting data to tracks from 43-76. The plug-in the bit is ignored.

  • D5 : Interrupt Disable. Setting is ignored sicne plug-in does not support interrupts.

  • D4 : Interrupt Enable. Setting is ignored sicne plug-in does not support interrupts.

  • D3 : Head unload. Removes head from the disk surface. Reading sector number will now become invalid. In addition, value of bit D7 from Port 1 (New read data available) become 1 (no new data).

  • D2 : Head load. Sets the disk head onto disk surface. Reading sector number now becomes valid. If additionally the bit D7 from Port 1 (New data available) is set, it is possible to read data from the disk.

  • D1 : Step Out. Move the disk head back by 1 track (the track number is decremented). It is required to check bit D1 of Port 1 (Move head) to have value 0.

  • D0 : Step In. Move the disk head ahead by 1 track (the track number is incremented). It is required to check bit D1 of Port 1 (Move head) to have value 0.

READ:

Reads the number of the sector. The value can be read only if a disk drive is selected and the disk head is positioned at the disk surface (by setting the bit D2).

  • D7 D6 : Unused bits; they are always 1.

  • D5 D4 D3 D2 D1: Number of the sector, counted from 0.

  • D0 : Sector True. If the value is 0, the offset in sector is 0 [6].

Port 3 (default address: 0x0A)

WRITE:

Write a byte to disk. In order to perform valid write, the Write Enable D7 bit of Port 2 must be set to 1. Before data are written to disk, it is required to check bit D0 from Port 1 (Enter new write data).

READ:

Read a byte from disk. In order to perform valid read, the Head load D2 bit of Port 2 must be set to 1. Only if bit D7 from Port 1 (New read data available) is set to 0, the read data are valid.

9.5.3. Program example

In this section, an example is presented showing how to read/write data from/to the floppy disk. At first, it writes one byte (letter A with ASCII value 65) to track 1, sector 18 and offset 20. Then, it reads the byte to operating memory at address 0x200.

The program uses 3 procedures (in assembler for Intel 8080) for setting the disk position (ltrack for loading the track number, lsector for loading the sector number, and loffset for loading the offset within the sector) and two more for data reading (read) and writing (write).

Example program for writing/reading using MITS 88-DISK
disk0  equ 0    ; disk number
track  equ 1    ; track number
sector equ 18   ; sector number
offset equ 20   ; offset within the sector
data   equ 'A'  ; data for writing

dcx sp          ; set stack register to 0xFFFF

mvi a, disk0    ; select disk
out 08h

call ltrack     ; set track number

call we         ; set 'write enable' sequence
call lsector    ; set sector number
call loffset    ; set sector offset
call write      ; write data

call lsector    ; set sector number (for clearing the offset)
call loffset    ; set sector offset
call read       ; read data

lxi h, readdata ; load address for reading the data
mov m, a        ; move the data there

hlt             ; end

ltrack0:        ; the procedure will set track number to 0
in 08h          ; read disk status
ani 1000000b    ; track 0 ?
rz              ; yes, return
mvi a, 1000b    ; head unload
out 09h
call movetrk    ; wait until the disk head can be moved
mvi a, 10b      ; step out, decrement track number
out 08h
jmp ltrack0

ltrack:         ; procedure sets a track number
call ltrack0    ; at first, set track number to 0
mvi b, track+1  ; b = track + 1
stepin:         ; stepin: {
dcr b           ;   b--;
rz              ;   if (b == 0) return;
call movetrk    ;   wait until the disk head can be moved
mvi a, 1        ;   step in, increment track number
out 09h
jmp stepin      ;   goto stepin;
                ; }

movetrk:        ; procedure waits until the disk head can be moved
in 08h          ; read disk status
ani 10b         ; can the disk head be moved?
jnz movetrk     ; nope, try again...
ret             ; yes, return

lsector:        ; procedure sets a sector number
mvi a, 100b     ; head load
out 09h
waits:
in 09h          ; read sector number
ani 3Fh         ; clear unused bits
rrc
cpi sector      ; is the number what is requested?
jnz waits       ; nope, try again
ret             ; yes, return

loffset:        ; procedure sets a sector offset
mvi b, offset+1 ; b = offset + 1
stepoff:        ; stepoff: {
dcr b           ;   b--;
rz              ;   if (b == 0) return;
call read       ;   read data; the offset is incremented
jmp stepoff     ;   goto stepoff;
                ; }

read:           ; procedure reads data from the disk
in 08h          ; read disk status
ani 100b        ; check if the disk head is loaded on the disk surface
rnz             ; if not, return
waitr:
in 08h          ; read disk status
ani 10000000b   ; New read data available ?
jnz waitr       ; nope, try again...
in 0Ah          ; yes, read data
ret             ; return

we:             ; procedure enables 'write enable' sequence
mvi a, 10000000b ; write enable
out 09h
ret

write:          ; procedure writes data to the disk
in 08h          ; read disk status
ani 100b        ; check if the disk head is loaded on the disk surface
rnz             ; if not, return
waitw:
in 08h          ; read disk status
ani 1           ; enter new write data ?
jnz waitw       ; nope, try again...
mvi a, data     ; yes, write data
out 0Ah
ret

org 200h
readdata: db 0

10. Serial board 88-sio

Altair 8800 computer was equipped with serial board called 88-SIO, or 88-2 SIO. It was a device which allowed connecting other devices using RS-232 interface. From one side it was attached to CPU on at least two ports (most commonly 0x10 and 0x11). The other side was ended with one or two physical ports (allowing to connect one or two devices). Real board supported both hardware and software interrupts.

The following image shows MITS 88-SIO-2 board.

Serial board MITS 88-SIO-2
Original manual of MITS 88-SIO serial board can be downloaded at this link.

10.1. Features

The plug-in emulates only basic functionality of the board. It has the following features:

  • allows to connect one device only

  • CPU ports can be set manually

  • interrupts are not implemented

  • setting of transfer speed, parity, number of stop bits is not supported

  • GUI

10.2. CPU Ports settings

MITS 88-SIO board is attached to CPU using multiple ports. By default, the used CPU ports are:

  • Status port: 0x03, 0x10, 0x14, 0x16, 0x18 (preferred: 0x10)

  • Data port: 0x02, 0x11, 0x15, 0x17, 0x19 (preferred: 0x11)

The reason why there are multiple "bindings" is that there existed various software which expected specific bindings. The presented default values are the most common ones.

These numbers can be changed in the Settings window:

Settings window
  • A: Attach Status SIO port to some new CPU port. The CPU port must be unique among both Status and Data ports attachments.

  • B: Detach Status SIO port from selected CPU port.

  • C: Attach Data SIO port to some new CPU port. The CPU port must be unique among both Status and Data ports attachments.

  • D: Detach Data SIO port from selected CPU port.

  • E: List of CPU ports to which the Status SIO port is attached

  • F: Clear the current attachements of the Status SIO port and attach it to default CPU ports

  • G: List of CPU ports to which the Data SIO port is attached

  • H: Clear the current attachements of the Data SIO port and attach it to default CPU ports

  • I: When selected, clicking on OK button will save the settings and will be applied at next emuStudio start.

10.3. Connecting devices

MITS 88-SIO board as emuStudio plug-in is a device which does nothing really useful. It just listens (and understands) commands coming from CPU through the I/O ports. The command is either a request for reading or request for writing to the attached device.

Theoretically any device which supports the basic I/O (reading/writing), can be attached to the board. More about plug-in internals can be found in programmer’s manual of emuStudio, which is not part of the user documentation.

Usually, attached devices were:

  • serial terminal

  • line printer

  • paper tape reader/punch

In current implementation of Altair 8800 emulator, the only suitable device which can be attached to the board is terminal ADM-3A from Lear Siegler, Inc and which is described in its own section.

10.4. Configuration file

Configuration file of virtual computers contain also settings of all the used plug-ins, including devices. Please read the section "Accessing settings of plug-ins" in the user documentation of Main module to see how the settings can be accessed.

The following table shows all the possible settings of MITS 88-SIO plug-in:

Table 6. Settings of MITS 88-SIO
Name Default value Valid values Description

dataPortNumberX

0x11

> 0 and < 256; X range from 0 upwards

X-th Number of Data Port

statusPortNumberX

0x10

> 0 and < 256; X range from 0 upwards

X-th Number of Status Port

As can be seen; the X represents a number, it’s a way how two SIO ports can be attached to multiple CPU ports.

10.5. Programming

In order to show something useful, let’s assume that a terminal LSI ADM-3A is attached to the board. Remember, the board only mediates the communication, it does not interpret any of the sent/received characters.

10.5.1. CPU Ports

The whole communication between the board (and attached device) and CPU is controlled by programming the two ports: Status port and Data port. The following table shows the ports and how they are used.

Table 7. Summary of CPU ports usage
Port Address Input Output

2

0x11

Read data

Write data

1

0x10

Read board status

Not used. Originally used for enabling/disabling interrupts.

Now, detailed description of the ports follow. Bits are ordered in a byte as follows:

D7 D6 D5 D4 D3 D2 D1 D0

where D7 is the most significant bit, and D0 the least significant bit.

10.5.2. Port 1 ("Control" port)

Default addresses: 0x03, 0x10, 0x14, 0x16, 0x18 (preferred is 0x10)

WRITE:

Controls input/output interrupts enable. If both interrupts are set to be enabled, it only empties transmitter buffer in the device, which was a post-step after interrupts being enabled. However, the plug-in does not implement interrupts support.

  • D7 D6 D5 D4 D3 D2 : unused bits

  • D1 D0 : Used for enabling/disabling interrupts. Not used in emuStudio.

READ:

Read status of the device.

  • D7 : Output device ready. Always 0 in the emulator.

  • D6 : Not used (always 0).

  • D5 : Data available (for writing to the attached device). Always 0 in the emulator, meaning that no data is pending to be written. Data are written immediately after OUT instruction.

  • D4 : Data overflow. Value 1 means a new word of data has been received before the previous word was inputted to the accumulator. In emuStudio, this never happens.

  • D3 : Framing error. Value 1 means that data bit has no valid stop bit. In emuStudio, this never happens.

  • D2 : Parity error. Value 1 means that received parity does not agree with selected parity. In emuStudio, this never happens.

  • D1 : Transmitter buffer empty. Value 1 means that the data word has been received from the attached device and it’s available for reading (from the Data port).

  • D0 : Input device ready. Value 1 means that the CPU can write data to the SIO (that the board is ready). Always 1 in the emulator.

10.5.3. Port 2 ("Data" port)

Default addresses: 0x02, 0x11, 0x15, 0x17, 0x19 (preferred is 0x11)

WRITE:

Write data to the attached device.

READ:

Read data from the attached device.

If the attached device sends asynchronously multiple data, the emulated board stores all in a buffer (queue) with unlimited capacity, so no data should be lost and can be read anytime.

10.5.4. Program example

In this section it will be shown a small "How to" program terminal using 88-SIO ports.

In emuStudio, it is enough to write data to Port 2, e.g.:

Example program for writing character on terminal
mvi a, 'H'
out 11h
mvi a, 'i'
out 11h

For writing strings, it is more practical to have a procedure.

Example program for writing text on terminal
lxi h, text  ; load address of 'text' label to HL
call print   ; print text
hlt          ; halt CPU

text: db 'Hello, world!',0

; Procedure for printing text to terminal.
; Input: pair HL must contain the address of the ASCIIZ string
print:
    mov a, m  ; load character from HL
    inx h     ; increment HL
    cpi 0     ; is the character = 0?
    rz        ; yes; quit
    out 11h   ; otherwise; show it
    jmp print ; and repeat from the beginning
Reading character from keyboard

For reading a character, it is required to read the Port 1 until the character is not ready. Then we can read it from Port 2.

Example procedure for reading a character from terminal
; Procedure will read a single character from terminal
; Input: none
; Output: register A will contain the character.
getchar:
    in 10h     ; read Port 1
    ani 1      ; is data ready ?
    jz getchar ; not; try again
    in 11h     ; yes; read it (into A register)
    ret
Reading text from keyboard

Now follows an example, which will read a whole line of characters into memory starting at address in DE pair. The procedure will interpret some control keys, like: backspace and ENTER keys.

Example program for reading text from terminal
lxi h, text        ; load address of 'text' label to HL
xchg               ; DE <-> HL
call getline       ; read line from the keyboard into DE

lxi h, text        ; load 'text' address again
call print         ; print the text on screen

hlt                ; halt CPU

text: ds 30        ; here will be stored the read text

;Procedure for reading a text from keyboard.
;Input: DE = address, where the text should be put after reading
;       C  = is used internally
getline:
    mvi c, 0       ; register C will be used as a counter of
                   ; read characters
next_char:
    in 10h         ; read Port 1: status
    ani 1          ; is the char ready for reading?
    jz next_char   ; not; try again
    in 11h         ; yes; read it to A register

    ; now ENTER and Backspace will be interpreted
    cpi 13         ; ENTER?
    jz getline_ret ; yes; it means end of input
    cpi 8          ; Backspace ?
    jnz save_char  ; if not; store the character

    ; Backspace interpretation
    mov a, c       ; A <- number of read characters
    cpi 0          ; are we at the beginning?
    jz next_char   ; yes; ignore the backspace

    dcx d          ; not; decrement DE
    dcr c          ; decrement count of read characters
    mvi a,8        ; "show" the backspace (terminal will
                   ; interpret this by moving the cursor
                   ; to the left by 1 char)
    out 11h
    mvi a, 32      ; "clear" the current character on screen
                   ; by a space character (ASCII code 32)
    out 11h

    mvi a,8        ; and move the cursor back again
    out 11h
    jmp next_char  ; jump to next char

save_char:         ; stores a character into memory at DE
    out 11h        ; show the character in A register
    stax d         ; store it at address DE
    inx d          ; increment DE
    inr c          ; increment number of read characters
    jmp next_char  ; jump to next char

getline_ret:       ; end of input
                   ; ENTER will be stored as CRLF
    mvi a,13       ; CR (Carriage Return)
    stax d         ; store the char
    inx d          ; increment DE
    mvi a, 10      ; LF (Line Feed)
    stax d         ; store the char
    inx d          ; increment DE
    mvi a, 0       ; char 0 (End-Of-Input)
    stax d         ; store the char
    ret            ; return

11. Terminal adm3a-terminal

Emulation of famous terminal from Lear Siegler, Inc. - ADM-3A. It had a nick name 'Dumb Terminal'. In the time (1974), due to its cheapness and speed capabilities required in that time, it became de facto standard in the industry. Often it was used in connection with MITS Altair 8800 computer, so the decision of which terminal to emulate was clear.

The maintenance manual can be downloaded at this link, operator’s manual here.

11.1. Display

The terminal could display 128 ASCII characters (upper-case and lower-case letters, punctuation and numbers). The original ADM-3 could display only 64 (only capital-letters and some other). For saving very expensive RAM the terminal offered size 12 rows x 80 columns, with optional extension to 24 rows x 80 columns. The size used in the emulator is hardcoded to 80 columns x 24 rows.

Besides, the emulator uses custom font colored green, with anti-aliasing support and double-buffering.

11.2. Keyboard

The terminal could generate always 128 ASCII characters (upper-case, lower-case, punctuation and numbers). Besides, it could generate special control characters which had effect on the current cursor position and were not sent to CPU.

The emulator allows to generate almost anything what your host keyboard can give. It is only up to font which characters it can display. The font cannot display any special non-US characters used in various languages. Just classic ASCII.

Besides, the terminal can capture control codes (holding CTRL plus some key), and special control codes (ESC + '=' plus some key). The following subsection lists all possible control and special control key combinations.

11.2.1. Control codes

The following table shows control codes (CTRL plus some key combinations). The table can be found in original manuals. The emulator is following it.

Table 8. Control codes
Code ASCII mnemonic Function in ADM-3A

CTRL+^

RS

Home cursor

CTRL+@

NUL

CTRL+A

SOH

CTRL+B

STX

CTRL+C

ETX

CTRL+D

EOT

CTRL+E

ENQ

Initiates ID message with automatic "Answer Back" option. [7]

CTRL+F

ACK

CTRL+G

BEL

Sounds audible beep in ADM-3A (not in emulator yet :( )

CTRL+H

BS

Backspace

CTRL+I

HT

CTRL+J

LF

Line feed

CTRL+K

VT

Upline

CTRL+L

FF

Forward space

CTRL+M

CR

Return

CTRL+N

SO

Unlock keyboard [7]

CTRL+O

SI

Lock keyboard [7]

CTRL+P

OLE

CTRL+Q

DCI

CTRL+R

DC2

CTRL+S

DC3

CTRL+T

DC4

CTRL+U

NAK

CTRL+V

SYN

CTRL+W

ETB

CTRL+X

CAN

CTRL+Y

EM

CTRL+Z

SUB

Clear screen

CTRL+[

ESC

Initiate load cursor

CTRL+x

FS

CTRL+]

GS

11.2.2. Absolute cursor position from the keyboard

The terminal also allowed to set the absolute cursor position, when in "Cursor control Mode". The ADM-3A emulator does not have such mode, but ESC+'=' X Y combinations allows to set the cursor position. As you could see in the Control codes section, pressing the ESC "Initiates load cursor" operation. If the user then presses = key, then the terminal takes next 2 keystrokes, and translates them into X and Y coordinates for the new position of the cursor. The following table shows the key-to-coordinate translation table.

Table 9. Translation of keystrokes to cursor coordinates
Key Number

o

79

' '

0

!

1

"

2

#

3

$

4

%

5

&

6

'

7

(

8

)

9

*

10

+

11

,

12

-

13

.

14

/

15

0

16

1

17

2

18

3

19

4

20

5

21

6

22

7

23

8

24

9

25

:

26

;

27

<

28

=

29

>

30

?

31

@

32

A

33

B

34

C

35

D

36

E

37

F

38

G

39

H

40

I

41

J

42

K

43

L

44

M

45

N

46

O

47

P

48

Q

49

R

50

S

51

T

52

U

53

V

54

W

55

X

56

Y

57

Z

58

[

59

\

60

]

61

^

62

_

63

`

64

a

65

b

66

c

67

d

68

e

69

f

70

g

71

h

72

i

73

j

74

k

75

l

76

m

77

n

78

11.3. ADM-3A Settings

It is possible to configure the terminal either from GUI or manually modifying configuration settings. In the case of manual file modification, emuStudio must be restarted (for more information, see section Configuration file).

The "settings" window [3] is shown in the following image:

Settings window of ADM-3A terminal
  • A: File for reading input (when redirected)

  • B: File for writing output (when redirected)

  • C: In automatic mode, how long the terminal should wait until it reads next input character from the file (in milliseconds)

  • D: Whether every keystroke will also cause to display it. Programs don’t always "echo" the characters back to the screen.

  • E: Whether terminal GUI should be always-on-top of other windows

  • F: Whether the display should use anti-aliasing.

  • G: Clears the screen.

  • H: Rolls the screen down by 1 line

  • I: If checked, then by pressing OK the settings will be saved to the configuration file. If not, they will be not saved. In any case, the effect of the settings will be visible immediately.

The terminal behaves differently when emuStudio is run in automatic (no GUI) mode. In that moment, input is redirected to be read from a file, and also output is redirected to be written to another file. The file names are configurable in the computer config file. Using redirection in GUI mode is currently not possible.

11.4. Configuration file

Configuration file of virtual computers contain also settings of all the used plug-ins, including devices. Please read the section "Accessing settings of plug-ins" in the user documentation of Main module to see how the settings can be accessed.

The following table shows all the possible settings of ADM-3A plug-in:

Table 10. Settings of LSI ADM-3A
Name Default value Valid values Description

halfDuplex

false

true / false

Whether every keystroke will also cause to display it.

inputFileName

adm3A-terminal.in

Path to existing file

File for reading input (when redirected)

outputFileName

adm3A-terminal.out

Path to existing file

File for writing output (when redirected)

inputReadDelay

0

> 0

How long the terminal should wait until it reads next input character from the file (in milliseconds)

alwaysOnTop

false

true / false

Whether terminal GUI should be always-on-top of other windows

antiAliasing

false

true / false

Whether the display should use anti-aliasing.

12. Virtual device simhPseudo-z80

Virtual device partially reimplemented from simh emulator. This device is used mainly for communication between CP/M 3 operating system for simh and emuStudio. Most of the original functionality is not implemented, but it is crucial for support of memory bank-switching.

13. Original software for Altair8800

Since Altair8800 virtual computer emulates a real machine, it’s possible to use real software written for the computer. As was mentioned in Altair8800 for emuStudio section, several operating systems and programs can be run on Altair. There are many disk and memory images of those systems available online, but only some were tested and proved to work. Some of the available online sites are:

In order to build custom disk images, please follow this link:

Most of the disk images were borrowed from great simh emulator. It’s obvious that some images were modified for simh. On the other hand, it’s not that obvious if the original images would actually work at all.

Tested and fully-functional images were:

  • Operating system CP/M v2.2 and 3

  • Altair DOS v1.0

  • BASIC programming language in various versions

Disk / memory images for software for Altair8800 are available on many online sites, such as:

Some manuals can be found at e.g. http://altairclone.com/altair_manuals.htm.

The following subsections describe in short how to boot some of those systems, along with screen-shots how it looks.

13.1. Boot ROM

Booting operating systems on Altair requires special ROM image to be loaded in operating memory [8].

Originally, more boot ROMs existed. Different boot ROMs were used to load the code from different devices. In current implementation of emuStudio, there is only one boot ROM supported - so called 'disk boot loader' (or DBL), which loads operating system from MITS 88-DISK (through CPU ports).

The boot loader is already available in a file boot.bin. You can find it at the download page.

The boot ROM must be loaded into memory at address 0xFF00 (hexadecimal). It is safe to jump to this address manually when operating system image file is mounted.

All subsequent sections assume that the boot loader has been loaded in the operating memory.

13.2. CP/M 2.2

During Altair8800 computer era, many operating systems, applications and programming languages have been developed. On of the most known operating systems is CP/M. It was written by Gary Kildall from Digital Research, Inc. At first it was mono-tasking and single-user operating system which didn’t need more than 64kB of memory. Subsequent versions added multi-user variants and they were ported to 16-bit processors.

The combination of CP/M and computers with S-100 bus [9] was big "industry standard", widely spread in 70’s up to 80’s years of twentieth century. The operating system took the burden of programming abilities from user, and this was one of the reasons why the demand for hardware and software was rapidly increased.

Tested image has name altcpm.dsk. It can be downloaded at this link.

In order to run CP/M, please follow these steps:

  1. Mounting disk images altcpm.dsk to drive A: in MITS 88-DISK.

  2. In emuStudio jump to location 0xFF00 [10]

  3. Optionally, you can set CPU frequency to 2000 kHz, which was Intel 8080 original frequency.

  4. Before starting emulation, show ADM-3A terminal [3]

  5. Run the emulation [10]

After these steps were completed, CP/M should start (an informational message appears) and command line prompt will be displayed:

Operating system CP/M 2.2
Command dir is working, ls is better dir. More information about CP/M commands can be found at this link.

13.3. CP/M 3

Steps for running CP/M 3 operating systems are not that different from CP/M 2. The disk image file is called cpm3.dsk and can be downloaded at this link. CP/M 3 came with two versions: banked and non-banked. The image is the banked version of CP/M. Also, simh authors provided custom BIOS and custom boot loader.

Manual of CP/M 3 can be found at this link. For more information about simh Altair8800 and CP/M 3, click here.

There are some requirements for the computer architecture, a bit different for CP/M 2.2.

13.3.1. CPU

It is recommended to use Z80 version of the computer as was presented in the section Altair8800 for emuStudio. CPU Intel 8080 will work for the operating system itself, but most provided applications require Z80.

13.3.2. Operating memory

Also, the operating memory needs to be set for memory banks [11]. The following parameters were borrowed from simh and were tested:

  • 8 memory banks

  • common address C000h

13.3.3. Boot ROM

There exist specific version of boot loader (modified probably by simh authors) to load CP/M into banked memory. It has name mboot.bin and you can find it at the download page. Before other steps, please load this image into operating memory at address 0xFF00 (hexadecimal).

13.3.4. Steps for booting CP/M 3

Specific steps how to boot CP/M 3 in emuStudio follow:

  1. Mounting disk images cpm3.dsk to drive A: in MITS 88-DISK.

  2. In emuStudio jump to location 0xFF00 [10]

  3. Optionally, you can set CPU frequency to 2500 kHz, which was Zilog Z80 original frequency.

  4. Before starting emulation, show ADM-3A terminal [3]

  5. Run the emulation [10]

The following image shows the look right after the boot:

Operating system CP/M 3 (banked version)

13.4. Altair DOS v1.0

Steps for booting Altair DOS v1.0 follow:

  1. Mounting disk images altdos.dsk to drive A: in MITS 88-DISK.

  2. In emuStudio jump to location 0xFF00 [10]

  3. Optionally, you can set CPU frequency to 2000 kHz, which was Intel 8080 original frequency.

  4. Before starting emulation, show ADM-3A terminal [3]

  5. Run the emulation [10]

The system will start asking some questions. According to the manual, the answers for emuStudio are:

  • MEMORY SIZE? → 64 or ENTER (if memory ROM is at 0xFFFF)

  • INTERRUPTS → N or just ENTER

  • HIGHEST DISK NUMBER? → 0 (if only 1 disk is mounted)

  • HOW MANY DISK FILES? → 3

  • HOW MANY RANDOM FILES? → 2

The basic commands you can use are e.g. MNT 0 - to mount the drive, and then DIR 0 to list the files.

If you want AltairDOS being able to automatically detect how much memory is installed on system, it is possible. The system does it by very nasty trick - testing if it can write to particular address (ofcourse, maximum is 16-bits - i.e. 64K of memory). If the result is the same as it was before reading, it means that it reached the "end of memory". But when it fails to detect the ROM, it fails to determine the size, too, and the output will be INSUFFICIENT MEMORY.

The following image shows how it looks like:

Operating system Altair DOS 1.0

13.5. BASIC

In this section will be presented how to boot MITS BASIC version 4.1. There is possible to boot also other versions, but the principle is always the same.

As it is written in simh manual: MITS BASIC 4.1 was the commonly used software for serious users of the Altair computer. It is a powerful (but slow) BASIC with some extended commands to allow it to access and manage the disk. There was no operating system it ran under.

After boot, you must mount the disk with MOUNT 0. Then, command FILES will show all files on the disk. In order to run a file, run command RUN "file". Manual can be found at this link.

It is assumed you have either boot.bin or mboot.bin mounted in the operating memory (see Boot ROM for more details).

Steps for booting BASIC follow:

  1. Mounting disk images mbasic.dsk to drive A: in MITS 88-DISK.

  2. In emuStudio jump to location 0xFF00 [10]

  3. Optionally, you can set CPU frequency to 2000 kHz, which was Intel 8080 original frequency.

  4. Before starting emulation, show ADM-3A terminal [3]

  5. Run the emulation [10]

The following image shows the look right after the boot:

Altair 8800 Basic 4.1

1. They were used for various bootloaders
2. If it ends with .hex suffix, it will be loaded as Intel HEX format, otherwise as binary
3. "peripheral devices" window in the Emulator panel in emuStudio
4. Be cautious with the settings. Incorrect values can result in disk image file damage. Default values are used for classic Altair8800 image files used by simh
5. According to manual the write sequence holds only for short time, maximally until the end of sector is reached. The plug-in does not limit the sequence period, it is deactivated only when the end of the sector is reached. In addition each first byte and the last byte of a sector should have set its MSB (7th bit) to 1. It was called the "sync bit" for easier identification of start or end of a sector. However, the plug-in does not require it.
6. According to manual, the bit is set for maximum 30 microseconds. Programs could detect the bit set and quickly start writing data until the Sector true came back again. It could be made in time easily, because CPU was much faster than disk itself. Plug-in does not limit the period. The value is 0 practically all the time, until first byte is written.
7. "In the original ADM-3A device, these codes were executable only from computer."
8. The purpose of a boot ROM is to load specific block of data from a device and then run it as if it was code. The code block is often called 'boot loader'. It is very small program which just loads either the whole or part of the operating system into memory and then jumps to it.
9. 8-bit computers sharing some similarities with Altair 8800
10. See "Debugger toolbar" in the user documentation of Main module
11. Please see section Bank switching