The Museum of HP Calculators

HP Articles Forum

[Return to the Index ]
[ Previous | Next ]

41 SOLVE & INTEG Bank-switched Implementation.

Posted by Ángel Martin on 21 June 2013, 11:12 a.m.

Programming highlights: MCODE Cathedrals.

Often when visiting a landmark or a commemorative building we feel the imposing presence of something that’s bigger than what any possible description could convey, and we proceed tip-toeing, speaking in whispers not to disturb the spirit of its creators... this is exactly how I feel about the addition of the SOLVE and INTEG “cathedral of MCODE” to the SandMath!

Leaving the mathematical prowess and attributes aside - as tremendous as they are – the housekeeping chores and implementation on the 41 platform are nothing short of spectacular. The original programmers we’re told adapted the already-existing code form the HP-15C, but they had to overcome a couple of real challenges to port it smoothly into the 41 platform. Possibly the 15C also required similar trickery, but I don’t know its internal structure so I can’t say.

The first striking thing is of course being able to return to an MCODE code stream after executing a user code program (FOCAL) – which calculates f(x). That alone can leave you thinking – as it did to me, suspecting the hows and the abouts being explained somehow by SIRTN and SILOOP, the two auxiliary functions written for this purpose. – Yet how did they exactly work? We need to understand the buffer-14 paradigm before we can answer this.

The unsung Hero

Yes, there’s the question of Buffer-14, the dedicated buffer in the Advantage that exhibits a rather idiosyncratic temperament: contrary to all other modules, the Advantage seems to be on a “search and destroy” mission, with the apparent aim to kill any previous existence of the buffer, judging by the polling events CALC_OFF and IO_SRVC.

Equally intriguing is the location of said buffer, which is situated (while it’s allowed to exist) below the Key assignments area – and not above it as it’s the normal way. This fact conflicts with the OS routines that manage the I/O area, like [PKIOAS] and others, and would create real havoc if it weren’t because the Advantage manages the buffer dynamically, creating it on-the-fly just when the execution starts, and killing it upon termination. So as far as the rest of the machine is concerned (OS included), it is as if buffer-14 had never existed!

But why all that hassle, you’d ask? Couldn’t they have used the normal approach to hold whatever data that needed to be stored in a standard-type buffer, like every other implementation does? I believe the reason was to have an absolute location for the buffer registers: with the starting location for buffer-14 always being 0x0C0 (192 dec) the access and retrival of the values stored there becomes a much easier affair, just using their fixed “register numbers”. This may have made using the 15C algorithms simpler, and avoids altogether the relative addressing problem presnt when the buffers are placed in their “regular” space (which incidentally I became very aware of while writing the 41Z complex stack buffer implementation).

However one of the implications of wedging a buffer below the key assignments area is that the code would first need to move them all – as well as all other buffers already present – up in memory, to make room for the newcomer. And conversely, this will have to be undone upon termination of the function execution.

Now you can imagine the housekeeping chores required, and the intricacies of the implementation in the code. That’s why the IO_SRVC event is constantly checking for the presence of buffer-14, proceeding to its removal if found at a non-suitable time.

Let’s add to this mounting MCODE nightmare the requirement that both SOLVE and INTEG would work in a nested way, which is something that the code will only discover having already created the buffer for the first function – so the buffer would have to be resized on the fly, not losing any previous information already contained.

Entering the Paralell Dimension.

And adding insult to injury, welcome to the parallel dimension of bank-switching: imagine now attempting to do all that from within an auxiliary bank (say bank-3), which when activated would not know a thing about the main one (little details like the FAT, etc); so it’s there to live and die by its own sword. Case in point being: what if the function f(x) to solve or integrate contains functions available within the same module, how then could they be found?

Well at least this one has an easy answer: bank-1 needs to be the active one while the FOCAL program runs, thus obviously the main FAT is also there and all will work out. So provided we can identify the exact points in the code where the execution is transferred to the FOCAL label we’d be home free, or would we? But beware, because then not only the MCODE execution needs to be resumed (how is still pending clarification), but it’ll also have to re-activate bank-3 as the very first thing it does.

Buffer-14 comes to the rescue.- Say there are two auxiliary functions, one of them SIRTN is sought for during the initialization, and its address is placed in the RPN return stack, just above the other address for the global LBL that calculates f(x). This will ensure that SIRTN will run after LBL f(x) is finished, ok so we’ve got control back – what to do with it? Say now that the second auxiliary function SILOOP is the very first (and only) line in SIRTN, that’ll send the execution back to our MCODE – way to go, but this is a new function that has no recollection of the past or knows nothing about whatever was done before, unless...

Unless of course we use the buffer as data structure to do the parameter passing! Isn’t this brilliant? Yes of course, that’s the answer: SILOOP will retrieve the necessary information from buffer-14 to resume, picking up exactly where it was left off prior to calling LBL f(x). Mind you. It’ll also have to make sure things are as expected when it “wakes up”: is the buffer there, which function was run (SOLVE or INTEG), and react adequately if some of the information is not there. This can happen if a user programs SILOOP inadvertently, of course (although they could have make it non-programmable I suspect they didn’t care anyway).

The last refinement included to speed up things was to also store in the buffer the addresses of both LBL f(x) and SIRTN itself, thus there’s no need to search for them in every iteration of the solution, and we know there may be from several to many depending of the difficulty of the function. Consider that the OS routine [ASRCH] is used to locate them both, and it’s a sequential search: first RAM for LBL f(x), then ROM – and there may be several plugged in.

You'll no doubt notice that in the new SandMath there are only three functions related to this: FROOT, (not so fruity :-), FINTG, and FLOOP (the fluppy one :-) – which sure correspond to SOLVE, INTEG, and SILOOP. But what about the whereabouts of SIRTN? No, it’s not one of the section headers - already used for other purposes- , and nor is it in the secondary FAT (that’d be impossible to pull off) – fortunately this is one of the added pluses of going bank-switched: SIRTN is in the FAT of bank-3 , all by itself so it’ll be found while [ASRCH] is called from the MCODE... all that extra work payed off and so we saved a precious FAT entry in the main FAT.

All in all, a stroke of genius - with all the ingredients of a work of art if you ask me. So I feel especially glad to finally have cracked this nut and include it in the SandMath, the yellow ribbon around the box.

Hope this dissertation wasn’t too boring, and that you enjoy it at least as much as I did working on it, although I doubt that can be possible.

Edited: 21 June 2013, 3:47 p.m.


[ Return to the Message Index ]

Go back to the main exhibit hall