mirror of
https://github.com/specht/champ.git
synced 2024-12-22 03:30:47 +00:00
documentation
This commit is contained in:
parent
21fe0f60c9
commit
946a32f7cc
115
README.md
115
README.md
@ -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.
|
||||
|
68
champ.rb
68
champ.rb
@ -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
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
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
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
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
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
BIN
doc/example03_3.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
13
examples/example01.s
Normal file
13
examples/example01.s
Normal 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
3
examples/example01.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
load:
|
||||
0x6000: example01.s
|
||||
entry: 0x6000
|
14
examples/example02.s
Normal file
14
examples/example02.s
Normal 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
3
examples/example02.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
load:
|
||||
0x6000: example02.s
|
||||
entry: 0x6000
|
21
examples/example03.s
Normal file
21
examples/example03.s
Normal 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
3
examples/example03.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
load:
|
||||
0x6000: example03.s
|
||||
entry: 0x6000
|
13
examples/plot3d.yaml
Normal file
13
examples/plot3d.yaml
Normal 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
|
13
plot3d.yaml
13
plot3d.yaml
@ -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
|
Loading…
Reference in New Issue
Block a user