documentation

This commit is contained in:
Michael Specht 2018-02-16 15:05:05 +01:00
parent 21fe0f60c9
commit 946a32f7cc
16 changed files with 235 additions and 31 deletions

115
README.md
View File

@ -36,20 +36,6 @@ We specified some source files (which will get compiled automatically) and some
Furthermore, we can disable subroutines by replacing the first opcode with a RTS (`instant_rts`). This is necessary in some cases because Champ does not emulate hardware and thus can not load data from disk, for example.
### Defining watches
You can watch registers or variables at certain program counter addresses by inserting a _champ directive_ in a comment after the respective code line in your assembler source code. All champ directives start with an _at sign_ (@). Here's an example:
```
LDA #2 ; load 2 into accumulator
ASL ; multiply by two
STA SOMEWHERE ; store result @Au
```
With the `@Au` directive, we tell champ to monitor the A register and interpret it as an unsigned 8 bit integer (likewise, `@As` would treat the value as a signed 8 bit integer).
By default, all champ values get recorded before the operation has been executed. To get the value after the operation, you can write: `@Au(post)`.
### Running the profiler
To start champ, type:
@ -64,6 +50,105 @@ This will run the emulator and write the HTML report to `report.html`. If you do
![Champ Screenshot](doc/screenshot.png?raw=true "Fig. 1 Champ Screenshot")
### Defining watches
You can watch registers or variables at certain program counter addresses by inserting a _champ directive_ in a comment after the respective code line in your assembler source code. All champ directives start with an _at sign_ (`@`). Here's an example ([example01.yaml](example01.yaml) / [example01.s](example01.s)):
```
DSK test
MX %11
ORG $6000
FOO EQU $8000
JSR TEST
RTS
TEST LDA #64 ; load 64 into accumulator
ASL ; multiply by two @Au
STA FOO ; store result @Au
RTS
```
Running this example results in the following watch graphs:
![A at PC 0x6006](doc/example01_1.gif?raw=true)
![A at PC 0x6007](doc/example01_2.gif?raw=true)
Champ generates a graph for every watch. You can see the watched variable plotted against the cycles, and also the PC address, file name, and source line number of the watch as well as the subroutine in which the watch was defined.
With the `@Au` directive, we tell champ to monitor the A register and interpret it as an unsigned 8 bit integer (likewise, `@As` would treat the value as a signed 8 bit integer).
By default, all champ values get recorded before the operation has been executed. To get the value after the operation, you can write: `@Au(post)`. Feel free to add as many champ directives as you need in a single line, each starting with a new at sign.
### Global variables
In addition to watching individual registers, you can also watch global variables (([example02.yaml](example02.yaml) / [example02.s](example02.s))):
```
DSK test
MX %11
ORG $6000
FOO EQU $8000 ; @u16
JSR TEST
RTS
TEST LDA #0
STA FOO
LDA #$40
STA FOO+1 ; @FOO(post)
RTS
```
Here, we declare the type of the global variable in the same place the variable itself is declared, using @u8@, @s8@, @u16@, or @s16@. Later, we can just watch the variable by issuing a champ directive like `@FOO@`. In this example, we use the `(post)` option to see the variable contents after the `STA` operation.
![FOO at PC 0x600b](doc/example02_1.gif?raw=true)
Here's another example with some more interesting plots ([example03.yaml](example03.yaml) / [example03.s](example03.s)):
```
DSK test
MX %11
ORG $6000
FOO EQU $8000
JSR TEST
RTS
TEST LDX #$FF
LDA #1
LOOP TAY
TXA
EOR #$FF
LSR
LSR
STA FOO
TYA
ADC FOO ; @Au(post)
DEX ; @Xu(post)
BNE LOOP
```
This is a small program which lets the accumulator grow quadratically while X decreases linearly:
![A at PC 0x6012](doc/example03_1.gif?raw=true)
![X at PC 0x6015](doc/example03_2.gif?raw=true)
### Two-dimensional watches
We can also watch pairs of variables by separating them with a comma in the champ directive:
```
DEX ; @Xu(post) @Au,FOO(post)
```
This will plot FOO against X:
![FOO against A at PC 0x6015](doc/example03_3.gif?raw=true)
## Did you know?
By the way, there's a full-fledged, incremental, standalone, no-dependencies GIF encoder in `pgif.c` that writes animated GIFs and uses some optimizations to further minimize space. It's stream-friendly and as you feed pixels in via `stdin`, it dutifully writes GIF data to `stdout` until `stdin` gets closed.
By the way, there's a full-fledged, incremental, standalone, no-dependencies GIF encoder in [pgif.c](pgif.c) that writes animated GIFs and uses some optimizations to further minimize space. It's stream-friendly and as you feed pixels in via `stdin`, it dutifully writes GIF data to `stdout` until `stdin` gets closed.

View File

@ -68,10 +68,18 @@ class Champ
exit(1)
end
end
@config = YAML.load(File.read(args.shift))
config_path = args.shift
@config = YAML.load(File.read(config_path))
@source_files = []
@config['load'].each_pair do |address, path|
@source_files << {:path => File.absolute_path(path), :address => address}
fixed_path = path.dup
unless fixed_path[0] == '/'
fixed_path = File.absolute_path(File.join(File.dirname(config_path), fixed_path))
end
@source_files << {
:path => fixed_path,
:address => address
}
end
@highlight_color = '#fce98d'
if @config['highlight']
@ -363,11 +371,19 @@ class Champ
histogram_max = histogram.values.max
width = 276
height = (watch[:components].size == 1) ? 286 : 286
height = 286
canvas_top = 20
canvas_left = 40
canvas_width = width - 60
canvas_height = height - 70
canvas_width = 256
canvas_height = 256
canvas_top = 10
canvas_left = 30
canvas_right = 10
canvas_bottom = 50
width = canvas_width + canvas_left + canvas_right
height = canvas_height + canvas_top + canvas_bottom
pixels = [0] * width * height
histogram.each_pair do |key, value|
@ -460,6 +476,52 @@ class Champ
end.join(', ')
label = "at #{label}"
print_s(pixels, width, height, width / 2 - 3 * label.size, height - 10, label, 63)
if watch[:components].size == 1
# this watch is 1D, add X axis labels for cycles
labels = []
labels << [0.0, '0']
format_str = '%d'
divisor = 1
if @max_cycle_count >= 1e6
format_str = '%1.1fM'
divisor = 1e6
elsif @max_cycle_count > 1e3
format_str = '%1.1fk'
divisor = 1e3
end
# labels << [1.0, sprintf(format_str, (@max_cycle_count.to_f / divisor)).sub('.0', '')]
remaining_space = canvas_width - labels.inject(0) { |a, b| a + b.size * 6 }
space_per_label = sprintf(format_str, (@max_cycle_count.to_f / divisor)).sub('.0', '').size * 6 * 2
max_tween_labels = remaining_space / space_per_label
step = ((@max_cycle_count / max_tween_labels).to_f / divisor).ceil
step = 1 if step == 0 # prevent infinite loop!
x = step
while x < @max_cycle_count / divisor
labels << [x.to_f * divisor / @max_cycle_count, sprintf(format_str, x).sub('.0', '')]
x += step
end
labels.each do |label|
s = label[1]
x = (label[0] * canvas_width).to_i + canvas_left
print_s(pixels, width, height,
(x - s.size * (6 * label[0])).to_i,
canvas_top + canvas_height + 7, s, 63)
(0..(canvas_height + 3)).each do |y|
pixels[(y + canvas_top) * width + x] |= 0x40
end
end
(0..1).each do |offset|
component_label = 'cycles'
print_s(pixels, width, height,
(canvas_left + canvas_width * 0.5 - component_label.size * 3 + offset).to_i,
canvas_top + canvas_height + 18,
component_label, 63)
end
end
tr = @highlight_color[1, 2].to_i(16)
tg = @highlight_color[3, 2].to_i(16)

BIN
doc/example01_1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
doc/example01_2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
doc/example02_1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
doc/example03_1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
doc/example03_2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
doc/example03_3.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

13
examples/example01.s Normal file
View File

@ -0,0 +1,13 @@
DSK test
MX %11
ORG $6000
FOO EQU $8000
JSR TEST
RTS
TEST LDA #64 ; load 64 into accumulator
ASL ; multiply by two @Au
STA FOO ; store result @Au
RTS

3
examples/example01.yaml Normal file
View File

@ -0,0 +1,3 @@
load:
0x6000: example01.s
entry: 0x6000

14
examples/example02.s Normal file
View File

@ -0,0 +1,14 @@
DSK test
MX %11
ORG $6000
FOO EQU $8000 ; @u16
JSR TEST
RTS
TEST LDA #0
STA FOO
LDA #$40
STA FOO+1 ; @FOO(post)
RTS

3
examples/example02.yaml Normal file
View File

@ -0,0 +1,3 @@
load:
0x6000: example02.s
entry: 0x6000

21
examples/example03.s Normal file
View File

@ -0,0 +1,21 @@
DSK test
MX %11
ORG $6000
FOO EQU $8000 ; @u8
JSR TEST
RTS
TEST LDX #$FF
LDA #1
LOOP TAY
TXA
EOR #$FF
LSR
LSR
STA FOO
TYA
ADC FOO ; @Au(post)
DEX ; @Xu(post) @Au,FOO(post)
BNE LOOP

3
examples/example03.yaml Normal file
View File

@ -0,0 +1,3 @@
load:
0x6000: example03.s
entry: 0x6000

13
examples/plot3d.yaml Normal file
View File

@ -0,0 +1,13 @@
load:
0x6000: ../plot3d/plot3d242.s
0x1200: ../plot3d/multtab.s
0x8400: ../plot3d/ldrwtab.s
0x8900: ../plot3d/SINETABLE
0x8b00: ../plot3d/object2.s
0x9000: ../plot3d/object1.s
0x9500: ../plot3d/FONT
0xb600: ../plot3d/projtab.s
entry: ENTRY
# highlight: '#c3def1'
instant_rts:
- LOAD1

View File

@ -1,13 +0,0 @@
load:
0x6000: plot3d/plot3d242.s
0x1200: plot3d/multtab.s
0x8400: plot3d/ldrwtab.s
0x8900: plot3d/SINETABLE
0x8b00: plot3d/object2.s
0x9000: plot3d/object1.s
0x9500: plot3d/FONT
0xb600: plot3d/projtab.s
entry: ENTRY
# highlight: '#c3def1'
instant_rts:
- LOAD1