11

A common idiom in 6502 assembly code is to protect sequences of code that rely on decimal mode from being interrupted. This because an interrupt handler typically clears decimal mode (cld), and thus the protected routine needs this. It looks like this:

sei
sed
...
some decimal mode math ops
...
cld
cli

A possible downside here is that maybe this code is called from within an interrupt handler and re-enables interrupts when they should still be inhibited (so as to not overlap). So I see the following approach as both equivalent and "better", since it accounts for that case:

php
sei
sed
...
plp

Is this correct and should this be the "preferred" way to protect decimal code from interrupts? Any downsides?

16
  • 7
    Far as I remember decimal mode is restored if it was active by the RTI at the end of the interrupt handling code. The bug is decimal mode isn't cleared when entering an interrupt on NMOS processors (it was fixed on CMOS versions).
    – Brian
    Jul 8 at 14:10
  • 3
    In your use case yes. Disabling interrupts around decimal mode code would only be necessary to protect against execution of interrupt handling code that doesn't clear decimal mode and then misbehaves when it is set. Again only on a NMOS 6502.
    – Brian
    Jul 8 at 14:34
  • 2
    @Brian Makes sense. So this is better described as a precaution against "buggy" interrupt handlers (which might be outside your own code; part of an "OS"), right?
    – Brian H
    Jul 8 at 14:40
  • 1
    Having code which disables interrupts saving the interrupt-enable status beforehand and restore it afterward is good policy except in cases where stack space is extremely limited; it's an issue which is often relevant in modern microcontroller libraries as well (and one which all too many vendors botch).
    – supercat
    Jul 8 at 15:32
  • 2
    @supercat I see... protecting buggy handlers, not protecting against buggy handlers (per se).
    – fadden
    Jul 8 at 18:17
4

Prohibiting interrupts around a decimal mode operation is a pattern to protect the interrupt service routine, not the decimal code sequences.

As was already emphasized by others, an interrupt automatically pushes the status register upon the stack. This includes the decimal bit. When the interrupt routine finishes, the RTI instruction pulls back that value and restores the original state of the decimal flag.

So your code example makes sense, but for protecting the interrupt handler code, not vice versa.

Your second code example addresses the case of calls from an interrupt handler properly, because the php / plp preserves the original state of the interrupt flag.

Another way to avoid problems would be to clear the decimal mode at the beginning of the interrupt handler (or having a handler that avoids instructions being affected by the decimal mode). It would add just a CLD to the beginning of the handler (because the original state is restored by RTI at the end) which has less effect on the timing than the possible jitter created with an SEI ... CLI block.

5
  • 2
    I'm not sure what "protecting the interrupt handler" is supposed to mean. Protect from what?. You can only protect it from it's own non-optimal programming. As you rightly say, an ISR that needs to have the decimal flag set to a specific state should just set it to that state. If it doesn't do that, it's just wrong.
    – tofro
    Jul 9 at 15:54
  • @tofro This may be referring to the bug on the NMOS versions of the 6502. (See comments under question.) The bug is that the processor failsto clear the decimal flag when dispatching the ISR. In that case, disabling interrupts around code which sets the decimal flag protects interrupts from this bug. Still it seems like a minor thing? Any interrupt code whose instructions are influenced by this flag should just clear it itself.
    – Kaz
    Jul 10 at 5:35
  • 1
    @Kaz I think its questionable whether this can be called a bug - The manuals very clearly say "Make a convention for the "standard setting" of the decimal flag and make sure if you set it otherwise, you re-set it back to the standard. They also make no promise whatsoever that the decimal flag would have a certain state in an ISR, rather the opposite: "Set it if you need it"
    – tofro
    Jul 10 at 6:42
  • The 8086 has a very similar problem with the direction flag - but nobody seems to call it a bug that it doesn't have a default state in an ISR.
    – tofro
    Jul 10 at 7:02
  • 1
    @tofro sometimes you are not in charge of the ISR code. For example, the Commodore 64 Kernal ISR does not clear the decimal flag and is using commands affected by it. Thus, if you want to write a piece of code without changing the existing ISR, the approach presented by Brian makes sense.
    – Peter B.
    Jul 14 at 7:17
22

TL;DR:

Protecting 6502 decimal mode code from interrupts

Decimal mode does not need to be protected from interrupts.

Decimal mode is not cancelled/changed by interrupt routines(*1).

  1. Decimal mode is recorded in a status register flag (SED/CLD)
  2. When entering an interrupt, the status register is pushed on the stack
  3. When leaving an interrupt it is pulled back

See also the Interrupts and Decimal Mode remarks of the 6502 Decimal Mode Tutorial

If at all, then buggy interrupt routines may need to be protected from decimal mode :))


In Detail

A common idiom in 6502 assembly code is to protect sequences of code that rely on decimal mode from being interrupted.

Serious? I have never seen that sequence in 40 years of 6502 programming - at least not for the purpose mentioned. It does not make much sense - except, see below.

Also, disabling interrupts does not disable NMI (or RESET), so even with that 'protection' an NMI may come along.

This because an interrupt handler typically clears decimal mode

Well, yes, interrupt code using ADC/SBC needs to set the mode it expects - usually binary. After all, an interrupt can happen any time, with decimal set or cleared, so the called interrupt routine has to set it the way needed. It does not have to set it back, as that's inherently done by RTI.


Fringe cases where it may make sense

  • One very, very rare case where it would make sense, is if an (expected) interrupt routine has no room for the two clock cycles needed to clear the decimal flag (CLD).

    Except, such a routine would usually as well be delicate to delayed interrupt activation - which is what that code does.

  • The other, maybe a bit more common one, is to accommodate interrupt routines that do not clear the decimal flag, but do assume it to be cleared and which can not be changed or enhanced to handle the decimal flag.

    This is usually the case when a program has been added to an environment were such a routine is already existing, stored in ROM with the interrupt pointer as well in ROM and pointing direct to this routine, with no way to hook.

    In all other cases, it should be possible to hook the interrupt, clear decimal and continue with the existing routine.

    Well, or changing the CPU to a 65C02, which does clear the decimal flag after saving the flag register, but before entering the interrupt code.


The 'Better' Approach

Well, saving the interrupt enable flag, before disabling is for sure not a bad idea, but it's slow and only to be taken if one has absolute no idea about the environment the routine is used in. Otherwise it is, like disabling interrupts when doing decimal, a great waste of time and resources.

It's the Environment, Stupid.

Saving the decimal state and even more so disabling interrupts should be avoided and never become the default case.

Using/setting decimal should be, like any other state and data changing behaviour, be part of a routines description, enabling users of such routines to work accordingly - like simply (re-)setting the wanted state after a subroutine again.

Disabling interrupts is even worse, as it changes the response to interrupts in a rather random manner, creating the worst kind of bugs to be searched. Just imagine some machine using raster interrupts to manage screen output. If some foreground program disables interrupts not synchronized with screen timing, some of the raster interrupts may sometimes fall a bit late, creating ugly glitches. Good luck in finding why and which routine and when does create one.

It's almost universally better to make sure the interrupts are compatible with either setting of the decimal flag.


*1 - Except for processing time taken and unless the interrupt code does willingly manipulate the saved flag register or returns without using an RTI.

4
  • 2
    The code php/sei/sed ... plp is the same size as sei/sed / cld/cli and only three cycles longer (11 cycles versus 8). Whether or not it should be considered preferable to the latter form, I wouldn't classify it as a "great" waste of time and resources.
    – supercat
    Jul 8 at 22:25
  • 2
    @supercat Any code that is not needed to solve an existing issue is waste of space and time. Keep in mind, the question is about a preferred way, not for circumvention of some very specific situation. So both code fragments are not to be used by default.
    – Raffzahn
    Jul 9 at 0:05
  • "saving the interrupt enable flag, before disabling is for sure not a bad idea. It's slow and..." - what are you trying to say here? Jul 9 at 8:11
  • @BruceAbbott well, I guess I could add a 'But...' :)
    – Raffzahn
    Jul 9 at 11:38
4

There's absolutely no reason for an interrupt service routine to save and restore the decimal flag - On interrupt, the flag is being saved to the stack automatically, and restored on RTI, so any "normal" code is seeing no change whatsoever, regardless of what the ISR does (except if it would manipulate the value on the stack, which is probably a bad idea anyways).

That also means that ISRs shouldn't assume a specific value of the decimal flag - if they need it set or re-set, they should set it so, but there's no need for a restore.

For the same reason, there's absolutely no need to disable interrupts in code that is working in decimal mode.

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.