650vue

650Vue Part 7: Status Register Bit Displays

In the previous section, we looked at drawing the values of the AC, XR, YR, SP, and PC registers as seven-segment LED displays, but there was one register missing—SR, the status register.

The status register is different from the other 6502 registers. While we can express SR as a single byte value, it's more useful for a programmer to see the 8 individual bits it is composed of, because each bit corresponds to a processor 'flag' that can be either on or off. The flags, from left to right, are usually represented by the letters NV-BDIZC, which stand for: Negative, oVerflow, "unused", Break, Decimal mode, Interrupt disable, Zero, and Carry. (We'll talk about the flags in more detail in a later update, but you can read more if you're curious.) To see the full code for the SR display, please see my commit in Github.

We can show the status register's eight bit flags as a row of eight lights. If a bit is 1, the light will be lit up; if the bit is 0, the light will be dark. It's not actually necessary to to check each flag individually in order to power these lights, because SR lives in the Vuex store as a single 8-bit value. All we need to do is convert the value in SR into an 8-digit binary number padded with leading zeros, and that string will tell us which lights should be lit. For example, if we have bits 5, 4, and 0 set to "on", and all the remaining bits left "off", then we want our status bits packed into a string like this: 00110001. The computed property bits() in the StatusLights component turns the value stored in SR into an 8-bit binary string (Figure 1). Remember that mapState lets us simplify this.$store.state.cpu.sr into this.cpu.sr:

<script>
import { mapState } from 'vuex';
export default {
  computed: {
    ...mapState(['cpu']),
    bits() {
      return this.cpu.sr.toString(2).padStart(8, '0');
    },
  }
}
</script>
Figure 1: Converting SR into an 8-digit binary number.

Displaying the status lights is simple. Because the bits() computed property returns a string of ones and zeros, we can simply read off the digits one at a time, from left to right, and draw the corresponding lights on or off as required. I made two "lamp" graphics called led_1 and led_0, and the template in Figure 2 handles the loop:

<template>
  <div class="status-panel">
    <img class="flags" src="@/assets/status_flags.png">
    <span v-for="n in 8" :key="n" class="light-box">
      <img class="light" :src="require(`@/assets/led_${bits[n - 1]}.png`)">
    </span>
  </div>
</template>
Figure 2: Drawing the processor status bits in the StatusLights component.

As you can see from the previous figures, the StatusLights component is actually quite simple. The status register display should look something like the following:

A row of eight indicator lights, each labelled with one of the 6502 status flags.
Figure 3: THERE. ARE. FOUR. LIGHTS! And also...four...other lights.

At this point, I indulged in a bit of over-engineering. For the moment, we don't actually care about reading flags individually, because the loop in Figure 2 will let us display the whole set in one go. However, at some point, the virtual machine will need to be able to read, set, and clear flags as it runs. We can define a set of constants—one for each of the eight flags (Figure 4):

export default {
  flags: {  
    SR_CARRY:     0x01,
    SR_ZERO:      0x02,
    SR_INTERRUPT: 0x04,
    SR_DECIMAL:   0x08,
    SR_BREAK:     0x10,
    SR_UNUSED:    0x20,
    SR_OVERFLOW:  0x40,
    SR_NEGATIVE:  0x80
  }
}
Figure 4: Constant definitions for the 6502's processor status flags.

I chose to define each flag with its hexadecimal representation because that's how I think of them after years of writing 6502 machine language. As an alternative, we could define each flag as a power of two, so that instead of 0x01, 0x02, 0x04 ..., we'd write 1 << 0, 1 << 1, 1 << 2 ... both representations are equally valid.

In order to read each flag individually, we need a getter in our Vuex store. Remember how we don't want to write getters if all we're doing is retrieving a simple store property? Well, here's a situation where a getter is the right tool for the job—when we need to manipulate a property before we retrieve it. In this case, we want to pass in one of our flag constants and let the store tell us if the bit that it represents is on or off. We add the flagStatus getter to our store. Figure 5 shows two versions of flagStatus(). The first version uses the store's internal state object, while the second uses some fancy nested argument destructuring to simplify the notation of the return value:

getters: {
  // the old way:
  flagStatus: (state) => (flag) => {
    return (state.cpu.sr & flag) > 0;
  }

  // with argument destructuring
  flagStatus: ({ cpu: { sr } }) => (flag) => {
    return (sr & flag) > 0;
  }
}
Figure 5: The flagStatus getter (two versions).

For example, to find out if the carry flag is on or off, a component just has to call our new getter like this: this.$store.getters.flagStatus(constants.flags.SR_CARRY). Experienced machine-language programmers will recognize the use of boolean & as a bitmask to read the value of a single bit.

Similar to the mapState helper, Vuex gives us a mapGetters helper. If we add ...mapGetters(['flagStatus']) to our component, we can use the more compact notation this.flagStatus(constants.flags.SR_CARRY).

We already have enough code written to make a very simple running machine. In the next section, we'll look at building some simple control logic and turn this thing on.

« PREV   Status Register Lights   NEXT »