From 064b72ae4eb22336438288a9664a37c0dd07f4bc Mon Sep 17 00:00:00 2001 From: Alejandro Soto Date: Tue, 6 Dec 2022 13:04:15 -0600 Subject: Implement gdbstub --- .gitignore | 1 + Makefile | 18 ++- rtl/top/conspiracion.sv | 3 +- sim/gdbstub.py | 134 ++++++++++++++++++ sim/link.ld | 32 +++++ sim/sim.py | 367 ++++++++++++++++++++++++++++++++++++++++++++++++ sim/start.S | 34 +++++ tb/avalon.hpp | 3 + tb/avalon.impl.hpp | 42 ++++-- tb/sim/link.ld | 32 ----- tb/sim/sim.py | 310 ---------------------------------------- tb/sim/start.S | 34 ----- tb/top/conspiracion.cpp | 187 ++++++++++++++++++------ 13 files changed, 764 insertions(+), 433 deletions(-) create mode 100644 sim/gdbstub.py create mode 100644 sim/link.ld create mode 100755 sim/sim.py create mode 100644 sim/start.S delete mode 100644 tb/sim/link.ld delete mode 100755 tb/sim/sim.py delete mode 100644 tb/sim/start.S diff --git a/.gitignore b/.gitignore index 5c5f2d1..4b52e55 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ hps_isw_handoff/ *.vcd qmegawiz_errors_log.txt cr_ie_info.json +u-boot diff --git a/Makefile b/Makefile index 66e9ff7..8f5399d 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,8 @@ VCD_DIR := vcd OBJ_DIR := obj RTL_DIR := rtl TB_DIR := tb -SIM_DIR := $(TB_DIR)/sim +SIM_DIR := sim +TB_SIM_DIR := $(TB_DIR)/sim SIM_OBJ_DIR := $(OBJ_DIR)/$(TOP)/sim VERILATOR := verilator CROSS_CC := arm-none-eabi-gcc @@ -28,10 +29,13 @@ trace/%: exe/% $(VCD_DIR)/% $(VCD_DIR)/%: mkdir -p $@ -sim: $(patsubst $(SIM_DIR)/%.py,sim/%,$(filter-out $(SIM_DIR)/sim.py,$(wildcard $(SIM_DIR)/*.py))) +sim: $(patsubst $(TB_SIM_DIR)/%.py,sim/%,$(wildcard $(TB_SIM_DIR)/*.py)) -sim/%: $(SIM_DIR)/sim.py $(SIM_DIR)/%.py exe/$(TOP) $(SIM_OBJ_DIR)/%.bin - @$< $(SIM_DIR)/$*.py $(OBJ_DIR)/$(TOP)/V$(TOP) $(SIM_OBJ_DIR)/$*.bin +sim/%: $(SIM_DIR)/sim.py $(TB_SIM_DIR)/%.py exe/$(TOP) $(SIM_OBJ_DIR)/%.bin + @$< $(TB_SIM_DIR)/$*.py $(OBJ_DIR)/$(TOP)/V$(TOP) $(SIM_OBJ_DIR)/$*.bin + +vmlaunch: $(SIM_DIR)/sim.py $(SIM_DIR)/gdbstub.py exe/$(TOP) + @$< $(SIM_DIR)/gdbstub.py $(OBJ_DIR)/$(TOP)/V$(TOP) u-boot/build/u-boot-dtb.bin $(SIM_OBJ_DIR)/%.bin: $(SIM_OBJ_DIR)/% $(CROSS_OBJCOPY) -O binary --only-section=._img $< $@ @@ -39,10 +43,14 @@ $(SIM_OBJ_DIR)/%.bin: $(SIM_OBJ_DIR)/% $(SIM_OBJ_DIR)/%: $(SIM_OBJ_DIR)/%.o $(SIM_OBJ_DIR)/start.o $(CROSS_CC) $(CROSS_LDFLAGS) -o $@ -g -T $(SIM_DIR)/link.ld -nostartfiles -nostdlib $^ -$(SIM_OBJ_DIR)/%.o: $(SIM_DIR)/%.c +$(SIM_OBJ_DIR)/%.o: $(TB_SIM_DIR)/%.c @mkdir -p $(SIM_OBJ_DIR) $(CROSS_CC) $(CROSS_CFLAGS) -o $@ -g -c $< -mcpu=arm810 +$(SIM_OBJ_DIR)/%.o: $(TB_SIM_DIR)/%.S + @mkdir -p $(SIM_OBJ_DIR) + $(CROSS_CC) $(CROSS_CFLAGS) -o $@ -g -c $< + $(SIM_OBJ_DIR)/%.o: $(SIM_DIR)/%.S @mkdir -p $(SIM_OBJ_DIR) $(CROSS_CC) $(CROSS_CFLAGS) -o $@ -g -c $< diff --git a/rtl/top/conspiracion.sv b/rtl/top/conspiracion.sv index c3ffb93..84b875e 100644 --- a/rtl/top/conspiracion.sv +++ b/rtl/top/conspiracion.sv @@ -3,6 +3,7 @@ module conspiracion input wire clk_clk, input wire rst_n, input wire halt, + output wire cpu_halted, output wire [12:0] memory_mem_a, output wire [2:0] memory_mem_ba, output wire memory_mem_ck, @@ -46,7 +47,7 @@ module conspiracion logic[3:0] data_be; logic[29:0] addr; logic[31:0] data_rd, data_wr; - logic reset_reset_n, cpu_clk, cpu_rst_n, cpu_halt, cpu_halted, + logic reset_reset_n, cpu_clk, cpu_rst_n, cpu_halt, ready, write, start, irq; `ifdef VERILATOR diff --git a/sim/gdbstub.py b/sim/gdbstub.py new file mode 100644 index 0000000..b262971 --- /dev/null +++ b/sim/gdbstub.py @@ -0,0 +1,134 @@ +import sys, socket + +start_halted = True + +def init(): + global client + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.bind(('127.0.0.1', 1234)) + sock.listen() + print('Listening for gdb on', sock.getsockname(), file=sys.stderr) + + client, peer = sock.accept() + sock.close() + print('Accepted connection from', peer, file=sys.stderr) + +buffer = b'' +haltFromStop = False + +def halt(): + global buffer, haltFromStop + + if haltFromStop: + reply(b'S05') + haltFromStop = False + + while True: + data = client.recv(4096) + if not data: + break + + buffer = buffer + data if buffer else data + + try: + start = buffer.index(b'$') + marker = buffer.index(b'#', start + 1) + except ValueError: + continue + + if marker + 2 >= len(buffer): + continue + + data = buffer[start + 1:marker] + cksum = int(buffer[marker + 1:marker + 3], 16) + + if cksum != (sum(data) & 0xff): + raise Exception(f'bad packet checksum: {buffer[start:marker + 3]}') + + buffer = buffer[marker + 3:] + + client.send(b'+') + + if data[0] == b'?'[0]: + out = b'S05' + elif data[0] == b'c'[0]: + assert not data[1:] #TODO + break + elif data[0] == b'D'[0]: + out = b'OK' + elif data[0] == b'g'[0]: + out = hexout(read_reg(gdb_reg(r)) for r in range(16)) + elif data[0] == b'm'[0]: + addr, length = (int(x, 16) for x in data[1:].split(b',')) + out = hexout(read_mem(addr, length)) + elif data[0] == b'M'[0]: + addrlen, data = data[1:].split(b':') + addr, length = (int(x, 16) for x in addrlen.split(b',')) + + data = bytes.fromhex(str(data, 'ascii')) + assert len(data) == length + + write_mem(addr, data) + out = b'OK' + elif data[0] == b'p'[0]: + reg = gdb_reg(int(data[1:], 16)) + out = hexout(read_reg(reg) if reg is not None else None) + else: + print('unhandled packet:', data) + out = b'' + + reply(out) + + haltFromStop = True + +def reply(out): + client.send(b'$' + out + b'#' + hexout(sum(out) & 0xff, 1)) + +def gdb_reg(n): + if 0 <= n < 8: + return (r0, r1, r2, r3, r4, r5, r6, r7)[n] + + if n == 15: + return pc + + if n == 0x19: + return cpsr + + mode = read_reg(cpsr) & 0b11111 + if 8 <= n < 13: + if mode == 0b10001: + regs = (r8_fiq, r9_fiq, r10_fiq, r11_fiq, r12_fiq) + else: + regs = (r8_fiq, r9_fiq, r10_fiq, r11_fiq, r12_fiq) + + return regs[n - 8] + + if 13 <= n < 15: + if mode == 0b10011: + regs = (r13_svc, r14_svc) + if mode == 0b10111: + regs = (r13_abt, r14_abt) + if mode == 0b11011: + regs = (r13_und, r14_und) + if mode == 0b10010: + regs = (r13_irq, r14_irq) + if mode == 0b10001: + regs = (r13_fiq, r14_fiq) + else: + regs = (r13_usr, r14_usr) + + return regs[n - 13] + + return None + +def hexout(data, size=4): + if data is None: + return b'' + elif type(data) is bytes: + return data.hex().encode('ascii') + elif type(data) is int: + data = [data] + + return b''.join(hex(d)[2:].zfill(2 * size)[:2 * size].encode('ascii') for d in data) + diff --git a/sim/link.ld b/sim/link.ld new file mode 100644 index 0000000..d26cb2a --- /dev/null +++ b/sim/link.ld @@ -0,0 +1,32 @@ +MEMORY +{ + HPS_SDRAM (rwx) : ORIGIN = 0x00000000, LENGTH = 512M +} + +SECTIONS +{ + ._img : + { + KEEP(*(.interrupt_vector)) + *(.text) + *(.text*) + *(.rodata) + *(.rodata*) + *(.data) + *(.data*) + *(.bss) + *(.bss*) + *(COMMON) + . = ALIGN(4); + } > HPS_SDRAM + + _stack_size = 4096; + _stack_end = ORIGIN(HPS_SDRAM) + LENGTH(HPS_SDRAM); + _stack_begin = _stack_end - _stack_size; + + . = _stack_begin; + ._stack : + { + . = . + _stack_size; + } > HPS_SDRAM +} diff --git a/sim/sim.py b/sim/sim.py new file mode 100755 index 0000000..16ceb54 --- /dev/null +++ b/sim/sim.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python3 + +import importlib.util, os, pathlib, random, socket, subprocess, sys + +module_path, verilated, image = sys.argv[1:] +test_name = pathlib.Path(module_path).stem +module = None + +seed = os.getenv('SIM_SEED', str(random.randint(0, 0x7fff_ffff))) + +all_regs = [ + ('r0', 'r0'), + ('r1', 'r1'), + ('r2', 'r2'), + ('r3', 'r3'), + ('r4', 'r4'), + ('r5', 'r5'), + ('r6', 'r6'), + ('r7', 'r7'), + ('r8', 'r8_usr'), + ('r8_usr', 'r8_usr'), + ('r8_fiq', 'r8_fiq'), + ('r9', 'r9_usr'), + ('r9_usr', 'r9_usr'), + ('r9_fiq', 'r9_fiq'), + ('r10', 'r10_usr'), + ('r10_usr', 'r10_usr'), + ('r10_fiq', 'r10_fiq'), + ('r11', 'r11_usr'), + ('r11_usr', 'r11_usr'), + ('r11_fiq', 'r11_fiq'), + ('r12', 'r12_usr'), + ('r12_usr', 'r12_usr'), + ('r12_fiq', 'r12_fiq'), + ('sp', 'r13_usr'), + ('sp_usr', 'r13_usr'), + ('sp_svc', 'r13_svc'), + ('sp_abt', 'r13_abt'), + ('sp_und', 'r13_und'), + ('sp_irq', 'r13_irq'), + ('sp_fiq', 'r13_fiq'), + ('r13', 'r13_usr'), + ('r13_usr', 'r13_usr'), + ('r13_svc', 'r13_svc'), + ('r13_abt', 'r13_abt'), + ('r13_und', 'r13_und'), + ('r13_irq', 'r13_irq'), + ('r13_fiq', 'r13_fiq'), + ('lr', 'r14_usr'), + ('lr_usr', 'r14_usr'), + ('lr_svc', 'r14_svc'), + ('lr_abt', 'r14_abt'), + ('lr_und', 'r14_und'), + ('lr_irq', 'r14_irq'), + ('lr_fiq', 'r14_fiq'), + ('r14', 'r14_usr'), + ('r14_usr', 'r14_usr'), + ('r14_svc', 'r14_svc'), + ('r14_abt', 'r14_abt'), + ('r14_und', 'r14_und'), + ('r14_irq', 'r14_irq'), + ('r14_fiq', 'r14_fiq'), + ('pc', 'pc'), + ('r15', 'pc'), + ('cpsr', 'cpsr'), + ('spsr_svc', 'spsr_svc'), + ('spsr_abt', 'spsr_abt'), + ('spsr_und', 'spsr_und'), + ('spsr_irq', 'spsr_irq'), + ('spsr_fiq', 'spsr_fiq'), + ] + +regs = {} +read_reg = lambda r: regs.setdefault(r, 0) + +dumped = [] +halted = False + +def recv_mem_dump(): + dumped.clear() + for line in sim_end: + line = line.strip() + if line == '=== dump-mem ===' or not line: + continue + elif line == '=== end-mem ===': + break + + base, data = line.split() + dumped.append((int(base, 16) << 2, bytes.fromhex(data))) + +def read_mem(base, length): + fragments = [] + i = 0 + + if halted and length > 0: + print('dump-mem', base >> 2, (length + base - (base & ~0b11) + 0b11) >> 2, file=sim_end, flush=True) + recv_mem_dump() + + while length > 0: + assert i < len(dumped), f'memory at 0x{base:08x} not dumped' + start, data = dumped[i] + delta = base - start + + if delta < 0: + i = len(dumped) + elif delta < len(data): + taken = min(length, len(data) - delta) + fragments.append(data[delta:delta + taken]) + + base += taken + length -= taken + else: + i += 1 + + return b''.join(fragments) + +def write_mem(base, data): + assert halted + + if not data: + return + + prefix = read_mem(base & ~0b11, base & 0b11) + suffix = read_mem(base + len(data), (4 - ((base + len(data)) & 0b11)) & 0b11) + print('patch-mem ', base >> 2, ' ', prefix.hex(), data.hex(), suffix.hex(), sep='', file=sim_end, flush=True) + + #TODO: Invalidate written addresses only + dumped.clear() + +def hexdump(base, memory): + lines = [] + offset = 0 + + while offset < len(memory) > 0: + taken = min(16, len(memory) - offset) + line_bytes = memory[offset:offset + taken] + + half = lambda rng: ' '.join(f'{line_bytes[i]:02x}' if i < taken else ' ' for i in rng) + left, right = half(range(0, 8)), half(range(8, 16)) + + ascii = ''.join(c if c.isascii() and c.isprintable() else '.' for c in map(chr, line_bytes)) + lines.append(f' {base:08x}: {left} {right} | {ascii}') + + base += 16 + offset += taken + + return '\n'.join(lines) + +def module_get(attr, default=None): + return getattr(module, attr, default) if module else None + +COLOR_RESET = '\033[0m' +COLOR_RED = '\033[31;1m' +COLOR_GREEN = '\033[32m' +COLOR_YELLOW = '\033[33;1m' +COLOR_BLUE = '\033[34;1m' + +def exit(*, success): + global seed + + if not success: + while_running() + if exec_args: + print('cmdline:', subprocess.list2cmdline(exec_args), file=sys.stderr) + + status, color = ('passed', COLOR_GREEN) if success else (f'failed (seed: {seed})', COLOR_RED) + print( \ + f'{color}Test \'{COLOR_YELLOW}{test_name}{COLOR_RESET}{color}\' ' + + f'{status}{COLOR_RESET}', file=sys.stderr) + + sys.exit(0 if success else 1) + +def dump_regs(): + order = {item[0]: i for i, item in enumerate(all_regs)} + next_col = 0 + + for reg, value in sorted(regs.items(), key=lambda item: order[item[0]]): + if next_col > 0: + print(' ', end='', file=sys.stderr) + + print(f'{reg:<8} = 0x{value:08x}', end='', file=sys.stderr) + if next_col == 3: + print(file=sys.stderr) + next_col = 0 + else: + next_col += 1 + + if next_col != 0: + print(file=sys.stderr) + +printed_while_running = False +def while_running(): + global printed_while_running + + if not printed_while_running: + print( + f'{COLOR_BLUE}While running test \'{COLOR_YELLOW}{test_name}' + \ + f'{COLOR_RESET}{COLOR_BLUE}\'{COLOR_RESET}') + + printed_while_running = True + +def test_assert(condition, message): + if not condition: + while_running() + print(f'{COLOR_RED}{message()}{COLOR_RESET}', file=sys.stderr) + + if regs: + dump_regs() + + exit(success=False) + +def unsigned(n): + assert -0x8000_0000 <= n <= 0xffff_ffff + return n + 0x1_0000_0000 if n < 0 else n + +def split_dword(n): + assert -0x8000_0000_0000_0000 <= n <= 0xffff_ffff_ffff_ffff + if n < 0: + n += 0x1_0000_0000_0000_0000 + + return (n >> 32, n & 0xffff_ffff) + +def int_bytes(n): + return n.to_bytes(4, 'little', signed=n < 0) if type(n) is int else n + +def assert_reg(r, expected): + actual = read_reg(r) + expected = unsigned(expected) + + test_assert( \ + actual == expected, \ + lambda: f'Register {r} = 0x{actual:08x}, expected 0x{expected:08x}') + +def assert_mem(base, value): + if type(value) is list: + value = b''.join(int_bytes(w) for w in value) + else: + value = int_bytes(value) + + actual = read_mem(base, len(value)) + test_assert( \ + actual == value, \ + lambda: \ + f'Memory at 0x{base:08x} holds:\n{hexdump(base, actual)}\n' + \ + f'But this was expected instead:\n{hexdump(base, value)}') + +init_regs = {} + +def init_reg(r, value): + global init_regs + assert init_regs is not None + init_regs[r] = unsigned(value) + +if test_name in os.getenv('SIM_SKIP', '').split(','): + print( \ + f'{COLOR_BLUE}Test \'{COLOR_YELLOW}{test_name}{COLOR_RESET}' + + f'{COLOR_BLUE}\' skipped{COLOR_RESET}', file=sys.stderr) + + exit(success=True) + +spec = importlib.util.spec_from_file_location('sim', module_path) +module = importlib.util.module_from_spec(spec) + +prelude = { + 'read_reg': read_reg, + 'read_mem': read_mem, + 'write_mem': write_mem, + 'assert_reg': assert_reg, + 'assert_mem': assert_mem, + 'init_reg': init_reg, + 'split_dword': split_dword, + } + +prelude.update({k: v for k, v in all_regs}) +module.__dict__.update(prelude) +spec.loader.exec_module(module) + +cycles = module_get('cycles', 1024) +mem_dumps = module_get('mem_dumps', []) + +if init := module_get('init'): + init() + +exec_args = [verilated, '--headless', '--no-tty', '--cycles', str(cycles), '--dump-regs'] + +for rng in mem_dumps: + length = rng.stop - rng.start + assert rng.start >= 0 and rng.stop > rng.start \ + and rng.step == 1 and ((rng.start | length) & 3) == 0 + + exec_args.extend(['--dump-mem', f'{rng.start >> 2},{length >> 2}']) + +for r, value in init_regs.items(): + exec_args.extend(['--init-reg', f'{r}={value}']) + +for addr, const in module_get('consts', {}).items(): + exec_args.extend(['--const', f'{addr},{const}']) + +for addr, filename in module_get('loads', {}).items(): + exec_args.extend(['--load', f'{addr},{filename}']) + +if module_get('start_halted', False): + exec_args.append('--start-halted') + +sim_end, target_end = socket.socketpair() +sim_end = sim_end.makefile('rw') +target_fd = target_end.fileno() + +exec_args.extend(['--control-fd', str(target_fd)]) + +init_regs = None +exec_args.append(image) + +exec_args.append(f'+verilator+seed+{seed}') +if not os.getenv('SIM_PULLX', 0): + exec_args.append('+verilator+rand+reset+2') + +process = subprocess.Popen(exec_args, pass_fds=(target_fd,)) +target_end.close() + +in_regs = False +halt = module_get('halt') + +while True: + for line in sim_end: + line = line.strip() + if line == '=== halted ===': + break + if line == '=== dump-regs ===': + in_regs = True + elif line == '=== end-regs ===': + in_regs = False + elif line == '=== dump-mem ===': + recv_mem_dump() + elif not line: + continue + elif in_regs: + value, reg = line.split() + regs[reg] = int(value, 16) + else: + while_running() + print(f'{COLOR_BLUE}{line}{COLOR_RESET}') + else: + break + + halted = True + if halt: + halt() + + print('continue', file=sim_end, flush=True) + if not halt: + break + +process.wait(timeout=1) +if process.returncode != 0: + exit(success=False) + +if final := module_get('final'): + final() + +if os.getenv('SIM_DUMP', ''): + dump_regs() + for rng in mem_dumps: + print(f'Memory range 0x{rng.start:08x}..0x{rng.stop:08x}') + print(hexdump(rng.start, read_mem(rng.start, rng.stop - rng.start))) + +exit(success=True) diff --git a/sim/start.S b/sim/start.S new file mode 100644 index 0000000..7639513 --- /dev/null +++ b/sim/start.S @@ -0,0 +1,34 @@ +.section .interrupt_vector + +__reset: + b _start + +__undefined: + b undefined + +__swi: + b __data_abort + +__prefetch_abort: + b __prefetch_abort + +__data_abort: + b __data_abort + +__irq: + b __irq + +__fiq: + b __fiq + +.text + +.global _start +_start: + ldr sp, =_stack_end + bl reset + b . + +.weak undefined +undefined: + b undefined diff --git a/tb/avalon.hpp b/tb/avalon.hpp index 30eac2c..f37b306 100644 --- a/tb/avalon.hpp +++ b/tb/avalon.hpp @@ -81,6 +81,7 @@ namespace taller::avalon void bail() noexcept; std::uint32_t dump(std::uint32_t addr); + void patch(std::uint32_t addr, std::uint32_t readdata); private: struct binding @@ -98,6 +99,8 @@ namespace taller::avalon unsigned avl_byteenable = 0; bool avl_read = false; bool avl_write = false; + + slave &resolve_external(std::uint32_t avl_address); }; } diff --git a/tb/avalon.impl.hpp b/tb/avalon.impl.hpp index 5ba514f..3af60d0 100644 --- a/tb/avalon.impl.hpp +++ b/tb/avalon.impl.hpp @@ -128,25 +128,45 @@ namespace taller::avalon std::uint32_t interconnect::dump(std::uint32_t addr) { std::uint32_t avl_address = addr << 2; + auto &dev = resolve_external(avl_address); + auto pos = (avl_address & ~dev.address_mask()) >> dev.word_bits(); + + std::uint32_t readdata; + while(!dev.read(pos, readdata)) + { + continue; + } + + return readdata; + } + + template + void interconnect::patch(std::uint32_t addr, std::uint32_t writedata) + { + std::uint32_t avl_address = addr << 2; + auto &dev = resolve_external(avl_address); + + auto pos = (avl_address & ~dev.address_mask()) >> dev.word_bits(); + + while(!dev.write(pos, writedata, 0b1111)) + { + continue; + } + } + + template + slave& interconnect::resolve_external(std::uint32_t avl_address) + { for(auto &binding : devices) { if((avl_address & binding.mask) == binding.base) { - auto &dev = binding.dev; - auto pos = (avl_address & ~dev.address_mask()) >> 2; - - std::uint32_t readdata; - while(!dev.read(pos, readdata)) - { - continue; - } - - return readdata; + return binding.dev; } } - fprintf(stderr, "[avl] attempt to dump memory hole at 0x%08x\n", addr); + fprintf(stderr, "[avl] attempt to access hole at 0x%08x\n", avl_address); assert(false); } } diff --git a/tb/sim/link.ld b/tb/sim/link.ld deleted file mode 100644 index d26cb2a..0000000 --- a/tb/sim/link.ld +++ /dev/null @@ -1,32 +0,0 @@ -MEMORY -{ - HPS_SDRAM (rwx) : ORIGIN = 0x00000000, LENGTH = 512M -} - -SECTIONS -{ - ._img : - { - KEEP(*(.interrupt_vector)) - *(.text) - *(.text*) - *(.rodata) - *(.rodata*) - *(.data) - *(.data*) - *(.bss) - *(.bss*) - *(COMMON) - . = ALIGN(4); - } > HPS_SDRAM - - _stack_size = 4096; - _stack_end = ORIGIN(HPS_SDRAM) + LENGTH(HPS_SDRAM); - _stack_begin = _stack_end - _stack_size; - - . = _stack_begin; - ._stack : - { - . = . + _stack_size; - } > HPS_SDRAM -} diff --git a/tb/sim/sim.py b/tb/sim/sim.py deleted file mode 100755 index 91fc348..0000000 --- a/tb/sim/sim.py +++ /dev/null @@ -1,310 +0,0 @@ -#!/usr/bin/env python3 - -import importlib.util, os, pathlib, random, subprocess, sys - -module_path, verilated, image = sys.argv[1:] -test_name = pathlib.Path(module_path).stem -module = None - -seed = os.getenv('SIM_SEED', str(random.randint(0, 0x7fff_ffff))) - -all_regs = [ - ('r0', 'r0'), - ('r1', 'r1'), - ('r2', 'r2'), - ('r3', 'r3'), - ('r4', 'r4'), - ('r5', 'r5'), - ('r6', 'r6'), - ('r7', 'r7'), - ('r8', 'r8_usr'), - ('r8_usr', 'r8_usr'), - ('r8_fiq', 'r8_fiq'), - ('r9', 'r9_usr'), - ('r9_usr', 'r9_usr'), - ('r9_fiq', 'r9_fiq'), - ('r10', 'r10_usr'), - ('r10_usr', 'r10_usr'), - ('r10_fiq', 'r10_fiq'), - ('r11', 'r11_usr'), - ('r11_usr', 'r11_usr'), - ('r11_fiq', 'r11_fiq'), - ('r12', 'r12_usr'), - ('r12_usr', 'r12_usr'), - ('r12_fiq', 'r12_fiq'), - ('sp', 'r13_usr'), - ('sp_usr', 'r13_usr'), - ('sp_svc', 'r13_svc'), - ('sp_abt', 'r13_abt'), - ('sp_und', 'r13_und'), - ('sp_irq', 'r13_irq'), - ('sp_fiq', 'r13_fiq'), - ('r13', 'r13_usr'), - ('r13_usr', 'r13_usr'), - ('r13_svc', 'r13_svc'), - ('r13_abt', 'r13_abt'), - ('r13_und', 'r13_und'), - ('r13_irq', 'r13_irq'), - ('r13_fiq', 'r13_fiq'), - ('lr', 'r14_usr'), - ('lr_usr', 'r14_usr'), - ('lr_svc', 'r14_svc'), - ('lr_abt', 'r14_abt'), - ('lr_und', 'r14_und'), - ('lr_irq', 'r14_irq'), - ('lr_fiq', 'r14_fiq'), - ('r14', 'r14_usr'), - ('r14_usr', 'r14_usr'), - ('r14_svc', 'r14_svc'), - ('r14_abt', 'r14_abt'), - ('r14_und', 'r14_und'), - ('r14_irq', 'r14_irq'), - ('r14_fiq', 'r14_fiq'), - ('pc', 'pc'), - ('r15', 'pc'), - ('cpsr', 'cpsr'), - ('spsr_svc', 'spsr_svc'), - ('spsr_abt', 'spsr_abt'), - ('spsr_und', 'spsr_und'), - ('spsr_irq', 'spsr_irq'), - ('spsr_fiq', 'spsr_fiq'), - ] - -regs = {} -read_reg = lambda r: regs.setdefault(r, 0) - -dumped = [] -def read_mem(base, length): - fragments = [] - i = 0 - - while length > 0: - assert i < len(dumped), f'memory at 0x{base:08x} not dumped' - start, data = dumped[i] - delta = base - start - - if delta < 0: - i = len(dumped) - elif delta < len(data): - taken = min(length, len(data) - delta) - fragments.append(data[delta:delta + taken]) - - base += taken - length -= taken - else: - i += 1 - - return b''.join(fragments) - -def hexdump(base, memory): - lines = [] - offset = 0 - - while offset < len(memory) > 0: - taken = min(16, len(memory) - offset) - line_bytes = memory[offset:offset + taken] - - half = lambda rng: ' '.join(f'{line_bytes[i]:02x}' if i < taken else ' ' for i in rng) - left, right = half(range(0, 8)), half(range(8, 16)) - - ascii = ''.join(c if c.isascii() and c.isprintable() else '.' for c in map(chr, line_bytes)) - lines.append(f' {base:08x}: {left} {right} | {ascii}') - - base += 16 - offset += taken - - return '\n'.join(lines) - -def module_get(attr, default=None): - return getattr(module, attr, default) if module else None - -COLOR_RESET = '\033[0m' -COLOR_RED = '\033[31;1m' -COLOR_GREEN = '\033[32m' -COLOR_YELLOW = '\033[33;1m' -COLOR_BLUE = '\033[34;1m' - -def exit(*, success): - global seed - - if not success: - while_running() - if exec_args: - print('cmdline:', subprocess.list2cmdline(exec_args), file=sys.stderr) - - status, color = ('passed', COLOR_GREEN) if success else (f'failed (seed: {seed})', COLOR_RED) - print( \ - f'{color}Test \'{COLOR_YELLOW}{test_name}{COLOR_RESET}{color}\' ' + - f'{status}{COLOR_RESET}', file=sys.stderr) - - sys.exit(0 if success else 1) - -def dump_regs(): - order = {item[0]: i for i, item in enumerate(all_regs)} - next_col = 0 - - for reg, value in sorted(regs.items(), key=lambda item: order[item[0]]): - if next_col > 0: - print(' ', end='', file=sys.stderr) - - print(f'{reg:<8} = 0x{value:08x}', end='', file=sys.stderr) - if next_col == 3: - print(file=sys.stderr) - next_col = 0 - else: - next_col += 1 - - if next_col != 0: - print(file=sys.stderr) - -printed_while_running = False -def while_running(): - global printed_while_running - - if not printed_while_running: - print( - f'{COLOR_BLUE}While running test \'{COLOR_YELLOW}{test_name}' + \ - f'{COLOR_RESET}{COLOR_BLUE}\'{COLOR_RESET}') - - printed_while_running = True - -def test_assert(condition, message): - if not condition: - while_running() - print(f'{COLOR_RED}{message()}{COLOR_RESET}', file=sys.stderr) - - if regs: - dump_regs() - - exit(success=False) - -def unsigned(n): - assert -0x8000_0000 <= n <= 0xffff_ffff - return n + 0x1_0000_0000 if n < 0 else n - -def split_dword(n): - assert -0x8000_0000_0000_0000 <= n <= 0xffff_ffff_ffff_ffff - if n < 0: - n += 0x1_0000_0000_0000_0000 - - return (n >> 32, n & 0xffff_ffff) - -def int_bytes(n): - return n.to_bytes(4, 'little', signed=n < 0) if type(n) is int else n - -def assert_reg(r, expected): - actual = read_reg(r) - expected = unsigned(expected) - - test_assert( \ - actual == expected, \ - lambda: f'Register {r} = 0x{actual:08x}, expected 0x{expected:08x}') - -def assert_mem(base, value): - if type(value) is list: - value = b''.join(int_bytes(w) for w in value) - else: - value = int_bytes(value) - - actual = read_mem(base, len(value)) - test_assert( \ - actual == value, \ - lambda: \ - f'Memory at 0x{base:08x} holds:\n{hexdump(base, actual)}\n' + \ - f'But this was expected instead:\n{hexdump(base, value)}') - -init_regs = {} - -def init_reg(r, value): - global init_regs - assert init_regs is not None - init_regs[r] = unsigned(value) - -if test_name in os.getenv('SIM_SKIP', '').split(','): - print( \ - f'{COLOR_BLUE}Test \'{COLOR_YELLOW}{test_name}{COLOR_RESET}' + - f'{COLOR_BLUE}\' skipped{COLOR_RESET}', file=sys.stderr) - - exit(success=True) - -spec = importlib.util.spec_from_file_location('sim', module_path) -module = importlib.util.module_from_spec(spec) - -prelude = { - 'read_reg': read_reg, - 'read_mem': read_mem, - 'assert_reg': assert_reg, - 'assert_mem': assert_mem, - 'init_reg': init_reg, - 'split_dword': split_dword, - } - -prelude.update({k: v for k, v in all_regs}) -module.__dict__.update(prelude) -spec.loader.exec_module(module) - -cycles = module_get('cycles', 1024) -mem_dumps = module_get('mem_dumps', []) - -if init := module_get('init'): - init() - -exec_args = [verilated, '--headless', '--no-tty', '--cycles', str(cycles), '--dump-regs'] - -for rng in mem_dumps: - length = rng.stop - rng.start - assert rng.start >= 0 and rng.stop > rng.start \ - and rng.step == 1 and ((rng.start | length) & 3) == 0 - - exec_args.extend(['--dump-mem', f'{rng.start >> 2},{length >> 2}']) - -for r, value in init_regs.items(): - exec_args.extend(['--init-reg', f'{r}={value}']) - -for addr, const in module_get('consts', {}).items(): - exec_args.extend(['--const', f'{addr},{const}']) - -for addr, filename in module_get('loads', {}).items(): - exec_args.extend(['--load', f'{addr},{filename}']) - -init_regs = None -exec_args.append(image) - -exec_args.append(f'+verilator+seed+{seed}') -if not os.getenv('SIM_PULLX', 0): - exec_args.append('+verilator+rand+reset+2') - -output = subprocess.run(exec_args, stdout=subprocess.PIPE, text=True) -if output.returncode != 0: - exit(success=False) - -in_regs = False -in_mem = False - -for line in output.stdout.split('\n'): - if line == '=== dump-regs ===': - in_regs = True - elif line == '=== dump-mem ===': - in_mem = True - elif not line: - continue - elif in_mem: - base, data = line.split() - dumped.append((int(base, 16) << 2, bytes.fromhex(data))) - elif in_regs: - value, reg = line.split() - regs[reg] = int(value, 16) - else: - while_running() - print(f'{COLOR_BLUE}{line}{COLOR_RESET}') - -if final := module_get('final'): - final() - -if os.getenv('SIM_DUMP', ''): - dump_regs() - for rng in mem_dumps: - print(f'Memory range 0x{rng.start:08x}..0x{rng.stop:08x}') - print(hexdump(rng.start, read_mem(rng.start, rng.stop - rng.start))) - -exit(success=True) diff --git a/tb/sim/start.S b/tb/sim/start.S deleted file mode 100644 index 7639513..0000000 --- a/tb/sim/start.S +++ /dev/null @@ -1,34 +0,0 @@ -.section .interrupt_vector - -__reset: - b _start - -__undefined: - b undefined - -__swi: - b __data_abort - -__prefetch_abort: - b __prefetch_abort - -__data_abort: - b __data_abort - -__irq: - b __irq - -__fiq: - b __fiq - -.text - -.global _start -_start: - ldr sp, =_stack_end - bl reset - b . - -.weak undefined -undefined: - b undefined diff --git a/tb/top/conspiracion.cpp b/tb/top/conspiracion.cpp index 07b35a5..15da2ea 100644 --- a/tb/top/conspiracion.cpp +++ b/tb/top/conspiracion.cpp @@ -197,11 +197,21 @@ int main(int argc, char **argv) parser, "no-tty", "Disable TTY takeoveer", {"no-tty"} ); + args::Flag start_halted + ( + parser, "start-halted", "Halt before running the first instruction", {"start-halted"} + ); + args::ValueFlag cycles ( parser, "cycles", "Number of core cycles to run", {"cycles"}, 256 ); + args::ValueFlag control_fd + ( + parser, "fd", "Control file descriptor", {"control-fd"}, -1 + ); + args::ValueFlagList dump_mem ( parser, "addr,length", "Dump a memory region", {"dump-mem"} @@ -209,7 +219,7 @@ int main(int argc, char **argv) args::ValueFlagList const_ ( - parser, "addr,value", "Add a constant map", {"const"} + parser, "addr,value", "Add a constant mapping", {"const"} ); args::ValueFlagList loads @@ -241,6 +251,13 @@ int main(int argc, char **argv) return EXIT_FAILURE; } + FILE *ctrl = stdout; + if(*control_fd != -1 && (ctrl = fdopen(*control_fd, "r+")) == nullptr) + { + std::perror("fdopen()"); + return EXIT_FAILURE; + } + Vconspiracion top; VerilatedVcdC trace; @@ -369,17 +386,136 @@ int main(int argc, char **argv) ttyJ0.takeover(); } - top.halt = 0; + top.halt = start_halted; top.rst_n = 0; cycle(); top.rst_n = 1; - for(unsigned i = 0; i < *cycles; ++i) + auto do_reg_dump = [&]() + { + std::fputs("=== dump-regs ===\n", ctrl); + + const auto &core = *top.conspiracion->core; + const auto ®file = core.regs->a->file; + + int i = 0; + for(const auto *name : gp_regs) + { + std::fprintf(ctrl, "%08x %s\n", regfile[i++], name); + } + + std::fprintf(ctrl, "%08x pc\n", core.control->pc << 2); + std::fprintf(ctrl, "%08x cpsr\n", core.psr->cpsr_word); + std::fprintf(ctrl, "%08x spsr_svc\n", core.psr->spsr_svc_word); + std::fprintf(ctrl, "%08x spsr_abt\n", core.psr->spsr_abt_word); + std::fprintf(ctrl, "%08x spsr_und\n", core.psr->spsr_und_word); + std::fprintf(ctrl, "%08x spsr_fiq\n", core.psr->spsr_fiq_word); + std::fprintf(ctrl, "%08x spsr_irq\n", core.psr->spsr_irq_word); + std::fputs("=== end-regs ===\n", ctrl); + }; + + auto do_mem_dump = [&](const mem_region *dumps, std::size_t count) { - cycle(); - if(failed) + std::fputs("=== dump-mem ===\n", ctrl); + for(std::size_t i = 0; i < count; ++i) { - break; + const auto &dump = dumps[i]; + + std::fprintf(ctrl, "%08x ", static_cast(dump.start)); + for(std::size_t i = 0; i < dump.length; ++i) + { + auto word = avl.dump(dump.start + i); + word = (word & 0xff) << 24 + | ((word >> 8) & 0xff) << 16 + | ((word >> 16) & 0xff) << 8 + | ((word >> 24) & 0xff); + + std::fprintf(ctrl, "%08x", word); + } + + std::fputc('\n', ctrl); + } + + std::fputs("=== end-mem ===\n", ctrl); + }; + + unsigned i = 0; + while(!failed && i < *cycles) + { + for(; i < *cycles; ++i) + { + cycle(); + if(failed || top.cpu_halted) [[unlikely]] + { + break; + } + } + + if(top.cpu_halted) + { + do_reg_dump(); + std::fputs("=== halted ===\n", ctrl); + + char *line = nullptr; + std::size_t buf_size = 0; + + while(true) + { + ssize_t read = getline(&line, &buf_size, ctrl); + if(read == -1) + { + if(!std::feof(ctrl)) + { + std::perror("getline()"); + failed = true; + } + + break; + } + + if(read > 0 && line[read - 1] == '\n') + { + line[read - 1] = '\0'; + } + + const char *cmd = std::strtok(line, " "); + if(!std::strcmp(cmd, "continue")) + { + top.halt = false; + break; + } else if(!std::strcmp(cmd, "dump-mem")) + { + mem_region dump = {}; + std::sscanf(std::strtok(nullptr, " "), "%zu", &dump.start); + std::sscanf(std::strtok(nullptr, " "), "%zu", &dump.length); + do_mem_dump(&dump, 1); + } else if(!std::strcmp(cmd, "patch-mem")) + { + std::uint32_t addr; + std::sscanf(std::strtok(nullptr, " "), "%u", &addr); + + const char *data = std::strtok(nullptr, " "); + std::size_t length = std::strlen(data); + + while(data && length >= 8) + { + std::uint32_t word; + std::sscanf(data, "%08x", &word); + + data += 8; + length -= 8; + + word = (word & 0xff) << 24 + | ((word >> 8) & 0xff) << 16 + | ((word >> 16) & 0xff) << 8 + | ((word >> 24) & 0xff); + + avl.patch(addr++, word); + } + } + } + + std::free(line); } } @@ -395,49 +531,20 @@ int main(int argc, char **argv) if(dump_regs || failed) { - std::puts("=== dump-regs ==="); - - const auto &core = *top.conspiracion->core; - const auto ®file = core.regs->a->file; - - int i = 0; - for(const auto *name : gp_regs) - { - std::printf("%08x %s\n", regfile[i++], name); - } - - std::printf("%08x pc\n", core.control->pc << 2); - std::printf("%08x cpsr\n", core.psr->cpsr_word); - std::printf("%08x spsr_svc\n", core.psr->spsr_svc_word); - std::printf("%08x spsr_abt\n", core.psr->spsr_abt_word); - std::printf("%08x spsr_und\n", core.psr->spsr_und_word); - std::printf("%08x spsr_fiq\n", core.psr->spsr_fiq_word); - std::printf("%08x spsr_irq\n", core.psr->spsr_irq_word); + do_reg_dump(); } const auto &dumps = *dump_mem; if(!dumps.empty()) { - std::puts("=== dump-mem ==="); + do_mem_dump(dumps.data(), dumps.size()); } - for(const auto &dump : dumps) + top.final(); + if(ctrl != stdout) { - std::printf("%08x ", static_cast(dump.start)); - for(std::size_t i = 0; i < dump.length; ++i) - { - auto word = avl.dump(dump.start + i); - word = (word & 0xff) << 24 - | ((word >> 8) & 0xff) << 16 - | ((word >> 16) & 0xff) << 8 - | ((word >> 24) & 0xff); - - std::printf("%08x", word); - } - - std::putchar('\n'); + std::fclose(ctrl); } - top.final(); return failed ? EXIT_FAILURE : EXIT_SUCCESS; } -- cgit v1.2.3