mirror of
https://github.com/specht/champ.git
synced 2024-12-13 04:29:13 +00:00
the big update
This commit is contained in:
parent
50f4dad58c
commit
c6ff6ac331
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
report.html
|
||||
report-files
|
||||
p65c02
|
||||
pgif
|
6
Makefile
6
Makefile
@ -1,6 +0,0 @@
|
||||
champ: champ.c labels.h watches.h
|
||||
gcc -o champ champ.c -lX11
|
||||
|
||||
labels.h: parse.rb plot3d20_Output.txt
|
||||
./parse.rb
|
||||
|
481
champ.rb
Executable file
481
champ.rb
Executable file
@ -0,0 +1,481 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'fileutils'
|
||||
require 'open3'
|
||||
require 'set'
|
||||
require 'tmpdir'
|
||||
require 'yaml'
|
||||
require 'stringio'
|
||||
|
||||
KEYWORDS = <<EOS
|
||||
ADC AND ASL BCC BCS BEQ BIT BMI BNE BPL BRA BRK BVC BVS CLC CLD CLI CLV
|
||||
CMP CPX CPY DEA DEC DEX DEY DSK EOR EQU INA INC INX INY JMP JSR LDA LDX
|
||||
LDY LSR MX NOP ORA ORG PHA PHP PHX PHY PLA PLP PLX PLY ROL ROR RTI RTS
|
||||
SBC SEC SED SEI STA STX STY STZ TAX TAY TRB TSB TSX TXA TXS TYA
|
||||
EOS
|
||||
|
||||
class Champ
|
||||
def initialize
|
||||
if ARGV.empty?
|
||||
STDERR.puts 'Usage: ./champ.rb [options] <config.yaml>'
|
||||
STDERR.puts 'Options:'
|
||||
STDERR.puts ' --max-frames <n>'
|
||||
exit(1)
|
||||
end
|
||||
@files_dir = 'report-files'
|
||||
FileUtils.rm_rf(@files_dir)
|
||||
FileUtils.mkpath(@files_dir)
|
||||
@max_frames = nil
|
||||
args = ARGV.dup
|
||||
while args.size > 1
|
||||
item = args.shift
|
||||
if item == '--max-frames'
|
||||
@max_frames = args.shift.to_i
|
||||
else
|
||||
STDERR.puts "Invalid argument: #{item}"
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
@config = YAML.load(File.read(args.shift))
|
||||
@source_files = []
|
||||
@config['load'].each do |tuple|
|
||||
@source_files << {:path => File.absolute_path(tuple[1]), :address => tuple[0]}
|
||||
end
|
||||
|
||||
@keywords = Set.new(KEYWORDS.split(/\s/).map { |x| x.strip }.reject { |x| x.empty? })
|
||||
@global_variables = {}
|
||||
@watches = {}
|
||||
@watches_for_index = []
|
||||
@label_for_pc = {}
|
||||
@pc_for_label = {}
|
||||
|
||||
# init disk image to zeroes
|
||||
@disk_image = [0] * 0x10000
|
||||
# load empty disk image
|
||||
load_image('empty', 0)
|
||||
@source_files.each do |source_file|
|
||||
@source_path = File.absolute_path(source_file[:path])
|
||||
@source_line = 0
|
||||
unless File.exist?(@source_path)
|
||||
STDERR.puts 'Input file not found.'
|
||||
exit(1)
|
||||
end
|
||||
Dir::mktmpdir do |temp_dir|
|
||||
FileUtils.cp(@source_path, temp_dir)
|
||||
|
||||
pwd = Dir.pwd
|
||||
Dir.chdir(temp_dir)
|
||||
if @source_path[-2, 2] == '.s'
|
||||
# compile the source
|
||||
merlin_output = `Merlin32 -V . #{File.basename(@source_path)}`
|
||||
if $?.exitstatus != 0 || File.exist?('error_output.txt')
|
||||
STDERR.puts merlin_output
|
||||
exit(1)
|
||||
end
|
||||
# collect Merlin output files
|
||||
@merlin_output_path = File.absolute_path(Dir['*_Output.txt'].first)
|
||||
@merlin_binary_path = @merlin_output_path.sub('_Output.txt', '')
|
||||
|
||||
# parse Merlin output files
|
||||
# TODO: Adjust addresses!!!
|
||||
parse_merlin_output(@merlin_output_path)
|
||||
load_image(@merlin_binary_path, source_file[:address])
|
||||
else
|
||||
load_image(@source_path, source_file[:address])
|
||||
end
|
||||
Dir.chdir(pwd)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_image(path, address)
|
||||
# puts "[#{sprintf('0x%04x', address)} - #{sprintf('0x%04x', address + File.size(path) - 1)}] - loading #{File.basename(path)}"
|
||||
File::binread(path).unpack('C*').each.with_index { |b, i| @disk_image[i + address] = b }
|
||||
end
|
||||
|
||||
def run
|
||||
Dir::mktmpdir do |temp_dir|
|
||||
if @config.include?('instant_rts')
|
||||
@config['instant_rts'].each do |label|
|
||||
@disk_image[@pc_for_label[label]] = 0x60 # insert RTS
|
||||
end
|
||||
end
|
||||
File.binwrite(File.join(temp_dir, 'disk_image'), @disk_image.pack('C*'))
|
||||
# build watch input for C program
|
||||
io = StringIO.new
|
||||
watch_index = 0
|
||||
@watches.keys.sort.each do |pc|
|
||||
@watches[pc].each do |watch0|
|
||||
watch0[:components].each do |watch|
|
||||
which = watch.include?(:register) ?
|
||||
sprintf('reg,%s', watch[:register]) :
|
||||
sprintf('mem,0x%04x', watch[:address])
|
||||
io.puts sprintf('%d,0x%04x,%d,%s,%s',
|
||||
watch_index, pc,
|
||||
watch0[:post] ? 1 : 0,
|
||||
watch[:type],
|
||||
which)
|
||||
end
|
||||
@watches_for_index << watch0
|
||||
watch_index += 1
|
||||
end
|
||||
end
|
||||
watch_input = io.string
|
||||
|
||||
@watch_values = {}
|
||||
start_pc = @pc_for_label[@config['entry']] || @config['entry']
|
||||
Signal.trap('INT') do
|
||||
puts 'Killing 65C02 profiler...'
|
||||
throw :sigint
|
||||
end
|
||||
@frame_count = 0
|
||||
cycle_count = 0
|
||||
last_frame_time = 0
|
||||
frame_cycles = []
|
||||
@cycles_per_function = {}
|
||||
@calls_per_function = {}
|
||||
call_stack = []
|
||||
last_call_stack_cycles = 0
|
||||
Open3.popen2("./p65c02 --hide-log --start-pc #{start_pc} #{File.join(temp_dir, 'disk_image')}") do |stdin, stdout, thread|
|
||||
stdin.puts watch_input.split("\n").size
|
||||
stdin.puts watch_input
|
||||
stdin.close
|
||||
catch :sigint do
|
||||
gi, go, gt = Open3.popen2("./pgif 280 192 4 > #{File.join(@files_dir, 'frames.gif')}")
|
||||
gi.puts '000000'
|
||||
gi.puts 'ffffff'
|
||||
gi.puts '000000'
|
||||
gi.puts 'ffffff'
|
||||
stdout.each_line do |line|
|
||||
# puts "> #{line}"
|
||||
parts = line.split(' ')
|
||||
if parts.first == 'jsr'
|
||||
pc = parts[1].to_i(16)
|
||||
cycles = parts[2].to_i
|
||||
@calls_per_function[pc] ||= 0
|
||||
@calls_per_function[pc] += 1
|
||||
unless call_stack.empty?
|
||||
@cycles_per_function[call_stack.last] ||= 0
|
||||
@cycles_per_function[call_stack.last] += cycles - last_call_stack_cycles
|
||||
end
|
||||
last_call_stack_cycles = cycles
|
||||
call_stack << pc
|
||||
elsif parts.first == 'rts'
|
||||
cycles = parts[1].to_i
|
||||
unless call_stack.empty?
|
||||
@cycles_per_function[call_stack.last] ||= 0
|
||||
@cycles_per_function[call_stack.last] += cycles - last_call_stack_cycles
|
||||
end
|
||||
last_call_stack_cycles = cycles
|
||||
call_stack.pop
|
||||
elsif parts.first == 'watch'
|
||||
watch_index = parts[1].to_i
|
||||
@watch_values[watch_index] ||= []
|
||||
@watch_values[watch_index] << parts[2, parts.size - 2].map { |x| x.to_i }
|
||||
elsif parts.first == 'screen'
|
||||
@frame_count += 1
|
||||
print "\rFrames: #{@frame_count}, Cycles: #{cycle_count}"
|
||||
this_frame_cycles = parts[1].to_i
|
||||
frame_cycles << this_frame_cycles
|
||||
data = parts[2, parts.size - 2].map { |x| x.to_i }
|
||||
gi.puts 'l'
|
||||
(0...192).each do |y|
|
||||
(0...280).each do |x|
|
||||
b = (data[y * 40 + (x / 7)] >> (x % 7)) & 1
|
||||
gi.print b
|
||||
end
|
||||
gi.puts
|
||||
end
|
||||
|
||||
gi.puts "d #{(this_frame_cycles - last_frame_time) / 10000}"
|
||||
last_frame_time = this_frame_cycles
|
||||
|
||||
if @max_frames && @frame_count >= @max_frames
|
||||
break
|
||||
end
|
||||
elsif parts.first == 'cycles'
|
||||
cycle_count = parts[1].to_i
|
||||
print "\rFrames: #{@frame_count}, Cycles: #{cycle_count}"
|
||||
end
|
||||
end
|
||||
gi.close
|
||||
gt.join
|
||||
end
|
||||
end
|
||||
puts
|
||||
|
||||
@cycles_per_frame = []
|
||||
(2...frame_cycles.size).each do |i|
|
||||
@cycles_per_frame << frame_cycles[i] - frame_cycles[i - 1]
|
||||
end
|
||||
if @cycles_per_frame.size > 0
|
||||
puts "Cycles per frame: #{@cycles_per_frame.inject(0) { |sum, x| sum + x } / @cycles_per_frame.size}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def write_report
|
||||
html_name = 'report.html'
|
||||
print "Writing report to file://#{File.absolute_path(html_name)} ..."
|
||||
File::open(html_name, 'w') do |f|
|
||||
report = DATA.read
|
||||
report.sub!('#{source_name}', File.basename(@source_path))
|
||||
|
||||
# write frames
|
||||
io = StringIO.new
|
||||
io.puts "<img class='screenshot' src='#{File.join(@files_dir, 'frames.gif')}' /><br />"
|
||||
if @cycles_per_frame.size > 0
|
||||
io.puts '<p>'
|
||||
io.puts "Frames recorded: #{@frame_count}<br />"
|
||||
io.puts "Average cycles/frame: #{@cycles_per_frame.inject(0) { |sum, x| sum + x } / @cycles_per_frame.size}<br />"
|
||||
io.puts '<p>'
|
||||
end
|
||||
report.sub!('#{screenshots}', io.string)
|
||||
|
||||
# write watches
|
||||
io = StringIO.new
|
||||
@watches_for_index.each.with_index do |watch, index|
|
||||
io.puts "<div style='display: inline-block;'>"
|
||||
io.puts "<h3>#{watch[:components].map { |x| x[:name] }.join('/')} (#{File.basename(@source_path)}:#{watch[:line_number]})</h3>"
|
||||
if @watch_values.include?(index)
|
||||
if watch[:components].size == 1
|
||||
io.puts '<em>TODO</em>'
|
||||
elsif watch[:components].size == 2
|
||||
histogram = {}
|
||||
@watch_values[index].each do |item|
|
||||
histogram[item.join('/')] ||= 0
|
||||
histogram[item.join('/')] += 1
|
||||
end
|
||||
histogram_max = histogram.values.max
|
||||
width = 256
|
||||
height = 256
|
||||
pixels = [63] * width * height
|
||||
histogram.each_pair do |key, value|
|
||||
key_parts = key.split('/').map { |x| x.to_i }
|
||||
(0..1).each do |i|
|
||||
if watch[:components][i][:type] == 's8'
|
||||
key_parts[i] = key_parts[i] + 128
|
||||
elsif watch[:components][i][:type] == 'u16'
|
||||
key_parts[i] = key_parts[i] >> 8
|
||||
elsif watch[:components][i][:type] == 's16'
|
||||
key_parts[i] = (key_parts[i] + 32768) >> 8
|
||||
end
|
||||
end
|
||||
x = key_parts[0]
|
||||
y = 255 - key_parts[1]
|
||||
pixels[y * width + x] = (((value.to_f / histogram_max) ** 0.5) * 63).to_i
|
||||
end
|
||||
|
||||
gi, go, gt = Open3.popen2("./pgif #{width} #{height} 64")
|
||||
(0...64).each do |i|
|
||||
g = ((i + 1) << 2) - 1
|
||||
gi.puts sprintf('%02x%02x%02x', g, g, g)
|
||||
end
|
||||
gi.puts 'f'
|
||||
gi.puts pixels.map { |x| sprintf('%02x', x) }.join("\n")
|
||||
gi.close
|
||||
gt.join
|
||||
watch_path = File.join(@files_dir, "watch_#{index}.gif")
|
||||
File::open(watch_path, 'w') do |f|
|
||||
f.write go.read
|
||||
end
|
||||
|
||||
io.puts "<img src='#{watch_path}'></img>"
|
||||
end
|
||||
else
|
||||
io.puts "<em>No values recorded.</em>"
|
||||
end
|
||||
io.puts "</div>"
|
||||
end
|
||||
report.sub!('#{watches}', io.string)
|
||||
|
||||
# write cycles
|
||||
io = StringIO.new
|
||||
io.puts "<table>"
|
||||
io.puts "<thead>"
|
||||
io.puts "<tr>"
|
||||
io.puts "<th>Addr</th>"
|
||||
io.puts "<th>CC</th>"
|
||||
io.puts "<th>CC %</th>"
|
||||
io.puts "<th>Calls</th>"
|
||||
io.puts "<th>CC/Call</th>"
|
||||
io.puts "<th>Label</th>"
|
||||
io.puts "</tr>"
|
||||
io.puts "</thead>"
|
||||
cycles_sum = @cycles_per_function.values.inject(0) { |a, b| a + b }
|
||||
@cycles_per_function.keys.sort do |a, b|
|
||||
@cycles_per_function[b] <=> @cycles_per_function[a]
|
||||
end.each do |pc|
|
||||
io.puts "<tr>"
|
||||
io.puts "<td>#{sprintf('0x%04x', pc)}</td>"
|
||||
io.puts "<td style='text-align: right;'>#{@cycles_per_function[pc]}</td>"
|
||||
io.puts "<td style='text-align: right;'>#{sprintf('%1.2f%%', @cycles_per_function[pc].to_f * 100.0 / cycles_sum)}</td>"
|
||||
io.puts "<td style='text-align: right;'>#{@calls_per_function[pc]}</td>"
|
||||
io.puts "<td style='text-align: right;'>#{@cycles_per_function[pc] / @calls_per_function[pc]}</td>"
|
||||
io.puts "<td>#{@label_for_pc[pc]}</td>"
|
||||
io.puts "</tr>"
|
||||
|
||||
end
|
||||
io.puts "</table>"
|
||||
report.sub!('#{cycles}', io.string)
|
||||
|
||||
f.puts report
|
||||
end
|
||||
puts ' done.'
|
||||
end
|
||||
|
||||
def parse_merlin_output(path)
|
||||
@source_line = -3
|
||||
File.open(path, 'r') do |f|
|
||||
f.each_line do |line|
|
||||
@source_line += 1
|
||||
parts = line.split('|')
|
||||
next unless parts.size > 2
|
||||
line_type = parts[2].strip
|
||||
if ['Code', 'Equivalence', 'Empty'].include?(line_type)
|
||||
next if parts[6].nil?
|
||||
next unless parts[6] =~ /[0-9A-F]{2}\/[0-9A-F]{4}/
|
||||
pc = parts[6].split(' ').first.split('/').last.to_i(16)
|
||||
code = parts[7].strip
|
||||
code_parts = code.split(/\s+/)
|
||||
next if code_parts.empty?
|
||||
|
||||
label = nil
|
||||
unless @keywords.include?(code_parts.first)
|
||||
label = code_parts.first
|
||||
code = code.sub(label, '').strip
|
||||
@label_for_pc[pc] = label
|
||||
@pc_for_label[label] = pc
|
||||
end
|
||||
|
||||
champ_directives = []
|
||||
if code.include?(';')
|
||||
comment = code[code.index(';') + 1, code.size].strip
|
||||
comment.scan(/@[^\s]+/).each do |match|
|
||||
champ_directives << match.to_s
|
||||
end
|
||||
end
|
||||
next if champ_directives.empty?
|
||||
|
||||
if line_type == 'Equivalence'
|
||||
if champ_directives.size != 1
|
||||
fail('No more than one champ directive allowed in equivalence declaration.')
|
||||
end
|
||||
item = {
|
||||
:address => code_parts[2].sub('$', '').to_i(16),
|
||||
:type => parse_champ_directive(champ_directives.first, true)[:type]
|
||||
}
|
||||
@global_variables[label] = item
|
||||
elsif line_type == 'Code'
|
||||
@watches[pc] ||= []
|
||||
champ_directives.each do |directive|
|
||||
watch = parse_champ_directive(directive, false)
|
||||
watch[:line_number] = @source_line
|
||||
@watches[pc] << watch
|
||||
end
|
||||
end
|
||||
else
|
||||
if line.include?(';')
|
||||
if line.split(';').last.include?('@')
|
||||
fail('Champ directive not allowed here.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def fail(message)
|
||||
STDERR.puts sprintf('[%s:%d] %s', File::basename(@source_path), @source_line, message)
|
||||
exit(1)
|
||||
end
|
||||
|
||||
def parse_champ_directive(s, global_variable = false)
|
||||
original_directive = s.dup
|
||||
# Au Au(post) As Xs(post) RX u8 Au,Xu,Yu
|
||||
result = {}
|
||||
s = s[1, s.size - 1] if s[0] == '@'
|
||||
if global_variable
|
||||
if ['u8', 's8', 'u16', 's16'].include?(s)
|
||||
result[:type] = s
|
||||
else
|
||||
fail("Error parsing champ directive: #{original_directive}")
|
||||
end
|
||||
else
|
||||
if s.include?('(post)')
|
||||
result[:post] = true
|
||||
s.sub!('(post)', '')
|
||||
end
|
||||
result[:components] = s.split(',').map do |part|
|
||||
part_result = {}
|
||||
if ['Au', 'As', 'Xu', 'Xs', 'Yu', 'Ys'].include?(part[0, 2])
|
||||
part_result[:register] = part[0]
|
||||
part_result[:name] = part[0]
|
||||
part_result[:type] = part[1] + '8'
|
||||
elsif @global_variables.include?(part)
|
||||
part_result[:address] = @global_variables[part][:address]
|
||||
part_result[:type] = @global_variables[part][:type]
|
||||
part_result[:name] = part
|
||||
else
|
||||
fail("Error parsing champ directive: #{original_directive}")
|
||||
exit(1)
|
||||
end
|
||||
part_result
|
||||
end
|
||||
if result[:components].size > 2
|
||||
fail("No more than two components allowed per watch in #{original_directive}")
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
['p65c02', 'pgif'].each do |file|
|
||||
unless FileUtils.uptodate?(file, ["#{file}.c"])
|
||||
system("gcc -o #{file} #{file}.c")
|
||||
unless $?.exitstatus == 0
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
champ = Champ.new
|
||||
champ.run
|
||||
champ.write_report
|
||||
|
||||
__END__
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>champ report</title>
|
||||
<style type='text/css'>
|
||||
body {
|
||||
background-color: #eee;
|
||||
font-family: monospace;
|
||||
}
|
||||
.screenshot {
|
||||
background-color: #222;
|
||||
box-shadow: inset 0 0 10px rgba(0,0,0,1.0);
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
th, td {
|
||||
text-align: left;
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>champ report for #{source_name}</h1>
|
||||
<div style='float: left; border-right: 1px solid #aaa;'>
|
||||
<h2>Frames</h2>
|
||||
#{screenshots}
|
||||
<h2>Cycles</h2>
|
||||
#{cycles}
|
||||
</div>
|
||||
<div style='margin-left: 440px; padding-top: 5px;'>
|
||||
<h2>Watches</h2>
|
||||
#{watches}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,17 +1,12 @@
|
||||
#include <X11/Xlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "labels.h"
|
||||
#include "watches.h"
|
||||
|
||||
#define SCALE 4
|
||||
#define SCREEN_WIDTH 280
|
||||
#define SCREEN_HEIGHT 192
|
||||
#define SCREEN_REFRESH_EVERY_CYCLES 10000
|
||||
|
||||
#define CARRY 0x01
|
||||
#define ZERO 0x02
|
||||
@ -21,15 +16,6 @@
|
||||
#define OVERFLOW 0x40
|
||||
#define NEGATIVE 0x80
|
||||
|
||||
Display *dsp;
|
||||
Window win;
|
||||
GC gc;
|
||||
unsigned int white, black;
|
||||
Atom wmDelete;
|
||||
XEvent evt;
|
||||
KeyCode keyQ;
|
||||
|
||||
uint8_t show_screen = 0;
|
||||
uint8_t show_log = 1;
|
||||
uint8_t show_call_stack = 0;
|
||||
|
||||
@ -44,41 +30,6 @@ uint64_t frame_count = 0;
|
||||
|
||||
uint16_t start_pc = 0x6000;
|
||||
uint16_t start_frame_pc = 0xffff;
|
||||
uint64_t max_frames = 0;
|
||||
FILE *watches_file = 0;
|
||||
|
||||
int init_display()
|
||||
{
|
||||
dsp = XOpenDisplay(NULL);
|
||||
if (!dsp)
|
||||
return 1;
|
||||
|
||||
int screen = DefaultScreen(dsp);
|
||||
white = WhitePixel(dsp, screen);
|
||||
black = BlackPixel(dsp, screen);
|
||||
|
||||
win = XCreateSimpleWindow(dsp, DefaultRootWindow(dsp), 0, 0,
|
||||
SCREEN_WIDTH * SCALE, SCREEN_HEIGHT * SCALE,
|
||||
0, black, black );
|
||||
|
||||
wmDelete = XInternAtom(dsp, "WM_DELETE_WINDOW", True);
|
||||
XSetWMProtocols(dsp, win, &wmDelete, 1);
|
||||
|
||||
gc = XCreateGC(dsp, win, 0, NULL);
|
||||
|
||||
// XSetForeground(dsp, gc, white);
|
||||
|
||||
long eventMask = StructureNotifyMask;
|
||||
eventMask |= ButtonPressMask|ButtonReleaseMask|KeyPressMask|KeyReleaseMask;
|
||||
XSelectInput(dsp, win, eventMask);
|
||||
|
||||
keyQ = XKeysymToKeycode(dsp, XStringToKeysym("Q"));
|
||||
|
||||
XMapWindow(dsp, win);
|
||||
|
||||
// wait until window appears
|
||||
do { XNextEvent(dsp,&evt); } while (evt.type != MapNotify);
|
||||
}
|
||||
|
||||
uint16_t yoffset[192] = {
|
||||
0x0000, 0x0400, 0x0800, 0x0c00, 0x1000, 0x1400, 0x1800, 0x1c00,
|
||||
@ -108,7 +59,7 @@ uint16_t yoffset[192] = {
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint16_t ip;
|
||||
uint16_t pc;
|
||||
uint8_t sp;
|
||||
uint64_t total_cycles;
|
||||
uint8_t a, x, y, flags;
|
||||
@ -116,7 +67,7 @@ typedef struct {
|
||||
|
||||
void init_cpu(r_cpu* cpu)
|
||||
{
|
||||
cpu->ip = 0;
|
||||
cpu->pc = 0;
|
||||
cpu->sp = 0xff;
|
||||
cpu->total_cycles = 0;
|
||||
cpu->a = 0;
|
||||
@ -128,6 +79,27 @@ void init_cpu(r_cpu* cpu)
|
||||
uint8_t ram[0x10000];
|
||||
r_cpu cpu;
|
||||
|
||||
typedef struct {
|
||||
uint32_t index;
|
||||
uint16_t pc;
|
||||
uint8_t post;
|
||||
enum {
|
||||
u8, s8, u16, s16
|
||||
} data_type;
|
||||
enum
|
||||
{
|
||||
MEMORY,
|
||||
REGISTER_A,
|
||||
REGISTER_X,
|
||||
REGISTER_Y
|
||||
} type;
|
||||
uint16_t memory_address;
|
||||
} r_watch;
|
||||
|
||||
r_watch *watches = 0;
|
||||
size_t watch_count = 0;
|
||||
int32_t watch_offset_for_pc_and_post[0x20000];
|
||||
|
||||
void load(char* path, uint16_t offset)
|
||||
{
|
||||
uint8_t buffer[0x10000];
|
||||
@ -140,20 +112,19 @@ void load(char* path, uint16_t offset)
|
||||
exit(1);
|
||||
}
|
||||
size = fread(buffer, 1, 0x10000, f);
|
||||
fprintf(stderr, "Read 0x%04x bytes from %s, saved to 0x%04x.\n", (int)size, path, offset);
|
||||
memcpy(ram + offset, buffer, size);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
uint8_t rip8()
|
||||
uint8_t rpc8()
|
||||
{
|
||||
return ram[cpu.ip++];
|
||||
return ram[cpu.pc++];
|
||||
}
|
||||
|
||||
uint16_t rip16()
|
||||
uint16_t rpc16()
|
||||
{
|
||||
uint16_t result = rip8();
|
||||
result |= ((uint16_t)rip8()) << 8;
|
||||
uint16_t result = rpc8();
|
||||
result |= ((uint16_t)rpc8()) << 8;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -169,41 +140,9 @@ uint16_t read16(uint16_t address)
|
||||
return result;
|
||||
}
|
||||
|
||||
void refresh_watches(uint16_t address)
|
||||
{
|
||||
// refresh global variable watches after write at address
|
||||
for (int i = 0; i < WATCH_COUNT; i++)
|
||||
{
|
||||
if ((WATCH_ADDRESSES[i] == address) ||
|
||||
((WATCH_TYPES[i] == WATCH_U16 || WATCH_TYPES[i] == WATCH_S16) &&
|
||||
(WATCH_ADDRESSES[i] == address - 1)))
|
||||
{
|
||||
int32_t value = 0;
|
||||
switch (WATCH_TYPES[i])
|
||||
{
|
||||
case WATCH_U8:
|
||||
value = (uint8_t)read8(WATCH_ADDRESSES[i]);
|
||||
break;
|
||||
case WATCH_S8:
|
||||
value = (int8_t)read8(WATCH_ADDRESSES[i]);
|
||||
break;
|
||||
case WATCH_U16:
|
||||
value = (uint16_t)read16(WATCH_ADDRESSES[i]);
|
||||
break;
|
||||
case WATCH_S16:
|
||||
value = (int16_t)read16(WATCH_ADDRESSES[i]);
|
||||
break;
|
||||
}
|
||||
fprintf(watches_file, "~ 0x%04x %s %d\n", cpu.ip, WATCH_LABELS[i], value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void write8(uint16_t address, uint8_t value)
|
||||
{
|
||||
ram[address] = value;
|
||||
if (watches_file)
|
||||
refresh_watches(address);
|
||||
}
|
||||
|
||||
void push(uint8_t value)
|
||||
@ -365,7 +304,7 @@ const char* const ADDRESSING_MODE_STRINGS[] = { ADDRESSING_MODES };
|
||||
|
||||
#define TEST_OPCODE(opcode) test_opcode = opcode;
|
||||
#define OPCODE_VARIANT(opcode, cycles, addressing_mode) \
|
||||
if (opcode_from_ip == opcode) { \
|
||||
if (opcode_from_pc == opcode) { \
|
||||
*_opcode = test_opcode; \
|
||||
*_addressing_mode = addressing_mode; \
|
||||
*_cycles = cycles; \
|
||||
@ -378,8 +317,8 @@ void fetch_next_opcode(uint8_t* _read_opcode, r_opcode* _opcode, r_addressing_mo
|
||||
* https://www.atarimax.com/jindroush.atari.org/aopc.html
|
||||
*/
|
||||
r_opcode test_opcode = NO_OPCODE;
|
||||
uint8_t opcode_from_ip = rip8();
|
||||
*_read_opcode = opcode_from_ip;
|
||||
uint8_t opcode_from_pc = rpc8();
|
||||
*_read_opcode = opcode_from_pc;
|
||||
|
||||
TEST_OPCODE(ADC)
|
||||
OPCODE_VARIANT(0x69, 2, immediate)
|
||||
@ -477,7 +416,7 @@ void fetch_next_opcode(uint8_t* _read_opcode, r_opcode* _opcode, r_addressing_mo
|
||||
OPCODE_VARIANT(0xE6, 5, zero_page)
|
||||
OPCODE_VARIANT(0xF6, 6, zero_page_x)
|
||||
OPCODE_VARIANT(0xEE, 6, absolute)
|
||||
OPCODE_VARIANT(0xFe, 7, absolute_x)
|
||||
OPCODE_VARIANT(0xFE, 7, absolute_x)
|
||||
TEST_OPCODE(JMP)
|
||||
OPCODE_VARIANT(0x4C, 3, absolute)
|
||||
OPCODE_VARIANT(0x6C, 5, indirect)
|
||||
@ -616,15 +555,15 @@ void branch(uint8_t condition, int8_t offset, uint8_t* cycles)
|
||||
{
|
||||
// branch succeeds
|
||||
*cycles += 1;
|
||||
if ((cpu.ip & 0xfff0) != ((cpu.ip + offset) & 0xfff0))
|
||||
if ((cpu.pc & 0xfff0) != ((cpu.pc + offset) & 0xfff0))
|
||||
*cycles += 1;
|
||||
cpu.ip += offset;
|
||||
cpu.pc += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void handle_next_opcode()
|
||||
{
|
||||
uint16_t old_ip = cpu.ip;
|
||||
uint16_t old_pc = cpu.pc;
|
||||
|
||||
// fetch opcode, addressing mode and cycles for next instruction
|
||||
uint8_t read_opcode = 0;
|
||||
@ -635,7 +574,7 @@ void handle_next_opcode()
|
||||
|
||||
if (opcode == NO_OPCODE || addressing_mode == NO_ADDRESSING_MODE)
|
||||
{
|
||||
fprintf(stderr, "Unhandled opcode at %04x: %02x\n", old_ip, read_opcode);
|
||||
fprintf(stderr, "Unhandled opcode at %04x: %02x\n", old_pc, read_opcode);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@ -646,47 +585,47 @@ void handle_next_opcode()
|
||||
switch (addressing_mode)
|
||||
{
|
||||
case immediate:
|
||||
immediate_value = rip8();
|
||||
immediate_value = rpc8();
|
||||
break;
|
||||
case relative:
|
||||
relative_offset = (int8_t)rip8();
|
||||
relative_offset = (int8_t)rpc8();
|
||||
break;
|
||||
case absolute:
|
||||
target_address = rip16();
|
||||
target_address = rpc16();
|
||||
break;
|
||||
case zero_page:
|
||||
target_address = rip8();
|
||||
target_address = rpc8();
|
||||
break;
|
||||
case indirect:
|
||||
target_address = read16(rip16());
|
||||
target_address = read16(rpc16());
|
||||
break;
|
||||
case zero_page_indirect:
|
||||
target_address = read16(rip8());
|
||||
target_address = read16(rpc8());
|
||||
break;
|
||||
case zero_page_x:
|
||||
target_address = (rip8() + cpu.x) % 0xff;
|
||||
target_address = (rpc8() + cpu.x) % 0xff;
|
||||
break;
|
||||
case zero_page_y:
|
||||
target_address = (rip8() + cpu.y) % 0xff;
|
||||
target_address = (rpc8() + cpu.y) % 0xff;
|
||||
break;
|
||||
case absolute_x:
|
||||
target_address = rip16();
|
||||
target_address = rpc16();
|
||||
if ((target_address >> 12) != ((target_address + cpu.x) >> 12))
|
||||
cycles += 1;
|
||||
target_address += cpu.x;
|
||||
break;
|
||||
case absolute_y:
|
||||
target_address = rip16();
|
||||
target_address = rpc16();
|
||||
if ((target_address >> 12) != ((target_address + cpu.y) >> 12))
|
||||
cycles += 1;
|
||||
target_address += cpu.y;
|
||||
break;
|
||||
case indexed_indirect_x:
|
||||
target_address = read16((rip8() + cpu.x) & 0xff);
|
||||
target_address = read16((rpc8() + cpu.x) & 0xff);
|
||||
break;
|
||||
case indirect_indexed_y:
|
||||
target_address = cpu.y;
|
||||
uint16_t temp = read16(rip8());
|
||||
uint16_t temp = read16(rpc8());
|
||||
if ((target_address >> 12) != ((target_address + temp) >> 12))
|
||||
cycles += 1;
|
||||
target_address += temp;
|
||||
@ -695,7 +634,7 @@ void handle_next_opcode()
|
||||
|
||||
if (show_log)
|
||||
{
|
||||
fprintf(stderr, "%04x | %d | %02x | %s %-18s | ", old_ip, cycles, read_opcode, OPCODE_STRINGS[opcode],
|
||||
fprintf(stderr, "# %04x | %d | %02x | %s %-18s | ", old_pc, cycles, read_opcode, OPCODE_STRINGS[opcode],
|
||||
ADDRESSING_MODE_STRINGS[addressing_mode]
|
||||
);
|
||||
}
|
||||
@ -836,19 +775,21 @@ void handle_next_opcode()
|
||||
update_zero_and_negative_flags(cpu.y);
|
||||
break;
|
||||
case JMP:
|
||||
cpu.ip = target_address;
|
||||
cpu.pc = target_address;
|
||||
// TODO handle page boundary behaviour?
|
||||
break;
|
||||
case JSR:
|
||||
// push IP - 1 because target address has already been read
|
||||
// push PC - 1 because target address has already been read
|
||||
trace_stack_function[trace_stack_pointer] = target_address;
|
||||
trace_stack[trace_stack_pointer] = cpu.sp;
|
||||
trace_stack_pointer--;
|
||||
calls_per_function[target_address]++;
|
||||
printf("jsr 0x%04x %d\n", target_address, cpu.total_cycles);
|
||||
fflush(stdout);
|
||||
|
||||
push(((cpu.ip - 1) >> 8) & 0xff);
|
||||
push((cpu.ip - 1) & 0xff);
|
||||
cpu.ip = target_address;
|
||||
push(((cpu.pc - 1) >> 8) & 0xff);
|
||||
push((cpu.pc - 1) & 0xff);
|
||||
cpu.pc = target_address;
|
||||
break;
|
||||
case LDA:
|
||||
cpu.a = (addressing_mode == immediate) ? immediate_value : read8(target_address);
|
||||
@ -939,17 +880,19 @@ void handle_next_opcode()
|
||||
cpu.flags = pop();
|
||||
t16 = pop();
|
||||
t16 |= ((uint16_t)pop()) << 8;
|
||||
cpu.ip = t16;
|
||||
cpu.pc = t16;
|
||||
break;
|
||||
case RTS:
|
||||
if (trace_stack[trace_stack_pointer + 1] == cpu.sp + 2)
|
||||
{
|
||||
printf("rts %d\n", cpu.total_cycles);
|
||||
fflush(stdout);
|
||||
trace_stack_pointer++;
|
||||
}
|
||||
|
||||
t16 = pop();
|
||||
t16 |= ((uint16_t)pop()) << 8;
|
||||
cpu.ip = t16 + 1;
|
||||
cpu.pc = t16 + 1;
|
||||
break;
|
||||
case SBC:
|
||||
sbc((addressing_mode == immediate) ? immediate_value : read8(target_address));
|
||||
@ -1004,7 +947,8 @@ void handle_next_opcode()
|
||||
};
|
||||
if (unhandled_opcode)
|
||||
{
|
||||
fprintf(stderr, "Opcode not implemented yet!\n");
|
||||
fprintf(stderr, "Opcode %s not implemented yet at PC 0x%04x\n",
|
||||
OPCODE_STRINGS[opcode], cpu.pc);
|
||||
exit(1);
|
||||
}
|
||||
cpu.total_cycles += cycles;
|
||||
@ -1021,45 +965,88 @@ void handle_next_opcode()
|
||||
flags_str[5] = 0;
|
||||
|
||||
fprintf(stderr, "A: %02x, X: %02x, Y: %02x, PC: %04x, SP: %02x, FLAGS: %02x %s | %10ld |",
|
||||
cpu.a, cpu.x, cpu.y, cpu.ip, cpu.sp, cpu.flags, flags_str, cpu.total_cycles);
|
||||
cpu.a, cpu.x, cpu.y, cpu.pc, cpu.sp, cpu.flags, flags_str, cpu.total_cycles);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void set_pixel(int px, int py, unsigned int color)
|
||||
int parse_int(const char* s, int base)
|
||||
{
|
||||
int x, y;
|
||||
XSetForeground(dsp, gc, color);
|
||||
for (y = 0; y < SCALE; y++)
|
||||
for (x = 0; x < SCALE; x++)
|
||||
XDrawPoint(dsp, win, gc, px * SCALE + x, py * SCALE + y);
|
||||
}
|
||||
|
||||
void render_hires_screen()
|
||||
{
|
||||
uint8_t current_screen = ram[0x30b];
|
||||
int x, y;
|
||||
for (y = 0; y < 192; y++)
|
||||
char *p = 0;
|
||||
int i = strtol(s, &p, base);
|
||||
if (p == s)
|
||||
{
|
||||
uint16_t line_offset = yoffset[y] | (current_screen == 1 ? 0x2000 : 0x4000);
|
||||
for (x = 0; x < 40; x++)
|
||||
{
|
||||
uint8_t byte = ram[line_offset + x];
|
||||
for (int px = 0; px < 7; px++)
|
||||
set_pixel(x * 7 + px, y, ((byte >> px) & 1) ? white : black);
|
||||
}
|
||||
fprintf(stderr, "Error parsing integer: %s", s);
|
||||
exit(1);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
int32_t find_address_for_label(const char* label)
|
||||
void handle_watch(uint16_t pc, uint8_t post)
|
||||
{
|
||||
for (int i = 0; i < LABEL_COUNT; i++)
|
||||
if ((strlen(label) == strlen(LABELS[i])) &&
|
||||
(strcmp(label, LABELS[i]) == 0))
|
||||
for (int k = 0; k < 0x10000; k++)
|
||||
if (label_for_address[k] == i)
|
||||
return k;
|
||||
return -1;
|
||||
int32_t offset = watch_offset_for_pc_and_post[((int32_t)pc << 1) | post];
|
||||
if (offset == -1)
|
||||
return;
|
||||
|
||||
int32_t old_index = -1;
|
||||
while (watches[offset].pc == pc && watches[offset].post == post)
|
||||
{
|
||||
r_watch* watch = &watches[offset];
|
||||
if (old_index == -1)
|
||||
{
|
||||
printf("watch %d", watch->index);
|
||||
}
|
||||
else
|
||||
if (old_index != watch->index)
|
||||
printf("\nwatch %d", watch->index);
|
||||
|
||||
old_index = watch->index;
|
||||
int32_t value = 0;
|
||||
if (watch->type == MEMORY)
|
||||
{
|
||||
switch (watch->data_type)
|
||||
{
|
||||
case u8:
|
||||
value = (uint8_t)read8(watch->memory_address);
|
||||
break;
|
||||
case s8:
|
||||
value = (int8_t)read8(watch->memory_address);
|
||||
break;
|
||||
case u16:
|
||||
value = (uint16_t)read16(watch->memory_address);
|
||||
break;
|
||||
case s16:
|
||||
value = (int16_t)read16(watch->memory_address);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Invalid data type!\n");
|
||||
exit(1);
|
||||
}
|
||||
printf(" %d", value);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (watch->type)
|
||||
{
|
||||
case REGISTER_A:
|
||||
value = (watch->data_type == u8) ? (uint8_t)cpu.a : (int8_t)cpu.a;
|
||||
break;
|
||||
case REGISTER_X:
|
||||
value = (watch->data_type == u8) ? (uint8_t)cpu.x : (int8_t)cpu.x;
|
||||
break;
|
||||
case REGISTER_Y:
|
||||
value = (watch->data_type == u8) ? (uint8_t)cpu.y : (int8_t)cpu.y;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Invalid type!\n");
|
||||
exit(1);
|
||||
}
|
||||
printf(" %d", value);
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
@ -1069,28 +1056,89 @@ int main(int argc, char** argv)
|
||||
printf("Usage: ./champ [options] <memory dump>\n");
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" --show-screen\n");
|
||||
printf(" --hide-log\n");
|
||||
printf(" --start-pc <address or label>\n");
|
||||
printf(" --frame-start <address or label>\n");
|
||||
printf(" --max-frames <n>\n");
|
||||
printf(" --watches <output filename>\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 0x20000; i++)
|
||||
watch_offset_for_pc_and_post[i] = -1;
|
||||
|
||||
char s[1024];
|
||||
int watch_index = 0;
|
||||
while (fgets(s, 1024, stdin))
|
||||
{
|
||||
if (!watches)
|
||||
{
|
||||
watch_count = parse_int(s, 0);
|
||||
watches = malloc(sizeof(r_watch) * watch_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
char *p = s;
|
||||
r_watch watch;
|
||||
memset(&watch, 0, sizeof(watch));
|
||||
watch.index = parse_int(p, 0);
|
||||
while (*(p++) != ',');
|
||||
watch.pc = parse_int(p + 2, 16);
|
||||
while (*(p++) != ',');
|
||||
watch.post = parse_int(p, 0);
|
||||
while (*(p++) != ',');
|
||||
if (strncmp(p, "u8", 2) == 0)
|
||||
watch.data_type = u8;
|
||||
else if (strncmp(p, "s8", 2) == 0)
|
||||
watch.data_type = s8;
|
||||
else if (strncmp(p, "u16", 3) == 0)
|
||||
watch.data_type = u16;
|
||||
else if (strncmp(p, "s16", 3) == 0)
|
||||
watch.data_type = s16;
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Invalid data type!");
|
||||
exit(1);
|
||||
}
|
||||
while (*(p++) != ',');
|
||||
if (strncmp(p, "mem", 3) == 0)
|
||||
{
|
||||
watch.type = MEMORY;
|
||||
while (*(p++) != ',');
|
||||
watch.memory_address = parse_int(p + 2, 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (*(p++) != ',');
|
||||
if (strncmp(p, "A", 1) == 0)
|
||||
watch.type = REGISTER_A;
|
||||
else if (strncmp(p, "X", 1) == 0)
|
||||
watch.type = REGISTER_X;
|
||||
else if (strncmp(p, "Y", 1) == 0)
|
||||
watch.type = REGISTER_Y;
|
||||
}
|
||||
int32_t offset = (((int32_t)watch.pc) << 1) | watch.post;
|
||||
|
||||
if (watch_offset_for_pc_and_post[offset] == -1)
|
||||
watch_offset_for_pc_and_post[offset] = watch_index;
|
||||
|
||||
watches[watch_index++] = watch;
|
||||
|
||||
// printf("watch index %d pc 0x%04x post %d data_type %d type %d memory_address 0x%04x offset %d value %d\n",
|
||||
// watch.index, watch.pc, watch.post, watch.data_type, watch.type,
|
||||
// watch.memory_address, offset, watch_offset_for_pc_and_post[offset]
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc - 1; i++)
|
||||
{
|
||||
if (strcmp(argv[i], "--show-screen") == 0)
|
||||
show_screen = 1;
|
||||
else if (strcmp(argv[i], "--hide-log") == 0)
|
||||
if (strcmp(argv[i], "--hide-log") == 0)
|
||||
show_log = 0;
|
||||
else if (strcmp(argv[i], "--start-pc") == 0)
|
||||
{
|
||||
char *temp = argv[++i];
|
||||
char *p = 0;
|
||||
start_pc = strtol(temp, &p, 0);
|
||||
if (p == temp)
|
||||
start_pc = find_address_for_label(temp);
|
||||
fprintf(stderr, "Using start PC: 0x%04x\n", start_pc);
|
||||
}
|
||||
else if (strcmp(argv[i], "--start-frame") == 0)
|
||||
@ -1098,30 +1146,8 @@ int main(int argc, char** argv)
|
||||
char *temp = argv[++i];
|
||||
char *p = 0;
|
||||
start_frame_pc = strtol(temp, &p, 0);
|
||||
if (p == temp)
|
||||
start_frame_pc = find_address_for_label(temp);
|
||||
fprintf(stderr, "Using frame start: 0x%04x\n", start_frame_pc);
|
||||
}
|
||||
else if (strcmp(argv[i], "--max-frames") == 0)
|
||||
{
|
||||
char *temp = argv[++i];
|
||||
char *p = 0;
|
||||
max_frames = strtol(temp, &p, 0);
|
||||
if (p == temp)
|
||||
max_frames = find_address_for_label(temp);
|
||||
fprintf(stderr, "Max frames: %d\n", max_frames);
|
||||
}
|
||||
else if (strcmp(argv[i], "--watches") == 0)
|
||||
{
|
||||
const char* filename = argv[++i];
|
||||
if (access(filename, F_OK) != -1)
|
||||
{
|
||||
fprintf(stderr, "Watch output file exists: %s, exiting...\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
watches_file = fopen(filename, "w");
|
||||
fprintf(stderr, "Writing watches to %s.\n", filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Unknown argument: %s\n", argv[i]);
|
||||
@ -1134,123 +1160,56 @@ int main(int argc, char** argv)
|
||||
|
||||
load(argv[argc - 1], 0);
|
||||
|
||||
if (show_screen)
|
||||
init_display();
|
||||
|
||||
init_cpu(&cpu);
|
||||
cpu.ip = start_pc;
|
||||
cpu.pc = start_pc;
|
||||
struct timespec tstart = {0, 0};
|
||||
clock_gettime(CLOCK_MONOTONIC, &tstart);
|
||||
unsigned long start_time = tstart.tv_sec * 1000000000 + tstart.tv_nsec;
|
||||
uint32_t next_display_refresh = 0;
|
||||
uint8_t old_screen_number = 0;
|
||||
int last_cycles = -1;
|
||||
while (1) {
|
||||
if (watches_file)
|
||||
{
|
||||
// handle register watches for certain IP locations
|
||||
uint8_t printed_reg_watches = 0;
|
||||
for (int i = 0; i < ARG_WATCH_COUNT; i++)
|
||||
{
|
||||
if (cpu.ip == ARG_WATCH_ADDRESSES[i])
|
||||
{
|
||||
int32_t value = 0;
|
||||
uint8_t reg = 0;
|
||||
if (ARG_WATCH_REGISTERS[i] == WATCH_A)
|
||||
reg = cpu.a;
|
||||
else if (ARG_WATCH_REGISTERS[i] == WATCH_X)
|
||||
reg = cpu.x;
|
||||
else if (ARG_WATCH_REGISTERS[i] == WATCH_Y)
|
||||
reg = cpu.y;
|
||||
if (ARG_WATCH_TYPES[i] == WATCH_S8)
|
||||
value = (int8_t)reg;
|
||||
else
|
||||
value = (uint8_t)reg;
|
||||
if (!printed_reg_watches)
|
||||
fprintf(watches_file, "@ 0x%04x ", cpu.ip);
|
||||
else
|
||||
fprintf(watches_file, ", ");
|
||||
fprintf(watches_file, "%s %d",
|
||||
WATCH_REGISTER_LABELS[ARG_WATCH_REGISTERS[i]],
|
||||
value);
|
||||
printed_reg_watches = 1;
|
||||
}
|
||||
}
|
||||
if (printed_reg_watches)
|
||||
fprintf(watches_file, "\n");
|
||||
}
|
||||
handle_watch(cpu.pc, 0);
|
||||
uint16_t old_pc = cpu.pc;
|
||||
handle_next_opcode();
|
||||
if ((start_frame_pc != 0xffff) && (cpu.ip == start_frame_pc))
|
||||
handle_watch(old_pc, 1);
|
||||
if ((start_frame_pc != 0xffff) && (cpu.pc == start_frame_pc))
|
||||
{
|
||||
if (last_frame_cycle_count > 0)
|
||||
{
|
||||
frame_cycle_count += (cpu.total_cycles - last_frame_cycle_count);
|
||||
frame_count += 1;
|
||||
// printf("%ld\n", (cpu.total_cycles - last_frame_cycle_count));
|
||||
// printf("%d\n", (uint64_t)((double)frame_cycle_count / frame_count));
|
||||
if ((max_frames > 0) && (frame_count >= max_frames))
|
||||
break;
|
||||
}
|
||||
last_frame_cycle_count = cpu.total_cycles;
|
||||
}
|
||||
// if (cycles)
|
||||
// {
|
||||
// // struct timespec tlap = {0, 0};
|
||||
// // clock_gettime(CLOCK_MONOTONIC, &tlap);
|
||||
// // unsigned long lap_time = tlap.tv_sec * 1000000000 + tlap.tv_nsec;
|
||||
// // unsigned long elapsed_time = lap_time - start_time;
|
||||
// // start_time = lap_time;
|
||||
// // printf("Time taken for %d cycles: %ld\n", cycles, elapsed_time);
|
||||
// }
|
||||
// else
|
||||
// break;
|
||||
if (show_screen)
|
||||
if (cpu.total_cycles / 100000 != last_cycles)
|
||||
{
|
||||
if (XEventsQueued(dsp, QueuedAfterFlush) > 0)
|
||||
last_cycles = cpu.total_cycles / 100000;
|
||||
printf("cycles %d\n", last_cycles * 100000);
|
||||
}
|
||||
if (ram[0x30b] != old_screen_number)
|
||||
{
|
||||
old_screen_number = ram[0x30b];
|
||||
uint8_t current_screen = old_screen_number;
|
||||
int x, y;
|
||||
printf("screen %d", cpu.total_cycles);
|
||||
for (y = 0; y < 192; y++)
|
||||
{
|
||||
int exit_program = 0;
|
||||
XNextEvent(dsp, &evt);
|
||||
|
||||
switch (evt.type) {
|
||||
|
||||
case (KeyRelease) :
|
||||
if (evt.xkey.keycode == keyQ)
|
||||
exit_program = 1;
|
||||
|
||||
case (ClientMessage) :
|
||||
if (evt.xclient.data.l[0] == wmDelete)
|
||||
exit_program = 1;
|
||||
break;
|
||||
|
||||
}
|
||||
if (exit_program)
|
||||
break;
|
||||
}
|
||||
if (ram[0x30b] != old_screen_number)
|
||||
{
|
||||
old_screen_number = ram[0x30b];
|
||||
render_hires_screen();
|
||||
uint16_t line_offset = yoffset[y] | (current_screen == 1 ? 0x2000 : 0x4000);
|
||||
for (x = 0; x < 40; x++)
|
||||
printf(" %d", ram[line_offset + x]);
|
||||
}
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "Total cycles: %d\n", cpu.total_cycles);
|
||||
fprintf(stderr, "Cycles per frame: %d\n", (uint64_t)((double)frame_cycle_count / frame_count));
|
||||
printf("%12s %6s %8s %8s %4s %s\n", "Total CC", "% CC", "Calls", "CC/Call", "Addr", "Label");
|
||||
for (uint32_t i = 0; i < 0x10000; i++)
|
||||
|
||||
if (watches)
|
||||
{
|
||||
if (cycles_per_function[i] > 0)
|
||||
{
|
||||
printf("%12d %5.2f%% %8d %8d %04x",
|
||||
cycles_per_function[i],
|
||||
cycles_per_function[i] * 100.0 / cpu.total_cycles,
|
||||
calls_per_function[i],
|
||||
cycles_per_function[i] / calls_per_function[i],
|
||||
i);
|
||||
if (label_for_address[i] >= 0)
|
||||
printf(" %s", LABELS[label_for_address[i]]);
|
||||
printf("\n");
|
||||
}
|
||||
free(watches);
|
||||
watches = 0;
|
||||
}
|
||||
if (watches_file)
|
||||
fclose(watches_file);
|
||||
|
||||
return 0;
|
||||
}
|
124
parse.rb
124
parse.rb
@ -1,124 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'yaml'
|
||||
|
||||
label_for_address = {}
|
||||
symbols = []
|
||||
watches = []
|
||||
arg_watches = []
|
||||
|
||||
File::open('plot3d20_Output.txt') do |f|
|
||||
f.each_line do |line|
|
||||
parts = line.split('|')
|
||||
next unless parts.size == 8
|
||||
address = parts[6].split(' ').first.split('/')[1]
|
||||
symbol = parts[7]
|
||||
symbol = symbol[1, symbol.size - 1]
|
||||
next if symbol[0] == '*'
|
||||
next if symbol[0] == ';'
|
||||
next if symbol[0] == ' '
|
||||
if symbol =~ /^\w+\s+EQU\s+\$([0-9A-F]{2,4})/
|
||||
address = $1
|
||||
end
|
||||
watch_this = symbol.match(/~(u8|s8|u16|s16)/)
|
||||
watch_args = symbol.scan(/(@(u8|s8|u16|s16)[AXY])/).map { |x| x.first }
|
||||
symbol = symbol.strip.split(' ').first
|
||||
next if address.nil?
|
||||
label_for_address[address.to_i(16)] = symbol
|
||||
symbols << symbol
|
||||
if watch_this
|
||||
watches << {
|
||||
:address => address.to_i(16),
|
||||
:label => symbol,
|
||||
:type => watch_this[1]
|
||||
}
|
||||
end
|
||||
watch_args.each do |watch_arg|
|
||||
arg_watches << {:register => watch_arg[watch_arg.size - 1],
|
||||
:type => watch_arg.sub('@', '')[0, watch_arg.size - 2],
|
||||
:address => address.to_i(16)}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
symbols.uniq!
|
||||
symbols.sort! { |a, b| a.downcase <=> b.downcase }
|
||||
|
||||
File::open('labels.h', 'w') do |f|
|
||||
f.puts "const uint16_t LABEL_COUNT = #{symbols.size};"
|
||||
f.puts "const char* LABELS[] = {";
|
||||
line_length = 0
|
||||
f.print ' '
|
||||
symbols.each.with_index do |symbol, _|
|
||||
part = "\"#{symbol}\""
|
||||
if _ < symbols.size - 1
|
||||
part += ', '
|
||||
end
|
||||
f.print part
|
||||
line_length += part.size
|
||||
if line_length > 60
|
||||
f.puts
|
||||
f.print ' '
|
||||
line_length = 0
|
||||
end
|
||||
end
|
||||
f.puts "};"
|
||||
|
||||
f.puts "const int16_t label_for_address[] = {"
|
||||
f.print ' '
|
||||
(0..0xffff).each do |address|
|
||||
value = -1
|
||||
if label_for_address.include?(address)
|
||||
value = symbols.index(label_for_address[address]);
|
||||
end
|
||||
f.print "#{value}, "
|
||||
if (address + 1) % 16 == 0
|
||||
f.puts
|
||||
f.print ' '
|
||||
end
|
||||
end
|
||||
f.puts "};";
|
||||
end
|
||||
|
||||
File::open('watches.h', 'w') do |f|
|
||||
f.puts "const uint16_t WATCH_COUNT = #{watches.size};"
|
||||
types = ['u8', 'u16', 's8', 's16'];
|
||||
types.each.with_index do |x, _|
|
||||
f.puts "#define WATCH_#{x.upcase} #{_ + 1}"
|
||||
end
|
||||
f.puts "const char* WATCH_LABELS[] = {";
|
||||
f.puts watches.map { |x| " \"#{x[:label]}\"" }.join(",\n");
|
||||
f.puts "};"
|
||||
f.puts "const uint16_t WATCH_ADDRESSES[] = {";
|
||||
f.puts watches.map { |x| " #{sprintf('0x%04x', x[:address])}" }.join(",\n");
|
||||
f.puts "};"
|
||||
f.puts "const uint8_t WATCH_TYPES[] = {";
|
||||
f.puts watches.map { |x| " #{types.index(x[:type]) + 1}" }.join(",\n");
|
||||
f.puts "};"
|
||||
|
||||
f.puts "const uint16_t ARG_WATCH_COUNT = #{arg_watches.size};"
|
||||
types = ['u8', 'u16', 's8', 's16'];
|
||||
f.puts "const uint16_t ARG_WATCH_ADDRESSES[] = {";
|
||||
f.puts arg_watches.map { |x| " #{sprintf('0x%04x', x[:address])}" }.join(",\n");
|
||||
f.puts "};"
|
||||
f.puts "const uint8_t ARG_WATCH_TYPES[] = {";
|
||||
f.puts arg_watches.map { |x| " #{types.index(x[:type]) + 1}" }.join(",\n");
|
||||
f.puts "};"
|
||||
registers = ['A', 'X', 'Y']
|
||||
registers.each.with_index do |x, _|
|
||||
f.puts "#define WATCH_#{x} #{_ + 1}"
|
||||
end
|
||||
f.puts "const uint8_t ARG_WATCH_REGISTERS[] = {";
|
||||
f.puts arg_watches.ma |