diff options
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | Makefile | 6 | ||||
| -rw-r--r-- | tools/disasm_coverage.py | 86 | ||||
| -rw-r--r-- | tools/mapreader.py | 171 |
4 files changed, 266 insertions, 1 deletions
@@ -23,4 +23,8 @@ shim.asm tools/scan_includes tools/pkmncompress tools/gfx +tools/*.pyc build/ + +coverage.png +coverage.stats @@ -28,7 +28,7 @@ GFX := $(patsubst %.png, $(BUILD)/%.2bpp, \ .SECONDEXPANSION: .PHONY: all -all: $(ROMS) compare +all: $(ROMS) compare coverage .PHONY: compare compare: $(ROMS) @@ -56,6 +56,10 @@ $(ROMS): $(OBJS) $(BUILD)/shim.asm: tools/make_shim.py shim.sym | $$(dir $$@) $(PYTHON) tools/make_shim.py -w -- $(filter-out $<, $^) > $@ +.PHONY: coverage +coverage: $(ROMS) + $(PYTHON) tools/disasm_coverage.py -m $(ROMS:.gb=.map) -b 0x40 + $(BUILD)/gfx.o: | $(GFX) $(BUILD)/%.o: $(BUILD)/%.asm | $$(dir $$@) $(RGBASM) $(RGBASMFLAGS) -M $(@:.o=.d) $(OUTPUT_OPTION) $< diff --git a/tools/disasm_coverage.py b/tools/disasm_coverage.py new file mode 100644 index 0000000..964e394 --- /dev/null +++ b/tools/disasm_coverage.py @@ -0,0 +1,86 @@ +# #!/usr/bin/env python3 +# coding: utf-8 + +from __future__ import division +import os +import argparse +from mapreader import MapReader +import png +from colorsys import hls_to_rgb + +if __name__ == "__main__": + # argument parser + ap = argparse.ArgumentParser() + ap.add_argument("-o", dest="filename", default="coverage.png") + ap.add_argument("-s", dest="statsname", default="coverage.stats") + ap.add_argument("-m", dest="mapfile", required=True) + ap.add_argument("-b", dest="num_banks", required=True, type=lambda x: int(x, 0)) + + args = ap.parse_args() + + width = 256 + bpp = 8 #bytes per pixel + height = (args.num_banks * 0x4000 + (width * bpp - 1)) // (width * bpp) + + r = MapReader() + f = open(args.mapfile, 'r') + l = f.readlines() + f.close() + + r.read_map_data(l) + bank_data = r.bank_data + hit_data = [[0 for x in range(width)] for y in range(height)] + + with open(args.statsname, 'w') as stats: + + for bank in range(0,args.num_banks): + + data = bank_data['ROM Bank'].get(bank, {'sections': [], 'used': 0, 'slack': 0x4000}) + + for s in data['sections']: + beg = (s['beg'] & 0x3FFF) + bank * 0x4000 + end = (s['end'] & 0x3FFF) + bank * 0x4000 + y_beg = beg // (width * bpp) + x_beg = (beg % (width * bpp)) // bpp + + y_end = end // (width * bpp) + x_end = (end % (width * bpp)) // bpp + + #print('beg {0} end {1}: {2}/{3} -- {4}/{5}'.format(beg, end, y_beg, x_beg, y_end, x_end)) + + # special case y_beg/x_beg and y_end/x_end + if (end - beg + 1 < bpp): + hit_data[y_beg][x_beg] += end - beg + 1 + + else: + hit_data[y_beg][x_beg] += bpp - ((beg % (width * bpp)) - x_beg * bpp) + hit_data[y_end][x_end] += ((end % (width * bpp)) - x_end * bpp + 1) + + # regular case + for y in range(y_beg, y_end + 1): + + x_line_beg = 0 if y_beg != y else x_beg + 1 + x_line_end = width - 1 if y_end != y else x_end - 1 + + for x in range(x_line_beg, x_line_end + 1): + + hit_data[y][x] += bpp + + + with open(args.filename, 'wb') as pngfile: + + w = png.Writer(width, height); + + pngdata = [] + for row in hit_data: + row_png_data = () + for col in row: + + if (0 != col): + print('Here') + rgb = [255 * x for x in hls_to_rgb(120/360, 1.0 - (col/bpp * (100 - 15))/100, 100/100)] + row_png_data += tuple(rgb) + + pngdata.append(row_png_data) + + w.write(pngfile, pngdata) diff --git a/tools/mapreader.py b/tools/mapreader.py new file mode 100644 index 0000000..41ad935 --- /dev/null +++ b/tools/mapreader.py @@ -0,0 +1,171 @@ +# #!/usr/bin/env python3 +# coding: utf-8 + +import re + +class MapReader: + + # {'ROM Bank': { 0: { 'sections': [ { 'beg': 1234, + # 'end': 5678, + # 'name': 'Section001', + # 'symbols': [ { 'symbol': 'Function1234', + # 'address: 1234, + # }, + # ] + # }, + # ], + # 'used': 1234, + # 'slack': 4567, + # }, + # }, + # 'OAM': { 'sections': [ { 'beg': 1234, + # 'end': 5678, + # 'name': 'Section002', + # 'symbols': [ { 'symbol': 'Data1234', + # 'address: 1234, + # }, + # ] + # }, + # ], + # 'used': 1234, + # 'slack': 4567, + # }, + # } + # + bank_data = {} + + bank_types = { + 'HRAM' : { 'size': 0x80, 'banked': False, }, + 'OAM' : { 'size': 0xA0, 'banked': False, }, + 'ROM Bank' : { 'size': 0x4000, 'banked': True, }, + 'SRAM Bank': { 'size': 0x2000, 'banked': True, }, + 'VRAM Bank': { 'size': 0x1000, 'banked': True, }, + 'WRAM Bank': { 'size': 0x2000, 'banked': True, }, + } + + # FSM states + INIT, BANK, SECTION = range(3) + + # $506D-$519A ($012E bytes) ["Type Matchups"] + section_header_regex = re.compile('\$([0-9A-Fa-f]{4})-\$([0-9A-Fa-f]{4}) \(.*\) \["(.*)"\]') + # $506D = TypeMatchups + section_data_regex = re.compile('\$([0-9A-Fa-f]{4}) = (.*)') + # $3ED2 bytes + slack_regex = re.compile('\$([0-9A-Fa-f]{4}) bytes') + + def __init__(self, *args, **kwargs): + self.__dict__.update(kwargs) + + def _parse_init(self, line): + + line = line.split(':', 1)[0] + parts = line.split(' #', 1) + + if (parts[0] in self.bank_types): + self._cur_bank_name = parts[0] + self._cur_bank_type = self.bank_types[self._cur_bank_name] + if (self._cur_bank_type['banked'] and len(parts) > 1): + parts[1] = parts[1].split(':', 1)[0] + parts[1] = parts[1].split(' ', 1)[0] + self._cur_bank = int(parts[1], 10) + if self._cur_bank_name not in self.bank_data: + self.bank_data[self._cur_bank_name] = {} + if self._cur_bank_type['banked']: + if self._cur_bank not in self.bank_data[self._cur_bank_name]: + self.bank_data[self._cur_bank_name][self._cur_bank] = {} + self._cur_data = self.bank_data[self._cur_bank_name][self._cur_bank] + else: + self._cur_data = self.bank_data[self._cur_bank_name] + + if ({} == self._cur_data): + self._cur_data['sections'] = [] + self._cur_data['used'] = 0 + self._cur_data['slack'] = self._cur_bank_type['size'] + return True + + return False + + def _parse_section_header(self, header): + + section_data = self.section_header_regex.match(header) + if section_data is not None: + beg = int(section_data.group(1), 16) + end = int(section_data.group(2), 16) + name = section_data.group(3) + self._cur_section = {'beg': beg, 'end': end, 'name': name, 'symbols': []} + self._cur_data['sections'].append(self._cur_section) + return True + return False + + def _parse_slack(self, data): + + slack_data = self.slack_regex.match(data) + slack_bytes = int(slack_data.group(1), 16) + self._cur_data['slack'] = slack_bytes + + used_bytes = 0 + + for s in self._cur_data['sections']: + used_bytes += s['end'] - s['beg'] + 1 + + self._cur_data['used'] = used_bytes + + def read_map_data(self, map): + + if type(map) is str: + map = map.split('\n') + + self._state = MapReader.INIT + self._cur_bank_name = '' + self._cur_bank_type = {} + self._cur_bank = 0 + self._cur_data = {} + + for line in map: + + line = line.rstrip() + if (MapReader.INIT == self._state): + + if (self._parse_init(line)): + self._state = MapReader.BANK + + elif (MapReader.BANK == self._state or MapReader.SECTION == self._state): + + if ('' == line): + self._state = MapReader.INIT + else: + + line = line.lstrip() + parts = line.split(': ', 1) + + if (MapReader.SECTION == self._state): + section_data = self.section_data_regex.match(parts[0]) + if section_data is not None: + address = int(section_data.group(1), 16) + name = section_data.group(2) + self._cur_section['symbols'].append({'name': name, 'address': address}) + continue + + if ('SECTION' == parts[0]): + if (self._parse_section_header(parts[1])): + self._state = MapReader.SECTION + elif ('SLACK' == parts[0]): + self._parse_slack(parts[1]) + self._state = MapReader.INIT + elif ('EMPTY' == parts[0]): + self._cur_data = {'sections': [], 'used': 0, 'slack': self._cur_bank_type['size']} + self._state = MapReader.INIT + + else: + pass + + for k, v in self.bank_data.items(): + if (self.bank_types[k]['banked']): + for _, vv in v.items(): + vv['sections'].sort(key=lambda x: x['beg']) + for vvv in vv['sections']: + vvv['symbols'].sort(key=lambda x: x['address']) + else: + v['sections'].sort(key=lambda x: x['beg']) + for vv in v['sections']: + vv['symbols'].sort(key=lambda x: x['address'])
\ No newline at end of file |
