The Museum of HP Calculators

HP Forum Archive 17

[ Return to Index | Top of Index ]

HP-41: a M-code question
Message #1 Posted by JMBaillard on 28 June 2007, 3:28 p.m.


does anyone know how to program in M-Code an instruction like XEQ IND 00 ?

It seems that

?NCXQ 24C7 is the entry point

with SETF7 for XEQ or CLRF7 for GTO

-But in what CPU register must be stored the address 00 ?

Regards, JMB.

Re: HP-41: a M-code question
Message #2 Posted by Howard Owen on 28 June 2007, 4:27 p.m.,
in response to message #1 by JMBaillard

User register 0 is not in a CPU register. It's in the user memory "peripheral." See pages 31-33 of "HP41 MCODE for Beginners" for a description of the layout of user memory. (Register 0 is at $1FF of the user memory peripheral.) You access peripherals with the WRIT and READ instructions, after selecting the peripheral number with PRPHSLCT. HP41 user memory is peripheral 0.


Re: HP-41: a M-code question
Message #3 Posted by Meindert Kuipers on 29 June 2007, 4:36 a.m.,
in response to message #1 by JMBaillard

This is a bit strange entry point to be calling from Mcode, since it will call a numeric label in User Code, so this makes me a bit curious why you would want to do this. I have done something comparable in the past, a Mcode routine to skip a certain number of (User Code) lines with SKPLIN at 0x2AF9. Could be useful after a flag test or compare.


Re: HP-41: a M-code question
Message #4 Posted by JMBaillard on 29 June 2007, 5:16 p.m.,
in response to message #3 by Meindert Kuipers


in fact, I'm not quite sure of this entry point but I would like to write a Gauss-Legendre integrator in M-code in which the constants could be inserted, thus avoiding the use of data registers to store these constants ( or very slow execution if they are in the "standard" program itself )

I've read in "Mcode for beginners" how to use status register c to find the absolute address of register R00, and I wonder if something like

... ( storing this absolute address in a proper CPU-register ) SETF7 ?NCXQ 24C7 would be equivalent to XEQ IND 00

Regards, JMB.

Re: HP-41: a M-code question
Message #5 Posted by Doug on 30 June 2007, 9:54 a.m.,
in response to message #4 by JMBaillard

I haven't tried that before. It does seem that f7 controls GTO/XEQ at that point. Looking @ ADRFCH 0004, it seems the register address is in the internal flag register, at 0008 it recalls it to C, clears the indirect bit then tests to see if it is a stack register (with ?FS 6, 5, 4).

If it is a stack register it stores the address in N then reads the register and returns with the unconverted LBL number (or text string) in C. Otherwise it's doing something with B in CHKADR.

A better way to implement such a thing might be to enter @ 24CC with the required LBL # or text string in C (in unconverted form), however, at that point R(X) controls the XEQ/GO choice. So one needs to execute SAVRC (27DF) before the above entry, or SAVRTN (27D3) if only XEQ is of interest.

It is not clear to me how you will use the function, SAVRTN will push the address located just after your function (actually the last byte of your function) in program memory, so it may be necessary to decrement the address twice before applying SAVRTN, yet then you are still presented with the problem of where you are at within the loop (no data are preserved within the CPU across user code calls). Initialize a counter in a ram register?

If you only want to execute a numeric LBL, time can be saved by skipping the BCDBIN call and just enter @ 24D1. Eg: something like:





LDI binary LBL number 0 to 63 (see the test @ 24D4)


Next time you see the process will be at your function entry point. So you will need some kind of counter in a ram register for branch purpose at entry to function, the fastest type counter would be a text string in say R00 that initializes to text null (simple increment in binary).

Another speed up would be to save the address of the found subroutine and your function call somewhere in ram register so no search is necessary after initial call. Both these addresses would fit in the high nibbles of R00 if R00(X) is used for the counter.

I haven't considered the problem of keyboard execution of the function, it may be impossible to "come back" or require exotic poll programming (how about we put it @4000 ? ,just kidding).


Re: HP-41: a M-code question
Message #6 Posted by JMBaillard on 30 June 2007, 4:55 p.m.,
in response to message #5 by Doug

thank you all for these informations
Best regards

Re: HP-41: a M-code question
Message #7 Posted by Doug on 30 June 2007, 7:17 p.m.,
in response to message #6 by JMBaillard

Now you have me thinking about your interesting idea.

Why bother with a LBL at all? Here is an idea, say your function name is, for example, GAUSS9. Then follow the function with the integrand! In this case one already knows where the entry to the user supplied integrand is, it might look like: (for example; F(X)=X**2+SIN(X))

store limits/step size/etc. at specified registers


The execute address is already present in the user PC, the last byte of GAUSS ("fall into" the subroutine). One just needs to set up R(X) so that when RTN is executed it returns to the byte before GAUSS9.

Lots more details, such as where do we go when the function is done? etc. This could be the fastest integrator method.


Re: HP-41: a M-code question
Message #8 Posted by JMBaillard on 1 July 2007, 5:21 p.m.,
in response to message #7 by Doug


I've tested the following M-code routine:

34D ?NCXQ 09C 27D3 378 READ13(c) 03C RCR3 270 RAMSLCT 038 READ DATA 331 ?NCXQ 090 24CC ..........

After storing the global label name in R00, it works, even if we SST the program in main memory. Unfortunately, the following M-code instructions ( .... after ?NCXQ 24CC ) are NOT executed!

So it seems impossible to write a Gaussian integrator this way. I did it in another way with the Gauss 5-point formula after coding in M-code the 5 coefficients, but using 5 entries in the FAT just for numbers is wasteful, and it would be even worse for the 16-point formula...

Regards, JMB.

P.S: I've read your last post and I don't really understand how it works: the Gaussian formula needs to compute the function at several arguments, so I don't see how the M-code routine could change these arguments if it is inserted in the function itself. ( sorry I'm only a beginner in M-code ... )

Re: HP-41: a M-code question
Message #9 Posted by Doug on 2 July 2007, 8:13 a.m.,
in response to message #7 by Doug

     "the Gaussian formula needs to compute
     the function at several arguments, so I don't see
     how the M-code routine could change these arguments
     if it is inserted in the function itself."
Guess I am not being sufficiently clear: A user code subroutine cannot be called by a microcode function, it will never return.

Thus one needs to use a "trick". One possible trick is to push the address of the function into the user code return stack so that when a user code RTN is hit, it returns to the function. It will return to the microcode at the entry point of the function.

In this code:

GAUSS9   your function
SIN      user code subroutine, f(X)
RTN      must end with RTN

SIN LASTX X^2 RTN is repeatedly executed until the integral is completed, for 5 point gauss with 10 sections it is executed 50 times. Your function must supply the value of X in X at each point and do your summation of the integral upon reentry (because at that point the result of the integrand, f(X), will be in X).

At entry to the function, it is necessary to determine which time we are going through the function, thus a ram counter is required, that's what the CLA ASTO 00 lines accomplish: initialize the counter (or use CLX STO 00).

At entry, READ e RCR 3 RAMSEL READ and the counter is now in C. If it's zero we know that this is the first time through (a result is not in X). Then increment the counter and WRITE (for next time through).

Except for the first time through: multiply f(X) (now in X) by the appropriate weight (determined by the counter) and sum it into a ram register. Every time through, except the last, using the counter, get the next abscissa and put it in X.

The function then modifies the user code return stack and falls into the user code subroutine. Here is what the exit from your function could look like (except for the last time through):

B=A  WPT           save address of PC for re-insert
XQNC 29C8 DECAD+1  backup two bytes
XQNC 27D5 SAVR10   it's our return address
GONC 24F3 XEQ20+1  put it in the user code return stack

For the last time through, one could simply recall the value of the integral and put it in X and stop. Or one could jump to the line following the RTN and use user code to recall the result. Here is one way to do that:

LDI  085  (hex value of RTN line, there must be a RTN)
C=0  XS
A=C  X
?A#C X
JNC  +3

As far as generating the abscissas and weights one option to consider is using synthetic functions RCL N and RCL M. One uses a synthetic 14 character text string in program then the above lines get the full precision data into X, for storage into registers.

Or, the first time into the function, the data could be generated and placed into registers, that's a lot of load digit instructions! Anyway, for multiple sections, ram registers should be used to avoid generating the Xi and Wi repeatedly.

On the other hand, if you are determined to not use registers for the Xi and Wi, or to generate higher order coef, a table grabber could be used so that each word holds a byte, instead of LD which takes two words to load a byte. For 12th order the table would be 84 words and possibly 10 words for the grabber.


Edited: 2 July 2007, 9:50 a.m.

[ Return to Index | Top of Index ]

Go back to the main exhibit hall