08-28-2019, 12:21 AM
Post: #1
 RobertM Member Posts: 75 Joined: Jul 2016
I'm trying to understand something in the 41C ROM format, particularly where it includes USER code.

I understand the ROM structure, and I know that UCODE areas of the ROM have a 2 word "prefix" that specifies (among other things) the number of regisers and how many bytes in the first register are required to copy the code from ROM to RAM.

Also, the LBL/END chain is calculated correctly for global labels and ENDs in the ROM ... except for the first LBL after a UCODE prefix. It appears that the first global LBL after a UCODE prefix can have any value for the bbbr rrrr rrrr values (hex C bbbr rrrr rrrr F nnnn kkkk ...).

PRINTER ROM, first label after first previx is LBL "PRPLOT", has "C8 00 ..." implying a 4 byte/0 register link.

PPCL ROM, first label after first prefix is LBL "MK", has "C6 00 ...", implying a 3 byte/0 register link.
PPCL ROM, first label after second prefix is LBL "LF", has a "C6 00 ...", also implying a 3 byte/0 register link.
PPCL.ROM, first label after third prefix is LBL "ML", has a "CA 00 ...", implying a 5 byte/0 register link.

Does anyone know the meaning of the first link values after a UCODE prefix in 41C ROMs?

Thanks,
-RobertM
08-28-2019, 04:56 AM (This post was last modified: 08-28-2019 04:57 AM by Ángel Martin.)
Post: #2
 Ángel Martin Senior Member Posts: 1,368 Joined: Dec 2013
I've put together multiple ROMS with FOCAL programs in them and normally I use the HEPAX as an intermediate step: write your FOCAL code, use COMPILE & HSAVEP, then your code is already in ROM format. That takes care of those prefix and post-fix words for you.

The label's link distances are therefore inherited from their value in RAM, nothing fancier than that. So I've always assumed that those were not used at all when the code is in ROM: after all there's no need for the CHAIN there (contrary to the RAM case), so it doesn't matter what values are stored - they'll be ignored anyway.

This is supported by the fact that the FOCAL programs so prepared can be placed anywhere in the ROM, and modifying their location won't mean a thing: they'll be found by the CAT\2 methods using [ARSCH] and [GTRMAD], which don't need the CHAIN.

Cheers,
ÁM
08-28-2019, 01:57 PM
Post: #3
 RobertM Member Posts: 75 Joined: Jul 2016
(08-28-2019 04:56 AM)Ángel Martin Wrote:  The label's link distances are therefore inherited from their value in RAM, nothing fancier than that.

Thanks Ángel ... that's what I was assuming.

Unfortunately that makes it a little difficult to "solve" my problem. I was assuming that a test case for a disassembler/assembler could be to disassemble a ROM to SRC, then re-assemble that SRC back to ROM and do a binary file compare of the original and reassembled ROMs. This works fine for ROMs that don't contain UCODE. But for those that do, those "first LBL linkages" cause a problem. Guess I could just provide a "hint" in a comment from the disassembler as to which "Cx yz" value to use when assembling. Rather inelegant, but should work.
09-01-2019, 04:42 AM
Post: #4
 hth Senior Member Posts: 414 Joined: Mar 2014
The global label links are the same as in RAM and they are fully deterministic in a ROM stored program.

What happens in COPY is that the old .END. becomes a new local END. Then copying takes place from ROM to RAM. The two words tell the number of registers needed (to ensure there is enough space for it) and the number or bytes to load for the first register. The reason for the latter is that the .END. is always right aligned in a register. Thus, it inserts 00 bytes to ensure that the END eventually coming from the ROM will end up right aligned in its register, as this will be the new .END.

In other words, the link from the first global alpha should point to the preceding END (which was the .END. before COPY), and given the algorithm, its location is known and always the same relative to the copied program.
09-04-2019, 08:41 PM
Post: #5
 RobertM Member Posts: 75 Joined: Jul 2016
(09-01-2019 04:42 AM)hth Wrote:  The global label links are the same as in RAM and they are fully deterministic in a ROM stored program.

...
In other words, the link from the first global alpha should point to the preceding END (which was the .END. before COPY), and given the algorithm, its location is known and always the same relative to the copied program.

I think we are talking about different things. I don't believe the global links in ROM are deterministic from just the source...they depend on how they were copied from RAM when put into a ROM. What I mean by this is that just having the source to the printer ROM, I can not generate a ROM file that will match exactly the original printer ROM, because I can't know what values to use in the Cx yz positions for ANY first LBL after a two word UCODE intro. (Examples are the Cx yz values for PRPLOT in the printer ROM, or MK, LF, ML, or LB for the PPCL.ROM).

Also, as Ángel said, you can move these UCODE blocks around in the ROM (e.g. put some MCODE before one) without changing the Cx yz values in the first LBL and things still work.

Curious if you disagree and if so, how you can determine those values from just the source.
09-04-2019, 09:45 PM (This post was last modified: 09-04-2019 09:47 PM by hth.)
Post: #6
 hth Senior Member Posts: 414 Joined: Mar 2014
(09-04-2019 08:41 PM)RobertM Wrote:
(09-01-2019 04:42 AM)hth Wrote:  The global label links are the same as in RAM and they are fully deterministic in a ROM stored program.

...
In other words, the link from the first global alpha should point to the preceding END (which was the .END. before COPY), and given the algorithm, its location is known and always the same relative to the copied program.

I think we are talking about different things. I don't believe the global links in ROM are deterministic from just the source...they depend on how they were copied from RAM when put into a ROM. What I mean by this is that just having the source to the printer ROM, I can not generate a ROM file that will match exactly the original printer ROM, because I can't know what values to use in the Cx yz positions for ANY first LBL after a two word UCODE intro. (Examples are the Cx yz values for PRPLOT in the printer ROM, or MK, LF, ML, or LB for the PPCL.ROM).

Also, as Ángel said, you can move these UCODE blocks around in the ROM (e.g. put some MCODE before one) without changing the Cx yz values in the first LBL and things still work.

Curious if you disagree and if so, how you can determine those values from just the source.

Eh, what?

Well, we probably talk past each other, but I will try to make my point of view again then.

I determine the values by means of the 'rpncomp' tool I have. It takes an RPN source file (or .raw) and turns it into an RPN program represented by an internal data structure (on the host computer). It was a long time ago I stopped fiddling with putting together ROMs on the HP-41 itself. The tool compiles local branches (GTO/XEQ) as well as build the global chain.

Take a look at the COPY routine in mainframe (HP-41 firmware) and how it works. It uses the old .END. to become a new local (plain ordinary) END. Then program bytes are copied into registers. The two extra words serve two purposes, to tell how many registers are needed (for PACKING - TRY AGAIN) and to tell how many NOPs go into the first register to fill up registers evenly. No new .END. is created by the COPY code, it is the END if the RPN program in ROM that becomes the new permanent .END..

The .END. must be right aligned in the register (by design), which is the reason why it pads with initial NULLs.

If you look at the source of COPY, you will see that the only thing it really does with the ENDs and global labels is to change the old .END. to be a new END. Nothing else is done with global chain instructions. Thus, I conclude that the END from ROM is the new .END. and as no global label is modified in any way, the global chain has to come from the ROM image. As it is a chain, it has to be chained all the way from the .END. and up. There can be previous programs in memory already, but we only need connect to the nearest global chain instruction, and that is the previous .END. which we just turned into an ordinary END. Furthermore, we know the distance to it, as we know it is right aligned in the previous register (as it was an .END.) and we know the number of pad NULLs. So we can put that distance in the first global label of the ROM stored program.

The global label chain does not matter at all as long as the program is in ROM, where we use XROM and the FAT, but it is crucial when the program is in RAM. As the COPY routine does nothing with them, they have be correct (and deterministic) when stored in ROM for COPY to work properly.

If you think about it, the COPY routine is remarkably short for what it does and it can only be this short because the original developers of the HP-41 were a group of very clever people that probably spent quite some time designing and fitting all this functionality into the small space available.

So I stand on my island and say the global chain matters and that there can only be one link value in global LBLs to make it all work properly.

On the other hand, you can of course represent a program from a source listing in different ways, as you can insert dummy NULLs and use alternative short/long forms of some instructions, such as RCL, STO, LBL, GTO. Which means that a printed program may not be identical if it is compiled to ROM again. That is a different story and it is perhaps here I misunderstood the question?
09-05-2019, 12:10 AM
Post: #7
 RobertM Member Posts: 75 Joined: Jul 2016
(09-04-2019 09:45 PM)hth Wrote:  So I stand on my island and say the global chain matters and that there can only be one link value in global LBLs to make it all work properly.

Aha! (slaps forhead!!) I finally understand! I think your "island" is solid ground. Sorry for being dense.

So basically, the first label in the UCODE block has the link setup to include the number of nulls generated (1 - number of bytes in first register) and then +3 for the "next" next link in the chain, which it can assume is right justified in a register (because it was the old .END. before the COPY). And the linkages don't matter while they exist in ROM.

As you pointed out, those engineers were extremely clever in their design solutions.

Thanks for all the help!

-Robert
09-05-2019, 01:27 AM (This post was last modified: 09-05-2019 02:21 AM by hth.)
Post: #8
 hth Senior Member Posts: 414 Joined: Mar 2014
(09-05-2019 12:10 AM)RobertM Wrote:  So basically, the first label in the UCODE block has the link setup to include the number of nulls generated (1 - number of bytes in first register) and then +3 for the "next" next link in the chain, which it can assume is right justified in a register (because it was the old .END. before the COPY). And the linkages don't matter while they exist in ROM.

As you pointed out, those engineers were extremely clever in their design solutions.

Thanks for all the help!

-Robert

I would change that 1 to a 7.

In my RPN compiler tool I start with (3 + nullsInFirstReg) as initial offset for global chain in my tools. Then I increase it by instruction size if it is not a global chain instruction. If it is a global chain, I set the offset to its instruction size and carry on. Some source maybe explains it better (or not):
Code:
buildChain :: [Node RpnInstr Context] -> (Int, [Node RpnInstr Context])
buildChain prog =
let (_, prog') = mapAccumL chain (3 + nullsInFirstReg) prog where
chain offset node@(INode rpn ctx) =
let chained = (size, INode (RpnChain rpn [byte0, byte1]) ctx)
size = sizeof rpn
(regs, bytes) = offset divMod 7
packedDistance = (bytes shiftL 9) .|. regs
byte0 = fromIntegral $192 .|. (packedDistance shiftR 8) byte1 = fromIntegral$ packedDistance .&. 0xff
in case rpn of
END -> chained
LBL (OpAlpha _) -> chained
otherwise -> (offset + size, node)
nullsInFirstReg = 7 - bytesInFirstReg
bytesInFirstReg = snd $registerCount progSize progSize = sum$ map (sizeof . rpnInNode) prog
in (progSize, prog')

I should add that this is based on studying the source code of COPY and it was a while back. I did a restudy a few days ago to answer in this thread. I have not looked at how existing native MCODE RPN loaders (there were more than one, I even wrote one a long time ago) do it, as it may pose a problem to them when the first global alpha label is not first. I mean, it feels somewhat non-trivial to do this in MCODE, at least to me.

But otherwise it makes perfect sense. It also explains those annoying single superfluous END instructions inserted and it makes the COPY routine surprisingly simple. It is really very clever.
09-05-2019, 07:50 AM (This post was last modified: 09-05-2019 07:50 AM by Ángel Martin.)
Post: #9
 Ángel Martin Senior Member Posts: 1,368 Joined: Dec 2013
(09-04-2019 09:45 PM)hth Wrote:  Take a look at the COPY routine in mainframe (HP-41 firmware) and how it works. It uses the old .END. to become a new local (plain ordinary) END. Then program bytes are copied into registers. The two extra words serve two purposes, to tell how many registers are needed (for PACKING - TRY AGAIN) and to tell how many NOPs go into the first register to fill up registers evenly. No new .END. is created by the COPY code, it is the END if the RPN program in ROM that becomes the new permanent .END..

We're in agreement that the program length needs to be included in ROM as well, otherwise COPY won't work. But that's not the same as the CHAIN information, right? There's no need for a pointer to the following global LBL or END instruction in ROM, I always assumed that those were written (in RAM) by COPY as part of the action...
09-05-2019, 04:32 PM (This post was last modified: 09-05-2019 04:33 PM by hth.)
Post: #10
 hth Senior Member Posts: 414 Joined: Mar 2014
(09-05-2019 07:50 AM)Ángel Martin Wrote:
(09-04-2019 09:45 PM)hth Wrote:  Take a look at the COPY routine in mainframe (HP-41 firmware) and how it works. It uses the old .END. to become a new local (plain ordinary) END. Then program bytes are copied into registers. The two extra words serve two purposes, to tell how many registers are needed (for PACKING - TRY AGAIN) and to tell how many NOPs go into the first register to fill up registers evenly. No new .END. is created by the COPY code, it is the END if the RPN program in ROM that becomes the new permanent .END..

We're in agreement that the program length needs to be included in ROM as well, otherwise COPY won't work. But that's not the same as the CHAIN information, right? There's no need for a pointer to the following global LBL or END instruction in ROM, I always assumed that those were written (in RAM) by COPY as part of the action...

I would also have assumed that, but after studying the source of COPY carefully, I cannot see any other explanation that the first global link must be the value that makes it line up with the END (previously right aligned .END.), in the previous register.

A clever thing here is that we for sure know the position of the preceding END and that it must exist by design, it is a beautiful trick. COPY does not need to bother about the global chain at all (apart from flipping a bit and updating one of the pointers in the 'c' register). The only downside is that it potentially leaves a trail of ENDs in CAT 1, to annoy the innocent (GTO .. pressing) user who likes to copy a lot of programs from ROMs.

I now took a look at my STOPRG which moves a RAM program into MLDL RAM. This one has never been published, and has a known bug, it will not clean up if it runs out of space. It was quite a throw back, I am amazed that I even recompile short GTO into long ones, which invalidates the global chain (in ROM), and it later goes into rebuilding the global chain again (in ROM), and has these two snippets:
Code:

STP21 C=C+1 M
C=C+1 M
C=C+1 M
C=0 S&X
WRIT Q

[..]

;NOW ENTER ALL GLOBAL LABELS
;INTO FAT...
;SINCE WE MAY HAVE BLOWN THE GLOBAL
;CHAIN DUE TO PROGRAM LENGTH CHANGE,
;WE MUST RECALCULATE THE GLOBAL
;CHAIN COMPLETELY!
A=C M ;FIX GHOST CHAIN TO NEXT END
C=C-1 M ;(THIS WILL BE THE .END.
FETCH ;WHICH EXISTS BEFORE WE
RCR 1 ;PERFORM A COPY!!!!!)

That old code does not seem to change global XEQ to XROMs though, so it has a few short-comings.

So at least I also did it 35 years ago. I am not sure about how other ROM loaders are written.
 « Next Oldest | Next Newest »

User(s) browsing this thread: 1 Guest(s)