Quick question, as the title implies. I'm in the process of writing an 'OS' for my DIY Z80 computer and one of the things I need to do to help debug any issues during the boot process is send a repeating sequence of data to an IO port in the event of a problem.

The problem I'm catering for is a very specific one where there is no RAM present - obviously this means no stack, either.

So what can I get away with in terms of program structure with no stack? I'm assuming a simple JP instruction and most opcodes that aren't CALL should work?

The code below doesn't seem to get executed - though this could be a problem specific to the failure mode of my setup, I just wanted to confirm that the code below will work with no stack?

Here's the error routine:

            LD      A,C                 ; Move error code into E
            LD      E,A                 ;
            LD      A,%10101010         ; Load HALT code into L
            LD      L,A

bterr_lp:   LD      A,L                 ; Get code to display
            OUT     (BASIC_IO),A        ; Update error display
            LD      BC,$FFFF            ; Set delay (1 sec approx.)
            NEG                         ; 8 T-states
            NEG                         ; 8 T-states
            NEG                         ; 8 T-states
            NEG                         ; 8 T-states
            DEC     BC                  ; 6 T-states
            LD      A,C                 ; 9 T-states
            OR      B                   ; 4 T-states
            JP      NZ,bterr_plp        ; 10 T-states

            EX      DE,HL               ; Swap error code with HALT code
            JP      bterr_lp
  • 7
    [Disclaimer: I'm no Z80 expert, but] It looks reasonable - however, you would probably have to prevent the CPU from experiencing interrupts at all, since interrupt handing would require a working stack.
    – Retrograde
    May 5 '18 at 10:45
  • 6
    @Cumbayah no really you can have interrupts without stack or RAM in form of watch dog which resets the program to defined state periodically the interrupt itself does not need a stack ... you can position SP into ROM where desired return address is stored and still use reti ...
    – Spektre
    May 5 '18 at 18:13
  • 1
    @Spektre Interesting approach. Maybe arguable if it constitutes real interrupt handling as you cannot truly resume from where you where when the interrupt occurred, but I can see how this technique can be useful in certain circumstances, to allow interrupts to be processed while staying in a known, functioning state. Will stuff this into my bag of tricks, thanks. :)
    – Retrograde
    May 5 '18 at 18:24
  • 4
    @Cumbayah: I've written code on a different CPU which used interrupts with no stack. Any time an interrupt occurred, the main program would always get restarted from the same point, and it used "state" variables to keep track of what it was doing in a way that would always either be valid or recognizable as invalid. Even if a glitch were to arbitrarily corrupt RAM, system behavior would remain within well-defined bounds.
    – supercat
    May 10 '18 at 15:47

All commands are allowed actually, whether you have RAM or not. It is just the consequences of some of the commands that you have to bear in mind. E.g., using CALL is fine even if you have no RAM, but you need to remember that your current PC will not be saved, that SP will still be decremented twice and that RET will simply return to whatever word in memory is currently pointed to by the SP.

More specifically, your code looks absolutely fine. Hence, I'd make sure you actually run it (Z80 executes from PC=0 after the restart - is this where your code is situated?) Also, interrupt processing is somewhat dangerous, especially in the IM 2 mode. If you allowed interrupts and have interrupts triggered by your hardware, the current PC won't be saved once again, and your interrupt processing code will have no default way of knowing where to return to after interrupt has been processed.

Oh, and by the way, using NEG for time-wasting is not particularly memory efficient, because you are getting 8 t-states per 2 bytes of memory. INC BC : DEC BC will give you 12 t-states in the same memory; PUSH HL : PUSH HL will take 22 t-states and only modify the value of SP.

  • Any time a CALL is executed, the Z80 will perform write cycles to save PC at the address given by SP. If the hardware is designed so that all writes will either update the expected location in RAM or do nothing, then one may say that CALL simply doesn't save the PC if SP isn't pointing at RAM. In some system designs, however, writes to some non-RAM addresses may have other possibly-unwanted consequences (e.g. if some peripherals are designed to be accessed using memory reads/writes rather than IN/OUT instructions, stray writes may perform unwanted I/O operations).
    – supercat
    May 6 '18 at 17:01
  • Marvellous, thanks @introspec - that at least confirms that the error display function should be working and I can look elsewhere for the issue. Interrupts are disabled well before this routine gets executed, so they're not an issue. The problem is almost definitely in hardware, so I've got some work to do with my logic analyser, but I just wanted to make sure there was no issue with this before I start digging into signals and so on. Thanks also for the advice on improving the delay code - fortunately memory space isn't an issue at the moment, but good advice is good advice. :)
    – nockieboy
    May 6 '18 at 21:10
  • @supercat, yes, good point.
    – introspec
    May 8 '18 at 16:37

If your design has no RAM, that doesn't mean you haven't got the stack. Stack is not a write-only "device", you can use it to read data as well. For example, you set SP to the table in ROM and then read data from there:

LD SP,table
...do smth...
...do smth...

Another option is to use table-driven jumps in the same way by using RET:

LD SP,jump_table
RET ;use first value from jump table

... somewhere in the code jumped to:
RET ;use second value from jump table
  • 1
    I would probably have never thought of using a stack like that, interesting suggestion. I was just referring to the stack in its commonly-accepted form (i.e. read/write-able) in my OP.
    – nockieboy
    May 11 '18 at 15:54
  • 1
    Those approaches could fit really nicely in combination. Start with LD SP,table / ret, and have the a table of interleaved routine addresses and parameters.
    – supercat
    May 15 '18 at 21:00

It's as obvious as you think: nothing other than CALL/RST, RET/RETI/RETN, PUSH, POP, EX (SP), HL/IX and the entry into interrupts should use the stack, and the code you've posted should be fine with no stack. Interrupts should be disabled upon reset to avoid a hardware race condition, but obviously NMIs can't be disabled so make sure you've got that input under control.

Maybe the first things to check is whether your assembler has compiled for the correct address, and that you're properly decoding for access to your ROM?

  • Thanks - I thought as much, but I just wanted confirmation really; this function is executed via a compare and a jump, so there should be no reason that it doesn't get executed, so I was a little confused that it wasn't working. I'll have to dig deeper into the compare and prior state of error vars etc. but yes, the ROM bank is definitely being addressed properly as I'm getting error outputs via my blinkenlights prior to this.
    – nockieboy
    May 6 '18 at 21:13
  • What would happen if one tried to use EX (SP),HL in an all-ROM system? Would this just be equivalent to the imaginary instruction LD HL,(SP)? Nov 5 '21 at 11:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.