Memory Map
This page will present overall address space utilization, the ROM entry points, and low-memory addresses of interest to programmers. Assembly language is required to take advantage of most of this information, but the memory addresses are presented in both split octal and decimal notation so that Basic programmers can use them (ref. PEEK).
Page Index | Address Space Utilization |
PAM/8 ROM Entry Points | |
PAM/8 RAM Locations | |
HDOS ROM Entry Points | |
HDOS RAM Locations | |
Note on dates |
A real H8 would support from 8 to 56K of static RAM installed in a 64K address space. The H8 emulator supports a full 56K of RAM.
Assuming that the original HDOS 2.0 H-17 disk driver is being used, the address space is used as follows:
Area | Size | From | To |
---|---|---|---|
PAM/8 ROM | 1.00K | 000.000A | 003.377A |
unused | 4.00K | 004.000A | 023.377A |
H-17 RAM | 1.00K | 024.000A | 027.377A |
H-17 ROM | 2.00K | 030.000A | 037.377A |
PAM/8 RAM | 64 bytes | 040.000A | 040.077A |
HDOS RAM | 576 bytes | 040.100A | 042.177A |
User RAM | 45.43K | 042.200A | 330.066A |
"Shared" | 5.37K | 330.067A | 355.257A |
Resident HDOS | 4.58K | 355.260A | 377.377A |
The H-17 driver is included in the resident HDOS area. The HUG replacement driver is 320 bytes larger than the original Heath version pushing everything else down by that amount, reducing by that amount the memory available to user programs.
What I've called the "shared" space is used to load the HDOS overlays and loadable device drivers, such as the AT: and LP: drivers. These are loaded on demand and, in the case of the overlays, user RAM can be swapped to disk temporarily to make room for them. The sizes of these components, in the current version, are:
Component size expressed in... | |||
---|---|---|---|
Component | Split Octal | Decimal | K/bytes |
HDOSOVL0 | 013.307A | 3015 | 2.9K |
HDOSOVL1 | 010.134A | 2140 | 2.1K |
LP: | 000.176A | 126 | 0.1K |
AT: | 000.330A | 216 | 0.2K |
Total | 025.171A | 5497 | 5.4K |
(These are the module sizes after they are loaded into memory. The size on disk will differ.)
The maximum memory available to a user program is 50.8K/bytes. A program this size would have two constraints:
Whenever an overlay-resident syscall is executed, user memory must be written to SY0: and the overlay read in, the syscall executed, and the user memory restored. This has a significant performance impact.
The data being worked on by the overlay-resident syscall can't be present in the "shared" block of memory that's swapped out.
Generally, an application will perform better if it loads the overlays and drivers into memory before allocating all system memory to itself. Cautions:
Load both overlays and any drivers you might need before dismounting the system disk. If you don't, the dismount will fail with an error code of EC.NPM ("no provisions made for remounting more disks").
Load the overlays and any drivers you might need before allocating all available system memory to your application (ref: .settop syscall). If you don't, the overlay load will fail with an error code of EC.NEM ("not enough RAM for this program").
Given that we're only talking about 5K/bytes in round numbers, it's worth it for overall flexibility and performance to run HDOS in stand-alone mode, preloading all the overlays and drivers. You will have to issue the command "set hdos stand-alone" once on the bootable disk, then you can use a PROLOGUE.SYS program to:
...at boot time. You can also load the drivers by hand using the LOAD command and RESET SY0: to cause the command handler to load the overlays.
I wouldn't expect any changes in HDOS (!) but the drivers may change over time making them larger or smaller, so you should always use the .settop syscall to size available user memory and not rely on the addresses shown in the table above.
There are three ROM routines that may be of interest:
Alarm will sound the front panel alarm for about 1/2 second. There are no entry or exit parameters.
CRC calculates a 16-byte cyclic redundancy check sequence for you. This is the same calculation as used on PAM/8 tape files.
You must initialize CRCSUM to zero (ref. PAM/8 RAM Locations). Pass each byte to CRC in register (A). The final CRC will be in CRCSUM.
This CRC is calculated such that if you pass the calculated CRC bytes through CRC and the result in CRCSUM is zero, the data block is good. Otherwise, there is an error.
Read the front panel key pad. You get full debounce and audio feedback (though the emulator will "swallow" the short click) and keycode translation to a simple numeric value. This is the routine that Basic calls when you use the PAD() function.
Note that you will stay in RCK until a key is read. If you want to check for a key first, you can IN 360Q; if the value you read is 377Q, there is no key down. Otherwise, you can call RCK to get the key value. This is a better approach for most real-time uses of the keypad.
The key value is returned in register (A) as per the following table. All values given are in decimal.
Key | Value | Key | Value |
0 | 0 | 8 | 8 |
1 | 1 | 9 | 9 |
2 | 2 | + | 10 |
3 | 3 | - | 11 |
4 | 4 | * | 12 |
5 | 5 | / | 13 |
6 | 6 | # | 14 |
7 | 7 | . (dot) | 15 |
Neither RCK nor the emulator support 2-key sequences.
If you want to read the keypad yourself without the support of RCK, here are the key values. The no-key-down value is 377Q.
Key | Value | Key | Value |
---|---|---|---|
0 | 0xfe | 8 | 0xef |
1 | 0xfc | 9 | 0xcf |
2 | 0xfa | + | 0xaf |
3 | 0xf8 | - | 0x8f |
4 | 0xf6 | * | 0x6f |
5 | 0xf4 | / | 0x4f |
6 | 0xf2 | # | 0x2f |
7 | 0xf0 | . (dot) | 0x0f |
PAM/8 uses the memory from 040.000A through 040.064A for local workspace and to store hardware control and interrupt vector access areas.
In general, you should stay away from modifying CTLFLG. The rest of it is fair game.
Address | ||||
Name | Octal | Dec. | Len. | Description |
REGI | 40.005A | 8197 | 1 |
Register index tells PAM/8 which register pair is being displayed when we're in register display mode. Values (decimal): 00 - SP 06 - DE 02 - AF 08 - HL 04 - BC 10 - PC |
DSPMOD | 40.007A | 8199 | 1 |
Display mode tells PAM/8 which mode the front panel LED display is in. Values (decimal): 0 - memory read (decimals off) *1 - memory write (decimals rotating) 2 - register read (decimals off) * 3 - register write (decimals rotating) * - these are the only values available to a user program; setting 1 and 3 will have no effect. |
.MFLAG | 40.010A | 8200 | 1 |
Mode flag provides controls over PAM/8's operation. This is a one-byte bit mask. Mask bits are: UO.HLT - 10000000B - Disable HALT proc.UO.NFR - 01000000B - No LED refresh UO.DDU - 00000010B - No LED update UO.CLK - 00000001B - User gets clock int. UO.NFR disables front panel refresh; the LEDs will remain dark. UO.DDU leaves the LEDs lighted but they are not refreshed so you can put your own content on the front panel. These are equivalent to Basic's CNTRL 2,0 and CNTRL 2,1 commands. HDOS sets UO.HLT and UO.CLK; please don't mess with them. If you need clock interrupts, save the HDOS vector at 40.038A and insert your own. After your clock processing, jump to HDOS's processing address. |
CTLFLG | 40.011A | 8201 | 1 |
This is PAM/8's control byte, used to determine what mode she's in from entry to entry. Except for CB.CLI, it's best to not mess with it, as any changes can have unintended consequences. For example, turning on CB.MTL will light the monitor lamp, but
it will also cause PAM/8 to take partial control of the machine. This is a one-byte bit mask. Values are: CB.SSI - 00000010B - Single step int. off CB.MTL - 00000100B - Monitor lamp on CB.CLI - 01000000B - Clock disabled CB.SPK - 10000000B - Speaker off You can set CB.CLI to disable the clock tick while leaving interrupts on. This will maximize performance, but .TICCNT will not advance and the disk system won't operate correctly. Take caution, here. |
ALEDS | 40.013A | 8203 | 6 |
These are the six address LEDs. If you set UO.DDU, you can put your own segment patterns in them. Bits are active off; e.g., a zero bit lights the corresponding segment. The segment pattern is: 11111110B (376Q) - center segment on11111101B (375Q) - top segment on 11111011B (373Q) - top-right segment on 11110111B (367Q) - bottom-right segment on 11101111B (357Q) - bottom segment on 11011111B (337Q) - bottom-left segment on 10111111B (277Q) - top-left segment on 01111111B (177Q) - decimal point on So, for example, the number '2' would be 11001000B (310Q). Try this in the debugger: :B:40010=201/203:B:40013/9=xxx/376 xxx/375 xxx/373 xxx/367 xxx/357 xxx/337 xxx/277 xxx/177 xxx/310 |
DLEDS | 40.021A | 8209 | 3 |
These are the three data LEDs. If you set UO.DDU, you can put your own segment patterns in them. See above for segment patterns. |
ABUSS | 40.024A | 8212 | 2 |
If PAM/8 is displaying or modifying memory, this is the address of the memory byte. You can change this in your program to control which memory location is being displayed. |
CRCSUM | 40.027A | 8215 | 2 |
The 16-bit CRC accumulated by calling CRC. |
TICCNT | 40.033A | 8219 | 2 |
The 16-bit 2ms tick counter. Use this as a stable source for timing information. |
UIVEC | 40.037A | 8223 | 21 |
Interrupt vector jump table. Install a jump instruction (303Q following by your routine address) before enabling an interrupt. |
clock | 40.037A | 8223 | 3 |
If HDOS is running, this vector will be loaded with HDOS's clock interrupt handler. If you absolutely have to have clock interrupts, replace HDOS's routine address with your own, and jump to HDOS's handler when you are through. Before exiting your program, replace HDOS's handler address. |
sstep | 40.042A | 8226 | 3 |
Jump to single-step handler. If PAM/8 didn't initiate a single step, she assumes that someone else did and jumps to this address. |
console | 40.045A | 8229 | 3 |
If HDOS is running, this will contain a jump to HDOS's console input handler. If you want to install your own, save HDOS's address and install yours and you will start getting receiver interrupts immediately. Reset the interrupt enable register to interrupt only on receiver full and replace HDOS's handler address before you exit. |
vec4 | 40.050A | 8232 | 3 |
Jump for interrupt vector 4 - unused by the emulator. |
vec5 | 40.053A | 8235 | 3 |
Jump for interrupt vector 5 - unused by the emulator. |
modem | 40.056A | 8238 | 3 |
Jump for the modem interrupt. Install a jump to your handler before enabling interrupts on the modem UART. |
scall | 40.061A | 8241 | 3 |
This vector contains a jump to HDOS's system call handler. |
These utility routines are in the so-called HDOS ROM that occupies address range 030.000A-037.377A.
Compare the string at (HL) with the string at (DE) for a length of (C). Return 'Z' if the strings are equal, 'C' if ((DE)) < ((HL)).
$COMP uses registers A,F,C,D,E,H,L.
Add the value in register (A) to the (HL) register pair. (A) is not altered.
$DADA uses registers F,H,L.
Add the value in register (A) to the (HL) register pair. Faster than $DADA but (A) is altered.
$DADA. uses registers A,F,H,L.
Divide the (BC) register pair by the (DE) register pair and return the integer result in the (HL) register pair.
$DU66 uses all registers.
Load (HL) indirect through (HL): Load the 16-bit word at the address in the (HL) pair into the (HL) register pair. Trivial example to illustrate:
STRING DB 'Print me',ENLCalling $HLIHL is equivalent to you coding:
MOV A,M...but shorter. This comes in very handy when you want to walk through HDOS memory data structures.
$HLIHL uses registers A,H,L.
Compare the (DE) and (HL) register pairs for equality only. Sets the 'Z' flag if they are equal.
$CDEHL uses registers A,F.
Complement the (HL) register pair.
$CHL uses registers A,F,H,L.
Indexed full word load. Load the (DE) pair with the word that is at the address in the (HL) pair plus the 16-bit displacement that immediately follows the call. Returns to the program following the displacement. This is used to pick 16-bit values out of a memory structure pointed to by the (HL) pair.
As an example, assume that you have the address of the device table entry for the 'SY' device in the (HL) register pair, and you want to pick out the address of the unit specific table, which is at offset DEV.UNT from the front of the table (see the note at the bottom of this page):
* Entry: (HL) has address of table entryHDOS makes heavy use of this to get around the lack of an indexed load instruction in the 8080A chip.
$INDL uses registers A,F,D,E.
Move the contents of memory at the address in the (DE) register pair to the address in the (HL) register pair. The length of the move is in the (BC) register pair. If the two areas overlap, $MOVE will work from back to front so that the block is moved correctly without a "ripple" effect.
$MOVE uses all registers.
Multiply the contents of the (DE) register pair by 10. Return the result in the (HL) register pair. Watch for overflow - the maximum result is 65,535.
$MU10 uses registers F,D,E,H,L.
Multiply the contents of the (DE) register pair by the contents of the (BC) register pair and return the result in the (HL) register pair. Watch for overflow - the maximum result is 65,535.
$MU66 uses all registers.
Multiply the contents of the (DE) register pair by the contents of register (A) and return the result in the (HL) register pair. Watch for overflow - the maximum result is 65,535.
$MU66 uses all registers.
Restore all registers. You would normally JMP to $RSTALL. See $SAVALL below for an example of its use.
$RSTALL, obviously, uses all registers.
Save all registers. You would normally call $SAVALL at the top of a called subroutine, when you want to exit that routine without altering any of the registers or flags. To exit the subroutine, JMP to $RSTALL. Example:
* This is my subroutine$SAVALL uses registers H, L.
Table jump. The call is followed by a table of 16-bit addresses. Register (A) must contain a table entry number (0, 1, 2, etc.). $TJMP will jump to the content of the entry that is ((A) * 2) bytes past the call. Example:
MVI A,2 Select entry 2$TJMP uses registers A, F.
Table branch. The call is followed by a table of 8-bit address offsets. Register (A) must contain a table entry number (0, 1, 2, etc.). $TBRA will add (A) to the address of the beginning of the table to find the correct offset. It will then add that offset to the address of the offset to find the routine address. That address will be loaded into the (PC) register pair.
The routines addressed by the table must be within 256 bytes of the call to $TBRA - you'll get assembly errors if they are not. Example:
MVI A,2 Select entryYou'll find $TBRA used in both the AT and LP device drivers.
$TBRA uses registers F,H, L.
Type on the console the text that immediately follows the call, and return to the instruction following the text. Make sure to use a high-order 1-bit delimeter at the end of the text. Example:
CALL $TYPTX$TYPTX uses registers A,F.
Type the text at the address in the (HL) register pair. Equivalent to the .PRINT Scall.
$TYPTX. uses registers A,F.
Unpack Decimal Digits - Convert the number in the (BC) register pair to ASCII decimal. Put the result at the address in the (HL) register pair, for a length of (A). The result is right-adjusted in the output field and filled with leading zeros as required.
$UDD uses all registers.
Zero a block of memory. The memory address is in the (HL) register pair. The length of the block is in register (B).
$ZERO uses registers A,F,B,H,L.
These are interesting low-memory locations used by resident HDOS code to manage the system. I would caution that these should be considered to be read-only locations. If you start writing down here, you're on your own.
Address | ||||
Name | Octal | Dec. | Len. | Description |
D.SYDD | 40.130A | 8280 | 3 | Jump to low-level disk driver. |
D.TT | 40.240A | 8352 | 1 | Current disk operation target track - great to watch in the front panel LEDs. |
D.TS | 40.241A | 8353 | 1 | Current disk operation target sector. |
D.DRVTB | 40.251A | 8361 | 8 | Current track number (where the heads are) and volume number for four drives, organized as sy0: tracks and volume, sy1: tracks and volume,.... |
D.HECNT | 40.261A | 8369 | 1 | Hard error count. |
D.SECNT | 40.262A | 8370 | 2 | Soft error count. |
D.OECNT | 40.264A | 8372 | 1 | Operation error count. |
D.E.MDS | 40.265A | 8373 | 1 | Missing data sync errors. |
D.E.HSY | 40.266A | 8374 | 1 | Missing header sync errors. |
D.E.CHK | 40.267A | 8375 | 1 | Data checksum errors. |
D.E.HCK | 40.270A | 8376 | 1 | Header checksum errors. |
D.E.VOL | 40.271A | 8377 | 1 | Wrong volume number errors. |
D.E.TRK | 40.272A | 8378 | 1 | Bad track seek errors. |
D.OPR | 40.273A | 8379 | 2 | Total operation count - reads.. |
D.OPW | 40.275A | 8381 | 2 | Total operation count - writes. |
S.DATE | 40.277A | 8383 | 9 | Current system date in printable ASCII. |
S.DATC | 40.310A | 8392 | 2 | Current system date, encoded. (See note below.) |
S.HIMEM | 40.316A | 8398 | 2 | System high memory address (377.377A in the emulator). |
S.SYSM | 40.320A | 8400 | 2 | FWA (first word address) resident system. |
S.USRM | 40.322A | 8402 | 2 | LWA (last word address) user memory (set by .SETTOP syscall). |
S.OMAX | 40.324A | 8404 | 2 | Maximum overlay size for this system. This is set to a value just a little larger than HDOSOVL0. You can use this value to calculate a maximum user memory address to use with .SETTOP, that will leave room for the overlays to be loaded without the need to swap user memory to disk first. |
S.CSLMD | 40.326A | 8406 | 1 | Console mode (ref: .CONSL scall) |
S.CONTY | 40.327A | 8407 | 1 | Console type flags (ref: .CONSL scall) |
S.CUSOR | 40.330A | 8408 | 1 | Current cursor position (ref: .CONSL scall) |
S.CONWI | 40.331A | 8409 | 1 | Console width (ref: .CONSL scall) |
S.CONFL | 40.332A | 8410 | 1 | Console flags (ref: .CONSL scall) |
S.CAADR | 40.333A | 8411 | 2 | Abort processing address |
S.CCTAB | 40.335A | 8413 | 2 | Addr. for Ctl-A, -B, -C processing |
S.DLINK | 40.346A | 8422 | 2 | Addr. of HDOS system data in high memory. This is useful for finding the system unit number, which is at offset 21Q from this address. The system unit number is the physical device (0, 1 or 2) that is presently logical unit 0. See boot for a complete description of how - and why - this works. |
S.CFWA | 40.352A | 8426 | 2 | FWA of the channel table. |
S.DFWA | 40.354A | 8428 | 2 | FWA of the device table (see note below this table). |
S.RFWA | 40.356A | 8430 | 2 | FWA resident HDOS code. |
AIO.UNI | 41.061A | 8497 | 1 | Active I/O area, current unit number. Set this to a unit number in binary (0, 1, 2) before calling the SY.DVD driver for low-level I/O. |
USERFWA, STACK |
42.200A | 8832 | n/a | This is not a byte or word value; it just seems like an convenient place to point out that free memory for user programs starts at 42.200A. The stack pointer is also initialized to 42.200A. |
Note on S.DFWA
This is a pointer to a table with the following structure:
ORG 0There is one 14-byte entry for each driver in the system. The first entry should be for the TT: driver; the second will generally be for the SY: driver. You can find it by looking for 'SY' in the first two characters.
The mounted unit mask is a bit field that will tell which drives have a disk mounted:
00000001B - Drive sy0:DEV.UNT is a pointer to a unit-specific data table, containing three 8-character entries, one per drive. Each of these entries has the following format:
ORG 0With this information, you can easily find the directory without having to read the disk label, you can make low-level driver calls to read the directory, and you can use the in-memory GRT to access file sectors and to chase the free chain to determine disk free space. Refer to the design notes on the project web site for more information on these subjects.
Again, I caution: Please treat these fields as read only.
Note on S.CFWA
This is a pointer to the beginning of the channel table. There are 7 table entries, corresponding with file channels -1 through 5; S.CFWA points to the entry for channel -1. Each entry starts with a link to the next entry in the list, so you can follow the chain to the entry of your choice. The link in channel five is zero.
Each entry is 42 bytes long and has the following format. (Note that this information comes from experimentation and not the HDOS source listings, so it may not be 100% complete or accurate.)
ORG 0Flag tends to be additive, if the device supports it. So if you open a disk file for read you will get a flag of 003Q (directory device, read); if you open it for write you will get a flag of 007Q (directory, read and write); and if you open it for update you'll get a flag of 017Q (directory, read, write and update).
If you open AT: or TT: for write, you'll get a code of 006Q (read and write). But you can only open LP: for write, and you'll get a code of 004Q (write only).
Most of the fields in the record are for directory devices, obviously: fields such as current group number and last sector index have no meaning with a character device like TT:. Note that the table carries the full and complete directory entry for the open file, along with the logical sector number of the directory block that carries the entry.
HDOS stores dates in a packed, 16-bit format. Year is stored as the number of years since 1970. Month and day are stored as is. Consider the date 27-Jan-92. HDOS would store that date as follows (in binary):
0010110000111011 = 27-Jan-92Storage order is year, month, day. Year gets 7 bits, month gets 4, and day gets the remaining 5 bits:
0010110 0001 11011 = 27-Jan-92In split octal, this date is 054.073A. HDOS will store the date in the normal low-order-byte first order used to store all 16-bit numbers, so this date would appear in S.DATC as 073 054.
It should be readily apparent that the year must be between 70d and 99d to be valid. It should also be readily apparent that HDOS is not Y2K compliant!