Daily bullshit

Continue our daily hydrology, this section we will simulate a CPU, in fact, the main job is to simulate a CPU instructions. I’ve read some great articles about emulating the NES step by step from an NES ROM, but I don’t think that’s a great way to tell a story.

6502 instructions

Before starting to emulate the CPU, please refer to this website, which describes the 6502 Reference. By clicking on the corresponding instruction, you can anchor to the corresponding introduction.

We can see that there are a lot of instructions on the NES. There are actually a lot of unofficial instructions on the NES, but don’t panic. Let’s simulate these instructions first.

Describe the CPU

Swift class is a reference type, and struct is a value type. For personal preference, I’ll declare it as class.

class CPU {
  var a: UInt8 = 0 // add register A
  var x: UInt8 = 0 // register x
  var y: UInt8 = 0 // register y
  var status: UInt8 = 0 // Status register
  var sp: UInt8 = 0 // Stack pointer
  var pc: UInt16 = 0 // Program counter
}
Copy the code
  • Register A is relatively common, mainly used to read and write data, some logical operations
  • Register X is similar to register A, but it can be more convenient +/ -1, mainly used for data transfer, operations, etc
  • Register Y has properties similar to x
  • The status register mainly stores the state information when the instruction is executed. There are multiple states in this section, namely C, Z, I, D, B, V, and N, which will be used in later codes
  • The stack pointer points to a block of stack memory which is expanded later
  • The program counter, in fact, can be understood as a subscript index of an array, which can be addressed from 0x0000 to 0xFFFF. The CPU finds the corresponding storage unit based on the PC, and the PC automatically incremented by 1 each time the CPU reads an instruction. Of course, we can also make it jump back to change the order of execution

BRK

Now, using the CPU as a whole to read “programs”, first implement the BRK instruction. Looking up the table in the above url, we find that BRK is $00(0x00, $is hexadecimal). Its Addressing Mode(address Mode) is Implied, so it breaks out of the loop by executing it directly to BRK. If you want to simulate the BRK instruction, you need to complete the simulation of the interrupt Mode.

extension CPU {
  func interpret(program: [UInt8]) {
    pc = 0
    while true {
      let code = program[Int(pc)]
      pc + = 1
      switch code {
        case 0x00:
          return
        default:
          break}}}}Copy the code

So far, this is boring.

LDA

Immediate, Zero Page, Zero Page X, Absolute X… One at a time, fix the Immediate mode, which is Opcode 0xa9, and change the status, which affects both Zero Flag and Negative Flag. Negative Flag is N) as we said above.

extension CPU {
  func interpret(program: [UInt8]) {
    pc = 0
    while true {
      let code = program[Int(pc)]
      pc + = 1
      switch code {
        // ...
        case 0xa9:
          let param = program[Int(pc)]
          pc + = 1
          a = param
          // Handle Zero Flag
          if a = = 0 {
            status | = 0b0000_0010
          } else {
            status & = (~0b0000_0010)}// Handle Negative Flag
          if a >> 7 = = 1 {
            status | = 0b1000_0000
          } else {
            status & = (~0b1000_0000)}default:
          break}}}}Copy the code

The LDA in Immediate mode has been removed, and other 0xa5, 0xb5… These, try to get your hands dirty.

The test case

Since the test target was enabled when I built the project, I can write some code to test the instructions we implemented. For testing purposes, we implement a reset function

extension CPU {
  func reset(a) {
    a = 0
    x = 0
    y = 0
    status = 0
    sp = 0
    pc = 0}}Copy the code

Then write the test function in the test target and run

func testSth(a) {
  let cpu = CPU()
  cpu.interpret(program: [0xa9.0x00.0x00])
  assert(cpu.status & 0b0000_0010 = = 0b10)
 
  cpu.reset()
  cpu.interpret(program: [0xa9.0x05.0x00])
  assert(cpu.a = = 0x05)
  assert(cpu.status & 0b0000_0010 = = 0)
  assert(cpu.status & 0b1000_0000 = = 0)}Copy the code

Now that we know a little bit about how to handle the on-on-off emulation instructions, we’ll continue to optimize the current code in the next section. The code will be reimplemented and put on gayHub.