6

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:

BOOT_ERROR:
            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.)
bterr_plp:
            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
6
  • 8
    [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.
    – user6576
    Commented May 5, 2018 at 10:45
  • 7
    @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
    Commented May 5, 2018 at 18:13
  • 2
    @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. :)
    – user6576
    Commented May 5, 2018 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
    Commented May 10, 2018 at 15:47
  • @supercat Is the different CPU you mentioned, some variety of PIC14 or PIC16? Commented Jun 28 at 10:32

3 Answers 3

7

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.

3
  • 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
    Commented May 6, 2018 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
    Commented May 6, 2018 at 21:10
  • @supercat, yes, good point.
    – introspec
    Commented May 8, 2018 at 16:37
5

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
POP HL
...do smth...
POP HL
...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
3
  • 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
    Commented May 11, 2018 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
    Commented May 15, 2018 at 21:00
  • 1
    wow, stack-threaded code! Commented Aug 18, 2022 at 20:07
3

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?

5
  • 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
    Commented May 6, 2018 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)? Commented Nov 5, 2021 at 11:30
  • @puppydrum64 Exactly the same thing it usually does! timing details: floooh.github.io/2021/12/06/z80-instruction-timing.html#ex-sphl Commented Aug 18, 2022 at 20:08
  • @puppydrum64 Exactly the same thing it usually does - meaning the Z80 will read 2 bytes from address SP into an invisible temporary register WZ, write 2 bytes from HL to address SP, and copy WZ to HL. Key point: The Z80 doesn't know there's no RAM. It just issues the write cycles regardless, and it doesn't know whether the data is actually getting written. Commented Aug 18, 2022 at 20:09
  • @puppydrum64 some game consoles use writes to ROM addresses to trigger bank switching, for example, so EX (SP),HL would swap the current ROM bank, probably leading to a crash. IIRC a lot of SNES Mario and Gameboy Pokemon crashes are because of unintended bank switching, if you manage to get some pointer to point to a ROM address that's supposed to point to a RAM address. Commented Aug 18, 2022 at 20:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .