Background

I have been working on my Chip-8 emulator as an exercise to get some hands on experience with building a project from scratch with Rust. One of the components I worked on today was the SHL Vx {, Vy} function, this function looks at the most significant bit of an 8 bit number and updates some program registers based on the value of that bit. Here is the definition from the Chip-8 manual for this function.

If the most-significant bit of Vx is 1, then Vf is set to 1, otherwise to 0. Then Vx is multiplied by 2.
  • Note: Vx is one of the 16 8 bit registers, and Vf is a flag register that is used to store a 1 or 0 for different calculations

Earlier in my Chip-8 development I had done a similar calculation for the SHR Vx {, Vy} function, except for the key difference of looking at the least significant bit in register Vx. I solved this function with the following understanding of how to determine bit values.

// Take a number, 5 in this case as an unsigned 8 bit value
// bitwise AND the number with single bit 1 in the bit location you are looking for
5u8 & 1 // we do not shift any bits because we are looking at the right most bit

// This AND of the bits looks like this
// 0 0 0 0 0 1 0 1 - 5
// 0 0 0 0 0 0 0 1 - 1
// --------------- - &
// 0 0 0 0 0 0 0 1 - result
// meaning that the least significant bit is 1 because 1 & 1 is 1.

This understanding lead me to write the following code for my SHR Vx {, Vy} function as part of the CPU implementation

fn op_8x06(&mut self, x: usize) {
  // find the bit value of the rightmost bit, convert to bool
  self.vf = (self.v[x] & 1) == 1;
  // only take the 8 bit value
  self.v[x] = (self.v[x] / 2) as u8;
}
  • Note: in my CPU implementation, self is a CPU that has a Vf register as a boolean value, and an array of V registers where I can index them based on the CPU op_code

  • my base CPU struct for context

  • pub struct Cpu {
        // index 16 bit register
        i: u16,
        // program counter
        pc: u16,
        // memory
        memory: [u8; 4096],
        // registers
        v: [u8; 16],
        // flag register
        vf: bool,
        // program stack
        stack: [u16; 16],
        // stack pointer
        sp: u8,
        // delay timer
        dt: u8,
    }
    

My unit test validated my thinking by ensuring that I was always getting the correct value for the least significant bit. For all my unit tests I follow the same basic flow. I create an instance of my CPU, make an opcode that will invoke my function, update some registers to known values and then assert that the state of my CPU updated to the correct new states.

#[test]
fn opcode_8xy6() {
  let opcode = 0x8126;
  let mut chip: Cpu = Cpu::new();
  chip.v[1] = 5;

  chip.handle_opcode(opcode);
  assert_eq!(chip.vf, true, "least significant bit is 1, Vf was updated");
  assert_eq!(chip.v[1], 2, "register Vx was updated");

  chip.reset();
  chip.v[1] = 2;
  chip.handle_opcode(opcode);
  assert_eq!(chip.vf, false, "lest significant bit is 0, Vf was updated");
  assert_eq!(chip.v[1], 1, "register Vx was updated");
}

Success, all of these unit tests pass……. however

The bug is already present and my tests didn’t pick it up because I didn’t yet fully understand how Rust was evaluating my bitwise comparison.

My Bug

Since I already implemented a similar function, I reached into that code and looked to modify it for my new use case to fit SHL Vx {, Vy}. I came up with this

// SHL Vx {, Vy}
fn op_8x0e(&mut self, x: usize) {
  // find the bit value of the leftmost bit (right 7 spaces for 8 bit int), convert to bool
  // if it is a 1, then set Vf to 1, else 0
  self.vf = self.v[x] & (1 << 7) == 1;
  // only take the 8 bit value
  self.v[x] = (self.v[x] as u16 * 2) as u8;
}

This seemed straight forward enough, and logically made sense. I took the same principal and assumed I would be able to apply it with this understanding of bitwise comparisons.

// 1 0 0 0 0 0 0 0 - 128
// 1 _ _ _ _ _ _ _ - 1 << 7
// --------------- - &
// 1 _ _ _ _ _ _ _ - result
// meaning that the most significant bit is 1 because 1 & 1 is 1... 
// this is where I should have noticed the error before going down a bad path

After writing this new function, I repurposed my unit test from the previous function and tweaked them slightly

#[test]
fn opcode_8xye() {
  let opcode = 0x812e;
  let mut chip: Cpu = Cpu::new();
  chip.v[1] = 128;

  chip.handle_opcode(opcode);
  assert_eq!(chip.vf, true, "Most significant bit is 1, Vf was updated");
  assert_eq!(chip.v[1], 0, "There was an overflow, register Vx was updated");

  chip.reset();
  chip.v[1] = 2;
  chip.handle_opcode(opcode);
  assert_eq!(chip.vf, false, "most significant bit is 0, Vf was updated");
  assert_eq!(chip.v[1], 4, "register Vx was updated");
}

My excitement to complete this function with ease was at an all time high, and I was excited to move on to the next function implementation. But I decided to run this new unit test…. and discovered a failure.

The line assert_eq!(chip.vf, true, "Most significant bit is 1, Vf was updated"); was not passing, and I could not figure out why this was the case. All of the logic was replicated from a previous problem. I did not realize the problem until I tried to complete a bitwise AND on my Dry Erase board. I realized that my error was not how I was performing the bitwise AND, but the error was my understanding of the result of a bitwise AND.

The Solution

128 & (1 << 7) does not evaluate to a 1 but rather evaluates to the integer 128. I Implicitly thought that my bitwise and result would just give me the individual bit value of the index I wanted, I failed to realize that the result was just a number that would be calculated and returned.

// My initial understanding
// 1 0 0 0 0 0 0 0 - 128
// 1 _ _ _ _ _ _ _ - 1 << 7
// --------------- - &
// 1 _ _ _ _ _ _ _ - result
// Result == 1

// What actually gets returned
// 1 0 0 0 0 0 0 0 - 128
// 1 0 0 0 0 0 0 0 - 1 << 7
// --------------- - &
// 1 0 0 0 0 0 0 0 - result
// Result == 128

So in order to correctly determine a bits value I needed to refactor my code to fit this new understanding of a bitwise result. Instead of trying to compare the result to a 1 I needed to check if the result was a value that is not 0. I could have just checked if the value was 128 in this case, but comparing to something not 0 it keeps the logic open to change for other values. Here is what I came up with.

fn op_8x0e(&mut self, x: usize) {
  // find the bit value of the leftmost bit (right 7 spaces for 8 bit int), convert to bool
  // if it is a 1, then set Vf to 1, else 0
  self.vf = self.v[x] & (1 << 7) != 0; // compare the result to 0 instead of 1
  // only take the 8 bit value
  self.v[x] = (self.v[x] as u16 * 2) as u8;
}

I also went and updated my implementation for SHR Vx {, Vy} to reflect what I learned from my own errors. The new changes using != 0 for bitwise results is validated by satisfying all of my unit tests.

This was an interesting logical error that I was not previously aware of, and this Chip-8 project has allowed me to learn many new and exciting things about lower level programming and Rust that I had not previously known.