[comp.sys.handhelds] Some HP-48 Internals Answers

bson@rice-chex.ai.mit.edu (Jan Brittenson) (02/28/91)

   Joe Ervin asked me some questions in a letter. It seemed to me that
the questions were fundamental enough to be answered on the net - I
haven't seen any in-depth discussions of these topics before; I hope
Joe will excuse me, and the rest of you bear with me. If you hate
everything related to HP-48 internals, hit the proper Junk key now. I
will stick to STAR syntax and AG mnemonics.


 > In all the examples of internal RPL I have seen, I have NOT seen any
 > loops or IF-THEN constructs, although I believe that Bill Wickes has
 > IF-THEN in his ASC routines.

   I recently posted a set of FFT-aiding routines here on
comp.sys.handhelds. Two of them use if-then constructs, and one has a
loop as well as local variables. The latter took quite some exper-
imentation to get right!

   Here is one of them, it collapses an array into a one-dimensional
list (I have reduced it to bare bones here - let's not bother with the
actual addresses):


	RPL
	  Prg			; PROGRAM
	    SaveLast_Need_1_arg	;   Housekeeping stuff
	    ArrayTo_array	;   Explode array
	    ListAlgPrgTo	;   Explode dimensions list
	    Equal2p
	    If_then_else	;   Then: (real->short) * (real->short)
	    Prg
	      Real_to_short_Swap
	      Real_to_short
	      Mul
	    End
	    Real_to_short	;   Else: real->short
	    ToList		;   Combine to list
	  End			; END
	ENDRPL


Now, broken down step by step:


RPL	Enters STAR RPL mode.

Prg	Start of program object (2d9d).

SaveLast_Need_1_arg
	Makes sure we have at least one argument.

ArrayTo_array
	Explodes the array - ARRY-> on an array. This yields a list of
	reals containing dimensional info, e.g. {2 3} for a 2-by-3
	array.

ListAlgPrgTo
	Explodes the list - LIST-> on a list. Like the user-mode
	command, expcept the the counter on level 1 is a short.

Equal2p If level 1 is the short 2, then return True, else False. True
	is #3a81 and False is #3ac0. These both appear as "External"
	on the stack, and can't be entered from user code. Like shorts
	(system binaries) are preferred internally for small integers,
	such as the list count above, True and False is the system RPL
	preferred way of passing around boolean flags.

If_then_else (#61ad8)
	If level 1 is True (#3a81) then execute the next object,
	otherwise execute the second next object.

Prg	Start of program object (2d9d). This program object is the
	then-clause. This gets evaluated if the list contained two
	elements, i.e. was 2-dimensional. Otherwise it was one-
	dimensional.

Real_to_short_Swap
	(Then-clause) Convert a real (from the array dimensions list)
	to a short and do a swap.

Real_to_short
	(Then-clause) Convert the second real to a short.

Mul	(Then-clause) Multiply the two shorts. Notice that we could
	just as well have multiplied the two reals (using the user-
	code real multiplication function) and then converted the
	result to a short. But short (20-bit unsigned integer)
	mulitplication is magnitudes faster than real (64-bit BCD)
	multiplication.

End	End (#312b) of the then-clause program object.

Real_to_short
	This is the else-clause. The array was 1-dimensional, so
	we just convert the size to a short.

ToList	Finally we put it all back to a list. The item count on
	level 1 is assumed to be a short.

End	This is the end of our program, and the end of the object.

ENDRPL	Signals end of STAR RPL mode.


 > It looks like in the internal RPL, you cannot simply push an object
 > onto the stack the way you do in user code.

   Sure you can. System RPL executes almost identically to user RPL.
The big difference is that you can't automatically interrupt it with
ATTN - the system RPL has to explicitly check for this. System RPL
also has a lot of constructs for implementing control and branch
functions - you can, for instance, write the If_then_else above in
system RPL by calling more primitive functions like Skip (which skips
the next token in the *caller's* thread), or pick the next token from
the caller's thread, or evaluate the token in the caller's thread. A
token here is either an object or the 20-bit address of an object. In
fact, If_then_else *is* written in system RPL if I remember correctly.
There are more specialized examples; we could, for instance, have
written an If_equal2p_then_else function if we wanted (and had the
option of putting it in a fixed location in ROM!).


 > This is apparantly because internal RPL consists strictly (I think) of
 > nibble addresses.  Because of this, you can't really put an object
 > into the thread, because the "threader", or whatever you call the code
 > which does the JUMP @(next rpl token) won't know what to do with an
 > object it wasn't expecting.

   A token is always either a pointer to a _type_ or an object, in
which case its first subtoken, the prefix, is a type. There is never
more than one level of indirection here. The types (#2d92 for
programs, #2a2c for strings, #2dcc for code, etc) all begin with
#28fc, which indicates that "this is a type." The code at #28fc
implements the semantics of the _type_. What it mostly does is to call
the type-dependent code implementing the type. I have to confess I am
on very weak ice here, so don't take this as the ultimate truth, it's
merely based on my observations. It's fairly clear that what most data
types do, is to simply push themselves on the stack. Then there are
others (lists and symbolics) that sometimes push themselves on the
stack, and sometimes behave as programs. Finally there are those that
always result in code being executed - programs (#2d9d) and code
(#2dcc). Then we have global and local names that evaluate their
values.

I think that exactly how this works is best illustrated by an example.

The code at #28fc looks like this:

		move.p2	10, c
		sub.a	c, a
		jump	@a

Assume that the next token is a program object. Then the sequence:

		move.a	@d0, a
		add	5, d0
		jump	@a

   Will 1) put the prefix (#2d9d) of the program in A. 2) Advance the
pointer (which now points immediately following the prefix). 3) Read
the contents of #2d9d (which is the "type" prefix #28fc) and jump
there (i.e. to #28fc). Upon arriving at #28fc, A will contain #2d9d.
10 is subtracted from it (the #28fc "type" prefix at #2d9d is preceded
by two addresses that implement certain semantics of "programs") to
yield the address of a pointer to the Eval-specific semantics for
"programs," i.e. a piece of ML code that does whatever "programs" are
supposed to do when they are executed (EVALed). This is where it then
jumps. D0 still points to the word following the prefix, and A is
#2d9d minus 10.

   As to the composition of RPL objects, I would recommend you to take
a look at one of Derek Nickel's postings which explicitly addresses
this. It should suffice to mention here that program, symbolic, and
list objects are almost identical apart from the prefixes.


 > There are some RPL tokens that require an argument, and the cases I
 > have seen of this always have the argument appear as the next token.
 > This is the case for the "type_check" token which appeared in your
 > recent example code, and I strongly suspect that this works with some
 > other tokens.

   Quite correct. Sometimes it's even more complicated, like the type
check routines that take any paired sequence of short tokens and
tokens to be executed if a match was made; up to an end (#312b). If a
match is made the matching process ends and the subsequent token is
executed. Otherwise the current address (in D0) is advanced to the
next pair. When it reaches an end (#312b) a "bad argument type" error
is issued. Some of these type matching functions also play tricks
with the LAST ARG data.


 > When I started looking at internal RPL, I was of the belief that the
 > internal RPL would be a lot like the user RPL. This does not appear to
 > be true. It seems that for any looping structure or IF-THEN structure,
 > or any more complicated structure, it looks like the internal RPL will
 > require different techniques.

   They are not really that much more complicated, just undocumented
and slightly different. They sometimes require knowledge of exactly
how they work to be taken full advantage of. One illustrative example
is loops. My understanding is that they basically consist of a head
and a tail. The head enters the loop (similar to START...NEXT in its
simpler form) and the tail terminates it. Now, what the head does more
exactly is to set the return address *to itself* and call the first
word of the loop. The end of the loop basically does an END, which
causes it to return to the loop head and reexecute it! Since a loop
call frame looks like any other call frame, the possibilities are
almost endless for implementing you own specialized NEXT, or STEP, or
UNTIL, or whatever your heart may desire. The tail can exit the loop
by unwinding one call frame level and continuing.

   You can, if you like, implement a specialized NEXT (or other loop
controls) and put it in a global variable. You can even implement
specialized IFs, skips, what may you desire, as well, by putting them
in a global variables. A small example (make sure your HP-48 is set to
HEX!):


Key in PUSH (ASC format) and de-ASC it:

"CCD20310008F146608DC2016C1CD"


Type:		#61ad8 #5a03 SYSEVAL	@ --> <61ad8>
		PUSH			@ --> External
		'IFFTE' STO

   Now we have stored the If_then_else routine (#61ad8) in IFFTE, or
more precisely, a pointer to it. Type in the following program (notice
that True (#3a81) and False (#3ac0) push themselves on the stack when
evaluated):

		<< IFFTE "TRUE" "FALSE" >> 'X' STO


   Then let's try it out (the @#XXXX notation used means "the object
at address XXXX" - the display will actually read "External" in our
example below):

		#3a81 SYSEVAL		@ --> @#3a81 (True)
		X			@ --> "TRUE"
		DROP
		#3ac0 SYSEVAL		@ --> @#3ac0 (False)
		X			@ --> "FALSE"

   Voila! A new user RPL control structure! Notice that our user
program X above is identical to the following (STAR syntax), with the
exception that << and >> tokens have been added to the user program;
also note that an initial "_" character escapes the implicit STAR
DATA.A of RPL mode:

		RPL
		  2d9d			; Program object
		    _global `IFFTE'	;   Token: Global symbol object
		    _string `TRUE'	;   Token: String object
		    _string `FALSE'	;   Token: String object
		  312b			; End
		ENDRPL


   For clarification, the global name and strings above are made
in-line objects, not pointers.


 > In fact, it appears that for many tasks, the normal user code is much,
 > much simpler, and will probably run nearly as fast.

   Simpler because they are more well-documented, yes. But not faster
by a long shot! System RPL mostly makes type checking superfluous,
except initially, and does most of its arithmetic using 20-bit
integers.

 > Surely there are many highly useful ROM routines that will be nice to
 > use, but aren't those generally accessible from user code using
 > SYSEVAL?

   Yes, of course. But who can read a program consisting solely on
SYSEVALs? Binary integers and SYSEVALs take a lot more space and are
much slower, although they can be "compiled" using Joseph Horn's PACK
program. I personally tend to test algorithms and generally make
prototypes in user RPL, and then simply recode it in system RPL for
robustness, speed, and size. Not everything of course, I find it
generally desirable to code the "workhorse" routines in system RPL and
call them from user RPL that can easily be customized.

 > One key advantage with internal RPL for me is that it can hold a CODE
 > object; a feat of which user code does not appear to be capable.

   This is most useful indeed. And while you're at it, with assemblers
and all, you may just as well do the rest in system RPL... Or at least
embed the ML in some argument checking.

 > I will refrain from asking you anymore direct questions for now, for
 > fear that I will totally overwhelm you.

   Au contraire; I'm glad you posed these questions. I remember
wondering over this too, and I'm sure a lot of people want to know. I
don't claim to know that much about how the HP-48 works; Rick Grevelle
and a dozen other net people probably know much more than I do.
Perhaps explaining the basics will get others started, I hope so -
which is why I spent a little extra effort on this reply (as well as
posted it to the net). I hope I am correct - I'm sure I will quickly
be corrected if I'm not!

						-- Jan Brittenson
						   bson@ai.mit.edu

akcs.dnickel@hpcvbbs.UUCP (Derek S. Nickel) (03/03/91)

Jan Brittenson writes:

>posted it to the net). I hope I am correct - I'm sure I will quickly
>be corrected if I'm not!

You also made a comment anout "very weak ice"...

The value 028FC is a "prolog signature", not an address of some code.
Try unassembling (not unthreading) at, for example, 02911 and you will
see that the fist two instructions (D=D-1 and some HST stuff) are
"unusual", if fact the HST stuff is a NOP.  But those first five nibbles
are used by other parts of the OS to identify a prolog object.

Is this clear (I fear its not).  My point is that 028FC is not an address
but actual machine code.  (unassembling at address 028FC only leads to
madness - trust me - I tried it before I realized what was going on).

Another point:  these prolog addresses (like 02D9D) are just like
prefixed machine code, only the "prefix" is far removed from the routine
that it is prefixing, but the data is close at hand (data follows the
prolog prefix).

exam[;es

examples:

02D9D program prolog (machine code at 02D9D)
   program data
0312bB End Marker...

(ugh! this example is getting out of hand - maybe later)

My most recent "HP 48SX Internals" manual covers this stuff with an
example.

        Derek S. Nickel
,

bson@rice-chex.ai.mit.edu (Jan Brittenson) (03/04/91)

In a posting of [3 Mar 91 06:40:04 GMT]
   akcs.dnickel@hpcvbbs.UUCP (Derek S. Nickel) writes:

 > The value 028FC is a "prolog signature", not an address of some code.
 > Try unassembling (not unthreading) at, for example, 02911 and you will
 > see that the fist two instructions (D=D-1 and some HST stuff) are
 > unusual", if fact the HST stuff is a NOP.  But those first five
 > nibbles are used by other parts of the OS to identify a prolog object.

This is what it looks like:

028fc   Type:
028fc 31a0     move.p2	#a, c
02900 b6a      sub.b	c, a
02903 808c     jump.a	@a
02907 82c20    data.a	span10nib	; ; #2c28
0290c e8030    data.a	L_0308e
02911   type_Short:
02911 cf820    TYPE
02916 487      brcs	L_0298f
02919 1c4      sub.a	5, d1
0291c 141      move.a	a, @d1
0291f 142      move.a	@d0, a
02922 164      add.a	5, d0
02925 808c     jump.a	@a
02929 94c20    data.a	span21nib	; ; #2c49
0292e f7030    data.a	L_0307f
02933   type_Real:
02933 cf820    TYPE
02938 465      brcs	L_0298f
0293b 1c4      sub.a	5, d1
0293e 141      move.a	a, @d1
02941 142      move.a	@d0, a
02944 164      add.a	5, d0
02947 808c     jump.a	@a

   Where is the "D=D-1 and some HST stuff"? (D=D-1 f = DEC.f D; "f" is
the field)

   The prefix at #2911 is #28fc. If A contains #2911, then the
threading instruction "JUMP.A @A" will jump to #28fc. At #28fc, 10
(decimal) will get subtracted from A, after which it will contain
#2911-10 = #2907. The "JUMP.A @A" then jumps indirect #2907, which is
#2c28, where a spanning routine for 10-nybble objects resides. D0
(which points to wherever the #2911 was found, i.e. where the object
is) is advanced by 10 past the 10-nybble object. Similarly, the ML
code at #2c48 advances D0 by 21, which is the size of a real.


 > Is this clear (I fear its not).  My point is that 028FC is not an
 > address but actual machine code.  (unassembling at address 028FC only
 > leads to madness - trust me - I tried it before I realized what was
 > going on).

- Mr. Spock, any madness readings?
- No Sir. Just plain logic.

						-- Jan Brittenson
						   bson@ai.mit.edu

					   Read my lisp: no new classes!

jurjen@cwi.nl (Jurjen NE Bos) (03/04/91)

[A discussion on whether 028FC is an address or two instructions CF and 820]

Well guys! Stop arguing! You both don't understand it.
It is Both!  That's the fun of the whole RPL.
Look at the two examples:

Program		Program
...		...
2A2B4		02911 <digits>
...		...
End		End

Both programs contain a real.  The left program contains a 0, which is on
address 2A2B4 in ROM.  The right program contains the real directly.
Both programs execute the same way, that is that an instruction gets executed
by the well-known sequence:
MOVE.a	(D0),A
ADD.a	#5,D0
JUMP	(A)
In the left case, this sequence jumps to the address 2911, executing code that
pushes the value 2A2B4 on stack (a pointer to the real). This sequence starts
with decrementing D (the free space counter), and does this effective NOP
820.
In the right case, the sequence jumps to 28FC, executing code that jumps
indirect to the address at 2911-#10, which is 2C28.  This code pushes the
address of the real on stack, just as the previous case.

Understood?

akcs.dnickel@hpcvbbs.UUCP (Derek S. Nickel) (03/06/91)

Jurjen,

I don't think we were arguing... Just "networking".  Tossing ideas back
and forth (be sure to duck) is what this group is about, no?  Posting
your ideas gets them out in the open (so they can be blasted apart by the
next poster).  Sooner or later it will a come together (the imfamous
"Unified HP48SX Therom").

And, thank you for your comments.

        Derek S. Nickel