• Please review our updated Terms and Rules here

Identifying a 'buffer' space at the end of a program

MykeLawson

Experienced Member
Joined
Mar 21, 2014
Messages
377
I know this is a pretty dumb question, and probably part of 'Z-80 Programming 101', and for that matter really not part of CP/M, per se; but here goes.... I am writing a program and I want to have whatever scratchpad buffer space begin at the end of the program. I'm trying to figure out the exact syntax toy put in the equate statement.

BUFFER EQU CODE_END+1

Doesn't seem to work right since the Assembler throws an error - ERROR: Symbol 'CODE_END' is undefined. I've looked through the Z-80 assembly language books I have, and either I'm searching for the wrong term, or...... Anyway, thanks for any help y'all can toss my way.
 
Put a label in just before the end, eg:

BUFFER_START:
.end

That is your buffer start now. Don't need to use any maths - it's that byte onwards. You don't need an opcode, but if you wanted one, you could use "nop" or DB - Or more commonly I see an actualy buffer with "BLOCK" to reserve some space, but there's no need at the end of the code to do that.

I usually put "db '<END-OF-CODE>' in at the end of my code so I can find it easily in memory with a debugger. It's not like I can't overwrite it as data later. Also, if you have an unknown application looking for it, it can text-search.

As for where the buffer ends, that's with the start of the BDOS or the BIOS usually, which is at the vector pointed to at location 0006 or 0001 - eg, LD HL,($0005) or LD HL,($0001) - Now HL has the top of ram for the BDOS, or three past the top of RAM for the BIOS - eg, Subtract 3 from HL and you have the BIOS location.

Your buffer size can be from "BUFFER_START" to "(0001)-3" now, and when you exit the program, just use RST 00 to restart and the BIOS should kick off loading the BDOS and CCP back in once again.

Or you can just use all the memory and have the user use the reset button to exit.

Regards
David
 
Put a label in just before the end, eg:

BUFFER_START:
.end

That is your buffer start now. Don't need to use any maths - it's that byte onwards. You don't need an opcode, but if you wanted one, you could use "nop" or DB - Or more commonly I see an actualy buffer with "BLOCK" to reserve some space, but there's no need at the end of the code to do that.

I usually put "db '<END-OF-CODE>' in at the end of my code so I can find it easily in memory with a debugger. It's not like I can't overwrite it as data later. Also, if you have an unknown application looking for it, it can text-search.

As for where the buffer ends, that's with the start of the BDOS or the BIOS usually, which is at the vector pointed to at location 0006 or 0001 - eg, LD HL,($0005) or LD HL,($0001) - Now HL has the top of ram for the BDOS, or three past the top of RAM for the BIOS - eg, Subtract 3 from HL and you have the BIOS location.

Your buffer size can be from "BUFFER_START" to "(0001)-3" now, and when you exit the program, just use RST 00 to restart and the BIOS should kick off loading the BDOS and CCP back in once again.

Or you can just use all the memory and have the user use the reset button to exit.

Regards
David
Thanks, I put BUFFER: right before the END statement and the error is gone now. Thanks.
 
But, If I'm not 'pushing' or 'popping' anything I'm hoping to not need any stack area. Or am I missing something again... LOL
In CP/M, the CCP starts your program with SP set to a stack area inside the CCP. If you plan on doing RET directly back to CCP, you had better not use much stack. If you exit by JMP 0 then you probably don't need to worry much about the stack - unless you plan on using a lot of buffer space. Some programs will set their SP to the address from the JMP at location 5 (e.g. "LHLD 6; SPHL"), although it is common to back-off a bit from that address. Or else your program can allocate space for it's own stack and set SP to that. Unless you're placing the stack beyond the END of your program, you don't need to worry about collision with your buffer area.

To your original question, none of the assemblers that I've used automatically assign a label to the end of the program. If you're using simple assemblers, then making a label at END will work. But beware that relocating assemblers like RMAC and M80 may confuse the issue, particularly if you use CSEG and DSEG. Making matters worse, RMAC/LINK combine CSEG and DSEG in a different order than M80/L80, so it will matter which segment has your buffer label relative to which assembler/linker you are using.

C/80 creates some symbols for end of code and end of data, but then remember that RMAC/LINK do things differently than M80/L80. C/80 handles this for SBRK and ALLOC, so you should use those facilities instead of trying to use those labels.
 
Are you using the CALL instruction? You need a stack for that. Are there any interrupts that need servicing? You need a stack for that...
Darn it; yes, there will be subroutine calls. So I guess I need to set 'stack' out past the the area I want to reserve. I need to do more thinking.....
 
In CP/M, the CCP starts your program with SP set to a stack area inside the CCP. If you plan on doing RET directly back to CCP, you had better not use much stack. If you exit by JMP 0 then you probably don't need to worry much about the stack - unless you plan on using a lot of buffer space. Some programs will set their SP to the address from the JMP at location 5 (e.g. "LHLD 6; SPHL"), although it is common to back-off a bit from that address. Or else your program can allocate space for it's own stack and set SP to that. Unless you're placing the stack beyond the END of your program, you don't need to worry about collision with your buffer area.

To your original question, none of the assemblers that I've used automatically assign a label to the end of the program. If you're using simple assemblers, then making a label at END will work. But beware that relocating assemblers like RMAC and M80 may confuse the issue, particularly if you use CSEG and DSEG. Making matters worse, RMAC/LINK combine CSEG and DSEG in a different order than M80/L80, so it will matter which segment has your buffer label relative to which assembler/linker you are using.

C/80 creates some symbols for end of code and end of data, but then remember that RMAC/LINK do things differently than M80/L80. C/80 handles this for SBRK and ALLOC, so you should use those facilities instead of trying to use those labels.
With the other programs I've used as a model, when the program has finished, it just does a jump to 0000. That seems to work; so far.....
 
With the other programs I've used as a model, when the program has finished, it just does a jump to 0000. That seems to work; so far.....
Yes, using JMP 0 releases you from restrictions on the CCP stack. You can just use it. You don't need to set or allocate your own stack. And if you choose to do that, you don't have to allocate it outside your program area. Many programs will:

Code:
    ds    64
stack:    ds    0
or something similar. Just make sure that BUFFER is the last thing you allocate. If you think you're going to use an enormous amount of stack, then set your stack to the bottom of the BDOS, e.g. "LHLD 6; SPHL".
 
That's why I left an entire page at FF00 in my OS build for the stack, so it's big enough for both CP/M and for the TPA if the program just wants to leave the stack where it is. 256 bytes is a decently large stack - unless the code is seriously recursive, in which case I hope the programmer would be aware enough to address the issue. Out of interest, is there a standard for stack management under CP/M?
 
Not that I know of. If you're using MP/M with interrupt-driven devices, the stack can grow quite a bit, depending on the load and number of active devices.
 
... Out of interest, is there a standard for stack management under CP/M?
Not sure what you mean by stack "management". As I described, the CCP calls your program with a small stack, and the program is responsible for setting up it's own stack if it uses any significant stack space (and expects to return directly to CCP). The JMP BDOS at location 5 is defined as also specifying the upper limit of memory available to the program (provided it exits via JMP 0 (or BDOS function 0), and so makes a good start for an alternate stack. The BDOS quickly switches to it's own stack, so the program does not have to worry about the stack space used by the BDOS and BIOS when making BDOS function calls (it does, however, need to worry if making direct BIOS calls). That mainly leaves interrupts - if used on the platform - as the variable. Interrupt routines are expected to switch stack and avoid excessive pressure on the user program stack.

All of this was documented by Digital Research, although maybe not all in one place.
 
I also add that using one single stack for everything may not be a great idea. Especially placing that stack at the top of memory, where any overflow is poised to corrupt the OS.
 
Maybe not all in one place... That is quite the understatement. When I have to take the train to work again, I will slowly read all of the CP/M books I've been collecting.

I guessed programs were expected to make a stack available, but having a common stack in shared memory can be useful in a task-switching system, so I saved some... Even with interrupts, 256 bytes is 128 recursions deep in most cases, which is usually not likely unless you have faulty code, and then a crash is very likely regardless. But my management I meant "A recommended way to address the stack" - but it seems that, as with most things in CP/M, it's left up to the programmer, which I kind of like about CP/M... I'm kind of tired of Windows telling me stuff like, "You can't do that, you don't have the authority" on a system I bought, and well, I like the idea that I can just overwrite the BIOS if I want and put anything I like there... Not a ROM in sight. Except in my boot-rom-disk, but that's just a file system in an out-of-the-way place up at F0000....
 
... But my management I meant "A recommended way to address the stack" ...
I'm still got getting what you mean. The CPU architecture defines how SP works, PUSH/POP/CALL/RET etc. Common sense dictates that you don't overrun your allocated stack space, and don't RET when there is not a valid PC at top-of-stack, etc.

I'll just say again that I think "one stack for all" is not a good idea, and that you have not thought of all the possible ways it could go wrong. If you are running standard CP/M programs, they are free to do what they want. They also may make assumptions based on the current SP - like that it is an upper bound of available memory - which is valid based on CP/M but not for a stack in page 0xff.
 
So then I come back to my original question - Where does CP/M store the default stack? And is there such a thing as a default location for a default stack?

I'm starting it at FFFF, but does CP/M leave it in the TPA, or somewhere in the FDOS or CCP space, or is it literally a case of "not sure?" -

Programs that follow the CP/M standard won't care that I set aside a page for the stack, and won't respect it, but programs written for the architecture specifically can take advantage of it :)

I can think of many things that could go wrong, and I'm seriously thinking maybe to put a hardware intercept in to catch it in case I get galloping stack syndrome, but even then I'm not that worried about it.

Cleaning the stack area before setting the stack makes it really easy to see how much stack is used at any time :) It's something I check with all my code.

David
 
So then I come back to my original question - Where does CP/M store the default stack? And is there such a thing as a default location for a default stack?
So, the term "default stack" is a bit vague. From the perspective of a program running in the TPA, as stated before, the CCP calls 0100H with the SP pointing to it's internal stack. The program is responsible for knowing that and using it appropriately.

When CP/M boots, the bootstrap jumps to the BIOS cold start entry. This entry is responsible for initializing the SP to something valid (typically an internal BIOS stack). It is also the BIOS that is responsible for interrupts and how those manage SP (typically using an internal interrupt-stack). The BIOS then jumps to the CCP, which sets up it's own internal stack in SP. And, as previously described, the BDOS saves the SP and uses it's own internal stack while processing BDOS function calls.
 
Too bad that the notion of a "privileged" mode didn't dawn on Intel or Zilog until the 80286. But then, I'm not sure what the initial vision was--maybe an OS-less chip dedicated to simple tasks.
 
Back
Top