Last edited · 4 revisions  

 


Before going much deeper into the system ROM code, let's take a look at how memory is arranged from a hardware and software point of view.

Here's a diagram showing the HP-85 memory from a hardware point of view:

It's a 64 Kbyte address space, 32 Kbytes for ROM and 32 Kbytes (-256 bytes) for RAM, plus that last 256 bytes of I/O memory mapped I/O registers that take the place of the last 256 bytes of RAM.  All of the plug-in ROMs share the same address space with system ROM 000.  Which one is mapped in at any given moment depends upon the ROM number that was last written to I/O register RSELEC (177430).

Here's a diagram showing how the system code uses the RAM, both in the DE-allocated state and in the ALLOCATED state:

I won't go into much depth at all about ALLOCATION.  It EXISTS primarily to make it both easy and quick to EDIT the program (de-allocated), and then easy and quick to RUN the program (allocated).  When you type in an ASCII line (be it a program line, a calculator mode statement, or a calculator mode expression), when you press END LINE, the line is read from the CRT's RAM (separate from the system RAM) into INPBUF, a buffer in the system reserved RAM, from which it gets PARSED (converted to the internal RPN 'tokenized' form) on the R12 stack.  If that completes successfully, then the tokenized line gets edited into the program (if it's a program line) or executed in place and then discarded.  If any variables are 'created' in calculator mode, then they're allocated in the higher memory area between CALVRB and LAVAIL.  When you RUN or CONTINUE a program, the calculator mode variables are discarded (although if the program is PAUSEd, you could create new calculator mode variables and/or access program variables, but when you CONTINUE execution of the program those calculator mode variables are, again, deleted before execution of the program resumes.  We'll cover allocation in more detail another day.

After power-on initialization (with NO plug-in ROMs that steal RAM and move FWUSER stuff to higher addresses), this is the contents of the beginning and end of the 'user' memory on a 32K RAM machine, along with where some of the system pointers 'point:'

103300 "MAIN"                          (30 bytes PCB)  FWUSER FWPRGM FWCURR
103304 000 000 100 000
103310 000 000 000 000 000 000 000 000
103320 000 000 000 000 000 000 000 000 (end of PCB)
103330 231 251 002 031 016             (A999 len=2 STOP EOL)
103335                                 NXTMEM, TOS, STSIZE, R12
 ...
177315                                 (62 bytes empty GOSUB/RETURN stack) RTNSTK CALVRB LAVAIL
177377                                 LWAMEM FWBIN NXTRTN

The remainder of this article will be focused on the MAIN Basic Program and the areas between LAVAIL and LWAMEM in the above diagram.

 

BINARY PROGRAMS

If, immediately after power-on, the first COMMAND is LOADBIN "SomeBPGM", then this happens (for the sake of this example, we'll posit that SomeBPGM is exactly 100 (octal) bytes long):

103300 "MAIN"                          (30 bytes PCB)  FWUSER FWPRGM FWCURR
103304 000 000 100 000
103310 000 000 000 000 000 000 000 000
103320 000 000 000 000 000 000 000 000 (end of PCB)
103330 231 251 002 031 016             (A999 len=2 STOP EOL)
103335                                 NXTMEM, TOS, STSIZE, R12
 ...
177215                                 (62 bytes empty GOSUB/RETURN stack) RTNSTK CALVRB LAVAIL
177277                                 FWBIN NXTRTN
177300                                 BINTAB (100 bytes, 177300-177377 inclusive, containing SomeBPGM)
177377                                 LWAMEM

Notice that the RTNSTK, CALVRB, LAVAIL, FWBIN, and NXTRTN pointers have all been adjusted upward (to lower addresses) by the length of the BPGM, and BINTAB has been set to point at the beginning of the BPGM.

 

GOSUB/RETURN STACK

Now, let's imagine that, after power-on and the LOADBIN, we type in these two program statements:

10 GOSUB 20
20 PAUSE

We'd have this (DE-allocated) memory map:

103300 "MAIN"                          (30 bytes PCB)  FWUSER FWPRGM FWCURR
103304 000 000 100 000
103310 000 000 000 000 000 000 000 000
103320 000 000 000 000 000 000 000 000 (end of PCB)
103330 020 000 004 133 040 000 016     (0010 len=4 GOSUB 2-byte-line#(0020) EOL)
103337 040 000 002 227 016             (0020 len=2 PAUSE EOL)
103344 231 251 002 031 016             (A999 len=2 STOP EOL)
103351                                 NXTMEM, TOS, STSIZE, R12
 ...
177215 xxx xxx xxx xxx xxx             (top of GOSUB/RETURN stack) RTNSTK CALVRB LAVAIL
177222 xxx xxx xxx xxx xxx
177227 xxx xxx xxx xxx xxx
177234 xxx xxx xxx xxx xxx             (62 bytes empty GOSUB/RETURN stack, 5 bytes per entry)
177241 xxx xxx xxx xxx xxx             (xxx are unused entries on the GOSUB/RETURN stack)
177246 xxx xxx xxx xxx xxx
177253 xxx xxx xxx xxx xxx
177260 xxx xxx xxx xxx xxx
177265 xxx xxx xxx xxx xxx
177272 xxx xxx xxx xxx xxx
177277 000                             (unused byte) FWBIN NXTRTN
177300                                 BINTAB (100 bytes, 177300-177377 inclusive, containing SomeBPGM)
177377                                 LWAMEM

Now, if we RUN this program, after the PAUSE on line# 20 happens, we'd have this (ALLOCATED) memory map:

103300 "MAIN"                          (30 bytes PCB)  FWUSER FWPRGM FWCURR
103304 000 000 140 051
103310 000 000 000 000 000 000 000 000
103320 000 000 001 000 000 000 000 000 (end of PCB)
103330 020 000 004 133 037 000 016     (0010 len=4 GOSUB 2-byte-line#(0020) EOL)
103337 040 000 002 227 016             (0020 len=2 PAUSE EOL)
103344 231 251 002 031 016             (A999 len=2 STOP EOL)
103351 002 000                         (length of program variable space, including these two bytes)
103353                                 NXTMEM, TOS, STSIZE, R12
 ...
177215 xxx xxx xxx xxx xxx             (top of GOSUB/RETURN stack) RTNSTK CALVRB LAVAIL
177222 xxx xxx xxx xxx xxx
177227 xxx xxx xxx xxx xxx
177234 xxx xxx xxx xxx xxx
177241 xxx xxx xxx xxx xxx             (xxx are unused entries on the GOSUB/RETURN stack)
177246 xxx xxx xxx xxx xxx
177253 xxx xxx xxx xxx xxx
177260 xxx xxx xxx xxx xxx
177265 xxx xxx xxx xxx xxx
177272 002 330 206 336 206             (CSTAT, PCR, RETURN ADDRESS) NXTRTN
177277 000                             (unused byte) FWBIN
177300                                 BINTAB (100 bytes, 177300-177377 inclusive, containing SomeBPGM)
177377                                 LWAMEM

Offset 22 in the PCB (103322 in this example) is P.GCNT which is the count of how many NESTED GOSUB RETURN addresses are on the GOSUB/RETURN stack, and it's been incremented to 1. If we look down in the bottom area (high addresses), we see 002 330 206 336 206 at address 177272. This is the RETURN ADDRESS on the GOSUB/RETURN stack as a result of the GOSUB 20 on line# 10. The first byte (002) is the value of R16 (CSTAT) when the GOSUB occurred, the next two bytes (330 206 = 103330) is the value of PCR when the GOSUB occurred, and the last two bytes (336 206 = 103336) is the value of R10 (PC) when the GOSUB occurred (where to RETURN to).

PCR is a 2-byte variable in the system reserved RAM which always points to the start of the line currently being executed (pointing to the line#).  In this case, if you look at address 103300, you'll see it's line# 10. The PC (R10) is the Program Counter, the pointer into the program's "stream of tokens" to the next token to be interpreted (and/or the addresses and values that act as arguments to the tokens).

We can also see here that there's an unused byte at the end of the GOSUB/RETURN stack.  Whether this was an oversight of the system designers that didn't cause any problems, or whether it was intentional to make the address manipulations easier and take fewer ROM bytes of code, who knows?

But another thing all of this demonstrates is the difference between a DE-allocated program (one that's being edited) and an ALLOCATED program (one that's running).  To ease the editing process, as lines are being moved around, added, and deleted, the program is DE-allocated at that point.  This means that:

  • There is NO variable storage allocated for the variables.
  • All variable references in the program are actual variable NAMES (stored in two bytes).
  • All line references in the program are actual line NUMBERS (stored in two bytes).

There's no variables in the above program, but there is the line# reference on line# 10 (GOSUB 20).  If you look at the DE-allocated memory dump above, you'll see that the 133 (GOSUB token) at 103333 is followed by two bytes: 040 000.  This is the BCD line# of the GOSUB target: 20 00 (least-significant byte first, this is a "little-endian" architecture).  But if you look at the same point in the ALLOCATED memory map, you'll find those two bytes have been changed to 037 000. That is now an OFFSET into the program (offset from FWCURR).  If we add 000037 to FWCURR (103300) we get 103337, which is where line# 20 starts. Additionally, you'll notice that two bytes have been added between the end of the program and the (empty) R12 operating stack.  This is where variables (if we had any) would be stored, and these first two bytes are the LENGTH of the variable storage area, which is just 2 since we don't have any variables.  But allocation and de-allocation is a large enough subject to deserve an article of its own, so we won't delve any deeper into that now (I keep saying).  BUT... you will note that the byte at 103306 in the PCB has changed from 100 to 140.  That '4' bit is the "program is ALLOCATED" flag.

 

FOR/NEXT STACK

Now, if instead of typing in the GOSUB program after power-on and LOADBIN, we typed in:

10 FOR I=1 TO 10
20 PAUSE
30 NEXT I

We'd end up with THIS program memory instead:

103300 "MAIN"                          (30 bytes PCB)  FWUSER FWPRGM FWCURR
103304 000 000 100 000
103310 000 000 000 000 000 000 000 000
103320 000 000 000 000 000 000 000 000 (end of PCB)
103330 020 000 017 214 021 040 111 032 001 000 000 (0010 len=17 FOR SNV_ADDRESS("I ") INT_CONST(1) ...
103343 010 032 020 000 000 244 016                     ... STO_SV INT_CONST(10) TO EOL)
103352 040 000 002 227 016             (0020 len=2 PAUSE EOL)
103357 060 000 005 021 040 111 217 016 (0030 LEN=5 SNV_ADDRESS("I ") NEXT EOL)
103367 231 251 002 031 016             (A999 len=2 STOP EOL)
103374                                 NXTMEM, TOS, STSIZE, R12
 ...
177215 xxx xxx xxx xxx xxx             (top of GOSUB/RETURN stack) RTNSTK CALVRB LAVAIL
177222 xxx xxx xxx xxx xxx
177227 xxx xxx xxx xxx xxx
177234 xxx xxx xxx xxx xxx             (62 bytes empty GOSUB/RETURN stack, 5 bytes per entry)
177241 xxx xxx xxx xxx xxx             (xxx are unused entries on the GOSUB/RETURN stack)
177246 xxx xxx xxx xxx xxx
177253 xxx xxx xxx xxx xxx
177260 xxx xxx xxx xxx xxx
177265 xxx xxx xxx xxx xxx
177272 xxx xxx xxx xxx xxx
177277 000                             (unused byte) FWBIN NXTRTN
177300                                 BINTAB (100 bytes, 177300-177377 inclusive, containing SomeBPGM)
177377                                 LWAMEM

Whew.  This is going to take some explaining.  ("Lucy!  You got some 'splainin' t'do!")

First: Tokenization.  I've glancingly referenced this already, and I'm not going to cover it in depth here, but basically it's this: when you enter a command or program statement, the PARSER (part of the operating system) chews through the ASCII stuff you entered, breaks it up into words, numbers, symbols and such, and (assuming you made no errors) converts it to an internal 'tokenized' format where each keyword is represented (mostly) by a single byte, the TOKEN for that keyword.  If you look at the source code at address 060015 (ROM 000), you'll see the "runtime table" for all 255 possible tokens (most of which are used, but a few are not).  The table source code contains the RUNTIME ADDRESS (the address of the routine that actually IMPLEMENTS the token when the command or program is executing), a brief comment as to what the token IS, the token NUMBER, and the ATTRIBUTES for the token.  The ATTRIBUTES are one or more bytes that describe a BUNCH of stuff to the operating system that help the system handle the token at parse, decompile (list), allocation, and de-allocation times.

Second: RPN (Reverse Polish Notation).  Just like HP's handheld calculators, the Series 80 computers (internally) are RPN machines.  The BASIC language is NOT RPN, so when commands and program lines are PARSED, part of that parsing and tokenization process is to convert the 'algebraic' input into the internal RPN representation, and part of the decompiling (listing) process is to go the other direction, to re-generate the algebraic representation from the internal RPN format.  This may seem like a LOT of work for no reason, but there IS method to the designers' madness: RPN is more efficient, both in storage format and in speed of execution.  When you have very limited RAM and a CPU clock speed just a little over 600 Khz, that's IMPORTANT.

Third: Variables.  On the HP-85, variable names were limited to a single letter, or a combination of a single letter and a single digit.  This means that variable names could be stored in 2 bytes, which also happens to be the storage requirements for a 16-bit address.  How convenient.

So, looking at the code above, the "10 FOR I=1 TO 10" line has been converted into (keeping in mind "little-endian" byte order, least significant byte at least significant address):

020 000          BCD line# 0010
017              LENGTH of bytes to follow
214              FOR token
021 040 111      SV_ADDRESS (fetch onto R12 op stack addr of Simple numeric Variable) "I "
032 001 000 000  INT_CONSTANT (with 3 bytes of 6 BCD digits) 000001 (pushed on R12 op stack)
010              STO_SV (store Simple numeric Variable) pops value and address, stores
032 020 000 000  INT_CONSTANT (with 3 bytes of 000010, pushed on stack)
244              TO token
016              EOL token

Let's not follow that any further down the rabbit hole right now, as we're trying to focus on the memory usage.

If we run this program, so that it PAUSEs in the midst of the FOR/NEXT loop, we find this in memory:

103300 "MAIN"                          (30 bytes PCB)  FWUSER FWPRGM FWCURR
103304 000 000 140 074
103310 000 000 000 000 000 000 000 000
103320 000 001 000 000 000 000 000 000 (end of PCB)
103330 020 000 017 214 021 076 000 032 001 000 000 (0010 len=17 FOR SNV_ADDRESS("I ") INT_CONST(1) ...
103343 010 032 020 000 000 244 016                     ... STO_SV INT_CONST(10) TO EOL)
103352 040 000 002 227 016             (0020 len=2 PAUSE EOL)
103357 060 000 005 021 076 000 217 016 (0030 LEN=5 SNV_ADDRESS("I ") NEXT EOL)
103367 231 251 002 031 016             (A999 len=2 STOP EOL)
103374 014 000
103376 012 011 252 252 252 252 377 001 000 000  (allocated storage for variable "I ")
103410                                 NXTMEM, TOS, STSIZE, R12
 ...
177167 000 207                         (address 103400 of variable VALUE)     CALVRB LAVAIL
177171 252 252 252 252 377 020 000 000 (FOR loop terminating ('TO') value)
177201 000 000 000 000 377 001 000 000 (FOR loop STEP value)
177211 330 206                         (address 103330 of start of FOR loop line, for tracing, errors)
177213 351 206                         (address 103351 of EOL at end of FOR loop line, for looping)
177215 xxx xxx xxx xxx xxx             (top of GOSUB/RETURN stack) RTNSTK
177222 xxx xxx xxx xxx xxx
177227 xxx xxx xxx xxx xxx
177234 xxx xxx xxx xxx xxx             (62 bytes empty GOSUB/RETURN stack, 5 bytes per entry)
177241 xxx xxx xxx xxx xxx             (xxx are unused entries on the GOSUB/RETURN stack)
177246 xxx xxx xxx xxx xxx
177253 xxx xxx xxx xxx xxx
177260 xxx xxx xxx xxx xxx
177265 xxx xxx xxx xxx xxx
177272 xxx xxx xxx xxx xxx
177277 000                             (unused byte) FWBIN NXTRTN
177300                                 BINTAB (100 bytes, 177300-177377 inclusive, containing SomeBPGM)
177377                                 LWAMEM

Again, the "allocated" flag has been set in the PCB (byte 103306).  The two bytes at 103307-103310 have been set to 000074, which is the LENGTH of the BASIC program (minus the allocated variables).  Adding this length to the value of FWCURR (103300) gets us 103374, which is immediately after the "final end" line, at the beginning of the block of allocated variables.  Also, the byte at 103321 (offset 21 in the PCB, P.FCNT) has gone to 001, i.e., we're in the middle of ONE nested FOR/NEXT loop.  The allocated variable ("I ") starts with a 2-byte "name block" that contains a truncated form of the variable name from which the ASCII form can be recovered, followed by (in this case) 10 (octal, 8 decimal) bytes of the VALUE of the variable, which in this case is an integer 1.  The 377 value in the 4th byte from the end tells us this is an INTEGER value (as opposed to REAL), and to just use the last three bytes as the integer value.  If the 4th byte from the last is NOT 377, then all 10 (8 dec) bytes are a REAL (packed BCD) number.  But enough of that (another time...).

The end of memory has changed, too.  One entry has been added to the (previously empty) FOR/NEXT stack at LAVAIL.  Each entry is 26 (octal) bytes long.  The first two bytes contain the absolute address of where the FOR/NEXT loop's controlling variable's VALUE is stored (not the "name header" for the variable, but the actual REAL or INTEGER value).  That's followed by 10 (octal) bytes of the terminating TO value that tells the system when the FOR/NEXT loop will be done, and that's followed by another 10 bytes of the STEP value (default of 1).  Again, in both of these cases, the numbers are in INTEGER format. Those two numbers are then followed by two more absolute addresses: 103330 which is the address of the FOR line (for purposes of TRACE-ing and/or error reporting), and 103351 which is the EOL at the end of the FOR line (but might also point to the '@' in the middle of a compound line) for looping (NEXT) purposes.

 

ASSIGN BUFFERS

If we enter this program (in DE-allocated form):

10 ASSIGN #1 TO "TEST"
20 PAUSE

(assuming we have a tape DATA file called "TEST"), we get this memory map:

103300 "MAIN"                          (30 bytes PCB)  FWUSER FWPRGM FWCURR
103304 000 000 100 000
103310 000 000 000 000 000 000 000 000
103320 000 000 000 000 000 000 000 000 (end of PCB)
103330 020 000 015 032 001 000 000     (0010 len=15 INT_CONST(1))
103337 005 004 124 105 123 124         (QUOTED_STRING len=4 "TEST")
103345 333 222 016                     (TO(ASSIGN) ASSIGN)
103350 040 000 002 227 016             (0020 len=2 PAUSE EOL)
103355 231 251 002 031 016             (A999 len=2 STOP EOL)
103362                                 NXTMEM, TOS, STSIZE, R12
 ...
177215 xxx xxx xxx xxx xxx             (top of GOSUB/RETURN stack) RTNSTK CALVRB LAVAIL
177222 xxx xxx xxx xxx xxx
177227 xxx xxx xxx xxx xxx
177234 xxx xxx xxx xxx xxx             (62 bytes empty GOSUB/RETURN stack, 5 bytes per entry)
177241 xxx xxx xxx xxx xxx             (xxx are unused entries on the GOSUB/RETURN stack)
177246 xxx xxx xxx xxx xxx
177253 xxx xxx xxx xxx xxx
177260 xxx xxx xxx xxx xxx
177265 xxx xxx xxx xxx xxx
177272 xxx xxx xxx xxx xxx
177277 000                             (unused byte) FWBIN NXTRTN
177300                                 BINTAB (100 bytes, 177300-177377 inclusive, containing SomeBPGM)
177377                                 LWAMEM

Notice that there are two "TO" tokens in Series 80 BASIC, one for FOR statements and one for ASSIGN statements.  While the ASCII keyword is the same, the TOKENS are completely different and depend upon the 'primary' keyword for meaning during parsing.  So, our program has been tokenized as "get an INTEGER 1, get a string "TEST", do the TO/ASSIGN," followed by the usual PAUSE and "final end" lines.  The high memory looks like we've already seen, with an empty GOSUB/RETURN stack and our mythical 100 byte BPGM.

Now, if we run the program until it hits the PAUSE, we get this ALLOCATED program:

103300 "MAIN"                          (30 bytes PCB)  FWUSER FWPRGM FWCURR
103304 000 000 140 062
103310 000 000 000 000 000 000 000 000
103320 000 000 000 000 000 000 000 000 (end of PCB)
103330 020 000 015 032 001 000 000     (0010 len=15 INT_CONST(1))
103337 005 004 124 105 123 124         (QUOTED_STRING len=4 "TEST")
103345 333 222 016                     (TO(ASSIGN) ASSIGN)
103350 040 000 002 227 016             (0020 len=2 PAUSE EOL)
103355 231 251 002 031 016             (A999 len=2 STOP EOL)
103362 002 000                         (length of program variable space, including these two bytes)
103364                                 NXTMEM, TOS, STSIZE, R12
 ...
176561 300 206                         CALVRB LAVAIL
176563 300 206
176565 000 001 000 000 000 000 000 000
176575 001 000 005 000 000 001 000 000
176605 000 000 000 000 000 000 000 000
176615 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377  256-byte (decimal)
176635 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377  400-byte (octal)
176655 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377  buffer for ASSIGN #1
176675 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377  to read in a record
176715 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377  from the tape (or disk)
176735 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377  which can then be used
176755 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377  to read numbers or
176775 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377  strings using READ#.
177015 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377
177035 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377
177055 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377
177075 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377
177115 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377
177135 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377
177155 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377
177175 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377 377
177215 xxx xxx xxx xxx xxx             (top of GOSUB/RETURN stack) RTNSTK
177222 xxx xxx xxx xxx xxx
177227 xxx xxx xxx xxx xxx
177234 xxx xxx xxx xxx xxx             (62 bytes empty GOSUB/RETURN stack, 5 bytes per entry)
177241 xxx xxx xxx xxx xxx             (xxx are unused entries on the GOSUB/RETURN stack)
177246 xxx xxx xxx xxx xxx
177253 xxx xxx xxx xxx xxx
177260 xxx xxx xxx xxx xxx
177265 xxx xxx xxx xxx xxx
177272 xxx xxx xxx xxx xxx
177277 000                             (unused byte) FWBIN NXTRTN
177300                                 BINTAB (100 bytes, 177300-177377 inclusive, containing SomeBPGM)
177377                                 LWAMEM

At runtime, the ASSIGN statement reserves 434-octal (256 + 28 decimal) bytes for the ASSIGN buffer. The first 34-octal bytes are slightly different for TAPE than they are for DISK, but both are documented in the tape software, right before the runtime routine for the ASSIGN statement at 027056 (ASIGN.).  Basically it's a bunch of stuff that lets the system know which program opened the buffer, which program is currently using the buffer, and a whole bunch of stuff about the buffer and the file, how much has been used, which tape record we're on, etc, etc. The important point I'm trying to show here is where these ASSIGN buffers are allocated in memory and how big they are.  10 (decimal) ASSIGN buffers are supported by the system, and there is a table in the system reserved RAM called ASNTBL where there are 24 (octal, 20 decimal) bytes allocated.  These bytes consist of 10 (decimal) 2-byte pointers which are 0 if the ASSIGN buffer is closed, and otherwise is a RELATIVE (to RTNSTK) offset to the buffer in question.  So, when the program tries to access an ASSIGN buffer, the buffer number is doubled and used as an offset into ASNTBL, from which a 2-byte relative offset is fetched, and it is then added to the value of RTNSTK to get an absolute pointer to the buffer.  NOTE: The PROGRAM gets allocated when your RUN, but the ASSIGN buffer doesn't get allocated/reserved until the ASSIGN statement is executed, and will get deleted/removed when the ASSIGN * statement is executed.

Everett Kaser
Nov 21, 2016