Contents
? OCTAL DEFINE BRAIN
> #1 25 +DO #1 117 +DO RND IF<0 57 ELSE 134 ENDIF PCHR +LOOP CRLF +LOOP END
? BRAIN
//\///\/\/\\//\\\/\\/\\/\\\//\\/\/\/\\///\//\\//\///\\\/\\\/\/\\\\/\\/\\\\////\
\\\\/\\//\\//\\\/\\\\\//\\//////\//\\\\/\\//\\\/\\//\\\//\\\\/\/\//\\\\/\\\/\\\
\/\\\/\\\/\\///\///\\///\\//\//////\//\////\/////\\////\\/\/\/\/\////\/\////\/\
//\////\///\\/\\//\\\\//////\/\\/\/\\/\\\\/\/\/\\\///\\/\\\\\\\\\\//\\////\/\\/
///\\\\\\///\/\/\\//\\\\\//////\/\\//\\/\\\\\\//\///\\\//\\//\//\/\\////\\//\/\
/\/\//\\\\/\/\/////\\\\\\/\\/\\//\/////\\\\/\\//\\//\\\\\\/\\\//\\\//\\////\\//
\/\//\/\//\\\///\//\/\//\//\/\\///\/\\///\/\\\\\\\\///\/\\\\/\\/\\//\\/\//\/\\\
\\///\\\\\//\///\/\\//\/\\\/\\/\///\/\/\/\/\\//\//\//\\//\\//\\///\/////\\\///\
\/\/\//\\\//////\\\//\\\\//\\\\////\\\/\\\\//\\\////\\/\/\/\/////\\\\\//\\//\\\
\\//\//\\\/\//\\\/\\//\///////\/\\\\/\\/\//\//\\/\\//\/\\////\/////\/\\\//\//\/
///\\/////\/\//\\\///\///\/\\/\\/\\\\\/\\/\////\\////\\\/\\\\\\\///////\\\/\\//
\\\\/\/\/\//\//\\//\\\/\/\\/\\\/\\\/\//\\//\///\/\/\\\/\\\\\\////\\////\\\/\/\/
\//\/\//\/\///\/\/\/\/\/\/\\\//\///\\\\\\/\\\\\\///\/\\\\\//\////\\////\\/\////
//\/\////\\/\\\////\\\///\\/\\/\///\///\\//\\\//\\//\///\\/\\/\/\//\//\///\\//\
///\\/\\//\/\\//\///\//\\/\/\/\/\\////\\\\/\///////\//\//\\/\\/\//\/\\/\////\/\
/\/\//\\\//\\/\\//\//\\//////\\///\//\/\\/\\////\\\\\\\\/\\//\//\/\\\\\\//\\\\/
\///\/\/\//\\///\\/\/\\/\\///\/\\////\\////\\/\/\/\///\\/\////\///\////\\///\\/
/////\\\\/////\//\/\/\\/\\\\\\\///\/\\///\\//\\/\\\/\\\///\\\////\//\//\///\/\/
\\\//\\//////\//\\/\//\\\\/\\/\\\///////\\/\\\//\\\\/////\/\////\\///\////\\\\/
/\\/\\\\///\\////\///////\//\//\\//\//\\\\/\\/\\\////\\\//\\///\\/\\/\///\//\\/
//\//\/\//\\/\///\/\\\//\\\\//\\//\/\/\\///\/\/\\\\\\\\/\\\\/\//\//\\\/\\\\/\\/
?
HP-IPL/OS is an Interpreted Programming Language and Operating
System for HP2100-series minicomputers. These robust machines were
popular in the '70's and many survive today in the hands of collectors,
and perhaps a few even performing their original tasks. As of 2010
newly manufactured versions of the machine in the form of PC cards and
(expensive) rack-mount systems are available. For vintage computer
collectors, these machines are fairly easy to repair (big fat boards
with mostly standard TTL circuitry) with multiple companies offering
replacement parts, and the various machines, interface boards and
software are well-documented. Some of the original software is
available
and fairly easy to run under simulation, however the available disk
operating systems (Time Share Basic E, F, Access and RTE 6) require
fairly specific hardware setups that are often out of reach by the
average hobbiest. Other available software that would run on a minimal
system is often too tedious or too limited. The HP-IPL/OS project was
started in 2001 by
Bob Shannon to provide an alternative software system that could run on
an 8KW machine with only a papertape reader emulator and a console, yet
be extendable to support 21MX-class machines and whatever hardware is
available.
I took up the task of hacking Bob's threaded interpreter and
initial primitive words into a practical kernel, by 2002 we
had something that worked enough to begin writing extensions. Bob
provided most of the hardware drivers, I did lots of IPL coding. The
system grew bit by bit until by 2004 we had enough functionality to do
stuff like run HP BASIC from disk, type in a program, then save the
binary image, program and all. That first primitive "TDOS" didn't care
what kind of disk it used, instead it made calls to a disk driver so it
could work on a 7900 or 7906 drive under simulation, or Bob's custom
IDE disk controller, with no changes to the actual dos code. Later
"XDOS" and "SFS" systems use the same mechanisms. Eventually I made my
own IDE disk interface for my system, and configured a build with both
7906 and IDE drivers so that I could copy a simulated 7906
disk system to my IDE disk system and boot it with no changes. In
2008 I added a Viniculum VDRIVE2 module to my IDE interface, at first
to save and restore disk images using a USB thumbdrive but it didn't
take long to hack together a simple streaming "VDOS" to save and load
files to and from the thumbdrive, reusing load/save components
originally written in 2002. I'm contemplating how to make a simpler
version of the VDRIVE2 adapter that could also function as a paper-tape
reader emulator through the same "microchannel" card.
Hardware
Requirements and Configuration
The minimum requirements for running HP-IPL/OS is a HP21xx CPU box
with at least 8KW (16KB) memory, a serial console interface card
connected to a terminal or PC, and a parallel interface card connected
to a paper-tape reader emulator for loading the software. A paper-tape
punch emulator helps but it is possible to dump changes to the console,
save the resulting text, then use a PC utility to convert the dump file
into a binary that can be reloaded.
A 21MX-type CPU with DCPC(DMA), DMS, and at least 64KW is required
to use the existing alternate memory binary save/load tools, the
magtape operating system, and/or the XDOS disk operating system. From
96KW-192KW is required to use the SFS package for applications that
need to stream data to and from buffered disk files (XDOS saves, loads,
renames and deletes files but can only process one file at a time, SFS
adds up to four buffers with independent file pointers for more
sophisticated applications). At one point 7970E magtape worked but can
no longer be tested due to lack of working hardware. Works under
simulation.
The IDE disk driver requires custom electronics that connects a IDE
hard disk to a 16-bit bi-directional parallel interface card, the
driver does not use DMA and contains no extended instructions. The 7900
and 7906 disk drivers use DMA channel 1 (loc 6) and as written require
a CPU that supports the DIV and MPY instructions. The disk drivers
require 32KW as written due to the ORG of the binary load code. Note
that AFAIK the 7900 and 7906 drivers have not been tested on real
hardware. Works under simulation.
The experimental VDOS system for storing files on a USB thumbdrive
requires custom electronics that connects a Viniculum VDRIVE2 module to
a 16-bit bi-directional parallel interface card, presently the
electronics are implemented as part of the IDE interface. The drivers
do not use DMA and do not require extended instructions. The base
package requires at least 16KW to implement a system able to load IPL
packages and save HP-IPL/OS builds. The full package requires a
21MX-type system with at least 64KW to load, run and save arbitrary
binary files.
HP-IPL/OS presently has drivers for the following peripherals...
# - these boards are installed in my HP2113.
* - this board was in my machine when I got it, presently not connected.
** - IDE requires a "registered" interface, my older card required
patching <IDE to always return 0.
Unused slots need a jumper or board of some kind inserted. Default
slots
for some devices conflict as not all of these are installed in my
system but are present in other systems. Different I/O interface cards
can be used for PTR, PTP and IDE depending on what sort of hardware is
attached. The "+" boards use 12V logic signals and require level-shift
circuits to interface to microcontrollers, the "MicroCircuit" board
uses TTL-compatible 5V logic signals that can be directly connected to
PIC pins etc.
For most builds CONFIG can be used to change the slot assignments
for TBG, TTY, PTR, PTP, BACI, HPIB, LPT, IDE and 790x, and also be used
to place the console on the BACI device rather than TTY. Builds
containing !CFGE (cfge.ipl) can be configured by setting switches
before running - put TTY slot in bits 0-5, BACI slot (opt) in bits
6-11, clear bit 12 for TTY or set for BACI console, bits 13-15 clear,
then run from location 70 rather than the usual location 2.
The running of autostart words (words that begin with !) can be
disabled if necessary by putting 177777 into the switch register then
running from location 2. If reconfiguring the initial TTY slot using
switch settings, autostarting can be disabled by setting bit 15,
clearing bits 6-12, then running from location 70.
Device assignments may also be changed by altering the following
memory locations...
270 - TBG slot
271 - BACI slot
272 - LPT slot
273 - 7900 slot (first board) or 7906 slot
274 - IDE slot
276 - HPIB slot
355 - TTY slot
356 - PTP slot
These can be changed using front-panel switches, or if the system is
up, by using the PUT command.
For example to change the BACI slot to 14 enter: 271 14 PUT
Once the system has been configured it needs to be punched to a new
ABS file (usually) using the SYSALL command. If PTP isn't available the
SimH HP2100 simulator can be used to produce the reconfigured build, or
the congen.ipl or version thereof used to dump the system to the
console and capture to a file, then L2ABS (in log2abs.ipl or the
hposutil.abs build) can be used under simulation to convert the dump
file into an ABS file (or make a PC utility that does the same thing,
both the dump format and the ABS file format are fairly simple). To use
a new configuration without saving restart the system by entering: 2 RUN
To boot one of the provided builds as-is without using
reconfiguration procedures, the TBG card needs to be in slot 10 (if the
build includes tbg.ipl), and the TTY (terminal) card needs to be in
slot 11. Some way to load the system is required, either a
"passthrough" paper-tape reader emulator and transfer software or some
other kind of paper-tape reader emulator with the desired ABS file
loaded. A terminal or terminal emulator needs to be set to match the
TTY card (for my system it's 2400 baud, 7 bits, even parity, 2 stop
bits) and connected to the TTY card's serial port before running the
system (I have only a single serial port on my PC so have to juggle).
The procedure for my HP2113 E-series machine is as follows...
Power up the machine
Ready the PTR file transfer with the desired ABS file so that it is
waiting to send the file
Select the S register, set bits 6-11 to correspond to the PTR slot
number, press Store
Press Preset IBL Preset Run - the transfer should begin
Wait until the machine halts and bits 0-5 light up indicating a
successful load
If only a single serial port is available, close the PTR sender and
start the terminal program
Select the P register, press Clear, set bit 1 to specify location 2,
press Store Preset Run
The HP-IPL/OS sign on message and the ? prompt should appear on the
terminal. Enter WORDS to make sure it works.
To run a build under simulation, obtain or compile a copy of the
SimH HP2100 simulator and put it in a path directory (for Windows say
C:\Windows, for Linux say /usr/local/bin). The easiest way (in my
opinion) to use the simulator is to install the provided batch or shell
scripts so that .sim scripts or .abs files can be double-clicked or
right-clicked to run. Once the scripts are set up and working it makes
it trivially easy to run a build or test a new build.
To run a build manually in the simulator, go to a command prompt
where the build is, run hp2100, and do something like this...
sim>set clk dev=10
sim>set tty dev=11
sim>set ptr dev=12
sim>set ptp dev=13
sim>set cpu 21mx
sim>set cpu 256k
sim>set throttle 800K
sim>load hposdemo.abs
sim>run 2
HP-IPL/OS DEMO 1.55
?
HP-IPL/OS
begins with a compact kernel that implements an indirectly-threaded
interpreter and a base set of "words" (code routines with names stored
in a "dictionary" in memory), from there "IPL" packages are loaded to
build up the desired level of functionality. Many packages are provided
to help create a variety of "builds" ranging from entirely memory-based
to disk operating systems. Words are stored in the dictionary as a
linked list, each word starts with four locations specifying the word's
length, the first four characters of the word's name, and the address
of the next word. The chain is terminated with a zero, marking the end
of the dictionary. Word names must be carefully specified to avoid
conflict but this primitive system is fast and efficient. Words are
removed by replacing the length location with a zero (usually using the
FORGET word from the extra.ipl package), which effectively removes all
words that follow. In
effect the dictionary functions like a sequential memory tape.
IPL
packages are simply a collection of commands that are redirected into
the otherwise interactive console. Some packages define temporary words
that prompt for configuration options, such as the extra.ipl package
that prompts for a key press to configure memory and block usage, and
the internal.ipl package that prompts to optionally remove block-data
support from the kernel for making compact 8KW builds that don't need
CREATE or other things that store large arrays in memory. Most
HP-IPL/OS builds for 16KW or more include the full kernel, the
extra.ipl package (or the more compact extra2.ipl package) to provide
command
line tools like EXPLAIN, RENAME and FORGET and a few commonly-used
string functions that aren't in the kernel, and the create.ipl package
(or the
smaller non-extended smallcre.ipl package) to provide the CREATE word
assembler. Additional packages can be added to support specific
hardware such as tbg.ipl for the Time Base Generator (TBG), baci.ipl
for the BACI serial port, hpib.ipl for the HPIB instrument buss, and
disk drivers for 7900 and 7906 drives and for an IDE disk drive
attached using a custom-made interface. Some packages provide
additional programming abilities such as double.ipl for 32-bit integer
math and decimal string conversions, bigshift.ipl for 32-bit shifts and
rotates, float.ipl and floatext.ipl for floating-point math and dms.ipl
for accessing "alternate memory" (memory past the normal 32KW address
space). Systems with 64KW or more can use packages that provide extra
words that make it easier to manipulate binary code in alternate memory
(altutil.ipl, fcam.ipl and the Show Alternate Memory dump utility in
sham.ipl), and implement a Simple File System for storing files on a
disk drive (xdos.ipl, sfs.ipl and associated utilities). However all of
the packages (at least beyond CREATE) are optional and are not the only
way these things can be done, just how it was done. The user is free to
explore alternate methods by adapting the provided code or by writing
entirely new packages.
HP-IPL/OS uses 5 stacks to store temporary data, the system stack
(S), the return stack (R), the string stack (X), and two more stacks (Y
and Z) for juggling numeric and string data. The stacks are limited in
size - 112 decimal locations for S, 128 decimal locations for R, X, Y
and Z. If a stack overflows or underflows the running program is
stopped with an error message and the stack pointers are reset. Data
can be stored in memory, zero-page locations 100 to 137 octal are
reserved for user programs or for more storage, data blocks can be
allocated in 1KW increments. Kernel words are provided for accessing
block memory without having to specify absolute memory locations but
it's perfectly OK to read or write any memory location desired (with
the obvious implication that if incorrect the program will crash or the
build corrupted, there is no protection as that would limit
functionality). Variables are words that push a memory address for
storing data, multiple-location variables can be declared to store
arrays but the programmer must be careful not to write outside of the
declared range, it is safer and more efficient to use block memory if
many data locations are needed. Constants are words that push a
specified number, constants that push memory locations can be used like
variables, the main difference is a variable pushes an address of a
location within the variable word, whereas a constant merely pushes a
number.
Whether typed from the console or from an IPL package, items on the
command line consist of word names, numbers to push to the system
stack, or quoted strings to push to the string stack. The maximum
command line length is 76 characters (column 78 if the interactive ?
prompt is displayed). Comments are indicated by the ";" character and
are not entered into the input buffer. The <PTR word redirects an
IPL file attached to the paper-tape reader (PTR) into the console as if
it were typed (the ? prompt is suppressed while loading), the IPL file
should end with CONSOLE to restore normal interactive input.
Alternatively <MS can be used to redirect input from whatever driver
MS (mass storage) is assigned to, default is PTR but can be pointed to
other code to stream to memory, disk or other devices (input and output
can be redirected separately by writing subroutine addresses to certain
memory locations - see summary.txt for details). The extra.ipl package
defines a convenient LOAD word which is essentially <MS with a
message if input is coming from a subroutine besides PTR.
Items on the command line are processed from left to right using RPN
notation where operands appear before operators, precedence is
indicated by order rather than parenthesis. For example to calculate
2*(5+17) and display the results in decimal format enter...
? DECIMAL 2 5 17 ADD MUL PNUM OCTAL
44
Numbers can be interpreted or displayed in DECIMAL OCTAL or BINARY
format, there is no hex format. The above command line selects decimal
mode, pushes 2, 5 and
17 to the system stack, runs ADD which pops and adds 5 and 17 and
replaces with 22 decimal, runs MUL to multiply 2 and 22, replacing with
44, runs PNUM which pops and prints 44, then restores octal mode.
Typically you want to return the system to OCTAL format after using
other number systems to avoid mistakes as HP memory addresses and data
is almost always specified in octal. The DMPS word can be used to
display the current contents of the system stack, which should be empty
unless there is data being passed. DUP OVER SWAP ROT and DROP perform
basic stack manipulation - DUP pushes another copy of the top-most
item, OVER pushes a copy of the item below the top-most item, SWAP
exchanges the top two items, ROT exchanges the two items below the top
item, and DROP erases the top item. Same as with other stack-based
systems.
Memory (and variables) are accessed using GET and PUT, address
GET pushes the data at address, address data PUT writes data to address
- this is different from FORTH, the reason PUT was written like this
was so a memory pointer can be maintained on the stack then written to
using DUP [something that pushes data] PUT then INC or other words used
to adjust the pointer. For example...
? 100 777 PUT
? 100 GET PNUM
000777
Most words expect parameters to be on the stack(s) but some words
intended only for immediate mode expect the parameters to be after the
word name. This is both for convenience and if needed permits a
variable
number of parameters without having to know how many items are on
the stack. One word that requires the parameter after the command is
DEFINE, which is used to define new words
from other words. Here is a word that shows memory locations with the
starting address and number of locations to show on the stack...
? OCTAL DEFINE SHM
> #1 SWAP +DO
> DUP INDEX DEC ADD GET PNUM
> +LOOP DROP END
? 100 2 SHM
000777 000000
The 777 is from the previous PUT example. +DO/+LOOP are similar to
FOR/NEXT in BASIC (a >STEP word permits specifying increment and
direction), INDEX pushes the current index, DEC decrements it before
adding to the specified address. The END at the end tells DEFINE to
stop defining, note that while defining a word the prompt changes to
> rather than ?. If 0 is specified for the number of locations it
prints 65,536 locations instead, with 32,768 of them being indirect
references as memory locations with bit 15 set access the memory
specified by the lower 15 bits. If error-checking is needed then it
must be specifically coded for, for example...
? OCTAL DEFINE SHME
> OVER IF<0 "ADDRESS OUT OF RANGE" $PRINT DROP DROP ELSE
> OVER OVER DEC ADD IF<0 "LOCATIONS OUT OF RANGE" $PRINT DROP DROP ELSE
> DUP IFZ "NO LOCATIONS SPECIFIED" $PRINT DROP DROP ELSE
> #1 SWAP +DO DUP INDEX DEC ADD GET PNUM +LOOP DROP
> ENDIF
> ENDIF
> ENDIF END
? 177777 1 SHME
ADDRESS OUT OF RANGE
? 77777 2 SHME
LOCATIONS OUT OF RANGE
? 100 0 SHME
NO LOCATIONS SPECIFIED
? 100 1 SHME
000777
? DMPS
?
The DMPS at the end is to make sure I didn't mess up the drops
(which could be more efficient but coded for more clarity instead).
Conditional code execution is controlled using the IFZ, IFNZ, IF<0,
ELSE and ENDIF words, the IFx words pop the stack and act accordingly
(IF<0 is true if less-than-zero, or bit 15 set). If complex
conditions are needed then they need to be computed first to reduce to
a single item to test, for example to trigger if either the top item or
the next item is non-zero and not remove the items, use OVER OVER OR
IFZ ... ENDIF. Perhaps the hardest thing about programming in IPL (or
any other stack-based system) is keeping track with what's on the
stacks, if data is accidentally interpreted as an address then bad
things can happen. If memory writes are involved, it is best to reload
the
system if a word crashes - unknown memory locations may have been
corrupted causing unpredictable effects later so never save a build
after something bugged out. In a way programming in IPL is similar to
programming in machine code, have to be very careful to make sure the
intended memory is being written to. Or else. In return for being
careful, you get a system that can modify any location in memory
(including the system itself) without limitations, and doesn't need to
carry the bloat that would otherwise be needed to make safe. In fact it
would be impractical to make a safe PUT since variables depend on being
able to write to locations within that variable (which resides in the
dictionary), it would require a large amount of code to distinguish
between addresses that are legal variables from addresses that are
random dictionary locations, and would preclude using PUT to otherwise
alter the dictionary (such as changing data within words without having
to redefine the word and everything after it, some packages depend on
being able to do that to hide their internal words and variables).
HP-IPL/OS assumes that what is specified is what it should do, without
artificial limitations bloating the code and preventing potential uses.
If any word in the definition isn't already present in the
dictionary the system stops defining and prints an error message. The
WORDS word prints a list of currently defined words, since only the
first 4 characters and the length are recorded, many words will have
"xxxx" to indicate don't-care characters. The extra.ipl defines extra
words for manipulating the dictionary - FORGET wordname removes a word
and all words after it, RENAME wordname newname renames a word to a new
name, EXPLAIN wordname lists the contents of a word if it can (in a
format that can be re-entered to redefine), WHEREIS wordname shows
where a word is in memory, ERASE removes the last package loaded using
<PTR <MS LOAD etc.
Words containing other words are called "high-level" words, each
word in
the definition occupies one memory location, each literal number
requires 2 locations, and each string requires 3 locations plus one
location for every 2 characters in the string. Plus the 4 locations for
the word header, one location for the "ENSEC" address and a location
for the "NEXT" address to indicate the end of the word. Words can also
be "primitive", or coded directly in machine code, in this format the
location following the header is the address of the actual machine code
to run, and the machine code exits by making an indirect jump to a
zero-page location containing the NEXT address. It'd be quite tedious
having to relocate and manually poke primitive words, instead use the
CREATE word (in the create.ipl package, the non-extended version is in
smallcre.ipl). CREATE works similarly to DEFINE except it creates
primitive words from assembly source rather than definitions from words
and constants. The machine code syntax for CREATE code is similar
to ASMB/HPASM/etc except labels can be up to 6 characters long, and
multi-location instructions must be specified on multiple lines using
OCT to indicate the instruction parameters (which is handy since then
labels can be assigned to them). As with any HP21xx mini assembler,
memory references must be either to zero page or within the current 1KW
page or you'll get page errors. There is no ORG since it's adding the
word to the dictionary, so to avoid page errors make sure the current
end-of-dictionary (WORDS or EOD PNUM) is well before the next page
boundary, depending on how much assembly is used. If the IPL file
starts with say a 200 octal word assembly program then counting
overhead EOD should be at least 206 octal words before the next 1KW
page. In that case if EOD PNUM says say 34200 or 35550 then there's
room but if it says say 35575 then there isn't enough room (next
boundary=36000 and 36000-35575=203). Of course it all depends on the
amount of code being created and where the code appears in the package
- sometimes it just comes down to trial and error - if a package
doesn't go, FORGET the first word then load some other needed
high-level package or word first to push it past the boundary.
Here is an example CREATE word that writes data to a specified I/O
device, along with a variable to hold the slot address and a high-level
wrapper for using, written as a package file...
;this package permits outputting to a device specified
;by the OUTA variable, usage: data OUTPUT
"Creating SOUT" $PRINT CRLF
OCTAL CREATE SOUT
* output word to I/O slot
* usage: [word] [slotadr] SOUT
JSB ZSPOP,I pop slot address
AND C77 mask to 6 bits
STA 1 put in B register
LDA PA get address of patch list
JSB PSUBA,I call patch sub to modify addresses
JSB ZSPOP,I pop the data to write
P1 OTA 0 \
P2 STC 0,C | usual output code
P3 SFS 0 | 0's will be replaced with
JMP *-1 | the specified slot address
P4 CLC 0 /
JMP END exit word
PA DEF *+1 address of patch list
DEF P1
DEF P2
DEF P3
DEF P4
OCT 0
C77 OCT 77
END
"Declaring OUTA and setting to 11" $PRINT CRLF
VARIABLE OUTA
OUTA 11 PUT
"Defining OUTPUT" $PRINT CRLF
DEFINE OUTPUT
OUTA GET SOUT END
"Done" $PRINT CRLF
CONSOLE
CREATE requires at least two data blocks for normal operation.
CREATE includes many pre-defined symbols for internal HP-IPL/OS
functions - JSB ZSPOP,I calls the code to pop the stack to the A
register, JSB ZSPSH,I pushes A to the stack, JSB PSUBA,I calls the
slot-patching code with the A register containing the address of the
patch list (which must end with 0) and the B register containing the
I/O slot address to merge into the I/O code. Usually real drivers do
this only once using an "auto-start" word (a word that begins with the
! character) to avoid patching and slowing down each transfer. Usually
the slot address for the device is placed in zero-page memory that
isn't used for other things, locations in the range of 140-147 should
be safe to use. Use CREATE /K wordname to preserve the existing symbol
table from a previous CREATE so that the code can access locations in
the previous word, and CREATE /L wordname to use all available data
blocks for the symbol and fixup tables. This is an example of why
sometimes it's really nice to be able to parse the command line,
VARIABLE also accepts an optional number after the variable name to
specify how many locations to reserve, which can come in handy if only
a few more locations are needed to push EOD past a 1KW boundary. Some
packages such as fed.ipl define temp words to calculate remaining room
and define pad variables as needed to avoid page errors.
The above example can be copied to a file (say "output.ipl"), then
loaded into a build containing CREATE (such as the hposutil.abs build)
by attaching the file to PTR and entering LOAD (or <PTR). Under
simulation it looks like...
HP-IPL/OS UTIL 1.54
? [control-e pressed to halt]
Simulation stopped, P: 02226 (JMP 2225)
sim>attach -e ptr output.ipl
sim>c
LOAD
Creating SOUT
045612 :
045612 :keep the changes
045612 :
045613 :
045614 :
045615 :
045616 :
045617 :
045620 :
045621 :
045622 :
045623 :
045624 :
045625 :
045626 :
045627 :
045630 :
045631 :
045632 :
045633 :
045634 :
045635 :
Fixing...
Declaring OUTA and setting to 11
Defining OUTPUT
Done
? 101 OUTPUT 102 OUTPUT CRLF
AB
To save a new build after loading a new package...
? [control-e pressed to halt]
Simulation stopped, P: 02226 (JMP 2225)
sim>attach ptp custom.abs
PTP: creating new file
sim>c
SYSALL
Writing...................
? [control-e pressed to halt]
sim>exit
Note that IPL files must have CR/LF (dos) line
ends, if editing files under Linux use a conversion utility like
unix2dos or flip -m, or use
an editor like Scite that permits specifying and preserves line end
format. HP-IPL/OS ignores line-feed characters directed into the
console and processes only the CR which isn't present in unix-format
text files. A similar note for terminal settings (especially if set for
compatibility with BCS programs) - normally the terminal should only
send CR when enter is pressed, no problems at the console if CRLF enter
is used but it will mess up anything using $IN to input string lines.
Add the nolf.ipl package if using HP-IPL/OS with CRLF terminal settings.
The kernel implements the following functions (among things)...
Drivers for the console, papertape reader and papertape punch devices
Subroutines for obtaining input lines, using stacks, patching device
addresses and other low-level stuff
A fixed set of zero-page memory locations other programs can use to
access internal functions
An indirectly-threaded interpreter and subroutines for pushing literals
and executing words
A core set of primitive machine-coded dictionary words for performing
essential
operations
Additional mixed and high-level words that make use of the core
primitive words
Infrastructure for redirecting console or MS output and input to and
from subroutines
Infrastructure for storing data in a user-specified number of 1KW data
blocks
Infrastructure for making new words from existing words or raw machine
code
The input line buffer can hold a maximum of 76 characters (not
counting the CR), the only editing options are backspace (ascii 8 or
ascii 127) to erase the last character, and escape (ascii 27) to cancel
the input line and start a new line. Comments are indicated by an
unquoted ";" character. Whitespace doesn't matter so long as at least
one space separates the elements. Do not
enter tabs, cursor keys, unicode text or any other non-plain-text
characters other than CR BS or Esc. Almost all existing HP-IPL/OS words
are in uppercase so usually HP-IPL/OS is run with the caps lock on,
however lowercase or mixed case word names
can be used as needed.
The kernel doesn't provide much in the way of operator convenience
but provides all the essential functions upon which to build more
complex systems. Typical builds almost always include extra.ipl (or one
of its derivitives) to provide dictionary manipulation tools, memory
dumping and saving and other often-used words that didn't make it into
the kernel itself. All but the smallest of builds usually include the
CREATE assembler for making words that need to be written in machine
code, such as device drivers and accessing features of the later
machines. Machine code can also be assembled to a fixed location in
upper memory above HP-IPL/OS' dictionary and data blocks and called
from programs or jumped to directly. The kernel also provides functions
for creating primitive words from raw machine code, these functions are
used by CREATE but can also be used to manually enter simple
machine-coded words provided any branches or memory references are
properly calculated to account for the position in the dictionary.
Buggy or no longer needed
words
need to be removed before entering more words or dictionary space will
be needlessly consumed. The command that's normally used to remove one
or more words (FORGET wordname) is part of the extra package - if
working with just the kernel, words can be deleted manually by entering
"wordname" $DEFADR 4 SUB 0 PUT but this can be risky, the system can be
corrupted if "wordname" does not exist. A safer way to remove words
using just the kernel is to enter this very simple $FORGET word that
has no effect if a word doesn't exist...
DEFINE $FORGET
$DEFADR DUP IFZ DROP ELSE 4 SUB #0 PUT ENDIF END
Don't worry if the code makes no sense yet, all the words used in
this definition will be explained in the following docs. The dictionary
is a sequential chain of words where each word links to the next word
until a zero is encountered, so to remove a word a zero is placed at
the beginning of a word header (the 4 SUB part), effectively removing
the word and all words that follow it. This simple word remover has no
protections and will happily remove kernel words if you tell it to
(which can be handy if you know what you're doing). Here's what it
looks like entering the $FORGET definition (prompts and all) and using
it to remove a TEST word defined for the occasion...
? DEFINE $FORGET
> $DEFADR DUP IFZ DROP ELSE 4 SUB #0 PUT ENDIF END
? DEFINE TEST
> "TEST" $PRINT END
? TEST
TEST
? "TEST" $FORGET
? TEST
TEST NOT FOUND
?
The following is a list of the more commonly-used kernel words with
explanations and usage examples, refer to summary.txt for a complete
list with brief explanations. The SimH HP2100 simulator was used to
produce most of the examples, operation on real hardware is similar
except the simulator commands need to be translated into actual
hardware actions such as attaching files to the papertape reader (PTR)
or papertape punch (PTP). The examples generally show what is actually
displayed - the ? and > are prompts generated by the system, don't
type them unless a > is actually used in the word, such as a CASE
structure that's testing for greater-than. When [some parameter] is
written in an explanation it means a literal number, don't type the
brackets, just the number representing the parameter... i.e. [from]
[to] DUMP means if you want to dump locations 100 to 107 use: 100 107
DUMP
WORDS lists the words that are presently in the dictionary, along
with the address of EOD (end of dictionary) and the amount of free
memory. EOD pushes just the end of dictionary address, EOD PNUM prints
it.
Here's what the kernel dictionary looks like after booting the
hpiplos1.abs binary...
HP-IPL/OS 8K V1.6
? WORDS
DO +DO INDEx +LOOx >STEx IFNZ IFZ IF<0 ENDIx ELSE UNTIx WHILx CASE = < > <=
>= <> DEFAxxx ENDCxxx EXECxxx WBOOx AND OR XOR ADD SUB INC DEC NOT 2CPL DUP
DROP OVER ROT SWAP GET PUT PNUM CRLF DECIxxx OCTAx BINAxx RADIx SP>S SB>S
XP>S XB>S YP>S YB>S ZP>S ZB>S END EOD DEFIxx DMPS S>SR SR>S PCHR PWRD CHRIx
S>X X>S S>Y Y>S S>Z Z>S MUL ASL ASR ROL ROR DIV RUN X>>Y X>>Z Y>>X Z>>X
$PRIxx $SWAx $CPY $DUP $DROx $LEN $ADR $XTExx $PUT $GET $CRExxx $STR $HEAx
$APPxxx $TAIx $IN $CAT $VAL <>COx >PTP <PTR MSPAxxx MSBOxx MSBIx MSWOxx
MSWIx MS$Oxx MS$Ix MSCRxx >MS <MS MS_Sxxx MS_Rxxxxxx CONSxxx #0 #1 @TL @TB1
@TB2 @ANVxx @LLP @ENSxx @CLH @LITxxxx @STRxxx @RTSxx @DIC @USR @BLK @END
@DIPxx RND TOKEx SDIC +IRQ -IRQ +AUTx -AUTx ADDCxxx INBLxxx OUTBxxxx GETIx
SETIx GETOx SETOx $DEFxxx HEADxxx WORDx ADDHxxxxx ADDHxxxxxx FIXLxxxx
ADDMxxxx VARIxxxx CONSxxxx ALLOxxxx ZEROxxxxx BPUT BGET
EOD=011534 FREE=002243
?
Only the first 4 characters of word names and the length are significant, the "xxx" characters mean "don't care". To HP-IPL/OS, TEST1 and TEST2 mean the same thing (TESTx). In the case of duplicates the last match is used, making it possible to add to or redefine existing words without having to remove them. Words which control the execution of blocks of code are disabled when at the command line level, these are placed at the beginning of the dictionary (DO to ENDCASE). Words that begin with @ push addresses of various subroutines and zero page variables. Words that begin with # are constants (0 and 1 are frequently used so using #0 or #1 saves memory). Words that begin with $ implement string functions. Words that begin with < or > usually (but not always) redirect the console from or to devices (also used for CASE conditions, and used in some packages to indicate general transfers). Words that contain a single > in the name usually transfer a single 16-bit value, words containing >> usually transfer strings. These are just (very) loose naming conventions, words can be named as needed so long as they're unique as far as HP-IPL/OS is concerned (unless purposely replacing an existing word), don't contain a quote character, don't consist entirely of numbers, and not be END since that's reserved for ending definitions. The exception is words that start with ! automatically run (autostart) when HP-IPL/OS starts up. All autostart words are run in the order they appear in the dictionary before the HP-IPL/OS version message is displayed.
PNUM
PCHR PWRD
$PRINT CRLF CHRIN $IN
Words for console input and output. PNUM pops and prints the number
at the
top of the S stack (the "stack") plus a space, PCHR pops and prints the
ascii character of the number at the top of the stack, PWRD pops and
prints a double-character, $PRINT pops and prints the string at the top
of the X stack (the "string stack"), CRLF outputs a CR/LF sequence to
start a new line, CHRIN obtains a single character and pushes it to the
stack, $IN obtains a line of text until CR (Enter) is received and
pushes the line to the string stack without the CR.
? 77 "ENTER:" $PRINT $IN $PRINT 40 PCHR PNUM
ENTER:SOMETHING
SOMETHING 000077
? "PRESS:" $PRINT CHRIN CRLF PNUM
PRESS:X
000130
?
Numerical string conversions, $VAL pops the string stack pushes the
value of the string to the stack, $STR pops the stack and pushes a
string representation to the string stack (without a trailing space).
? "123" $VAL PNUM "NEXT" $PRINT CRLF 456 $STR $PRINT "NEXT" $PRINT
000123 NEXT
000456NEXT
?
DEFINE [wordname] defines a new word from literal numbers and
strings and other words.
When defining from the ? prompt the prompt changes to > instead,
carefully enter each line (up to 76 characters per line). If any of the
words on the line are not present in the dictionary it prints the first
4 characters of the word then NOT FOUND and the definition is cancelled
without changing the dictionary. End the definition with END, anything
after the end is interpreted as a command outside of the definition.
DEFINE must only be used at the command-line level, never from within a
definition.
? DEFINE TEST
> "TEST" $PRINT END TEST
TEST
?
Note that there is no built-in "easy" command way to remove words
using just the kernel, normally this is done using FORGET from the
extra.ipl package. The TEST word can be manually removed by entering
"TEST" $DEFADR 4 SUB 0 PUT but be very careful, with this sequence if
the quoted word does not exist the effect is unpredictable and could
potentially corrupt the system.
VARIABLE [wordname] defines a word that when executed pushes a
memory address which can be used to store a value. VARIABLE [wordname]
[count] defines a word containing multiple locations that can be used
for an array. CONSTANT [wordname] [value] defines a word that pushes
the value, if the value corresponds to a constant memory location then
it can be used like a variable. VARIABLE and CONSTANT must only be used
at the command-line level, never from within a definition. PUT and GET
are used to store or retrieve values to and from memory. [address]
[value] PUT stores value into addess (address and value are removed
from the stack), [address] GET pops the address and replaces it with
the value at that address.
? OCTAL
? CONSTANT MYMEM 110
? VARIABLE V1
? MYMEM PNUM
000110
? V1 PNUM
011727
? MYMEM 222 PUT
? V1 333 PUT
? MYMEM GET PNUM V1 GET PNUM
000222 000333
?
Use extreme care when using PUT, there is no protection whatsoever
(nor should there be since that would limit its usefulness). Perhaps a
safer way to implement variables should have been implemented, but that
would slow down and increase the size of the system. If you feel you
cannot be trusted with the power of PUT then perhaps define a VP
(variable put) word to use instead...
OCTAL DEFINE VP
OVER CASE ;case branch on address value
< 100 #1 ;error if less than 100
> 147 ;if more than 147 check to make sure a variable...
OVER DUP DEC GET SUB IFNZ #1 ;error if (adr-1)<>adr
ELSE OVER DEC DEC GET 124321 SUB IFNZ #1 ;error if not var
ELSE #0 ENDIF ENDIF ;appears to be a var, no error
DEFAULT #0 ;no error if from 100-147
ENDCASE IFNZ " OUT OF RANGE " $PRINT WBOOT ;warmboot
ELSE PUT ENDIF ;if in allowed memory do the PUT
END
? OCTAL DEFINE VP
> OVER CASE < 100 #1 > 147 OVER DUP DEC GET SUB IFNZ #1 ELSE
> OVER DEC DEC GET 124321 SUB IFNZ #1 ELSE #0 ENDIF ENDIF
> DEFAULT #0 ENDCASE IFNZ " OUT OF RANGE " $PRINT WBOOT
> ELSE PUT ENDIF END
? MYMEM 377 VP MYMEM GET PNUM
000377
? V1 1777 VP V1 GET PNUM
001777
? 147 555 VP 147 GET PNUM
000555
? 17000 111 VP 17000 GET PNUM
OUT OF RANGE
?
This optional safety-net only works for simple variables and the
"user" zero-page locations. It won't work for multi-location array
variables
or for system variables (the @ words). The words used in this example
will be explained below.
These control the number system used for numbers when entered at the
command-line level and when printing numbers and using numerical string
conversions. BINARY selects binary representation, DECIMAL selects
decimal representation, OCTAL selects the usual octal representation.
When in decimal mode values > 32767 are displayed or converted as
negative numbers without leading zeros, binary representation is always
converted to 16 characters and octal representation is always converted
to 6 digits. RADIX pushes the current number system to the stack which
is occasionally used to restore the current number system upon exit.
When using other number systems it is recommended that the system be
returned to OCTAL to avoid mistakes when specifying memory addresses
and values which almost always assume octal.
? OCTAL 377 DECIMAL PNUM OCTAL
255
? RADIX PNUM
000010
? RADIX DECIMAL PNUM OCTAL
8
?
Note - when defining words containing literal octal values OCTAL
must be used outside of the definition. OCTAL etc do not affect the
interpretation of literal numbers when used inside of word definitions,
only how numbers are printed and converted. Often word definitions
begin with OCTAL DEFINE wordname to ensure that the proper number
system is selected.
These words are used to juggle words on the stack. DUP pushes a copy
of the value at the top of the stack, OVER pushes a copy of the value
beneath the top value (pushes over the top value), SWAP exchanges the
two top-most stack values, ROT exchanges the two values beneath the top
of the stack, and DROP pops and discards the value at the top of the
stack. DMPS is a tool that displays the stack without affecting its
contents.
? 1 2 3 DMPS
000001
000002
000003
? SWAP DMPS
000001
000003
000002
? ROT DMPS
000003
000001
000002
? OVER DMPS
000003
000001
000002
000001
? DROP DROP DROP DUP DMPS
000003
000003
? DROP DROP
These transfer values between stacks, i.e. S>X pops a value from
the S (main) stack and pushes it to the X stack, Z>S pops a value
from the Z stack and pushes it to the main stack, etc. Very useful when
values need to be popped and saved to get to deeper values, then
restored. Also useful for holding exit flags, pointers, etc to avoid
cluttering up the main stack.
? 101 S>Z 102 S>Z 100 DMPS
000100
? Z>S Z>S DMPS
000100
000102
000101
? DROP DROP DROP
$DUP
$SWAP $DROP
X>>Y Y>>X X>>Z Z>>X
$CPY $CAT
These juggle strings. $DUP makes a copy of the top-most string on
the string (X) stack, $SWAP exchanges the two top-most strings, $DROP
removes the top-most string, X>>Y moves a string to the Y stack,
Y>>X moves a string from the Y stack back to the string stack,
X>>Z and Z>>X transfer strings to and from the Z stack.
$CPY makes a copy of the top-most string and pushes it to the Y stack
(same as $DUP X>>Y). $CAT combines the two top-most strings into
a single string.
Generally the X stack should be reserved for strings since that's
where all string functions take their parameter(s) from. The Y and Z
stacks can be used for strings and numbers but care must be taken not
to get them mixed up and use Y>>X etc when Y holds a numeric
value, usually I use Y for strings and Z for numeric values to avoid
confusing them.
? "HELLO " "WORLD" X>>Y $PRINT Y>>X $PRINT
HELLO WORLD
? "WORLD" "HELLO " $SWAP $CAT $PRINT
HELLO WORLD
?
These permit adding characters to and removing characters from
strings. $APPEND pops an ascii value from the number (S) stack and
appends the character to the string on the string (X) stack (which must
exist, use "" to push an empty string). $HEAD removes the first
character of a non-empty string and pushes its ascii value to the
number stack. $TAIL removes the last character of a non-empty string
and pushes its ascii value to the number stack. $LEN pushes the length
of a string without affecting the string.
? "ABCDE" $HEAD PNUM $TAIL PNUM $PRINT
000101 000105 BCD
? "" 101 $APPEND 102 $APPEND 103 $APPEND $PRINT
ABC
? "12345" $LEN PNUM $PRINT
000005 12345
?
[number of chars] [ascii value] $CREATE creates a string containing
repeating characters. [position] $GET pushes the ascii value of a
character within a string (numbering starts at 0). [position] [ascii
value] $PUT replaces a character within a string. $ADR pushes the
address of the first word of a string on the string stack.
? OCTAL "AB" 5 40 $CREATE $CAT "YZ" $CAT $DUP $PRINT
AB YZ
? 2 103 $PUT $DUP $PRINT
ABC YZ
? 10 $GET PCHR
Z
? $ADR GET PWRD
AB
? $DROP
These
words implement flow control, the IFxx words pop the stack then if zero
(IFZ), not zero (IFNZ) or has bit 15 set (IF<0) then the code
following the IF word is executed until the corresponding ELSE or ENDIF
is encountered. If the test is not true then execution continues after
the corresponding ELSE. These can be nested to any level.
? DEFINE IFTEST
> DUP IFZ "ZERO" DROP ELSE
> IF<0 "LESS THAN ZERO" ELSE
> "NOT ZERO"
> ENDIF
> ENDIF
> $PRINT END
? 0 IFTEST
ZERO
? 1 IFTEST
NOT ZERO
? 177777 IFTEST
LESS THAN ZERO
?
To do traditional comparisons the IFxx word is preceeded by an
expression that results in a single value to test, for example "if V1
< V2 then ... endif" is coded as V1 GET V2 GET SUB IF<0 ... ENDIF
(V1 and V2 are variables). It's ok for there to be no code between
IF<0 and ELSE for computing <= and >= situations.
CASE
= <
> <= >= <> DEFAULT ENDCASE
These implement another kind of flow control and may be more
suitable for more complex conditions. CASE pops a value from the stack
and directs control to one of the branches defined by = < > <=
>= <> or DEFAULT. Each of the condition codes must be followed
by a literal number, a variable or a constant, followed by the code for
the branch. Only the code for the first true condition runs, even if
more than one condition is true. Code following DEFAULT (if present)
runs if none of the conditions are true. Code in an execution branch
skips to past the ENDCASE if another condition tag or DEFAULT is
encountered. CASE structures can be nested to any depth.
Using CASE, "if V1 < V2 then ... endif" translates to V1 GET CASE
< V2 ... ENDCASE, a bit more reasonable than having to translate
conditions into a subtraction formulaes, however the second parameter
has to be a literal, variable or constant, not another stack value.
? VARIABLE A
? VARIABLE B
? DEFINE ?AB
> A GET CASE
> > B "A > B" $PRINT
> < B "A < B" $PRINT
> DEFAULT "A = B" $PRINT
> ENDCASE END
? A 1 PUT
? B 2 PUT
? ?AB
A < B
? A 10 PUT ?AB
A > B
? B 10 PUT ?AB
A = B
? A 177777 PUT B 0 PUT ?AB
A < B
?
DO pushes the next location to the return stack to permit repeating
a block of code. WHILE pops the main stack and if the value is
non-zero, continues interpreting from the location on the return stack,
repeating code following the corresponding DO, otherwise removes the
loop address from the return stack to continue. UNTIL is similar but
loops until a non-zero value is on the stack. DO loops can be nested a
reasonable number of times depending on the number of addresses
currently on the the return stack, which is also used when a word runs
another word (the return stack can hold a maximum of 128 addresses, if
exceeded an "R stack overflow" error occurs).
? DEFINE DOTEST
> DO
> "ENTER SOMETHING:" $PRINT $IN
> $LEN IFZ $DROP #1 ELSE $PRINT CRLF #0 ENDIF
> UNTIL END
? DOTEST
ENTER SOMETHING:THIS
THIS
ENTER SOMETHING:THAT
THAT
ENTER SOMETHING:
? DEFINE DOWTEST
> "ENTER SOMETHING:" $PRINT $IN
> $LEN IFZ $DROP ELSE
> DO
> $TAIL PCHR $LEN
> WHILE $DROP
> ENDIF END
? DOWTEST
ENTER SOMETHING:SOMETHING
GNIHTEMOS
?
+DO and +LOOP iterate a block of code a certain number of times
while providing an index if the current iteration value is needed.
[start] [end] +DO pushes the next location to the return stack, pops
the end index value from the main stack and pushes it to the return
stack, and pops the start index value from the main stack and pushes it
to the return stack. +LOOP (by default) increments the value at the top
of the return stack, compares it to the end value, and if not equal
loops back to rerun the code after the corresponding +DO. INDEX pushes
the current index value. +DO +LOOP loops can be nested but not as deep
because each level consumes 3 locations on the return stack. >STEP
pops the main stack and stores it in an internal location used by +LOOP
to determine what to add to the index to permit looping backwards,
however the result must eventually equal the specified end value or the
the code will loop forever, +LOOP always resets the step value back to
1 after terminating a loop.
? DEFINE SIMPLE+LOOP
> 2 7 +DO INDEX PNUM +LOOP END
? SIMPLE+LOOP
000002 000003 000004 000005 000006 000007
? DEFINE FANCY+LOOP
> 7 2 +DO INDEX PNUM -1 >STEP +LOOP END
? FANCY+LOOP
000007 000006 000005 000004 000003 000002
?
Be careful when using >STEP as it affects every other word
containing +DO +LOOP. If calling any sort of string function or other
complex code from within a loop using an alternate step value, use #1
>STEP right after the +DO
so all other code will see the normal increment, then use >STEP for
an alternate step value right before the +LOOP that uses it. Sometimes
it's easier to code a backwards loop as a value on the stack with a
regular DO loop (depending what else needs to be accessed from the
stack). Another way to reverse a loop without actually looping
backwards is to invert the index value, something like #0 7 +DO 7 INDEX
SUB [some code using value on stack counting from 7 to 0] DROP +LOOP is
safer. >STEP is for cases where it really makes it easier, such as
when numerous things need to use INDEX.
These words pop two values, perform an operation and push the
results.
AND, OR and XOR perform bit-wise logic. ADD, SUB, MUL, DIV perform
16-bit mathematical operations, the values are popped in an order that
makes sense when reading from left to right (i.e. 3 2 SUB pushes 1 and
4 2 DIV pushes 2). There
is no built-in provision for detecting overflow, if required overflow
can be detected by examining bit 15 of the operands and the result.
? DECIMAL
? 7 11 22 ADD MUL 4 SUB PNUM
227
? 16 2 DIV PNUM 15 2 DIV PNUM
8 7
? OCTAL DEFINE OVADD
> OVER OVER S>Z S>Z ADD DUP 100000 AND
> Z>S Z>S OVER OVER S>Z S>Z AND 100000 AND IFNZ DROP " OVF " $PRINT ELSE
> Z>S Z>S OVER OVER S>Z S>Z XOR 100000 AND IFZ DROP ELSE IFZ " OVL "
> $PRINT ENDIF ENDIF ENDIF Z>S DROP Z>S DROP END
? 2 2 OVADD PNUM DMPS
000004
? 100000 100001 OVADD PNUM DMPS
OVF 000001
? 100000 77777 OVADD PNUM DMPS
177777
? 177777 2 OVADD PNUM DMPS
OVL 000001
?
INC
DEC NOT
2CPL ASL ASR ROL ROR
These words replace the top word of the stack with an altered value.
INC and DEC increment and decrement (tos=tos+1 and tos=tos-1). NOT
inverts the bits, all 0's become 1's and all 1's become 0's. 2CPL does
a two's complement operation (same as NOT INC), when doing signed math
this is the same as multiplying by -1. ASL and ASR perform arithmetic
shift left and right, the vacated bit is replaced with a 0 bit and the
bit that is shifted out is discarded. ROL and ROR perform rotate left
and rotate right, no bits are lost, just moved.
? DECIMAL 222 INC DUP PNUM
223
? DEC DUP PNUM
222
? BINARY DUP PNUM
0000000011011110
? NOT DUP PNUM
1111111100100001
? ASL DUP PNUM
1111111001000010
? ASR DUP PNUM
0111111100100001
? ROR DUP PNUM
1011111110010000
? ROL DUP PNUM
0111111100100001
? DECIMAL DUP PNUM
32545
? 2CPL PNUM
-32545
? OCTAL
These determine how the system memory is used. @END is a
psuedo-variable that defines the last location in memory used by
HP-IPL/OS (although there isn't much enforcement of this except when
using the block access words), normally this should be set to the
address of a 1KW boundary - 1. In the stock kernel @END is set to
17777, if using the kernel on a real 8KW machine this is into the
bootloader so make sure the 64-word bootloader program is
write-protected, or take care not to get into that memory (I read that
it is write-protected). The @BLK psuedo-variable determines the end of
the usable dictionary space and the start of block data memory,
normally it is set to an even 1KW boundary. In the stock kernel @BLK is
set to 14000. EOD pushes the end of the currently defined dictionary,
the FREE= display is calculated as one less than the start of block
memory minus EOD (there has to be room for the zero that terminates the
dictionary). ALLOCATE pops the stack and allocates that many 1KW data
blocks, setting @BLK to the beginning of block memory. If not already
on a 1KW boundary then @BLK is set to the next lowest boundary, if @END
is set to the end of physical memory then to protect the boot area
while maximizing memory set @END after using ALLOCATE, this shorts the
last block a bit but should be fine. For that matter on an 8KW machine
it's probably best to use internal.ipl to totally eliminate the block
support words, since no current apps that'll work in that little memory
use block storage. There is no fundamental reason why blocks must begin
on a 1KW boundary, but some applications (such as XDOS) assume that it
does.
@END and ALLOCATE is how the kernel is configured to support larger
memory sizes...
To configure for 16KW, 2 data blocks and 2KW high memory enter: @END
33777 PUT 2 ALLOCATE
To configure for 32KW, 4 data blocks and 4KW high memory enter: @END
67777 PUT 4 ALLOCATE
To configure for 8KW with no blocks and no high memory enter: @END
17677 PUT @BLK 17700 PUT
BPUT and BGET provide a controlled way to access block memory.
[value] [offset] [block] BPUT writes value to an offset within a block,
[offset] [block] BGET pushes the value at an offset within a block.
Block numbers and offsets begin at 0. [block] ZEROBLOCK zeroes the
specified block but note that if using MS (mass storage) vectors they
must be reset after using ZEROBLOCK.
? 123 456 0 BPUT
? 456 0 BGET PNUM
000123
? 0 ZEROBLOCK 456 0 BGET PNUM
000000
? 444 0 1 BPUT
? 444 0 2 BPUT
ERROR
? 444 2222 1 BPUT
ERROR
? MSPAPER ;restore MS after using ZEROBLOCK
These are used to redirect the console from and to papertape.
<PTR causes console input to come from the papertape reader, while
redirected the ? and > prompts are suppressed. The redirected stream
must end with <>CON or CONSOLE to restore normal console
operation, CONSOLE is the same as <>CON except it also resets the
MS vectors back to PTR/PTP. Redirected IPL packages *must* end with a
line containing CONSOLE, <>CON, or some other word that undoes
the redirection, otherwise the load will not be terminated (to recover
halt and execute from location 2). Packages that
need to restore the console temporarily to get configuration
information use <>CON so that the load can continue if loading
from something besides papertape. >PTP causes all text output
(except for the ? prompts) to go to the papertape punch, until
<>CON or CONSOLE is used.
HP-IPL/OS 8K V1.6
?
Simulation stopped, P: 02225 (SFS 11)
sim>attach -e ptr ../ipl/internal.ipl
sim>c
? <PTR
Loading configurator
Press the appropriate key...
1) Stock 8K config with 2 blocks
2) Remove blocks and block support
3) Remove blocks and constants/variables
> 2
Loading $DEFADR
Loading HEADER$
Loading WORDS
Loading ADDHEADER
Loading ADDHEADER$
Loading FIXLINKS
Loading ADDMLVAR
Loading VARIABLE
Loading CONSTANT
Done
?
Simulation stopped, P: 02226 (JMP 2225)
sim>attach -e ptr ../ipl/8kextra2.ipl
sim>c
? <PTR
Protecting boot block...
Loading FORGET
Loading PDEF
Loading EXPLAIN
Loading $EQUAL
Loading $SLICE
Loading $TRIM
Loading RENAME
Loading WHEREIS
Loading DUMP
Loading ABSOUT
Loading PTZERO
Loading SYSGEN
Done
? WORDS
DO +DO INDEx +LOOx >STEx IFNZ IFZ IF<0 ENDIx ELSE UNTIx WHILx CASE = < > <=
>= <> DEFAxxx ENDCxxx EXECxxx WBOOx AND OR XOR ADD SUB INC DEC NOT 2CPL DUP
DROP OVER ROT SWAP GET PUT PNUM CRLF DECIxxx OCTAx BINAxx RADIx SP>S SB>S
XP>S XB>S YP>S YB>S ZP>S ZB>S END EOD DEFIxx DMPS S>SR SR>S PCHR PWRD CHRIx
S>X X>S S>Y Y>S S>Z Z>S MUL ASL ASR ROL ROR DIV RUN X>>Y X>>Z Y>>X Z>>X
$PRIxx $SWAx $CPY $DUP $DROx $LEN $ADR $XTExx $PUT $GET $CRExxx $STR $HEAx
$APPxxx $TAIx $IN $CAT $VAL <>COx >PTP <PTR MSPAxxx MSBOxx MSBIx MSWOxx
MSWIx MS$Oxx MS$Ix MSCRxx >MS <MS MS_Sxxx MS_Rxxxxxx CONSxxx #0 #1 @TL @TB1
@TB2 @ANVxx @LLP @ENSxx @CLH @LITxxxx @STRxxx @RTSxx @DIC @USR @BLK @END
@DIPxx RND TOKEx SDIC +IRQ -IRQ +AUTx -AUTx ADDCxxx $DEFxxx HEADxxx WORDx
ADDHxxxxx ADDHxxxxxx FIXLxxxx ADDMxxxx VARIxxxx CONSxxxx FORGxx PDEF
EXPLxxx $EQUxx $SLIxx $TRIx RENAxx WHERxxx DUMP ABSOxx PTZExx SYSGxx
EOD=012310 FREE=005367
?
HP-IPL/OS implements a simple but versatile system that lets
anything that uses the MS (mass storage) words for input and output to
be redirected to any device capable of streaming data. In a nutshell,
location 350 (ZMINP or just MS input) is a pointer to a subroutine for
inputting data, and location 351 (ZMOUT or MS output) is a pointer to a
subroutine for outputting data. By default these locations are set to
the PTR and PTP drivers but other drivers can provide words that
redirect MS input and/or MS output to their own subroutines. What it
means is I can reuse things written long ago for papertape on new
devices like a thumbdrive with no code changes, all that's needed is a
stream driver. MS input and output streams are always bytes,
appropriate for byte-based things like papertape but for 16-bit things
like memory and disk blocks, the drivers must pack and unpack the bytes
and maintain a flag so it can restart a stream with the correct byte.
If streaming to a disk then the driver (or firmware) needs to detect
when the buffer is full, write it to a disk block, then start
collecting data for the next block. MS streams don't have an inherent
end-of-file indication other than the formatting of the data itself
(for example IPL files end with CONSOLE and ABS binaries end with
zeroes). MS drivers can provide an EOF indication if they wish, a
reasonable approach is if a file is read past the end, return a zero
and set an error code to be returned by the driver's status function.
MSPAPER sets MS input and output to the PTR and PTP devices. <MS
redirects data from the MS input device into the console, when MS input
is PTR then <MS has the same effect as <PTR. >MS redirects
console output to the MS output device, when MS output is PTP then
>MS has the same effect as >PTR. The <MS word is often used to
load IPL files from disk, where MS input is set to get data from an
input file stream driver or from alternate memory containing the IPL
code to load.
INBLOCK
OUTBLOCK GETIP SETIP GETOP SETOP
These are MS drivers that connect to data blocks allocated by
ALLOCATE. [block] INBLOCK sets MS input to the start of the specified
block. [block] OUTBLOCK sets MS output to the start of the specified
block. GETIP pushes the current input byte pointer (where the next byte
will be read from), [byte offset] SETIP sets the input byte pointer
(where the next byte will be written to). GETOP and SETOP do the same
for the output byte pointer.
? 0 OUTBLOCK
? "HELLO BLOCK" MS$OUT MSCRLF
? "LINE TWO" MS$OUT MSCRLF
? 0 INBLOCK
? MS$IN $PRINT
HELLO BLOCK
? MS$IN $PRINT
LINE TWO
? 4 SETIP MSBIN PCHR
O
? GETIP PNUM
000005
? 1 SETOP "A" MS$OUT 0 SETIP MS$IN $PRINT
HALLO BLOCK
? MSPAPER
MS_SAVE saves the current MS input and output vectors to internal
memory. MS_RESTORE restores the previous MS input and output vectors.
These can be used to temporarily save the current settings then restore
the settings afterwards, but there is only one pair of temp variables
so they cannot be nested, use sparingly. Primary uses are so words used
at the console that use MS for their own purposes can save and restore
the MS settings, and to make up for a side-effect of ZEROBLOCK, which
leaves MS output set to a memory block (but if ZEROBLOCK saved/restored
itself it would make the word about 7 locations larger and every
location counts, if it made use of MS_SAVE/RESTORE then it would
disrupt other code that saves/restores upon entry/exit, best to leave
it alone - sometimes the compact way is a bit odd).
The $DEFADR word pops a string containing a word name, then pushes
the word's entry address, also called the word address (WA). If the
word isn't in the dictionary it pushes a 0. For high-level IPL words WA
contains the address
of the "enter secondary" code which starts the threaded interpreter on
another branch, ending when the address for "return from secondary" is
encountered. For low-level machine-coded words, WA contains the
actual address of the code to run, which exits by making an indirect
jump to the "next" code. The word's header is the 4 locations before WA
- length of the word's name, the first 2 characters, the next 2
characters, then a link to the next header. If the length is 0 then the
end of the dictionary has been reached.
This is a rather low-level function but can be very useful for
certain things. Since it indicates where a word is, it provides a means
to remove words (the only way using only the kernel), make
self-deleting words (handy for packages that need to run a temporary
configurator or check EOD to make sure machine code won't cross a 1KW
boundary), patch constants or even change words within words, alter the
link so that an application can hide its variables and subroutines, and
execute other words regardless of where (or if) they appear in the
dictionary.
? DEFINE TEMP
> "TEMP" $PRINT END
? "TEMP" $DEFADR PNUM
011540
? "TEMP" $DEFADR 4 SUB 0 PUT ;REMOVES TEMP AND ALL AFTER
? "TEMP" $DEFADR PNUM
000000
? DEFINE APP
> DEC ;WILL REPLACE WITH WA OF MAIN WORD
> END
? VARIABLE V1
? VARIABLE V2
? DEFINE SUB
> "IN THE SUB" $PRINT END
? DEFINE MAIN
> SUB ;BLA BLA
> END
? "APP" $DEFADR INC "MAIN" $DEFADR PUT ;REPLACE DEC WITH MAIN
? "APP" $DEFADR DEC "MAIN" $DEFADR DEC GET PUT ;WRAP APP AROUND MAIN
? APP
IN THE SUB
? EOD PNUM "APP" $DEFADR DEC GET PNUM ;MAKE SURE APP LINK=EOD
011615 011615
? WORDS
...edited...
ADDMxxxx VARIxxxx CONSxxxx ALLOxxxx ZEROxxxxx BPUT BGET APP
EOD=011615 FREE=002162
?
RUN pops the stack and directly jumps to that address, as with
low-level words, code can return to HP-IPL/OS by executing 124321 or an
indirect jump to "next". 2 RUN restarts HP-IPL/OS. EXECUTE pops the
stack and sets the threaded interpreter to that address, can be used to
run words by the address returned by $DEFADR. WBOOT warm-boots the
system, stacks are reset and the prompt is displayed, but autostart
words are not run making it a handy way to exit a word back to the
prompt.
? 120 102000 PUT
? 121 124321 PUT
? DEFINE RUNTEST
> 120 RUN
> "WORKED" $PRINT END
? RUNTEST
HALT instruction 102000, P: 00121 (JMP 321,I)
sim> c
WORKED
? 120 RUN
HALT instruction 102000, P: 00121 (JMP 321,I)
sim> c
? DEFINE TEMP
> "TEMP" $PRINT END
? "TEMP" $DEFADR EXECUTE
TEMP
? DEFINE WTEST
> IFZ WBOOT ENDIF
> PNUM END
? 1 1 1 DMPS
000001
000001
000001
? 1 WTEST
000001
? 0 WTEST
? DMPS
? 2 RUN
HP-IPL/OS 8K V1.5
?
These are constants that push 0 or 1, taking up one less location
than using literal 0 or 1.
The stock kernel doesn't provide a ready-made way to pause the
system so that a single serial port can be shared between the console
and the PTR emulator. The kernel does however provide a way to create a
machine-coded word manually... if building a system on real hardware
with only a single serial port for both the terminal and a pass-through
PTR emulator, type this in...
? OCTAL
? "HLT" ADDHEADER$ @DIPTR GET INC ADDCODE
? 102007 ADDCODE 124321 ADDCODE FIXLINKS
The ADDHEADER$ word pops a string containing a word name and starts a word header, leaving @DIPTR set to the new word's WA location. The ADDCODE word pops the stack, writes it to the address specified by @DIPTR, then increments @DIPTR. For a machine-coded word WA = address of machine code to run, usually the next address so @DIPTR GET INC ADDCODE puts WA+1 in the WA location. Next ADDCODE is used to add 102007, the halt instruction, then 124321, an indirect jump to the "next" vector. Finally FIXLINKS is used to pop info pushed by ADDHEADER$ to set the word name length of the HLT word, adjust the next word link to point to the end of dictionary and put a zero there. Very low level stuff but handy when a bit of machine code is needed - if doing anything that branches @DIPTR GET PNUM can be used to display the current code pointer to pack the jump and memory reference instructions correctly. For true hackers only, but HLT is useful - to load an IPL package with a single serial port enter HLT <PTR then close the terminal emulator, swap the serial cable to the PTR emulator, launch the PTR file transfer program, press the Run switch, when the lights stop blinking and sure the load is complete, close the transfer program, swap the serial cable back to the console, launch the terminal emulator and press Enter.
While the kernel supports complex programming (all of
HP-IPL/OS derives from it), it doesn't provide many tools for examining
and manipulating the dictionary (WORDS is about it). The extra.ipl
package provides a set of commonly-used words, mostly command-line
tools but also additional words used by other packages. The reason for
putting these words in a separate package is for versatility, systems
built for different purposes don't need all of the words in extra.ipl
(some of which I never use but others do so can't remove them).
Presently there are at least four versions of the extra words...
extra.ipl - the original version with all the extras and an
interactive memory configurator
extra2.ipl - a more compact version that omits some words and has no
configurator
8kextra.ipl - a very trimmed down version for 8KW that includes just a
few command-line tools
8kextra2.ipl - another version for 8KW block-less builds that puts back
the programming words
My favorite is extra2.ipl, it provides all the extras that other
packages use, plus the original extra.ipl cannot be loaded if only a
single serial
port is available (can't use a menu when the only serial cable is
connected to
the PTR emulator). For really trimmed
down builds the 8kextra2.ipl package can be used even for larger memory
builds, it has the usual programming extras and a functional set of
"compacted" tools that don't confirm or print error messages.
Note - when using an
alternate extra package that doesn't prompt for configuration, after
loading use
@END [last addr] PUT [#blocks]
ALLOCATE to configure memory as needed.
Examples from extra2.ipl (these allocate 2 blocks)...
; For 16KW, no himem: @END 37677 PUT 2 ALLOCATE
; For 16KW, 1KW himem: @END 35777 PUT 2 ALLOCATE
; For 16KW, 2KW himem: @END 33777 PUT 2 ALLOCATE
; For 32KW, 4KW himem: @END 67777 PUT 2 ALLOCATE
The memory indication in the sign on message is automatically
altered when using extra.ipl's configurator, to alter the message after
manual memory adjustment change location 2010...
? 2010 "32" $ADR GET PUT $DROP
? 2 RUN
HP-IPL/OS 32K V1.6
Change the "32" to the amount of memory configuring for (the $DROP
just removes the string, not needed if 2 RUN done to verify). Use
VERSION from the version.ipl package to change the entire string or
otherwise place packed text in locations 2003-2014.
Configuring a 32KW system with 4 blocks and 4KW high mem using the
original extra.ipl package...
HP-IPL/OS 8K V1.6
?
Simulation stopped, P: 02225 (SFS 11)
sim>attach -e ptr ../ipl/extra.ipl
sim>c
? <PTR
Loading Configurator
Select configuration...
1) keep existing setup
2) 16K w/ 2 blocks
3) 16K w/ 2 blocks and 2K hi-mem
4) 32K w/ 4 blocks and 4K hi-mem
> 4
Configured to 32K w/himem
Loading $EQUAL
Loading $SLICE
Loading $TRIM
Loading ABSOUT
Loading PTZERO
Loading MARKCON
Loading SETCON
Loading GLOBAL
LOADING DUMP
LOADING PDEF
LOADING EXPLAIN
Loading FORGET
Loading ERASE
Loading FETCH
Loading STASH
Loading HIDEDUPS
Loading RENAME
Loading DELETE
Loading UNDELETE
Loading WHEREIS
Loading LOAD
Done
? WORDS
DO +DO INDEx +LOOx >STEx IFNZ IFZ IF<0 ENDIx ELSE UNTIx WHILx CASE = < > <=
>= <> DEFAxxx ENDCxxx EXECxxx WBOOx AND OR XOR ADD SUB INC DEC NOT 2CPL DUP
DROP OVER ROT SWAP GET PUT PNUM CRLF DECIxxx OCTAx BINAxx RADIx SP>S SB>S
XP>S XB>S YP>S YB>S ZP>S ZB>S END EOD DEFIxx DMPS S>SR SR>S PCHR PWRD CHRIx
S>X X>S S>Y Y>S S>Z Z>S MUL ASL ASR ROL ROR DIV RUN X>>Y X>>Z Y>>X Z>>X
$PRIxx $SWAx $CPY $DUP $DROx $LEN $ADR $XTExx $PUT $GET $CRExxx $STR $HEAx
$APPxxx $TAIx $IN $CAT $VAL <>COx >PTP <PTR MSPAxxx MSBOxx MSBIx MSWOxx
MSWIx MS$Oxx MS$Ix MSCRxx >MS <MS MS_Sxxx MS_Rxxxxxx CONSxxx #0 #1 @TL @TB1
@TB2 @ANVxx @LLP @ENSxx @CLH @LITxxxx @STRxxx @RTSxx @DIC @USR @BLK @END
@DIPxx RND TOKEx SDIC +IRQ -IRQ +AUTx -AUTx ADDCxxx INBLxxx OUTBxxxx GETIx
SETIx GETOx SETOx $DEFxxx HEADxxx WORDx ADDHxxxxx ADDHxxxxxx FIXLxxxx
ADDMxxxx VARIxxxx CONSxxxx ALLOxxxx ZEROxxxxx BPUT BGET $EQUxx $SLIxx $TRIx
ABSOxx PTZExx MARKxxx SETCxx GLOBxx DUMP PDEF EXPLxxx FORGxx ERASx FETCx
STASx HIDExxxx RENAxx DELExx UNDExxxx WHERxxx LOAD
EOD=014627 FREE=043150
?
Configuring a 32KW system with 4 blocks and 4KW high memory using
the extra2.ipl package...
HP-IPL/OS 8K V1.6
?
Simulation stopped, P: 02225 (SFS 11)
sim>attach -e ptr ../ipl/extra2.ipl
sim>c
? <PTR
Loading $EQUAL
Loading $SLICE
Loading $TRIM
Loading ABSOUT
Loading PTZERO
LOADING DUMP
LOADING PDEF
LOADING EXPLAIN
Loading FORGET
Loading ERASE
Loading FETCH
Loading STASH
Loading RENAME
Loading WHEREIS
Loading LOAD
Done
? @END 67777 PUT 4 ALLOCATE
? 2010 "32" $ADR GET PUT $DROP
? 2 RUN
HP-IPL/OS 32K V1.6
? WORDS
DO +DO INDEx +LOOx >STEx IFNZ IFZ IF<0 ENDIx ELSE UNTIx WHILx CASE = < > <=
>= <> DEFAxxx ENDCxxx EXECxxx WBOOx AND OR XOR ADD SUB INC DEC NOT 2CPL DUP
DROP OVER ROT SWAP GET PUT PNUM CRLF DECIxxx OCTAx BINAxx RADIx SP>S SB>S
XP>S XB>S YP>S YB>S ZP>S ZB>S END EOD DEFIxx DMPS S>SR SR>S PCHR PWRD CHRIx
S>X X>S S>Y Y>S S>Z Z>S MUL ASL ASR ROL ROR DIV RUN X>>Y X>>Z Y>>X Z>>X
$PRIxx $SWAx $CPY $DUP $DROx $LEN $ADR $XTExx $PUT $GET $CRExxx $STR $HEAx
$APPxxx $TAIx $IN $CAT $VAL <>COx >PTP <PTR MSPAxxx MSBOxx MSBIx MSWOxx
MSWIx MS$Oxx MS$Ix MSCRxx >MS <MS MS_Sxxx MS_Rxxxxxx CONSxxx #0 #1 @TL @TB1
@TB2 @ANVxx @LLP @ENSxx @CLH @LITxxxx @STRxxx @RTSxx @DIC @USR @BLK @END
@DIPxx RND TOKEx SDIC +IRQ -IRQ +AUTx -AUTx ADDCxxx INBLxxx OUTBxxxx GETIx
SETIx GETOx SETOx $DEFxxx HEADxxx WORDx ADDHxxxxx ADDHxxxxxx FIXLxxxx
ADDMxxxx VARIxxxx CONSxxxx ALLOxxxx ZEROxxxxx BPUT BGET $EQUxx $SLIxx $TRIx
ABSOxx PTZExx DUMP PDEF EXPLxxx FORGxx ERASx FETCx STASx RENAxx WHERxxx
LOAD
EOD=013547 FREE=044230
?
The more compact extra2.ipl package provides about 0.5KW more room
in the dictionary by leaving out some of the old console words I rarely
if ever use...
MARKCON SETCON GLOBAL - a way to selectively hide/restore words
(don't need)
HIDEDUPS [wordname] - renames duplicates to a space to hide (dups don't
bother me)
DELETE [wordname] - renames a word to space to hide (don't need)
UNDELETE - prompts to rename words named to a space (don't need)
FORGET [wordname] checks to see if a word exists, if it does it asks
to remove the word and all words that follow it. If Y is pressed it
does it. The 8KW versions don't confirm. If a driver is
removed (in particular a console driver like !NOLF), immediately do 2
RUN to restart the system to make sure the
driver's code is unhooked and no longer executing before adding more
code to the dictionary.
? DEFINE TEST
> "TEST" $PRINT END
? TEST
TEST
? FORGET TEST
Forget TEST and everything after it? Y
Done
? TEST
TEST NOT FOUND
?
ERASE is like an automatic forget for entire IPL packages. Whenever
<PTR, <MS, LOAD or any other MS-based load is used, a variable
named @LLP (last load point) is set to the end of the dictionary before
the
load. ERASE checks @LLP and if pointing to a valid word, prompts to
erase from that word. This is handy for removing a failed load when an
error occurs (word not found, page error, dictionary overflow, etc), or
loading a package for temporary use. As with FORGET, if ERASE is used
to remove a package containing a driver, best to follow by 2 RUN to
reset HP-IPL/OS and make sure the the driver is no longer active.
HP-IPL/OS 32K V1.6
?
Simulation stopped, P: 02226 (JMP 2225)
sim>attach -e ptr ../ipl/screen.ipl
sim>c
? LOAD
$DST NOT FOUND
? ERASE
Erase from ESC (Y/N)? Y
Done
?
By default, all supplied builds (except for the kernel and 8K build)
are set so that if no loads are performed the default ERASE action is
to remove any new code typed into the build. When making a new build,
this default can be set by entering @LLP EOD PUT before saving the new
build. The default erase point can be set to any word by doing @LLP
"wordname" $DEFADR 4 SUB PUT (be careful typing these) or set to be
after a specific word by doing @LLP "wordname" $DEFADR DEC GET PUT. To
force a default erase point after a coldboot/restart, define an
autostart word (a word beginning with !) that sets @LLP as desired.
EXPLAIN [wordname] lists the contents of high-level words in a
format that can be re-entered back into the dictionary. Not only is
this useful for examining words, but can be used to dump words
elsewhere (usually blocks) to permit rearranging the dictionary. This
only works for high-level words, machine-coded words can't be explained
and have to be reassembled to relocate. EXPLAIN works only from the
console (like all words that take parameters after the command). PDEF
pops an address from the stack that represents WA (from $DEFADR) and
lists the definition, it's what EXPLAIN calls to do the actual
explaining.
? EXPLAIN EXPLAIN
OCTAL DEFINE EXPLxxx ;ENSEC=012524
TOKEx IFNZ "Need name" $PRIxx ELSE SDIC IFZ "Not found"
$PRIxx ELSE PDEF ENDIx ENDIx END
? "EXPLAIN" $DEFADR PDEF
OCTAL DEFINE EXPLxxx ;ENSEC=012524
TOKEx IFNZ "Need name" $PRIxx ELSE SDIC IFZ "Not found"
$PRIxx ELSE PDEF ENDIx ENDIx END
If only high-level words exist after a word that needs to be removed
(no variables, constants or machine-coded words), and they'll fit into
the allocated blocks in source form, then a word can be removed while
keeping words after it...
? DEFINE W2
> END
? DEFINE W3
> "W3" $PRINT END
? DEFINE W4
> "W4" $PRINT END
? ;REMOVE W2 WHILE KEEPING W3 AND W4
? 0 OUTBLOCK >MS ;REDIR MS TO BLOCK 0 THEN REDIR CONSOLE TO MS
? EXPLAIN W3
? EXPLAIN W4
? "CONSOLE" $PRINT CRLF
? CONSOLE ;RESTORE NORMAL CONSOLE OUTPUT
? FORGET W2
Forget W2 and everything after it? Y
Done
? 0 INBLOCK <MS ;RELOAD W3 AND W4
? W3
W3
? W4
W4
? W2
W2 NOT FOUND
?
RENAME [wordname] [newname] changes the name of a word. This does
not affect the operation of other words already in the dictionary that
use the word, as words are stored by address, not by name.
? DEFINE DEC
> "DAC" $PRINT END
? DAC
DAC NOT FOUND
? RENAME DEC DAC
? DAC
DAC
?
[block] "wordname" STASH copies a word using PDEF to the specified
block, ending with CONSOLE. [block] FETCH reloads the word saved by
STASH. If only a few words need to be saved when removing middle words
from the dictionary, and there are enough blocks, this is easier than
the manual method shown in the EXPLAIN example.
? 0 "DAC" STASH
? FORGET DAC
Forget DAC and everything after it? Y
Done
? 0 FETCH
? DAC
DAC
?
Note - CREATE uses blocks 0 and 1 so if using this technique (or the
manual method) with machine-coded packages block 2 or higher must be
used to save words.
It wouldn't be difficult to make a multi-word stasher using TOKEN
and SDIC kernel words, TOKEN gets the next parameter and sets @TL @TB1
and @TB2 to the length and first 4 characters and pushes a 0, if no
more parameters then it pushes a 1. SDIC searches the dictionary
for a word matching @TL @TB1 @TB2 and if found pushes the word address
twice, otherwise pushes a single 0. A multi-stasher would have to take
parms both before and after the command name so the block plus a
variable number of word names can be specified. Something like...
DEFINE MSTASH ;usage: block MSTASH word1 word2 etc
MS_SAVE OUTBLOCK >MS
DO
TOKEN IFNZ #1 ELSE
SDIC IFZ CONSOLE "Not found" $PRINT WBOOT ELSE
PDEF #0
ENDIF
ENDIF
UNTIL
"CONSOLE" $PRINT CRLF CONSOLE MS_RESTORE
END
FETCH can be used to fetch the combined package.
WHEREIS [wordname] displays the word address and the memory occupied
by a word and its header.
? WHEREIS DAC
Word address = 013553
Occupies 013547 to 013562
?
[start] [end] DUMP displays memory from start to end in octal and
text. DUMP always switches to octal mode regardless of the radix when
entering, not restored.
? 13547 13562 DUMP
13547:000003 042101 041440 013563 002775 003572 000003 042101 ..DAC .s...z..DA
13557:041400 000003 006314 003002 000000 000000 000000 000000 C...............
?
$EQUAL pops a string and compares it to the next string, which is
left on the string stack. If the strings are equal it pushes a 1, if
not it pushes a 0. [from] [to] $SLICE pushes a substring of a string on
the stack, the original string is left on the string stack. $TRIM pops
a string, removes leading and trailing spaces and pushes the trimmed
string.
? "STRING" "STRING" $EQUAL PNUM $PRINT
000001 STRING
? "STRING" "STRING2" $EQUAL PNUM $PRINT
000000 STRING
? "STRING" 2 5 $SLICE $PRINT CRLF $PRINT
RING
STRING
? " TRIN " $TRIM "S" $PRINT $PRINT "G" $PRINT
STRING
?
[from] [to] ABSOUT outputs memory to MS output in ABS format. PTZERO
writes 20 zeroes to MS output to use for a leader and trailer, some PTR
emulators or software require leading zeroes, trailing zeroes are
required to terminate an ABS load. These can be used to save a system
to papertape or whatever MS is set to.
? 0 MSTASH RENAME WHEREIS LOAD MSTASH
? FORGET STASH
Forget STASx and everything after it? Y
Done
? 0 FETCH
? WORDS
DO +DO INDEx +LOOx >STEx IFNZ IFZ IF<0 ENDIx ELSE UNTIx WHILx CASE = < > <=
>= <> DEFAxxx ENDCxxx EXECxxx WBOOx AND OR XOR ADD SUB INC DEC NOT 2CPL DUP
DROP OVER ROT SWAP GET PUT PNUM CRLF DECIxxx OCTAx BINAxx RADIx SP>S SB>S
XP>S XB>S YP>S YB>S ZP>S ZB>S END EOD DEFIxx DMPS S>SR SR>S PCHR PWRD CHRIx
S>X X>S S>Y Y>S S>Z Z>S MUL ASL ASR ROL ROR DIV RUN X>>Y X>>Z Y>>X Z>>X
$PRIxx $SWAx $CPY $DUP $DROx $LEN $ADR $XTExx $PUT $GET $CRExxx $STR $HEAx
$APPxxx $TAIx $IN $CAT $VAL <>COx >PTP <PTR MSPAxxx MSBOxx MSBIx MSWOxx
MSWIx MS$Oxx MS$Ix MSCRxx >MS <MS MS_Sxxx MS_Rxxxxxx CONSxxx #0 #1 @TL @TB1
@TB2 @ANVxx @LLP @ENSxx @CLH @LITxxxx @STRxxx @RTSxx @DIC @USR @BLK @END
@DIPxx RND TOKEx SDIC +IRQ -IRQ +AUTx -AUTx ADDCxxx INBLxxx OUTBxxxx GETIx
SETIx GETOx SETOx $DEFxxx HEADxxx WORDx ADDHxxxxx ADDHxxxxxx FIXLxxxx
ADDMxxxx VARIxxxx CONSxxxx ALLOxxxx ZEROxxxxx BPUT BGET $EQUxx $SLIxx $TRIx
ABSOxx PTZExx DUMP PDEF EXPLxxx FORGxx ERASx FETCx RENAxx WHERxxx LOAD
MSTAxx
EOD=013533 FREE=044244
?
Simulation stopped, P: 02225 (SFS 11)
sim>attach ptp ../../mybuild.abs
PTP: creating new file
sim>c
? PTZERO 2 EOD ABSOUT PTZERO
?
Simulation stopped, P: 02225 (SFS 11)
sim>detach ptp
IPL code can be used to program lots of things, but it cannot
directly access hardware or make use of additional features of the
later HP2100-series machines such as DMS (dynamic memory system),
floating point or other extended instructions. For these things
machine-code is needed. The indirectly threaded interpreter is fairly
fast but is still interpreted - for each word in a definition it has to
get the address representing the word, get the contents of that address
representing the address of some machine code to run, run that code
which has to exit by jumping to the next code, which then starts the
process over. For high level words, the first address is for "enter
secondary" which pushes where it was to the return stack so it can
start interpreting the words in the sub-word, then exit with an address
for "return from secondary" that pops the return stack to continue
interpreting from where the word was called from. Yikes! all that
happens for almost every word encountered during execution, it's a
wonder it achieves reasonable speed at all. IPL does OK but sometimes
recoding a function in machine code can result in greatly improved
execution speed.
CREATE comes in two flavors - the full version in the create.ipl
package presently uses 6371 octal locations (about 3.25KW) and supports
most extended and 21MX instructions, the small version in the
smallcre.ipl package presently uses 5156 octal locations (about 2.6KW)
and supports only the original HP2116 instruction set. Both versions
require at least 2 data blocks (2KW) for storing symbols and fixup
addresses.
The full version also creates a PRESET word which (sort of)
simulates pressing the Preset button, sometimes needed to make loaded
binaries work correctly. This word disables interrupts, tells HP-IPL/OS
that it disabled interrupts, does a brute force CLC slot STF slot for
slots 6 to 57, and clears the overflow flag. Quite possibly overkill.
If a PRESET is needed when using the small CREATE, or if your needs are
simpler and you would rather save memory, the following version of
PRESET "should" work for loading "most" software...
CREATE PRESET
CLF 0
CLA
STA 461
CLC 0
CLO
END
The 21MX manual says CLC 0 clears control for all devices, but does
not say it sets all flags as happens when actually pressing the Preset
button. Most things shouldn't care but who knows. HP-IPL/OS does its
own CLC 0 on startup so this isn't needed to load a HP-IPL/OS build. Of
course to load a binary you'll need some loader code up in high memory,
something that reads ABS data for loading binaries from papertape, or
if using a 21MX machine, the main/alt swapper/runner program put into
memory at 77000 by the ALTSAVE word in the DMS package, then just have
to load a binary into alt mem and jump to 77000 to run the binary from
location 2. Jump to 77000 manually or from the binary being run to swap
HP-IPL/OS back in, leaving the binary and whatever was in its data
buffers in alt mem. Anyway...
CREATE is composed of many words and variables, to avoid cluttering
the dictionary and prevent running the internal words, the package
wraps the initial CREATE word around all the internal components and
patches the initial word to run the main word. Here's what the
create.ipl package looks like when loading...
?
Simulation stopped, P: 02225 (SFS 11)
sim>attach -e ptr ../ipl/create.ipl
sim>c
? LOAD
Loading CREATE and support
Loading A=B
Loading A>=B
Loading PUTSYMBOL
Loading FINDSYMBOL
Loading PUTFIX
Loading GETFIX
Loading CONVERT_TO_ADDRESS
Loading IHASH
Loading 2D1
Loading MATCHSIMPLE
Loading MATCHIO
Loading MATCHEXT
Declaring ISLOT ITYPE
Loading TEST_ASG
Loading S_A S_B S_ASG S_1 S_2 S_3 S_4
Loading DROP_B
Loading ICOMPOUND
Loading PARSE_FRAGMENT
Loading MATCHCOMPOUND
Loading $ML2CODE
Loading MAIN
Folding into CREATE
Loading PRESET
022006 :
...etc, addresses depend on where loading to
022040 :
Fixing...
Done
?
Usage (here [] means optional): CREATE wordname [/K] [/L]
The /K option (as in CREATE wordname /K) tells CREATE to keep the
previous symbol table, useful when other words need to access memory or
code within a previous word. The /L option (full version only) tells
CREATE to use all data blocks for symbols and addresses.
Here's a simple usage from the command prompt...
? CREATE HLT
020633 : HLT 7
020634 : END
? HLT "CONTINUING" $PRINT
HALT instruction 102007, P: 20634 (JMP 321,I)
sim>c
CONTINUING
?
Typical IPL package format for CREATE code...
;comments
"Loading SOMEWORD" $PRINT CRLF
CREATE SOMEWORD
* comments
symbol EQU value comments
label code comments
code comments
...etc
END
;comments
"Loading ANOTHERWORD" $PRINT CRLF
CREATE ANOTHERWORD /K
code etc
END
;high level words...
DEFINE APPWORD
ipl code
END
"Done" $PRINT
CONSOLE
Instructions must be entered in uppercase. Numbers are always octal
(it switches to OCTAL mode upon start) except when using DEC to specify
a decimal value in a memory location. Labels can be up to 6 characters
long and must begin in the first column (in an IPL file or entered
text, not counting the prompt). Code must begin in the second column,
or if preceded by a label there must be at least one space between the
label and the instruction. Comments must be placed well away from the
code to avoid a "Parse error", at least column 11 or greater and at
least 9 positions past the beginning of the instruction. When the END
directive is encountered, a JMP ZNXT,I (ZNXT=321) is assembled to jump
to HP-IPL/OS' "next" code and assembly stops. END also places an END
label at the exit instruction so the easy way to exit a word to skip
data at the end is to use JMP END, although a couple microseconds can
be saved by using JMP ZNXT,I instead. If there is no data to skip then
no extra instructions are needed. If any references to labels were
used, once the code has been entered "Fixing..." is displayed while it
sifts through all the references replacing them with addresses.
The fixing process can take a long time so if loading on real
hardware via a transfer program (especially when using a single port
where the console cannot be seen) make sure plenty of time is given
before assuming the load is finished and closing the transfer program.
This delay also means that when pasting CREATE code into the console
using character/line delays (i.e. HyperTerminal), only one word can be
pasted at a time.
The instruction format is similar to ASMB etc except the only
supported directives are EQU and END. DEF, OCT, DEC and ASC,n
(n=#locations in decimal, syntax is different from ASMB)
can be used to reserve and specify memory locations, these must begin
in column 2 or greater. If an odd number of characters is used in an
ASC psuedoinstruction, the low byte of the last word will contain a
space (some HP assemblers may encode a 0 byte instead). If B is
appended to numbers (used by ASMB to specify octal) it is ignored.
There obviously
is no ORG, the code is assembled to where
EOD happens to be plus the header and WA location (if you need code in
a specific location, use a cross-assembler and load it into high-memory
somewhere where it won't conflict - if the machine code ends in JMP
321,I it will still behave like a word when executed). The "*"
character represents the
current location, as in DEF *+1 etc but only simple +/- offsets are
supported and the offset number must be specified in octal (invalid
octal numbers evaluate to 0, no error indicated).
Multi-location
instructions must be coded on multiple lines using OCT to specify the
additional locations and contents, for example a DIV instruction would
be coded DIV on one line followed by DEF address pointing to the value
to divide by. Examine the instruction reference manual carefully, some
instructions like CBT and MBT require a 3rd OCT 0 line that's not
apparent unless the text for the instruction is read, not shown in the
charts.
As with all HP21xx machine code, memory references must be either to
zero page or the current 1KW page, so generally CREATE code should not
cross a 1KW boundary (22000, 34000, 36000, or any octal address with an
even number in the 4th digit from the right). If there are any
references to locations beyond the current page or zero page then "Page
error" is displayed during the fix process along with the errant
references and the word is not created. The easiest way to avoid page
errors is to keep track of the end of dictionary (EOD, displayed by
WORDS or EOD PNUM) to make sure there's
enough room for the code before reaching a 1KW boundary. If not, load a
high-level word or package first, or if memory usage isn't a concern
DEFINE ~PAD [#locations needed]. Another technique is to write mostly
relocatable code where all references are very close or done indirectly
through a table at the beginning of the code - but this wastes memory.
When building a system, page errors are not much of an issue if EOD
is checked and a bit of thought is put into the loading process -
builds are usually composed of many packages, some mostly
machine-coded,
some mostly or entirely plain IPL, and some packages that aren't
dependent on the machine-coded packages (or at least packages not
already loaded successfully). When a page error occurs, use ERASE to
remove the partial
load, then load one or more
non-dependent high-level packages so that when machine-coded packages
are really needed EOD is sitting at or just after a 1KW boundary. It's
not
that difficult and in trade for sometimes having to think a bit you get
more dictionary space for doing more stuff. If you can't be bothered
with figuring out a working load order, then at least check EOD before
loading a package containing CREATE code and if it looks like it's not
going to fit within the current page, make a dummy variable to push EOD
into the next page.
Where page errors really are an issue is when creating IPL
applications (as
opposed to building a base system) where the thing runs from a menu or
a library file (run and forget) and there's no telling where EOD will
be when loaded. In this case, memory usage usually isn't an issue since
once the application is run it'll be removed, but load failure is very
bad. The solution is code the IPL package so that it checks EOD itself
and if not enough room, adds a pad variable just big enough to get past
the boundary. Here's one of the page-crossing fixes from fed.ipl...
;----------------------------------------------------------
; LNLEN requires ~30 octal words, do the page-crossing thing...
2000 EOD 1777 AND SUB 34 SUB ;push difference, if < 0 then...
VARIABLE ~PAD 15
;enough to push to start of next 1KW page under all conditions
DEFINE TEMP
IF<0
"Declaring ~PAD to cross page boundary" $PRINT CRLF
"TEMP" $DEFADR 4 SUB #0 PUT ;delete just TEMP
ELSE
"~PAD" $DEFADR 4 SUB #0 PUT ;EOD ok, delete ~PAD and TEMP
ENDIF END
TEMP
"Creating LNLEN..." $PRINT CRLF
CREATE LNLEN ;2/18/08
* ENTRY: MEMADR ON STACK
* EXIT: LENGTH OF TEXT LINE ON STACK
JSB ZSPOP,I GET MEMADR
ADA C38 ADD 38 DEC (LAST WORD)
LDB C39 GET 39 DEC (MAX LEN)
STB LNLEN SAVE IN LNLEN TO RETURN
LOOP LDB 0,I GET WORD POINTED TO BY A
CPB SPACES DOUBLE SPACES?
JMP DECPTR YES - DECREMENT LEN AND PTR
JMP RETVAL NO - RETURN VALUE
DECPTR ADA CM1 SUBTRACT 1 FROM POINTER
LDB LNLEN GET LINE LENGTH
ADB CM1 SUBTRACT 1 FROM LENGTH
STB LNLEN SAVE BACK
SZB SKIP IF 0 (DONE)
JMP LOOP
RETVAL LDA LNLEN GET LEN
JSB ZSPSH,I PUSH TO STACK
JMP ZNXT,I EXIT WORD
LNLEN OCT 0 LINE LEN
C38 DEC 38
C39 DEC 39
CM1 DEC -1
SPACES OCT 20040
END
;----------------------------------------------------------
Basically the technique is to count the octal number of memory
locations needed, then do...
2000 EOD 1777 AND SUB [#locations + 4] SUB
VARIABLE ~PAD [#locations - 13 octal]
DEFINE TEMP
IF<0
"Declaring ~PAD to cross page boundary" $PRINT CRLF
"TEMP" $DEFADR 4 SUB #0 PUT ;delete just TEMP
ELSE
"~PAD" $DEFADR 4 SUB #0 PUT ;EOD ok, delete ~PAD and TEMP
ENDIF END
TEMP
How it works - at command line level it subtracts the 1KW
portion of EOD (EOD 1777 AND) from 2000 octal, then subtracts the
required number of locations plus the header from that, leaving the
results on the stack. Next it makes a ~PAD variable with enough
locations specified so when added to the variable overhead pushes EOD
far enough ahead so the machine code will be in the next page once
CREATE adds the word header. Next it defines a TEMP word that checks to
see if the test SUB left on the stack is negative, if it is it displays
a message and uses the $DEFADR manual forget technique to delete just
the TEMP word, leaving ~PAD in place. If the SUB test is >=0 then it
forgets ~PAD too, not needed.
CREATE predefines many symbols that can be used to access internal
HP-IPL/OS variables and code routines. Some are pretty easy to use like
JSB ZSPOP,I to pop the stack to the A register and JSB ZSPSH,I to push
A to the stack. Some are a bit trickier but useful like JSB PSUBA,I
with A set to the address of a patch list (must end with 0) and B set
to a slot number to patch into the locations - that one is the basis
for almost everything that self-patches using an autostart word.
Symbol Loc Description
------ --- -----------
ZNXT 321 indirect NEXT vector
ZSPSH 323 indirect S push
ZSPOP 324 indirect S pop
ZRPSH 325 indirect R push
ZRPOP 326 indirect R pop
ZXPSH 327 indirect X push
ZXPOP 330 indirect X pop
ZYPSH 331 indirect Y push
ZYPOP 332 indirect Y pop
ZZPSH 333 indirect Z push
ZZPOP 334 indirect Z pop
ZCOUT 337 indirect char-out vector
ZCHIN 341 indirect char-in vector
ZCRLF 340 indirect crlf-out vector
ZPTWD 342 indirect word-out vector
ZPBFL 343 indirect string-out vector - addr in B, #words in A
ZIN 346 indirect standard-in vector
ZOUT 347 indirect standard-out vector
ZMINP 350 indirect MS in vector
ZMOUT 351 indirect MS out vector
ISAVE 475 indirect state-save/-irq vector
IREST 476 indirect state-restore/+irq vector
TMRLO 473 location of timer low (tbg)
TMRHI 474 location of timer high (tbg)
TMP1 311 temp locations
TMP2 312
TMP3 313
TMP4 314
SP 301 S stack pointer
RP 302 R stack pointer
XP 303 etc
YP 304
ZP 305
BM2A 463 address of sign-on string
ASENA 465 auto-start enable if <>0
TXTWP 466 text-was-printed flag
PSUBA 470 patch sub address - addr in A, slot# in B
CHINS 471 actual chrin sub address
CHOUS 472 actual chrout sub address
CON1 433 contains -1
IENAV 462 address of int.enable sub
ABFLG 260 alternate boot flag
ABVEC 261 alt boot vector
WDENA 262 watchdog enable
WDTMR 263 watchdog timer
WDTOV 264 watchdog timeout value
These don't cover all of the zero-page locations (such as 461 which
contains the global interupt enable flag), and some are probably
never used, but these are the locations that have been predefined since
CREATE was created so not messing with it. If making a custom version,
each symbol deleted from the create.ipl source saves about 9 locations.
Last modified 6/9/2010