Tools

Utilities around the emulator: the Breakasm assembler, the BreaksDebug inspector, PPU/APU players and the demo programs

Utilities around the emulator: from the simplest possible 6502 assembler to the inspector of the gate-level core and the players of the PPU/APU register dumps.

This page describes the programs in the Tools/ folder of the repository: the assembler Breakasm, which the debugger uses to assemble the entered source; the Breaks Debugger, a GUI for checking the 6502 core; the replay tools PPU Player and APU Player; the speed profiling demos PpuPumpkin and ApuPumpkin; the audio playback utility Slooow Plaaayer; and the demo programs SignalPlotDemo and InfernoColormap for the custom WinForms controls.

Breakasm

Breakasm logo

As simple and dumb assembler as possible, to generate code.

To run:

Breakasm [-l <file.lst>] <source.asm> <output.prg>
Example: Breakasm -l test.lst test.asm test.prg

The -l <file.lst> option writes an assembly listing (address, emitted bytes and the source line) to the given file.

There is also a Python port with the same behaviour and command line:

python breakasm.py [-l <file.lst>] <source.asm> <output.prg>

PRG file is always 64 Kbytes (the size of 6502 address space). The current assembly pointer (ORG) can be set anywhere in the PRG.

Syntax

The source text is split into lines of the following format:

[LABEL:] COMMAND [OPERAND1, OPERAND2, OPERAND3] ; Comments

The label (LABEL) is optional. The command (COMMAND) contains 6502 instruction or one of the assebmler directives. The operands depend on the command.

Expressions

All operands are evaluated with the built-in expression engine. Labels, defines and complex expressions are supported in every operand position:

LDA     MyData + 32 * entry_size + 12
STA     (Base << 4) | 1
BYTE    Table + 2

Supported operations (from high to low priority):

()      grouping
! ~     logical not, bitwise not (unary)
* / %   multiply, divide, modulo
+ -     add, subtract
<< >>   shift left, shift right
<<< >>> rotate left, rotate right
> >= < <=  comparisons (result: 0 or 1)
== !=   equality (result: 0 or 1)
& | ^   bitwise and, or, xor

Numbers can be decimal (12), hexadecimal ($12, #$12) or binary via defines. An immediate operand starts with # (LDA #5). A whole operand wrapped in parentheses is indirect addressing (JMP (addr)), while parentheses inside an expression are grouping only.

Multi-pass assembling

Breakasm assembles the source in several passes until all identifiers, labels and defines are resolved. This also resolves the Zero Page / Absolute ambiguity: if a forward-referenced label turns out to be below $100, the assembler automatically uses the shorter zero page opcode (unless the ABS directive forces the absolute one).

Embedded Directives

DirectiveDescription
ORGSet the current PRG assembly position.
INCLUDEProcess a nested source file
DEFINEDefine a simple constant
BYTEOutput a byte or string
WORDOutput uint16_t in little-endian order. You can use both numbers as well as labels and addresses.
ENDFinish the assembling
PROCESSORDefines type of processor for informational purposes
ABSHint: the next instruction with a Zero Page / Absolute ambiguity (address below $100) must use the absolute opcode

Example of the ABS directive:

        ABS
        LDA     $10         ; uses AD 10 00 (absolute), not A5 10 (zero page)
        LDA     $20         ; the hint is consumed by the previous instruction: A5 20 (zero page)

Example Source Code

Not to write too much, I will just show you an example of the source code. Do it the same way and it should work.

; Test program

LABEL1:

    PROCESSOR 6502
;    ORG     $100

    DEFINE  KONST   #5

    LDX     KONST

AGAIN:
    NOP
    LDA     SOMEDATA, X         ; Load some data
    JSR     ADDSOME             ; Call sub
    STA     $12, X
    CLC
    BCC     AGAIN               ; Test branch logic

ADDSOME:                        ; Test ALU
    ADC     KONST
    PHP                         ; Test flags in/out
    PLP
    RTS

    ASL     A

SOMEDATA:
    BYTE    12, $FF, "Hello, world" 
    WORD    AGAIN

END

Support for NES and mappers

Dragging some NES and mapper prisserans into a generalized assembler is a bad idea.

Build multiple PRG files and compile a .nes file from them yourself if you need to.

Breaks Debugger

Breaks Debugger logo

A simple GUI for checking the operation of the 6502 Core component (M6502Core).

BreaksDebug screenshot

The PropertyGrids shows signals, registers and rest of DebugInfo of the 6502 core. Buttons above are manual control of the respective pins. The CPU Memory tab displays the HexDump of the memory. The Assembler tab shows a TextBox with the source, which will be assembled with the built-in Breakasm and loaded immediately into memory.

Breakasm and M6502Core are in their respective Interop DLLs.

Build

You must use Debug/Release x86/x64 configuration to build. AnyCPU is not suitable because it uses native code in Interop DLLs.

Just don't forget to switch back to Any CPU when you need to edit forms. Microsoft has a problem with this.

It is also necessary to install all packages using NuGet (see packages.config).

Processor State Dump for Wiki

To enable a detailed dump of all internals directly in Markdown and with pictures, you need to create the WikiMarkdown folder.

The state of the processor will be dumped every half-cycle. The dump will include the state of the internals as a Markdown table and a picture of what is going on at the bottom of the processor (connecting registers and internal buses).

About that:

JSR (0x20), T1 (PHI1)

Component/SignalState
DispatcherT0: 0, /T0: 1, /T1X: 0, 0/IR: 1, FETCH: 0, /ready: 0, WR: 0, ACRL1: 0, ACRL2: 1, T5: 0, T6: 0, ENDS: 1, ENDX: 1, TRES1: 1, TRESX: 0
Interrupts/NMIP: 1, /IRQP: 1, RESP: 0, BRK6E: 0, BRK7: 1, DORES: 0, /DONMI: 1
Extra Cycle CounterT1: 1, TRES2: 1, /T2: 1, /T3: 1, /T4: 1, /T5: 1
Decoder95: JSR (TX), 121: /IR6, 126: /IR7
CommandsS_ADL, SB_S, DB_ADD, Z_ADD, SUMS, ADD_SB7, ADD_SB06, ADH_PCH, ADL_PCL, ADH_ABH, ADL_ABL, DL_ADH, DL_DB
ALU Carry In0
DAA0
DSA0
Increment PC1
Regs
IR0x20
PD0x00
Y0x00
X0x05
S0x0E
AI0x00
BI0xC0
ADD0xFB
AC0x6C
PCL0x09
PCH0xC0
ABL0x0E
ABH0xC0
DL0xC0
DOR0xC0
FlagsC: 1, Z: 0, I: 1, D: 0, B: 1, V: 0, N: 0
Buses
SB0xFB
DB0xC0
ADL0x0E
ADH0xC0
Processor state at JSR (0x20), T1 (PHI1)

UnitTest

To run the debugger in unit test mode, you need to create a JSON like this:

{
	"CompileFromSource": true,
	"MemDumpInput": "mem.bin",
	"AsmSource": "Test.asm",
	"RunUntilBrk": true,
	"RunCycleAmount": true,
	"CycleMax": 10000,
	"RunUntilPC": true,
	"PC": "0x3469",
	"TraceMemOps": false,
	"TraceCLK": true,
	"DumpMem": true,
	"JsonResult": "res.json",
	"MemDumpOutput": "mem2.bin"
}

And run with parameter: BreaksDebugger Test.json.

As input code, you can use 64 Kbytes memory dump (MemDumpInput) or assembly language source (AsmSource).

Simulator will run until it encounters instruction BRK (RunUntilBrk) or specified number of cycles (RunCycleAmount, CycleMax) (These are cycles, not half-cycles).

The output will be a memory dump after simulation (MemDumpOutput) and JSON with simulation results (JsonResult).

PPU Player

Workflow:

PPU Player workflow diagram PPU Player screenshot

How to get PPU register dumps

You can use the main emulator (breaknes). In the settings you need to enable PPURegump=True and select the folder where regdump will be saved.

Don't forget to set the correct CPU divider in the PPUPlayer settings to match the CPU with which the regdump was obtained.

Regdump entries format:

#pragma pack(push, 1)
struct RegDumpEntry
{
	uint32_t	clkDelta;	// Delta of previous CLK counter (CPU Core clock cycles) value at the time of accessing to the register
	uint8_t 	reg; 		// Register index + Flag (msb - 0: write, 1: read)
	uint8_t 	value;		// Written value. Not used for reading.
	uint16_t	padding;	// Not used (yet?)
};
#pragma pack(pop)

CPU I/F Timing

This section describes when (and for how long) to use the CPU I/F to read/write the PPU register.

No 6502 load/store instruction can technically execute faster than a single PCLK.

So we will make the assumption that the CPU I/F (signal /DBE and others) will be active for the entire PCLK cycle (/PCLK + PCLK).

If this does not work, we will do some more research on how to do it better.

EDIT: This model works if you use a correct regdump that writes to PPU registers correctly. If you write to PPU registers at the wrong moment (e.g. not during VBlank), visual artifacts may appear.

APU Player

About image by u/ThatPixelArtDude.

How to get APU register dumps

You can use the main emulator (breaknes). In the settings you need to enable APURegump=True and select the folder where regdump will be saved.

Regdump entries format:

#pragma pack(push, 1)
struct RegDumpEntry
{
	uint32_t	clkDelta;	// Delta of previous CLK counter (CPU Core clock cycles) value at the time of accessing to the register
	uint8_t 	reg; 		// Register index + Flag (msb - 0: write, 1: read)
	uint8_t 	value;		// Written value. Not used for reading.
	uint16_t	padding;	// Not used (yet?)
};
#pragma pack(pop)

PpuPumpkin

A demo for profiling the speed of PPUSim in order to optimize it.

PPU executed 21477272 cycles in real 20375 msec
You're 20.38 times slower :(
PPUSim profiler output screenshot

ApuPumpkin

A demo for profiling the speed of APUSim in order to optimize it.

APU+Core executed 21477272 cycles in real 63266 msec
You're 63.27 times slower :(

Slooow Plaaayer

Slooow Plaaayer screenshot

Utility for playing audio samples that were obtained from a slow source (such as APUSim).

Sample sources:

Principle of operation

The Background Worker runs in the background and collects samples from the source (SourceSamples) into an intermediate buffer (SampleBuf) with a slight delay.

Once every (real) second, statistics are updated and a signal graph is plotted.

If the source samples run out, the worker falls asleep until it gets a new source.

SignalPlotDemo

SignalPlotDemo screenshot

Demonstration of the custom WinForms SignalPlotControl.

In the menu you can select two types of signals: normalized noise and sine wave (kinda).

InfernoColormap

InfernoColormap demo screenshot

Demo to test the inferno palette from Matplotlib (https://matplotlib.org/stable/gallery/color/colormap_reference.html)

TBD: Make a procedural generation of ints array, because it looks sad.