| # Copyright 2025 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """ |
| A script to parse an ELF file and generate Verilog hex-formatted memory files |
| for ITCM and DTCM, and a simulator arguments file. |
| """ |
| |
| import argparse |
| import os |
| import logging |
| from elftools.elf.elffile import ELFFile |
| |
| # Define the memory map for the Kelvin core |
| MEM_MAP = { |
| 'itcm': {'base': 0x00000000, 'size': 8 * 1024, 'file': 'itcm.mem'}, |
| 'dtcm': {'base': 0x00010000, 'size': 32 * 1024, 'file': 'dtcm.mem'}, |
| } |
| |
| def contains(mem_info, addr): |
| """Checks if the given address is within the memory region.""" |
| return mem_info['base'] <= addr < mem_info['base'] + mem_info['size'] |
| |
| def process_and_dump_segments(segments, mem_info, out_dir, word_size_bytes=16): |
| """ |
| Processes a list of memory segments, sorts them, and dumps them to a |
| Verilog hex file, handling memory locations. Returns True if data was |
| written. |
| """ |
| file_path = os.path.join(out_dir, mem_info['file']) |
| mem_base = mem_info['base'] |
| |
| with open(file_path, 'w') as f: |
| if not segments: |
| logging.error("No segments found for %s. Created empty file.", |
| mem_info['file']) |
| return False |
| |
| segments.sort(key=lambda s: s['p_addr']) |
| last_addr_written = -1 |
| |
| for seg in segments: |
| p_addr = seg['p_addr'] |
| data = seg['data'] |
| data_len = len(data) |
| current_addr_offset = p_addr - mem_base |
| |
| # Check if the segment start address is aligned to the memory word size. |
| if current_addr_offset % word_size_bytes != 0: |
| logging.error( |
| f"ELF segment at address 0x{p_addr:08x} has an unaligned " |
| f"offset of 0x{current_addr_offset:08x}. Exiting. " |
| ) |
| sys.exit(1) |
| |
| if current_addr_offset > last_addr_written: |
| aligned_addr = (current_addr_offset // word_size_bytes) |
| f.write(f"@{aligned_addr:08x}\n") |
| |
| for i in range(0, data_len, word_size_bytes): |
| chunk = data[i:i + word_size_bytes] |
| while len(chunk) < word_size_bytes: |
| chunk += b'\x00' |
| word = int.from_bytes(chunk, byteorder='little') |
| f.write(f"{word:0{word_size_bytes*2}x}\n") |
| |
| last_addr_written = current_addr_offset + data_len - 1 |
| return True |
| |
| def find_tohost_addr(elf): |
| """Finds the 'tohost' symbol in the ELF file's symbol table.""" |
| symtab = elf.get_section_by_name('.symtab') |
| if not symtab: |
| logging.warning("No symbol table found in ELF file.") |
| return None |
| for symbol in symtab.iter_symbols(): |
| if symbol.name == 'tohost': |
| logging.info("Found 'tohost' symbol at 0x%08x", symbol['st_value']) |
| return symbol['st_value'] |
| logging.warning("'tohost' symbol not found in ELF file.") |
| return None |
| |
| def main(): |
| """Main function to parse ELF and generate files.""" |
| logging.basicConfig(level=logging.INFO, |
| format='%(levelname)s: %(message)s') |
| |
| parser = argparse.ArgumentParser( |
| description='Generate memory and argument files from an ELF file.') |
| parser.add_argument('--elf_file', required=True, help='Path to input ELF.') |
| parser.add_argument('--out_dir', required=True, help='Output directory for .mem files.') |
| args = parser.parse_args() |
| |
| if not os.path.exists(args.out_dir): |
| logging.info("Output directory '%s' not found. Creating it.", args.out_dir) |
| os.makedirs(args.out_dir) |
| |
| logging.info("Processing ELF file: %s", args.elf_file) |
| |
| itcm_segments = [] |
| dtcm_segments = [] |
| tohost_addr = None |
| run_opts_file = os.path.join(args.out_dir, 'elf_run_opts.f') |
| |
| try: |
| with open(args.elf_file, 'rb') as f: |
| elf = ELFFile(f) |
| tohost_addr = find_tohost_addr(elf) |
| |
| for segment in elf.iter_segments(): |
| if segment['p_type'] != 'PT_LOAD': |
| continue |
| |
| p_addr = segment['p_paddr'] |
| data = segment.data() |
| logging.info("Found segment at 0x%08x, size %d bytes", p_addr, len(data)) |
| |
| if contains(MEM_MAP['itcm'], p_addr): |
| itcm_segments.append({'p_addr': p_addr, 'data': data}) |
| elif contains(MEM_MAP['dtcm'], p_addr): |
| dtcm_segments.append({'p_addr': p_addr, 'data': data}) |
| else: |
| logging.warning("Segment at 0x%08x is outside known memory map. Skipping.", p_addr) |
| |
| except Exception as e: |
| if isinstance(e, FileNotFoundError): |
| logging.error(f"ERROR: ELF file not found at {args.elf_file}") |
| else: |
| logging.error("An error occurred: %s", e, exc_info=True) |
| open(run_opts_file, 'w').close() |
| open(os.path.join(args.out_dir, MEM_MAP['itcm']['file']), 'w').close() |
| open(os.path.join(args.out_dir, MEM_MAP['dtcm']['file']), 'w').close() |
| exit(1) |
| |
| # Process segments and dump memory files |
| itcm_written = process_and_dump_segments( |
| itcm_segments, MEM_MAP['itcm'], args.out_dir) |
| dtcm_written = process_and_dump_segments( |
| dtcm_segments, MEM_MAP['dtcm'], args.out_dir) |
| |
| # Generate the arguments file |
| with open(run_opts_file, 'w') as f_args: |
| logging.info("Generating arguments file: %s", run_opts_file) |
| if itcm_written: |
| f_args.write(f"+ITCM_MEM_FILE=" + |
| f"{os.path.join(args.out_dir, MEM_MAP['itcm']['file'])}\n") |
| if dtcm_written: |
| f_args.write(f"+DTCM_MEM_FILE=" + |
| f"{os.path.join(args.out_dir, MEM_MAP['dtcm']['file'])}\n") |
| if tohost_addr is not None: |
| f_args.write(f"+TOHOST_ADDR='h{tohost_addr:08x}\n") |
| |
| logging.info("Successfully generated memory and argument files.") |
| |
| if __name__ == '__main__': |
| main() |