diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..4a4726a --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use_nix diff --git a/src/lib/Cpu.zig b/src/lib/Cpu.zig index aff799d..4ec4941 100644 --- a/src/lib/Cpu.zig +++ b/src/lib/Cpu.zig @@ -17,6 +17,20 @@ pub const CodeModel = enum(u24) { code22 = 0x3F_FFFF, }; +// see: https://en.wikipedia.org/wiki/Atmel_AVR_instruction_set#Instruction_set_inheritance +pub const InstructionSet = enum { + avr1, + avr2, + @"avr2.5", + avr3, + avr4, + avr5, + @"avr5.1", + avr6, + avrxmega, + avrtiny10, +}; + const InstructionEffect = enum { none, @@ -37,6 +51,7 @@ trace: bool = false, // Device: code_model: CodeModel, +instruction_set: InstructionSet, sio: SpecialIoRegisters, flash: Flash, sram: RAM, diff --git a/src/lib/io.zig b/src/lib/io.zig index 407eee7..d14890a 100644 --- a/src/lib/io.zig +++ b/src/lib/io.zig @@ -6,6 +6,8 @@ pub const Flash = struct { ctx: ?*anyopaque, vtable: *const VTable, + + /// Size of the flash memory in bytes. Is always 2-aligned. size: usize, pub fn read(mem: Flash, addr: Address) u16 { @@ -60,6 +62,8 @@ pub const RAM = struct { ctx: ?*anyopaque, vtable: *const VTable, + + /// Size of the RAM memory space in bytes. size: usize, pub fn read(mem: RAM, addr: Address) u8 { @@ -133,6 +137,8 @@ pub const IO = struct { pub const Address = u6; ctx: ?*anyopaque, + + /// Size of the EEPROM in bytes. vtable: *const VTable, pub fn read(mem: IO, addr: Address) u8 { diff --git a/src/main.zig b/src/main.zig index 5e730d8..02d6bf3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,10 +9,23 @@ pub fn main() !u8 { var cli = args_parser.parseForCurrentProcess(Cli, allocator, .print) catch return 1; defer cli.deinit(); - if (cli.positionals.len == 0) - @panic("usage: aviron [--trace] "); + if (cli.options.help or (cli.positionals.len == 0 and !cli.options.info)) { + var stderr = std.io.getStdErr(); + + try args_parser.printHelp( + Cli, + cli.executable_name orelse "aviron", + stderr.writer(), + ); + + return if (cli.options.help) @as(u8, 0) else 1; + } // Emulate Atmega382p device size: + + // TODO: Add support for more MCUs! + std.debug.assert(cli.options.mcu == .atmega328p); + var flash_storage = aviron.Flash.Static(32768){}; var sram = aviron.RAM.Static(2048){}; var eeprom = aviron.EEPROM.Static(1024){}; @@ -21,31 +34,6 @@ pub fn main() !u8 { .sp = 2047, }; - for (cli.positionals) |file_path| { - var elf_file = try std.fs.cwd().openFile(file_path, .{}); - defer elf_file.close(); - - var source = std.io.StreamSource{ .file = elf_file }; - var header = try std.elf.Header.read(&source); - - var pheaders = header.program_header_iterator(&source); - while (try pheaders.next()) |phdr| { - if (phdr.p_type != std.elf.PT_LOAD) - continue; // Header isn't lodead - - const dest_mem = if (phdr.p_vaddr >= 0x0080_0000) - &sram.data - else - &flash_storage.data; - - const addr_masked: u24 = @intCast(phdr.p_vaddr & 0x007F_FFFF); - - try source.seekTo(phdr.p_offset); - try source.reader().readNoEof(dest_mem[addr_masked..][0..phdr.p_filesz]); - @memset(dest_mem[addr_masked + phdr.p_filesz ..][0 .. phdr.p_memsz - phdr.p_filesz], 0); - } - } - var cpu = aviron.Cpu{ .trace = cli.options.trace, @@ -55,6 +43,7 @@ pub fn main() !u8 { .io = io.memory(), .code_model = .code16, + .instruction_set = .avr5, .sio = .{ .ramp_x = null, @@ -72,6 +61,43 @@ pub fn main() !u8 { io.sreg = &cpu.sreg; + if (cli.options.info) { + var stdout = std.io.getStdOut().writer(); + try stdout.print("Information for {s}:\n", .{@tagName(cli.options.mcu)}); + try stdout.print(" Generation: {s: >11}\n", .{@tagName(cpu.instruction_set)}); + try stdout.print(" Code Model: {s: >11}\n", .{@tagName(cpu.code_model)}); + try stdout.print(" RAM: {d: >5} bytes\n", .{flash_storage.memory().size}); + try stdout.print(" Flash: {d: >5} bytes\n", .{sram.memory().size}); + try stdout.print(" EEPROM: {d: >5} bytes\n", .{eeprom.memory().size}); + return 0; + } + + // Load all provided executables: + for (cli.positionals) |file_path| { + var elf_file = try std.fs.cwd().openFile(file_path, .{}); + defer elf_file.close(); + + var source = std.io.StreamSource{ .file = elf_file }; + var header = try std.elf.Header.read(&source); + + var pheaders = header.program_header_iterator(&source); + while (try pheaders.next()) |phdr| { + if (phdr.p_type != std.elf.PT_LOAD) + continue; // Header isn't lodead + + const dest_mem = if (phdr.p_vaddr >= 0x0080_0000) + &sram.data + else + &flash_storage.data; + + const addr_masked: u24 = @intCast(phdr.p_vaddr & 0x007F_FFFF); + + try source.seekTo(phdr.p_offset); + try source.reader().readNoEof(dest_mem[addr_masked..][0..phdr.p_filesz]); + @memset(dest_mem[addr_masked + phdr.p_filesz ..][0 .. phdr.p_memsz - phdr.p_filesz], 0); + } + } + const result = try cpu.run(null); std.debug.print("STOP: {s}\n", .{@tagName(result)}); @@ -79,10 +105,39 @@ pub fn main() !u8 { return 0; } +// not actually marvel cinematic universe, but microcontroller unit ; +pub const MCU = enum { + atmega328p, +}; + const Cli = struct { help: bool = false, - trace: bool = false, + mcu: MCU = .atmega328p, + info: bool = false, + + pub const shorthands = .{ + .h = "help", + .t = "trace", + .m = "mcu", + .I = "info", + }; + pub const meta = .{ + .summary = "[-h] [-t] [-m ] ...", + .full_text = + \\AViRon is a simulator for the AVR cpu architecture as well as an basic emulator for several microcontrollers from Microchip/Atmel. + \\ + \\Loads at least a single file into the memory of the system and executes it with the provided MCU. + \\ + \\The code can use certain special registers to perform I/O and exit the emulator. + , + .option_docs = .{ + .help = "Prints this help text.", + .trace = "Trace all executed instructions.", + .mcu = "Selects the emulated MCU.", + .info = "Prints information about the given MCUs memory.", + }, + }; }; const IO = struct {