Monday, January 8, 2024

Using sim65 to Test NES Code

I needed a period-63 LFSR implementation in 6502 assembly for some NES code I was writing. Normally, I like to unit test code, but it wasn't obvious how to do that with an NES. Thankfully, the cc65 project already has a great simulator with semi-hosted C stdio support that you can use for this sort of thing. This post explains how I tested my LFSR implementation.

I started with the LFSR code itself, in lfsr.asm:

.export _lfsr_state
.export _lfsr_update

.segment "DATA"
_lfsr_state: .res 1

.segment "CODE"

.proc _lfsr_update
        ; period 63 lfsr with polynomial x^6 + x^5 + 1, computed in top 6 bits
        ; Bottom 2 bits must always be 0.
        lda _lfsr_state
        asl
        bpl :+
        eor #4
:       bcc :+
        eor #4
:       sta _lfsr_state
        rts
.endproc

I'm still new to 6502 coding, so I'm sure this is suboptimal, but it seemed like a good start. The whole point of the exercise is to make it easier to optimize small routines, after all.

Then I made main.c:

#include <stdio.h>
#include <stdint.h>

extern void lfsr_update(void);
extern uint8_t lfsr_state;

int main(void)
{
  uint8_t i = 0;
  uint8_t first;
  
  lfsr_state = 0x4;
  first = lfsr_state;
  do {
    printf("%d: %02X\n", i++, lfsr_state);
    lfsr_update();
  } while (first != lfsr_state);
  return 0;
}

This initializes the LFSR (in the top 6 bits of lfsr_state) to a non-zero value, then updates it until it sees the same value again, printing the intermediate values.

Assuming you installed cc65 already, you can compile this with the commands:

$ cl65 --target sim6502 -o test main.c lfsr.asm

Then you can simulate it with the command:

$ sim65 test

This should give the output:

0: 04
1: 08
2: 10
3: 20
4: 40
5: 84
6: 0C
7: 18
8: 30
9: 60
10: C4
11: 88
12: 14
13: 28
14: 50
15: A4
16: 4C
17: 9C
18: 3C
19: 78
20: F4
21: E8
22: D0
23: A0
24: 44
25: 8C
26: 1C
27: 38
28: 70
29: E4
30: C8
31: 90
32: 24
33: 48
34: 94
35: 2C
36: 58
37: B4
38: 6C
39: DC
40: B8
41: 74
42: EC
43: D8
44: B0
45: 64
46: CC
47: 98
48: 34
49: 68
50: D4
51: A8
52: 54
53: AC
54: 5C
55: BC
56: 7C
57: FC
58: F8
59: F0
60: E0
61: C0
62: 80

This shows that the LFSR cycles after 63 steps, as intended. It also always leaves bits 0-1 clear.

The major limitation of this approach for testing is that if your routines are not using cc65's normal calling convention, you may have to write a wrapper function to be able to call them from C.

The sim65 tool also allows cycle counts to help optimization, but I don't think it can produce any sort of instruction trace that would make it more useful for debugging.

No comments:

Post a Comment