├── .envrc ├── .github └── workflows │ └── build.yml ├── .gitignore ├── README.md ├── build.zig ├── build.zig.zon ├── doc └── avr-instruction-set-manual.pdf ├── linker.ld ├── samples ├── hello-world.zig └── math.zig ├── shell.nix ├── src ├── lib │ ├── Cpu.zig │ ├── aviron.zig │ ├── decoder.zig │ └── io.zig ├── libtestsuite │ └── lib.zig ├── main.zig ├── shared │ └── isa.zig ├── testconfig.zig └── testrunner.zig ├── testsuite.avr-gcc ├── README.md └── instructions │ ├── mul.S │ ├── muls.S │ └── mulsu.S ├── testsuite ├── dummy.zig ├── instructions │ ├── add.S │ ├── cbi.S │ ├── in-stdio.S │ ├── mul.elf │ ├── mul.elf.json │ ├── muls.elf │ ├── muls.elf.json │ ├── mulsu.elf │ ├── mulsu.elf.json │ ├── out-exit-0.S │ ├── out-exit-42.S │ ├── out-stderr.S │ ├── out-stdout.S │ ├── sbi.S │ └── sub.S ├── lib │ └── write-chan.zig ├── regs.inc └── simulator │ ├── scratch-reg0.S │ ├── scratch-reg1.S │ ├── scratch-reg2.S │ ├── scratch-reg3.S │ ├── scratch-reg4.S │ ├── scratch-reg5.S │ ├── scratch-reg6.S │ ├── scratch-reg7.S │ ├── scratch-reg8.S │ ├── scratch-reg9.S │ ├── scratch-rega.S │ ├── scratch-regb.S │ ├── scratch-regc.S │ ├── scratch-regd.S │ ├── scratch-rege.S │ └── scratch-regf.S └── tools ├── generate-tables.zig ├── isa.txt └── no-avr-gcc.zig /.envrc: -------------------------------------------------------------------------------- 1 | use_nix 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | jobs: 9 | build: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, windows-latest, macos-latest] 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup Zig 19 | uses: mlugg/setup-zig@v1 20 | with: 21 | version: 0.14.0 22 | 23 | - name: Build 24 | run: zig build install 25 | 26 | - name: Run Test Suite 27 | run: zig build test 28 | 29 | - name: Run Samples 30 | run: | 31 | ./zig-out/bin/aviron --info 32 | ./zig-out/bin/aviron --trace zig-out/samples/math.elf 33 | ./zig-out/bin/aviron zig-out/samples/hello-world.elf 34 | 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-out 2 | .zig-cache 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AViRon 2 | 3 | This repo is now found [here](https://github.com/ZigEmbeddedGroup/microzig/tree/main/simulators/aviron) 4 | 5 | AVR simulator in Zig. 6 | 7 | > DISCLAIMER: This is work in project and can currently emulate a good amount of the AVR instruction set without cycle counting. 8 | 9 | ## Development 10 | 11 | Use [Zig 0.14.0](https://ziglang.org/download/#release-0.14.0) to compile AViRon. 12 | 13 | ### Repo Architecture 14 | 15 | ```sh 16 | . 17 | ├── doc # Documents for Aviron or AVR 18 | ├── samples # Source of examples that can be run on the simulator 19 | ├── src # Source code 20 | │ ├── lib # - Aviron emulator 21 | │ ├── libtestsuite # - Source code of the testsuite library 22 | │ └── shared # - Shared code between the tools, generated code and simulator 23 | ├── testsuite # Contains the test suite of Aviron 24 | │ ├── instructions # - Tests for single instructions 25 | │ ├── lib # - Tests for the libtestsuie 26 | │ ├── simulator # - Tests for the simulator per se 27 | │ └── testrunner # - Tests for the test runner (conditions, checks, ...) 28 | ├── testsuite.avr-gcc # Contains code for the test suite that cannot be built with LLVM right now 29 | │ └── instructions 30 | └── tools # Code for tooling we need during development, but not for deployment 31 | ``` 32 | 33 | ### Tests 34 | 35 | Run the test suite by invoking 36 | 37 | ```sh-session 38 | [~/projects/aviron]$ zig build test 39 | [~/projects/aviron]$ 40 | ``` 41 | 42 | The `test` step will recursively scan the folder `testsuite` for files of the following types: 43 | 44 | - *compile* `.S` 45 | - *compile* `.c` 46 | - *compile* `.cpp` 47 | - *compile* `.zig` 48 | - *load* `.bin` 49 | - *load* `.elf` 50 | 51 | File extensions marked *compile* will be compiled or assembled with the Zig compiler, then executed with the test runner. Those files allow embedding a JSON configuration via Zig file documentation comments: 52 | 53 | ```zig 54 | //! { 55 | //! "stdout": "hello", 56 | //! "stderr": "world" 57 | //! } 58 | const testsuite = @import("testsuite"); 59 | 60 | export fn _start() callconv(.C) noreturn { 61 | testsuite.write(.stdout, "hello"); 62 | testsuite.write(.stderr, "world"); 63 | testsuite.exit(0); 64 | } 65 | ``` 66 | 67 | For files marked as *load*, another companion file `.bin.json` or similar contains the configuration for this test. 68 | 69 | The [JSON schema](src/testconfig.zig) allows description of how the file is built and the test runner is executed. It also contains a set of pre- and postconditions that can be used to set up the CPU and validate it after the program exits. 70 | 71 | If you're not sure if your code compiled correctly, you can use the `debug-testsuite` build step to inspect the generated files: 72 | 73 | ```sh-session 74 | [~/projects/aviron]$ zig build debug-testsuite 75 | [~/projects/aviron]$ tree zig-out/ 76 | zig-out/ 77 | ├── bin 78 | │ └── aviron-test-runner 79 | └── testsuite 80 | ├── instructions 81 | │ ├── cbi.elf 82 | │ ├── in-stdio.elf 83 | ├── ... 84 | │ ├── out-stdout.elf 85 | │ └── sbi.elf 86 | ├── lib 87 | │ └── write-chan.elf 88 | └── simulator 89 | ├── scratch-reg0.elf 90 | ├── scratch-reg1.elf 91 | ├── ... 92 | ├── scratch-rege.elf 93 | └── scratch-regf.elf 94 | ``` 95 | 96 | You can then disassemble those files with either `llvm-objdump` or `avr-objdump`: 97 | 98 | ```sh-session 99 | [~/projects/aviron]$ llvm-objdump -d zig-out/testsuite/instructions/out-exit-0.elf 100 | 101 | zig-out/testsuite/instructions/out-exit-0.elf: file format elf32-avr 102 | 103 | Disassembly of section .text: 104 | 105 | 00000000 <_start>: 106 | 0: 00 27 clr r16 107 | 2: 00 b9 out 0x0, r16 108 | 109 | [~/projects/aviron]$ avr-objdump -d zig-out/testsuite/instructions/out-exit-0.elf 110 | 111 | zig-out/testsuite/instructions/out-exit-0.elf: file format elf32-avr 112 | 113 | Disassembly of section .text: 114 | 115 | 00000000 <_start>: 116 | 0: 00 27 eor r16, r16 117 | 2: 00 b9 out 0x00, r16 ; 0 118 | 119 | [~/projects/aviron]$ 120 | ``` 121 | 122 | The test runner is located at [src/testrunner.zig](src/testrunner.zig) and behaves similar to the main `aviron` executable, but introduces a good amount of checks we can use to inspect and validate the simulation. 123 | 124 | ### Updating AVR-GCC tests 125 | 126 | To prevent a hard dependency on the `avr-gcc` toolchain, we vendor the binaries for all tests defined in the folder `testsuite.avr-gcc`. To update the files, you need to invoke the `update-testsuite` build step: 127 | 128 | ```sh-session 129 | [~/projects/aviron]$ zig build update-testsuite 130 | [~/projects/aviron]$ 131 | ``` 132 | 133 | After that, `zig build test` will run the regenerated tests. 134 | 135 | **NOTE:** The build will not detect changed files, so you have no guarantee that running `zig build update-testsuite test` will actually do the right thing. If you're working on the test suite, just use `zig build update-testsuite && zig build test` for this. 136 | 137 | ## Links 138 | 139 | - https://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf 140 | - http://www.avr-asm-tutorial.net/avr_en/micro_beginner/instructions.html 141 | - https://github.com/dwelch67/avriss/blob/master/opcodes.txt 142 | - https://microchipdeveloper.com/8avr:status 143 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Build = std.Build; 3 | const LazyPath = Build.LazyPath; 4 | const ResolvedTarget = Build.ResolvedTarget; 5 | 6 | const TestSuiteConfig = @import("src/testconfig.zig").TestSuiteConfig; 7 | 8 | const samples = [_][]const u8{ 9 | "math", 10 | "hello-world", 11 | }; 12 | 13 | const avr_target_query = std.Target.Query{ 14 | .cpu_arch = .avr, 15 | .cpu_model = .{ .explicit = &std.Target.avr.cpu.atmega328p }, 16 | .os_tag = .freestanding, 17 | .abi = .none, 18 | }; 19 | 20 | // Although this function looks imperative, note that its job is to 21 | // declaratively construct a build graph that will be executed by an external 22 | // runner. 23 | pub fn build(b: *Build) !void { 24 | // Targets 25 | const test_step = b.step("test", "Run test suite"); 26 | const run_step = b.step("run", "Run the app"); 27 | const debug_testsuite_step = b.step("debug-testsuite", "Installs all testsuite examples"); 28 | const update_testsuite_step = b.step("update-testsuite", "Updates the folder testsuite with the data in testsuite.avr-gcc. Requires avr-gcc to be present!"); 29 | 30 | // Deps 31 | const args_dep = b.dependency("args", .{}); 32 | const ihex_dep = b.dependency("ihex", .{}); 33 | 34 | // Dep modules 35 | 36 | const args_module = args_dep.module("args"); 37 | const ihex_module = ihex_dep.module("ihex"); 38 | 39 | // Options 40 | const target = b.standardTargetOptions(.{}); 41 | const optimize = b.standardOptimizeOption(.{}); 42 | 43 | // Modules 44 | 45 | const isa_module = b.createModule(.{ 46 | .root_source_file = b.path("src/shared/isa.zig"), 47 | }); 48 | const isa_tables_module = b.createModule(.{ 49 | .root_source_file = generateIsaTables(b, isa_module), 50 | .imports = &.{ 51 | .{ .name = "isa", .module = isa_module }, 52 | }, 53 | }); 54 | const aviron_module = b.addModule("aviron", .{ 55 | .root_source_file = b.path("src/lib/aviron.zig"), 56 | .imports = &.{ 57 | .{ .name = "autogen-tables", .module = isa_tables_module }, 58 | .{ .name = "isa", .module = isa_module }, 59 | }, 60 | }); 61 | 62 | const testsuite_module = b.addModule("aviron-testsuite", .{ 63 | .root_source_file = b.path("src/libtestsuite/lib.zig"), 64 | }); 65 | 66 | // Main emulator executable 67 | const aviron_exe = b.addExecutable(.{ 68 | .name = "aviron", 69 | .root_source_file = b.path("src/main.zig"), 70 | .target = target, 71 | .optimize = optimize, 72 | }); 73 | aviron_exe.root_module.addImport("args", args_module); 74 | aviron_exe.root_module.addImport("ihex", ihex_module); 75 | aviron_exe.root_module.addImport("aviron", aviron_module); 76 | b.installArtifact(aviron_exe); 77 | 78 | const run_cmd = b.addRunArtifact(aviron_exe); 79 | run_cmd.step.dependOn(b.getInstallStep()); 80 | if (b.args) |args| { 81 | run_cmd.addArgs(args); 82 | } 83 | run_step.dependOn(&run_cmd.step); 84 | 85 | const avr_target = b.resolveTargetQuery(avr_target_query); 86 | 87 | // Samples 88 | for (samples) |sample_name| { 89 | const sample = b.addExecutable(.{ 90 | .name = sample_name, 91 | .root_source_file = b.path(b.fmt("samples/{s}.zig", .{sample_name})), 92 | .target = avr_target, 93 | .optimize = .ReleaseSmall, 94 | .strip = false, 95 | }); 96 | sample.bundle_compiler_rt = false; 97 | sample.setLinkerScript(b.path("linker.ld")); 98 | sample.root_module.addImport("testsuite", testsuite_module); 99 | 100 | // install to the prefix: 101 | const install_elf_sample = b.addInstallFile(sample.getEmittedBin(), b.fmt("samples/{s}.elf", .{sample_name})); 102 | b.getInstallStep().dependOn(&install_elf_sample.step); 103 | } 104 | 105 | // Test suite: 106 | 107 | try addTestSuite(b, test_step, debug_testsuite_step, target, avr_target, optimize, args_module, aviron_module); 108 | 109 | try addTestSuiteUpdate(b, update_testsuite_step); 110 | } 111 | 112 | fn addTestSuite( 113 | b: *Build, 114 | test_step: *Build.Step, 115 | debug_step: *Build.Step, 116 | host_target: ResolvedTarget, 117 | avr_target: ResolvedTarget, 118 | optimize: std.builtin.OptimizeMode, 119 | args_module: *Build.Module, 120 | aviron_module: *Build.Module, 121 | ) !void { 122 | const unit_tests = b.addTest(.{ 123 | .root_source_file = b.path("src/main.zig"), 124 | .target = host_target, 125 | .optimize = optimize, 126 | }); 127 | test_step.dependOn(&b.addRunArtifact(unit_tests).step); 128 | 129 | const testrunner_exe = b.addExecutable(.{ 130 | .name = "aviron-test-runner", 131 | .root_source_file = b.path("src/testrunner.zig"), 132 | .target = host_target, 133 | .optimize = optimize, 134 | }); 135 | testrunner_exe.root_module.addImport("args", args_module); 136 | testrunner_exe.root_module.addImport("aviron", aviron_module); 137 | 138 | debug_step.dependOn(&b.addInstallArtifact(testrunner_exe, .{}).step); 139 | 140 | { 141 | var walkdir = try b.build_root.handle.openDir("testsuite", .{ 142 | .iterate = true, 143 | }); 144 | defer walkdir.close(); 145 | 146 | var walker = try walkdir.walk(b.allocator); 147 | defer walker.deinit(); 148 | 149 | while (try walker.next()) |entry| { 150 | if (entry.kind != .file) 151 | continue; 152 | 153 | if (std.mem.eql(u8, entry.path, "dummy.zig")) { 154 | // This file is not interesting to test. 155 | continue; 156 | } 157 | 158 | const FileAction = union(enum) { 159 | compile, 160 | load, 161 | ignore, 162 | unknown, 163 | }; 164 | 165 | const extension_to_action = .{ 166 | .c = .compile, 167 | .cpp = .compile, 168 | .S = .compile, 169 | .zig = .compile, 170 | 171 | .bin = .load, 172 | .elf = .load, 173 | 174 | .inc = .ignore, 175 | .h = .ignore, 176 | .json = .ignore, 177 | }; 178 | 179 | const ext = std.fs.path.extension(entry.basename); 180 | const action: FileAction = inline for (std.meta.fields(@TypeOf(extension_to_action))) |fld| { 181 | const action: FileAction = @field(extension_to_action, fld.name); 182 | 183 | if (std.mem.eql(u8, ext, "." ++ fld.name)) 184 | break action; 185 | } else .unknown; 186 | 187 | const ConfigAndExe = struct { 188 | binary: LazyPath, 189 | config: TestSuiteConfig, 190 | }; 191 | 192 | const cae: ConfigAndExe = switch (action) { 193 | .unknown => std.debug.panic("Unknown test action on file testsuite/{s}, please fix the build script.", .{entry.path}), 194 | .ignore => continue, 195 | 196 | .compile => blk: { 197 | var file = try entry.dir.openFile(entry.basename, .{}); 198 | defer file.close(); 199 | 200 | const config = try parseTestSuiteConfig(b, file); 201 | 202 | const custom_target = if (config.cpu) |cpu| 203 | b.resolveTargetQuery(std.Target.Query.parse(.{ 204 | .arch_os_abi = "avr-freestanding-eabi", 205 | .cpu_features = cpu, 206 | }) catch @panic(cpu)) 207 | else 208 | avr_target; 209 | 210 | const file_ext = std.fs.path.extension(entry.path); 211 | const is_zig_test = std.mem.eql(u8, file_ext, ".zig"); 212 | const is_c_test = std.mem.eql(u8, file_ext, ".c"); 213 | const is_asm_test = std.mem.eql(u8, file_ext, ".S"); 214 | 215 | std.debug.assert(is_zig_test or is_c_test or is_asm_test); 216 | 217 | const source_file = b.path(b.fmt("testsuite/{s}", .{entry.path})); 218 | const root_file = if (is_zig_test) 219 | source_file 220 | else 221 | b.path("testsuite/dummy.zig"); 222 | 223 | const test_payload = b.addExecutable(.{ 224 | .name = std.fs.path.stem(entry.basename), 225 | .target = custom_target, 226 | .optimize = config.optimize, 227 | .strip = false, 228 | .root_source_file = if (is_zig_test) root_file else null, 229 | .link_libc = false, 230 | }); 231 | test_payload.want_lto = false; // AVR has no LTO support! 232 | test_payload.verbose_link = true; 233 | test_payload.verbose_cc = true; 234 | test_payload.bundle_compiler_rt = false; 235 | 236 | test_payload.setLinkerScript(b.path("linker.ld")); 237 | 238 | if (is_c_test or is_asm_test) { 239 | test_payload.addIncludePath(b.path("testsuite")); 240 | } 241 | if (is_c_test) { 242 | test_payload.addCSourceFile(.{ 243 | .file = source_file, 244 | .flags = &.{}, 245 | }); 246 | } 247 | if (is_asm_test) { 248 | test_payload.addAssemblyFile(source_file); 249 | } 250 | if (is_zig_test) { 251 | test_payload.root_module.addAnonymousImport("testsuite", .{ 252 | .root_source_file = b.path("src/libtestsuite/lib.zig"), 253 | }); 254 | } 255 | 256 | debug_step.dependOn(&b.addInstallFile( 257 | test_payload.getEmittedBin(), 258 | b.fmt("testsuite/{s}/{s}.elf", .{ 259 | std.fs.path.dirname(entry.path).?, 260 | std.fs.path.stem(entry.basename), 261 | }), 262 | ).step); 263 | 264 | break :blk ConfigAndExe{ 265 | .binary = test_payload.getEmittedBin(), 266 | .config = config, 267 | }; 268 | }, 269 | .load => blk: { 270 | const config_path = b.fmt("{s}.json", .{entry.basename}); 271 | const config = if (entry.dir.openFile(config_path, .{})) |file| cfg: { 272 | defer file.close(); 273 | break :cfg try TestSuiteConfig.load(b.allocator, file); 274 | } else |_| @panic(config_path); 275 | 276 | break :blk ConfigAndExe{ 277 | .binary = b.path(b.fmt("testsuite/{s}", .{entry.path})), 278 | .config = config, 279 | }; 280 | }, 281 | }; 282 | 283 | const write_file = b.addWriteFile("config.json", cae.config.toString(b)); 284 | 285 | const test_run = b.addRunArtifact(testrunner_exe); 286 | test_run.addArg("--config"); 287 | test_run.addFileArg(write_file.getDirectory().path(b, "config.json")); 288 | test_run.addArg("--name"); 289 | test_run.addArg(entry.path); 290 | 291 | test_run.addFileArg(cae.binary); 292 | 293 | test_run.expectExitCode(0); 294 | 295 | test_step.dependOn(&test_run.step); 296 | } 297 | } 298 | } 299 | 300 | fn addTestSuiteUpdate( 301 | b: *Build, 302 | invoke_step: *Build.Step, 303 | ) !void { 304 | const avr_gcc = if (b.findProgram(&.{"avr-gcc"}, &.{})) |path| LazyPath{ 305 | .cwd_relative = path, 306 | } else |_| b.addExecutable(.{ 307 | .name = "no-avr-gcc", 308 | .target = b.graph.host, 309 | .root_source_file = b.path("tools/no-avr-gcc.zig"), 310 | }).getEmittedBin(); 311 | 312 | { 313 | var walkdir = try b.build_root.handle.openDir("testsuite.avr-gcc", .{ .iterate = true }); 314 | defer walkdir.close(); 315 | 316 | var walker = try walkdir.walk(b.allocator); 317 | defer walker.deinit(); 318 | 319 | while (try walker.next()) |entry| { 320 | if (entry.kind != .file) 321 | continue; 322 | 323 | const FileAction = union(enum) { 324 | compile, 325 | ignore, 326 | unknown, 327 | }; 328 | 329 | const extension_to_action = .{ 330 | .c = .compile, 331 | .cpp = .compile, 332 | .S = .compile, 333 | 334 | .inc = .ignore, 335 | .h = .ignore, 336 | .json = .ignore, 337 | .md = .ignore, 338 | }; 339 | 340 | const ext = std.fs.path.extension(entry.basename); 341 | const action: FileAction = inline for (std.meta.fields(@TypeOf(extension_to_action))) |fld| { 342 | const action: FileAction = @field(extension_to_action, fld.name); 343 | if (std.mem.eql(u8, ext, "." ++ fld.name)) 344 | break action; 345 | } else .unknown; 346 | 347 | switch (action) { 348 | .unknown => std.debug.panic("Unknown test action on file testsuite/{s}, please fix the build script.", .{entry.path}), 349 | .ignore => continue, 350 | 351 | .compile => { 352 | var file = try entry.dir.openFile(entry.basename, .{}); 353 | defer file.close(); 354 | 355 | const config = try parseTestSuiteConfig(b, file); 356 | 357 | const gcc_invocation = Build.Step.Run.create(b, "run avr-gcc"); 358 | gcc_invocation.addFileArg(avr_gcc); 359 | gcc_invocation.addArg("-o"); 360 | gcc_invocation.addArg(b.fmt("testsuite/{s}/{s}.elf", .{ std.fs.path.dirname(entry.path).?, std.fs.path.stem(entry.basename) })); 361 | gcc_invocation.addArg(b.fmt("-mmcu={s}", .{config.cpu orelse @panic("Uknown MCU!")})); 362 | //avr_target.cpu_model.explicit.llvm_name orelse @panic("Unknown MCU!")})); 363 | for (config.gcc_flags) |opt| { 364 | gcc_invocation.addArg(opt); 365 | } 366 | gcc_invocation.addArg("-I"); 367 | gcc_invocation.addArg("testsuite"); 368 | gcc_invocation.addArg(b.fmt("testsuite.avr-gcc/{s}", .{entry.path})); 369 | 370 | const write_file = b.addWriteFile("config.json", config.toString(b)); 371 | 372 | const copy_file = b.addSystemCommand(&.{"cp"}); // todo make this cross-platform! 373 | copy_file.addFileArg(write_file.getDirectory().path(b, "config.json")); 374 | copy_file.addArg(b.fmt("testsuite/{s}/{s}.elf.json", .{ std.fs.path.dirname(entry.path).?, std.fs.path.stem(entry.basename) })); 375 | 376 | invoke_step.dependOn(&gcc_invocation.step); 377 | invoke_step.dependOn(©_file.step); 378 | }, 379 | } 380 | } 381 | } 382 | } 383 | 384 | fn parseTestSuiteConfig(b: *Build, file: std.fs.File) !TestSuiteConfig { 385 | var code = std.ArrayList(u8).init(b.allocator); 386 | defer code.deinit(); 387 | 388 | var line_buffer: [4096]u8 = undefined; 389 | 390 | while (true) { 391 | var fbs = std.io.fixedBufferStream(&line_buffer); 392 | file.reader().streamUntilDelimiter(fbs.writer(), '\n', null) catch |err| switch (err) { 393 | error.EndOfStream => break, 394 | else => |e| return e, 395 | }; 396 | const line = fbs.getWritten(); 397 | 398 | if (std.mem.startsWith(u8, line, "//!")) { 399 | try code.appendSlice(line[3..]); 400 | try code.appendSlice("\n"); 401 | } 402 | } 403 | 404 | const json_text = std.mem.trim(u8, code.items, "\r\n\t "); 405 | if (json_text.len == 0) 406 | return TestSuiteConfig{}; 407 | 408 | return try std.json.parseFromSliceLeaky( 409 | TestSuiteConfig, 410 | b.allocator, 411 | json_text, 412 | .{ 413 | .allocate = .alloc_always, 414 | }, 415 | ); 416 | } 417 | 418 | fn generateIsaTables(b: *Build, isa_mod: *Build.Module) LazyPath { 419 | const generate_tables_exe = b.addExecutable(.{ 420 | .name = "aviron-generate-tables", 421 | .root_source_file = b.path("tools/generate-tables.zig"), 422 | .target = b.graph.host, 423 | .optimize = .Debug, 424 | }); 425 | generate_tables_exe.root_module.addImport("isa", isa_mod); 426 | 427 | const run = b.addRunArtifact(generate_tables_exe); 428 | 429 | const tables_zig_file = run.addOutputFileArg("tables.zig"); 430 | 431 | return tables_zig_file; 432 | } 433 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = .aviron, 3 | .version = "0.1.0", 4 | .fingerprint = 0x5dcc57b3c9d31477, 5 | .dependencies = .{ 6 | .args = .{ 7 | .url = "git+https://github.com/MasterQ32/zig-args#9425b94c103a031777fdd272c555ce93a7dea581", 8 | .hash = "args-0.0.0-CiLiqv_NAAC97fGpk9hS2K681jkiqPsWP6w3ucb_ctGH", 9 | }, 10 | .ihex = .{ 11 | .url = "git+https://github.com/ikskuh/zig-ihex#2c399fc3ab2669df8b0c8a514ca194757f41f826", 12 | .hash = "ihex-0.1.0-1POu20MyAABXXjtXpHXpEZXrNYl34xVCTO8ye1BHOtF_", 13 | }, 14 | }, 15 | .paths = .{ 16 | "README.md", 17 | "build.zig", 18 | "build.zig.zon", 19 | "doc", 20 | "linker.ld", 21 | "samples", 22 | "shell.nix", 23 | "src", 24 | "testsuite", 25 | "testsuite.avr-gcc", 26 | "tools", 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /doc/avr-instruction-set-manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZigEmbeddedGroup/aviron/92cb11a3c8897fc9b3465475461c472569912006/doc/avr-instruction-set-manual.pdf -------------------------------------------------------------------------------- /linker.ld: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | flash (rx) : ORIGIN = 0, LENGTH = 32K 4 | ram (rw!x) : ORIGIN = 0x800100, LENGTH = 2K 5 | } 6 | 7 | SECTIONS 8 | { 9 | .text : 10 | { 11 | KEEP(*(.vectors)) 12 | 13 | *(.text*) 14 | } > flash 15 | 16 | .data : 17 | { 18 | __data_start = .; 19 | *(.rodata*) 20 | *(.data*) 21 | __data_end = .; 22 | } > ram AT> flash 23 | 24 | .bss (NOLOAD) : 25 | { 26 | __bss_start = .; 27 | *(.bss*) 28 | __bss_end = .; 29 | } > ram 30 | 31 | __data_load_start = LOADADDR(.data); 32 | } 33 | -------------------------------------------------------------------------------- /samples/hello-world.zig: -------------------------------------------------------------------------------- 1 | const testsuite = @import("testsuite"); 2 | 3 | export fn _start() callconv(.C) noreturn { 4 | testsuite.write(.stdout, "hello, world!\r\n"); 5 | testsuite.exit(0); 6 | } 7 | -------------------------------------------------------------------------------- /samples/math.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub export fn _start() callconv(.C) noreturn { 4 | var a: usize = 1 + 2; 5 | 6 | for (0..10) |p| { 7 | const k = p; 8 | std.mem.doNotOptimizeAway(k); 9 | a += k; 10 | } 11 | 12 | // @as(*const fn () void, @ptrFromInt(a))(); 13 | 14 | asm volatile ("out 0x00, 0x00"); 15 | 16 | unreachable; 17 | } 18 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | {pkgs ? import {}}: 2 | pkgs.mkShell { 3 | nativeBuildInputs = [ 4 | pkgs.zig_0_11_0 5 | pkgs.llvmPackages_16.bintools 6 | pkgs.pkgsCross.avr.buildPackages.gcc 7 | ]; 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/Cpu.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const isa = @import("decoder.zig"); 3 | const io_mod = @import("io.zig"); 4 | 5 | const Flash = io_mod.Flash; 6 | const RAM = io_mod.RAM; 7 | const EEPROM = io_mod.EEPROM; 8 | const IO = io_mod.IO; 9 | 10 | const Cpu = @This(); 11 | 12 | pub const CodeModel = enum(u24) { 13 | /// 16 bit program counter 14 | code16 = 0x00_FFFF, 15 | 16 | /// 22 bit program counter, 17 | code22 = 0x3F_FFFF, 18 | }; 19 | 20 | // see: https://en.wikipedia.org/wiki/Atmel_AVR_instruction_set#Instruction_set_inheritance 21 | pub const InstructionSet = enum { 22 | avr1, 23 | avr2, 24 | @"avr2.5", 25 | avr3, 26 | avr4, 27 | avr5, 28 | @"avr5.1", 29 | avr6, 30 | avrxmega, 31 | avrtiny10, 32 | }; 33 | 34 | const InstructionEffect = enum { 35 | none, 36 | 37 | /// Prevents execution of the next instruction, but performs decoding anyways 38 | skip_next, 39 | 40 | /// The run command will return error.Breakpoint 41 | breakpoint, 42 | 43 | /// The run command will return error.Sleep 44 | sleep, 45 | 46 | watchdog_reset, 47 | }; 48 | 49 | // Options: 50 | trace: bool = false, 51 | 52 | // Device: 53 | code_model: CodeModel, 54 | instruction_set: InstructionSet, 55 | sio: SpecialIoRegisters, 56 | flash: Flash, 57 | sram: RAM, 58 | eeprom: EEPROM, 59 | io: IO, 60 | 61 | // State 62 | pc: u24 = 0, 63 | regs: [32]u8 = [1]u8{0} ** 32, 64 | sreg: SREG = @bitCast(@as(u8, 0)), 65 | 66 | instr_effect: InstructionEffect = .none, 67 | 68 | pub fn reset(cpu: *Cpu) void { 69 | cpu.pc = 0; 70 | cpu.regs = [1]u8{0} ** 32; 71 | cpu.sreg = @bitCast(@as(u8, 0)); 72 | } 73 | 74 | pub const RunError = error{InvalidInstruction}; 75 | pub const RunResult = enum { 76 | breakpoint, 77 | enter_sleep_mode, 78 | reset_watchdog, 79 | out_of_gas, 80 | }; 81 | 82 | pub fn run(cpu: *Cpu, mileage: ?u64) RunError!RunResult { 83 | var rest_gas = mileage; 84 | 85 | while (true) { 86 | if (rest_gas) |*rg| { 87 | if (rg.* == 0) 88 | return .out_of_gas; 89 | rg.* -= 1; 90 | } 91 | 92 | const skip = blk: { 93 | defer cpu.instr_effect = .none; 94 | break :blk switch (cpu.instr_effect) { 95 | .none => false, 96 | .skip_next => true, 97 | .breakpoint => return .breakpoint, 98 | .sleep => return .enter_sleep_mode, 99 | .watchdog_reset => return .reset_watchdog, 100 | }; 101 | }; 102 | 103 | const pc = cpu.pc; 104 | const inst = try isa.decode(cpu.fetchCode()); 105 | 106 | if (cpu.trace) { 107 | // std.debug.print("TRACE {s} {} 0x{X:0>6}: {}\n", .{ 108 | // if (skip) "SKIP" else " ", 109 | // cpu.sreg, 110 | // pc, 111 | // fmtInstruction(inst), 112 | // }); 113 | 114 | std.debug.print("TRACE {s} {} [", .{ 115 | if (skip) "SKIP" else " ", 116 | cpu.sreg, 117 | }); 118 | 119 | for (cpu.regs, 0..) |reg, i| { 120 | if (i > 0) 121 | std.debug.print(" ", .{}); 122 | std.debug.print("{X:0>2}", .{reg}); 123 | } 124 | 125 | std.debug.print("] 0x{X:0>6}: {}\n", .{ 126 | pc, 127 | fmtInstruction(inst), 128 | }); 129 | } 130 | 131 | if (!skip) { 132 | switch (std.meta.activeTag(inst)) { 133 | .unknown => return error.InvalidInstruction, 134 | inline else => |tag| { 135 | const info = @field(inst, @tagName(tag)); 136 | 137 | if (@TypeOf(info) == void) { 138 | @field(instructions, @tagName(tag))(cpu); 139 | } else { 140 | @field(instructions, @tagName(tag))(cpu, info); 141 | } 142 | }, 143 | } 144 | } 145 | } 146 | } 147 | 148 | fn shiftProgramCounter(cpu: *Cpu, by: i12) void { 149 | cpu.pc = @intCast(@as(i32, @intCast(cpu.pc)) + by); 150 | } 151 | 152 | fn fetchCode(cpu: *Cpu) u16 { 153 | const value = cpu.flash.read(cpu.pc); 154 | cpu.pc +%= 1; // increment with wraparound 155 | cpu.pc &= @intFromEnum(cpu.code_model); // then wrap to lower bit size 156 | return value; 157 | } 158 | 159 | fn push(cpu: *Cpu, val: u8) void { 160 | const sp = cpu.getSP(); 161 | cpu.sram.write(sp, val); 162 | cpu.setSP(sp -% 1); 163 | } 164 | 165 | fn pop(cpu: *Cpu) u8 { 166 | const sp = cpu.getSP() +% 1; 167 | cpu.setSP(sp); 168 | return cpu.sram.read(sp); 169 | } 170 | 171 | fn pushCodeLoc(cpu: *Cpu, val: u24) void { 172 | const pc: u24 = val; 173 | const mask: u24 = @intFromEnum(cpu.code_model); 174 | 175 | if ((mask & 0x0000FF) != 0) { 176 | cpu.push(@truncate(pc >> 0)); 177 | } 178 | if ((mask & 0x00FF00) != 0) { 179 | cpu.push(@truncate(pc >> 8)); 180 | } 181 | if ((mask & 0xFF0000) != 0) { 182 | cpu.push(@truncate(pc >> 16)); 183 | } 184 | } 185 | 186 | fn popCodeLoc(cpu: *Cpu) u24 { 187 | const mask = @intFromEnum(cpu.code_model); 188 | 189 | var pc: u24 = 0; 190 | if ((mask & 0xFF0000) != 0) { 191 | pc |= (@as(u24, cpu.pop()) << 16); 192 | } 193 | if ((mask & 0x00FF00) != 0) { 194 | pc |= (@as(u24, cpu.pop()) << 8); 195 | } 196 | if ((mask & 0x0000FF) != 0) { 197 | pc |= (@as(u24, cpu.pop()) << 0); 198 | } 199 | 200 | return pc; 201 | } 202 | 203 | const WideReg = enum(u8) { 204 | x = 0, 205 | y = 1, 206 | z = 2, 207 | fn base(wr: WideReg) usize { 208 | return 26 + 2 * @intFromEnum(wr); 209 | } 210 | }; 211 | 212 | const IndexRegReadMode = enum { eind, ramp, raw }; 213 | 214 | fn readWideReg(cpu: *Cpu, comptime reg: WideReg, comptime mode: IndexRegReadMode) u24 { 215 | return compose24( 216 | switch (mode) { 217 | .raw => 0, 218 | .ramp => switch (reg) { 219 | .x => if (cpu.sio.ramp_x) |ramp| cpu.io.read(ramp) else 0, 220 | .y => if (cpu.sio.ramp_y) |ramp| cpu.io.read(ramp) else 0, 221 | .z => if (cpu.sio.ramp_z) |ramp| cpu.io.read(ramp) else 0, 222 | }, 223 | .eind => if (cpu.sio.e_ind) |e_ind| cpu.io.read(e_ind) else 0, 224 | }, 225 | cpu.regs[reg.base() + 1], 226 | cpu.regs[reg.base() + 0], 227 | ); 228 | } 229 | 230 | const IndexRegWriteMode = enum { raw, ramp }; 231 | 232 | fn writeWideReg(cpu: *Cpu, reg: WideReg, value: u24, comptime mode: IndexRegWriteMode) void { 233 | const parts = decompose24(value); 234 | cpu.regs[reg.base() + 0] = parts[0]; 235 | cpu.regs[reg.base() + 1] = parts[1]; 236 | if (mode == .ramp) { 237 | switch (reg) { 238 | .x => if (cpu.sio.ramp_x) |ramp| cpu.io.write(ramp, parts[2]), 239 | .y => if (cpu.sio.ramp_y) |ramp| cpu.io.write(ramp, parts[2]), 240 | .z => if (cpu.sio.ramp_z) |ramp| cpu.io.write(ramp, parts[2]), 241 | } 242 | } 243 | } 244 | 245 | const instructions = struct { 246 | // Copy: 247 | 248 | /// MOV – Copy Register 249 | /// This instruction makes a copy of one register into another. The source register Rr is left unchanged, while 250 | /// the destination register Rd is loaded with a copy of Rr. 251 | inline fn mov(cpu: *Cpu, info: isa.opinfo.d5r5) void { 252 | // Rd ← Rr 253 | cpu.regs[info.d.num()] = cpu.regs[info.r.num()]; 254 | } 255 | 256 | /// MOVW – Copy Register Word 257 | /// This instruction makes a copy of one register pair into another register pair. The source register pair Rr 258 | /// +1:Rr is left unchanged, while the destination register pair Rd+1:Rd is loaded with a copy of Rr + 1:Rr. 259 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 260 | inline fn movw(cpu: *Cpu, info: isa.opinfo.D4R4) void { 261 | // d ∈ {0,2,...,30}, r ∈ {0,2,...,30 262 | const Rd = info.D.num(); 263 | const Rr = info.R.num(); 264 | 265 | // Rd+1:Rd ← Rr+1:Rr 266 | cpu.regs[Rd + 0] = cpu.regs[Rr + 0]; 267 | cpu.regs[Rd + 1] = cpu.regs[Rr + 1]; 268 | } 269 | 270 | /// SWAP – Swap Nibbles 271 | /// Swaps high and low nibbles in a register. 272 | inline fn swap(cpu: *Cpu, info: isa.opinfo.d5) void { 273 | const Nibbles = packed struct(u8) { low: u4, high: u4 }; 274 | 275 | // R(7:4) ← Rd(3:0), R(3:0) ← Rd(7:4) 276 | const src: Nibbles = @bitCast(cpu.regs[info.d.num()]); 277 | const dst = Nibbles{ .low = src.high, .high = src.low }; 278 | cpu.regs[info.d.num()] = @bitCast(dst); 279 | } 280 | 281 | // ALU Bitwise: 282 | 283 | const LogicOp = enum { @"and", @"or", xor }; 284 | inline fn perform_logic(cpu: *Cpu, d: u5, rhs: u8, comptime op: LogicOp) void { 285 | const src = cpu.regs[d]; 286 | const res = switch (op) { 287 | .@"and" => src & rhs, 288 | .@"or" => src | rhs, 289 | .xor => src ^ rhs, 290 | }; 291 | 292 | cpu.regs[d] = res; 293 | cpu.sreg.z = (res == 0); 294 | cpu.sreg.n = (res & 0x80) != 0; 295 | cpu.sreg.v = false; 296 | cpu.sreg.s = (cpu.sreg.n != cpu.sreg.v); 297 | } 298 | 299 | /// AND – Logical AND 300 | /// Performs the logical AND between the contents of register Rd and register Rr, and places the result in the 301 | /// destination register Rd. 302 | inline fn @"and"(cpu: *Cpu, info: isa.opinfo.d5r5) void { 303 | // Rd ← Rd • Rr 304 | return perform_logic(cpu, info.d.num(), cpu.regs[info.r.num()], .@"and"); 305 | } 306 | 307 | /// EOR – Exclusive OR 308 | /// Performs the logical EOR between the contents of register Rd and register Rr and places the result in the 309 | /// destination register Rd. 310 | inline fn eor(cpu: *Cpu, info: isa.opinfo.d5r5) void { 311 | // Rd ← Rd 312 | return perform_logic(cpu, info.d.num(), cpu.regs[info.r.num()], .xor); 313 | } 314 | 315 | /// OR – Logical OR 316 | /// Performs the logical OR between the contents of register Rd and register Rr, and places the result in the 317 | /// destination register Rd. 318 | inline fn @"or"(cpu: *Cpu, info: isa.opinfo.d5r5) void { 319 | // Rd ← Rd v Rr 320 | return perform_logic(cpu, info.d.num(), cpu.regs[info.r.num()], .@"or"); 321 | } 322 | 323 | /// ANDI – Logical AND with Immediate 324 | /// Performs the logical AND between the contents of register Rd and a constant, and places the result in the 325 | /// destination register Rd. 326 | inline fn andi(cpu: *Cpu, info: isa.opinfo.d4k8) void { 327 | // Rd ← Rd • K 328 | return perform_logic(cpu, info.d.num(), info.k, .@"and"); 329 | } 330 | 331 | /// ORI – Logical OR with Immediate 332 | /// Performs the logical OR between the contents of register Rd and a constant, and places the result in the 333 | /// destination register Rd 334 | inline fn ori(cpu: *Cpu, info: isa.opinfo.d4k8) void { 335 | // Rd ← Rd v K 336 | return perform_logic(cpu, info.d.num(), info.k, .@"or"); 337 | } 338 | 339 | // ALU Arithmetic: 340 | 341 | /// INC – Increment 342 | /// Adds one -1- to the contents of register Rd and places the result in the destination register Rd. 343 | /// The C Flag in SREG is not affected by the operation, thus allowing the INC instruction to be used on a 344 | /// loop counter in multiple-precision computations. 345 | /// When operating on unsigned numbers, only BREQ and BRNE branches can be expected to perform 346 | /// consistently. When operating on two’s complement values, all signed branches are available. 347 | inline fn inc(cpu: *Cpu, info: isa.opinfo.d5) void { 348 | // Rd ← Rd + 1 349 | 350 | const src = cpu.regs[info.d.num()]; 351 | 352 | const res = src +% 1; 353 | 354 | cpu.regs[info.d.num()] = res; 355 | 356 | cpu.sreg.z = (res == 0); 357 | cpu.sreg.v = (src == 0x7F); 358 | cpu.sreg.n = (res & 0x80) != 0; 359 | cpu.sreg.s = (cpu.sreg.n != cpu.sreg.v); 360 | } 361 | 362 | /// DEC – Decrement 363 | /// Subtracts one -1- from the contents of register Rd and places the result in the destination register Rd. 364 | /// The C Flag in SREG is not affected by the operation, thus allowing the DEC instruction to be used on a 365 | /// loop counter in multiple-precision computations. 366 | /// When operating on unsigned values, only BREQ and BRNE branches can be expected to perform 367 | /// consistently. When operating on two’s complement values, all signed branches are available. 368 | inline fn dec(cpu: *Cpu, info: isa.opinfo.d5) void { 369 | // Rd ← Rd - 1 370 | 371 | const src = cpu.regs[info.d.num()]; 372 | 373 | const res = src -% 1; 374 | cpu.regs[info.d.num()] = res; 375 | 376 | cpu.sreg.z = (res == 0); 377 | cpu.sreg.v = (src == 0x80); 378 | cpu.sreg.n = (res & 0x80) != 0; 379 | cpu.sreg.s = (cpu.sreg.n != cpu.sreg.v); 380 | } 381 | 382 | inline fn generic_add(cpu: *Cpu, info: isa.opinfo.d5r5, c: bool) void { 383 | const lhs = cpu.regs[info.d.num()]; 384 | const Rd: Bits8 = @bitCast(lhs); 385 | const rhs = cpu.regs[info.r.num()]; 386 | const Rr: Bits8 = @bitCast(rhs); 387 | 388 | const res: u8 = lhs +% rhs +% @intFromBool(c); 389 | cpu.regs[info.d.num()] = res; 390 | 391 | const R: Bits8 = @bitCast(res); 392 | 393 | cpu.sreg.z = (res == 0); 394 | cpu.sreg.n = (res & 0x80) != 0; 395 | cpu.sreg.c = (Rd.b7 and Rr.b7) or (Rr.b7 and !R.b7) or (!R.b7 and Rd.b7); 396 | cpu.sreg.h = (Rd.b3 and Rr.b3) or (Rr.b3 and !R.b3) or (!R.b3 and Rd.b3); 397 | cpu.sreg.v = (Rd.b7 and Rr.b7 and !R.b7) or (!Rd.b7 and !Rr.b7 and R.b7); 398 | cpu.sreg.s = (cpu.sreg.n != cpu.sreg.v); 399 | } 400 | 401 | /// ADD – Add without Carry 402 | /// Adds two registers without the C Flag and places the result in the destination register Rd. 403 | inline fn add(cpu: *Cpu, info: isa.opinfo.d5r5) void { 404 | // Rd ← Rd + Rr 405 | return generic_add(cpu, info, false); 406 | } 407 | 408 | /// ADC – Add with Carry 409 | /// Adds two registers and the contents of the C Flag and places the result in the destination register Rd. 410 | inline fn adc(cpu: *Cpu, info: isa.opinfo.d5r5) void { 411 | // Rd ← Rd + Rr + C 412 | return generic_add(cpu, info, cpu.sreg.c); 413 | } 414 | 415 | const SubCpConfig = struct { 416 | zero: enum { clear_only, replace }, 417 | writeback: bool, 418 | carry: bool, 419 | flip: bool, 420 | 421 | const sub: SubCpConfig = .{ .zero = .replace, .writeback = true, .carry = false, .flip = false }; 422 | const sbc: SubCpConfig = .{ .zero = .clear_only, .writeback = true, .carry = true, .flip = false }; 423 | const cp: SubCpConfig = .{ .zero = .replace, .writeback = false, .carry = false, .flip = false }; 424 | const cpc: SubCpConfig = .{ .zero = .clear_only, .writeback = false, .carry = true, .flip = false }; 425 | const neg: SubCpConfig = .{ .zero = .replace, .writeback = true, .carry = false, .flip = true }; 426 | }; 427 | inline fn generic_sub_cp(cpu: *Cpu, d: isa.Register, _rhs: u8, comptime conf: SubCpConfig) void { 428 | const lhs: u8 = if (conf.flip) _rhs else cpu.regs[d.num()]; 429 | const rhs: u8 = if (conf.flip) cpu.regs[d.num()] else _rhs; 430 | const Rd: Bits8 = @bitCast(lhs); 431 | const Rr: Bits8 = @bitCast(rhs); 432 | const result: u8 = if (conf.carry) 433 | lhs -% rhs -% @intFromBool(cpu.sreg.c) 434 | else 435 | lhs -% rhs; 436 | const R: Bits8 = @bitCast(result); 437 | 438 | if (conf.writeback) { 439 | cpu.regs[d.num()] = result; 440 | } 441 | 442 | // Set if there was a borrow from bit 3; cleared otherwise. 443 | // H = ~Rd3 • Rr3 + Rr3 • R3 + R3 • ~Rd3 444 | cpu.sreg.h = (!Rd.b3 and Rr.b3) or (Rr.b3 and R.b3) or (R.b3 and !Rd.b3); 445 | 446 | // Set if two’s complement overflow resulted from the operation; cleared otherwise. 447 | // V = Rd7 • ~Rr7 • ~R7 + ~Rd7 • Rr7 • R7 448 | cpu.sreg.v = (Rd.b7 and !Rr.b7 and !R.b7) or (!Rd.b7 and Rr.b7 and R.b7); 449 | 450 | // Set if the absolute value of the contents of Rr plus previous carry is larger than the absolute value of Rd; cleared otherwise. 451 | // C = ~Rd7 • Rr7 + Rr7 • R7 + R7 • ~Rd7 452 | cpu.sreg.c = (!Rd.b7 and Rr.b7) or (Rr.b7 and R.b7) or (R.b7 and !Rd.b7); 453 | 454 | const z = (result == 0); 455 | switch (conf.zero) { 456 | // Set if the result is $00; cleared otherwise. 457 | // Z = ~R7 • ~R6 • ~R5 • ~R4 • ~R3 • ~R2 • ~R1 • ~R0 458 | .replace => cpu.sreg.z = z, 459 | 460 | // Previous value remains unchanged when the result is zero; cleared otherwise. 461 | // Z = ~R7 • ~R6 • ~R5 • ~R4 • ~R3 • ~R2 • ~R1 • ~R0 • Z 462 | .clear_only => cpu.sreg.z = z and cpu.sreg.z, 463 | } 464 | 465 | // Set if MSB of the result is set; cleared otherwise. 466 | // N = R7 467 | cpu.sreg.n = R.b7; 468 | 469 | // S = N ⊕ V, for signed tests. 470 | cpu.sreg.s = (cpu.sreg.n != cpu.sreg.v); 471 | } 472 | 473 | /// SUB – Subtract Without Carry 474 | /// Subtracts two registers and places the result in the destination register Rd. 475 | inline fn sub(cpu: *Cpu, info: isa.opinfo.d5r5) void { 476 | // Rd ← Rd - Rr 477 | return generic_sub_cp(cpu, info.d, cpu.regs[info.r.num()], SubCpConfig.sub); 478 | } 479 | 480 | /// SUBI – Subtract Immediate 481 | /// Subtracts a register and a constant, and places the result in the destination register Rd. This instruction is 482 | /// working on Register R16 to R31 and is very well suited for operations on the X, Y, and Z-pointers. 483 | inline fn subi(cpu: *Cpu, info: isa.opinfo.d4k8) void { 484 | // Rd ← Rd - K 485 | return generic_sub_cp(cpu, info.d.reg(), info.k, SubCpConfig.sub); 486 | } 487 | 488 | /// SBC – Subtract with Carry 489 | /// Subtracts two registers and subtracts with the C Flag, and places the result in the destination register Rd. 490 | inline fn sbc(cpu: *Cpu, info: isa.opinfo.d5r5) void { 491 | // Rd ← Rd - Rr - C 492 | return generic_sub_cp(cpu, info.d, cpu.regs[info.r.num()], SubCpConfig.sbc); 493 | } 494 | 495 | /// SBCI – Subtract Immediate with Carry SBI 496 | /// Subtracts a constant from a register and subtracts with the C Flag, and places the result in the destination 497 | /// register Rd. 498 | inline fn sbci(cpu: *Cpu, info: isa.opinfo.d4k8) void { 499 | // Rd ← Rd - K - C 500 | return generic_sub_cp(cpu, info.d.reg(), info.k, SubCpConfig.sbc); 501 | } 502 | 503 | /// NEG – Two’s Complement 504 | /// Replaces the contents of register Rd with its two’s complement; the value $80 is left unchanged. 505 | inline fn neg(cpu: *Cpu, info: isa.opinfo.d5) void { 506 | // Rd ← $00 - Rd 507 | return generic_sub_cp(cpu, info.d, 0x00, SubCpConfig.neg); 508 | } 509 | 510 | /// COM – One’s Complement 511 | /// This instruction performs a One’s Complement of register Rd. 512 | inline fn com(cpu: *Cpu, info: isa.opinfo.d5) void { 513 | // Rd ← $FF - Rd 514 | const src = cpu.regs[info.d.num()]; 515 | 516 | const res: u8 = 0xFF - src; 517 | 518 | cpu.regs[info.d.num()] = res; 519 | 520 | cpu.sreg.z = (res == 0); 521 | cpu.sreg.n = (res & 0x80) != 0; 522 | cpu.sreg.c = true; 523 | cpu.sreg.v = false; 524 | cpu.sreg.s = (cpu.sreg.n != cpu.sreg.v); 525 | } 526 | 527 | // Multiplier ALU Arithmetic: 528 | 529 | /// TODO! 530 | inline fn fmul(cpu: *Cpu, info: isa.opinfo.d3r3) void { 531 | _ = cpu; 532 | std.debug.print("fmul {}\n", .{info}); 533 | @panic("fmul not implemented yet!"); 534 | } 535 | 536 | /// TODO! 537 | inline fn fmuls(cpu: *Cpu, info: isa.opinfo.d3r3) void { 538 | _ = cpu; 539 | std.debug.print("fmuls {}\n", .{info}); 540 | @panic("fmuls not implemented yet!"); 541 | } 542 | 543 | /// TODO! 544 | inline fn fmulsu(cpu: *Cpu, info: isa.opinfo.d3r3) void { 545 | _ = cpu; 546 | std.debug.print("fmulsu {}\n", .{info}); 547 | @panic("fmulsu not implemented yet!"); 548 | } 549 | 550 | const MulConfig = struct { 551 | Lhs: type, 552 | Rhs: type, 553 | Result: type, 554 | }; 555 | inline fn generic_mul(cpu: *Cpu, d: isa.Register, r: isa.Register, comptime config: MulConfig) void { 556 | // R1:R0 ← Rd × Rr 557 | const lhs_raw: u8 = cpu.regs[d.num()]; 558 | const rhs_raw: u8 = cpu.regs[r.num()]; 559 | 560 | const lhs: config.Lhs = @bitCast(lhs_raw); 561 | const rhs: config.Rhs = @bitCast(rhs_raw); 562 | 563 | const result: config.Result = @as(config.Result, lhs) *% @as(config.Result, rhs); 564 | 565 | const raw_result: u16 = @bitCast(result); 566 | 567 | const split_result = decompose16(raw_result); 568 | cpu.regs[0] = split_result[0]; 569 | cpu.regs[1] = split_result[1]; 570 | 571 | cpu.sreg.c = ((raw_result & 0x8000) != 0); 572 | cpu.sreg.z = (raw_result == 0); 573 | } 574 | 575 | /// MUL – Multiply Unsigned 576 | /// 577 | /// This instruction performs 8-bit × 8-bit → 16-bit unsigned multiplication. 578 | /// 579 | /// The multiplicand Rd and the multiplier Rr are two registers containing unsigned numbers. The 16-bit 580 | /// unsigned product is placed in R1 (high byte) and R0 (low byte). Note that if the multiplicand or the 581 | /// multiplier is selected from R0 or R1 the result will overwrite those after multiplication. 582 | /// 583 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 584 | inline fn mul(cpu: *Cpu, info: isa.opinfo.d5r5) void { 585 | // R1:R0 ← Rd × Rr (unsigned ← unsigned × unsigned) 586 | return generic_mul(cpu, info.d.reg(), info.r.reg(), .{ 587 | .Result = u16, 588 | .Lhs = u8, 589 | .Rhs = u8, 590 | }); 591 | } 592 | 593 | /// MULS – Multiply Signed 594 | /// 595 | /// The multiplicand Rd and the multiplier Rr are two registers containing signed numbers. The 16-bit signed 596 | /// product is placed in R1 (high byte) and R0 (low byte). 597 | /// 598 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 599 | inline fn muls(cpu: *Cpu, info: isa.opinfo.d4r4) void { 600 | // R1:R0 ← Rd × Rr (signed ← signed × signed) 601 | return generic_mul(cpu, info.d.reg(), info.r.reg(), .{ 602 | .Result = i16, 603 | .Lhs = i8, 604 | .Rhs = i8, 605 | }); 606 | } 607 | 608 | /// MULSU – Multiply Signed with Unsigned 609 | /// 610 | /// This instruction performs 8-bit × 8-bit → 16-bit multiplication of a signed and an unsigned number. 611 | /// 612 | /// The multiplicand Rd and the multiplier Rr are two registers. The multiplicand Rd is a signed number, and 613 | /// the multiplier Rr is unsigned. The 16-bit signed product is placed in R1 (high byte) and R0 (low byte). 614 | /// 615 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 616 | inline fn mulsu(cpu: *Cpu, info: isa.opinfo.d3r3) void { 617 | // R1:R0 ← Rd × Rr (signed ← signed × unsigned) 618 | return generic_mul(cpu, info.d.reg(), info.r.reg(), .{ 619 | .Result = i16, 620 | .Lhs = i8, 621 | .Rhs = u8, 622 | }); 623 | } 624 | 625 | // Wide ALU Arithmetic: 626 | 627 | const register_pairs_4 = [4]usize{ 24, 26, 28, 30 }; 628 | 629 | /// ADIW – Add Immediate to Word 630 | /// Adds an immediate value (0 - 63) to a register pair and places the result in the register pair. This 631 | /// instruction operates on the upper four register pairs, and is well suited for operations on the pointer 632 | /// registers. 633 | /// 634 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 635 | inline fn adiw(cpu: *Cpu, info: isa.opinfo.d2k6) void { 636 | // Rd+1:Rd ← Rd+1:Rd + K 637 | 638 | const base = register_pairs_4[info.d]; 639 | 640 | const src = compose16(cpu.regs[base + 1], cpu.regs[base + 0]); 641 | 642 | const res = src +% info.k; 643 | 644 | const res2 = decompose16(res); 645 | cpu.regs[base + 0] = res2[0]; 646 | cpu.regs[base + 1] = res2[1]; 647 | 648 | cpu.sreg.z = (res == 0); 649 | cpu.sreg.n = ((res & 0x8000) != 0); 650 | cpu.sreg.v = ((src & 0x8000) == 0) and ((res & 0x8000) != 0); 651 | cpu.sreg.c = ((src & 0x8000) != 0) and ((res & 0x8000) == 0); 652 | cpu.sreg.s = (cpu.sreg.n != cpu.sreg.v); 653 | } 654 | 655 | /// SBIW – Subtract Immediate from Word 656 | /// Subtracts an immediate value (0-63) from a register pair and places the result in the register pair. This 657 | /// instruction operates on the upper four register pairs, and is well suited for operations on the Pointer 658 | /// Registers. 659 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 660 | inline fn sbiw(cpu: *Cpu, info: isa.opinfo.d2k6) void { 661 | // Rd+1:Rd ← Rd+1:Rd - K 662 | 663 | const base = register_pairs_4[info.d]; 664 | 665 | const src = compose16(cpu.regs[base + 1], cpu.regs[base + 0]); 666 | 667 | const res = src -% info.k; 668 | 669 | const res2 = decompose16(res); 670 | cpu.regs[base + 0] = res2[0]; 671 | cpu.regs[base + 1] = res2[1]; 672 | 673 | cpu.sreg.z = (res == 0); 674 | cpu.sreg.n = ((res & 0x8000) != 0); 675 | cpu.sreg.c = ((src & 0x8000) == 0) and ((res & 0x8000) != 0); 676 | cpu.sreg.v = cpu.sreg.c; 677 | cpu.sreg.s = (cpu.sreg.n != cpu.sreg.v); 678 | } 679 | 680 | // ALU Shifts 681 | 682 | const ShiftConfig = struct { 683 | msb: enum { sticky, carry, zero }, 684 | }; 685 | inline fn generic_shift(cpu: *Cpu, info: isa.opinfo.d5, comptime conf: ShiftConfig) void { 686 | const src: u8 = cpu.regs[info.d.num()]; 687 | 688 | const msb: u8 = switch (conf.msb) { 689 | .zero => 0x00, 690 | .carry => if (cpu.sreg.c) 0x80 else 0x00, 691 | .sticky => (src & 0x80), 692 | }; 693 | const res: u8 = (src >> 7) | msb; 694 | 695 | cpu.regs[info.d.num()] = res; 696 | 697 | // Set if the result is $00; cleared otherwise. 698 | // Z = ~R7 • ~R6 • ~R5 • ~R4 • ~R3 • ~R2 • ~R1 • ~R0 699 | cpu.sreg.z = (res == 0); 700 | 701 | // Set if, before the shift, the LSB of Rd was set; cleared otherwise. 702 | // C = Rd0 703 | cpu.sreg.c = (src & 0x01) != 0; 704 | 705 | // Set if MSB of the result is set; cleared otherwise. 706 | // N = R7 707 | cpu.sreg.n = (src & 0x80) != 0; 708 | 709 | // V = N ⊕ C, for N and C after the shift. 710 | cpu.sreg.v = (cpu.sreg.n != cpu.sreg.c); 711 | 712 | // N ⊕ V, for signed tests. 713 | cpu.sreg.n = (cpu.sreg.n != cpu.sreg.v); 714 | } 715 | 716 | /// Shifts all bits in Rd one place to the right. Bit 7 is held constant. Bit 0 is loaded into the C Flag of the 717 | /// SREG. This operation effectively divides a signed value by two without changing its sign. The Carry Flag 718 | /// can be used to round the result. 719 | inline fn asr(cpu: *Cpu, info: isa.opinfo.d5) void { 720 | return generic_shift(cpu, info, .{ .msb = .sticky }); 721 | } 722 | 723 | /// LSR – Logical Shift Right 724 | /// Shifts all bits in Rd one place to the right. Bit 7 is cleared. Bit 0 is loaded into the C Flag of the SREG. 725 | /// This operation effectively divides an unsigned value by two. The C Flag can be used to round the result. 726 | inline fn lsr(cpu: *Cpu, info: isa.opinfo.d5) void { 727 | return generic_shift(cpu, info, .{ .msb = .zero }); 728 | } 729 | 730 | /// ROR – Rotate Right through Carry 731 | /// Shifts all bits in Rd one place to the right. The C Flag is shifted into bit 7 of Rd. Bit 0 is shifted into the C 732 | /// Flag. This operation, combined with ASR, effectively divides multi-byte signed values by two. Combined 733 | /// with LSR it effectively divides multi-byte unsigned values by two. The Carry Flag can be used to round the 734 | /// result. 735 | inline fn ror(cpu: *Cpu, info: isa.opinfo.d5) void { 736 | return generic_shift(cpu, info, .{ .msb = .carry }); 737 | } 738 | 739 | // ASL = LSL = ADD Rd, Rd 740 | // ROL = ADC Rd, Rd 741 | 742 | // I/O: 743 | 744 | /// OUT – Store Register to I/O Location 745 | /// Stores data from register Rr in the Register File to I/O Space (Ports, Timers, Configuration Registers, etc.). 746 | inline fn out(cpu: *Cpu, info: isa.opinfo.a6r5) void { 747 | // I/O(A) ← Rr 748 | cpu.io.write(info.a, cpu.regs[info.r.num()]); 749 | } 750 | 751 | /// IN - Load an I/O Location to Register 752 | /// Loads data from the I/O Space (Ports, Timers, Configuration Registers, etc.) into register Rd in the Register File. 753 | inline fn in(cpu: *Cpu, info: isa.opinfo.a6d5) void { 754 | // Rd ← I/O(A) 755 | cpu.regs[info.d.num()] = cpu.io.read(info.a); 756 | } 757 | 758 | /// CBI – Clear Bit in I/O Register 759 | /// Clears a specified bit in an I/O register. This instruction operates on the lower 32 I/O registers – 760 | /// addresses 0-31. 761 | inline fn cbi(cpu: *Cpu, info: isa.opinfo.a5b3) void { 762 | // I/O(A,b) ← 0 763 | cpu.io.writeMasked(info.a, info.b.mask(), 0x00); 764 | } 765 | 766 | /// SBI – Set Bit in I/O Register 767 | /// Sets a specified bit in an I/O Register. This instruction operates on the lower 32 I/O Registers – addresses 0-31. 768 | inline fn sbi(cpu: *Cpu, info: isa.opinfo.a5b3) void { 769 | // I/O(A,b) ← 1 770 | cpu.io.writeMasked(info.a, info.b.mask(), 0xFF); 771 | } 772 | 773 | // Branching: 774 | 775 | /// BRBC – Branch if Bit in SREG is Cleared 776 | /// Conditional relative branch. Tests a single bit in SREG and branches relatively to PC if the bit is cleared. 777 | /// This instruction branches relatively to PC in either direction (PC - 63 ≤ destination ≤ PC + 64). Parameter 778 | /// k is the offset from PC and is represented in two’s complement form. 779 | inline fn brbc(cpu: *Cpu, info: isa.opinfo.k7s3) void { 780 | // If SREG(s) = 0 then PC ← PC + k + 1, else PC ← PC + 1 781 | const pc_offset: i7 = @bitCast(info.k); 782 | if (!cpu.sreg.readBit(info.s)) { 783 | cpu.shiftProgramCounter(pc_offset); 784 | } 785 | } 786 | 787 | /// BRBS – Branch if Bit in SREG is Set 788 | /// Conditional relative branch. Tests a single bit in SREG and branches relatively to PC if the bit is set. This 789 | /// instruction branches relatively to PC in either direction (PC - 63 ≤ destination ≤ PC + 64). Parameter k is 790 | /// the offset from PC and is represented in two’s complement form. 791 | inline fn brbs(cpu: *Cpu, bits: isa.opinfo.k7s3) void { 792 | // If SREG(s) = 1 then PC ← PC + k + 1, else PC ← PC + 1 793 | 794 | const pc_offset: i7 = @bitCast(bits.k); 795 | if (cpu.sreg.readBit(bits.s)) { 796 | cpu.shiftProgramCounter(pc_offset); 797 | } 798 | } 799 | 800 | /// SBIC – Skip if Bit in I/O Register is Cleared 801 | /// This instruction tests a single bit in an I/O Register and skips the next instruction if the bit is cleared. This 802 | /// instruction operates on the lower 32 I/O Registers – addresses 0-31. 803 | inline fn sbic(cpu: *Cpu, info: isa.opinfo.a5b3) void { 804 | // If I/O(A,b) = 0 then PC ← PC + 2 (or 3) else PC ← PC + 1 805 | const val = cpu.io.read(info.a); 806 | if ((val & info.b.mask()) == 0) { 807 | cpu.instr_effect = .skip_next; 808 | } 809 | } 810 | 811 | /// SBIS – Skip if Bit in I/O Register is Set 812 | /// This instruction tests a single bit in an I/O Register and skips the next instruction if the bit is set. This 813 | /// instruction operates on the lower 32 I/O Registers – addresses 0-31. 814 | inline fn sbis(cpu: *Cpu, info: isa.opinfo.a5b3) void { 815 | // If I/O(A,b) = 1 then PC ← PC + 2 (or 3) else PC ← PC + 1 816 | const val = cpu.io.read(info.a); 817 | if ((val & info.b.mask()) != 0) { 818 | cpu.instr_effect = .skip_next; 819 | } 820 | } 821 | 822 | /// SBRC – Skip if Bit in Register is Cleared 823 | /// This instruction tests a single bit in a register and skips the next instruction if the bit is cleared. 824 | inline fn sbrc(cpu: *Cpu, info: isa.opinfo.b3r5) void { 825 | // If Rr(b) = 0 then PC ← PC + 2 (or 3) else PC ← PC + 1 826 | const val = cpu.regs[info.r.num()]; 827 | if ((val & info.b.mask()) == 0) { 828 | cpu.instr_effect = .skip_next; 829 | } 830 | } 831 | 832 | /// SBRS – Skip if Bit in Register is Set 833 | /// This instruction tests a single bit in a register and skips the next instruction if the bit is set. 834 | inline fn sbrs(cpu: *Cpu, info: isa.opinfo.b3r5) void { 835 | // If Rr(b) = 1 then PC ← PC + 2 (or 3) else PC ← PC + 1 836 | const val = cpu.regs[info.r.num()]; 837 | if ((val & info.b.mask()) != 0) { 838 | cpu.instr_effect = .skip_next; 839 | } 840 | } 841 | 842 | /// This instruction performs a compare between two registers Rd and Rr, and skips the next instruction if Rd = Rr. 843 | inline fn cpse(cpu: *Cpu, info: isa.opinfo.d5r5) void { 844 | // If Rd = Rr then PC ← PC + 2 (or 3) else PC ← PC + 1 845 | 846 | const Rd = cpu.regs[info.d.num()]; 847 | const Rr = cpu.regs[info.r.num()]; 848 | if (Rd == Rr) { 849 | cpu.instr_effect = .skip_next; 850 | } 851 | } 852 | 853 | // Jumps 854 | 855 | /// JMP – Jump 856 | /// Jump to an address within the entire 4M (words) Program memory. See also RJMP. 857 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 858 | /// 859 | /// NOTE: 32 bit instruction! 860 | inline fn jmp(cpu: *Cpu, info: isa.opinfo.k6) void { 861 | // PC ← k 862 | const ext = cpu.fetchCode(); 863 | cpu.pc = (@as(u24, info.k) << 16) | ext; 864 | } 865 | 866 | /// RJMP – Relative Jump 867 | /// Relative jump to an address within PC - 2K +1 and PC + 2K (words). For AVR microcontrollers with 868 | /// Program memory not exceeding 4K words (8KB) this instruction can address the entire memory from 869 | /// every address location. See also JMP. 870 | inline fn rjmp(cpu: *Cpu, bits: isa.opinfo.k12) void { 871 | cpu.shiftProgramCounter(@as(i12, @bitCast(bits.k))); 872 | } 873 | 874 | /// RCALL – Relative Call to Subroutine 875 | /// Relative call to an address within PC - 2K + 1 and PC + 2K (words). The return address (the instruction 876 | /// after the RCALL) is stored onto the Stack. See also CALL. For AVR microcontrollers with Program 877 | /// memory not exceeding 4K words (8KB) this instruction can address the entire memory from every 878 | /// address location. The Stack Pointer uses a post-decrement scheme during RCALL. 879 | inline fn rcall(cpu: *Cpu, info: isa.opinfo.k12) void { 880 | // PC ← PC + k + 1 881 | cpu.pushCodeLoc(cpu.pc); // PC already points to the next instruction 882 | rjmp(cpu, info); 883 | } 884 | 885 | /// RET – Return from Subroutine 886 | /// Returns from subroutine. The return address is loaded from the STACK. The Stack Pointer uses a pre- 887 | /// increment scheme during RET. 888 | inline fn ret(cpu: *Cpu) void { 889 | // PC(15:0) ← STACK Devices with 16-bit PC, 128KB Program memory maximum. 890 | // PC(21:0) ← STACK Devices with 22-bit PC, 8MB Program memory maximum. 891 | cpu.pc = cpu.popCodeLoc(); 892 | } 893 | 894 | /// RETI – Return from Interrupt 895 | /// Returns from interrupt. The return address is loaded from the STACK and the Global Interrupt Flag is set. 896 | /// Note that the Status Register is not automatically stored when entering an interrupt routine, and it is not 897 | /// restored when returning from an interrupt routine. This must be handled by the application program. The 898 | /// Stack Pointer uses a pre-increment scheme during RETI. 899 | inline fn reti(cpu: *Cpu) void { 900 | // PC(15:0) ← STACK Devices with 16-bit PC, 128KB Program memory maximum. 901 | // PC(21:0) ← STACK Devices with 22-bit PC, 8MB Program memory maximum. 902 | cpu.pc = cpu.popCodeLoc(); 903 | cpu.sreg.i = true; 904 | } 905 | 906 | /// CALL – Long Call to a Subroutine 907 | /// Calls to a subroutine within the entire Program memory. The return address (to the instruction after the 908 | /// CALL) will be stored onto the Stack. (See also RCALL). The Stack Pointer uses a post-decrement 909 | /// scheme during CALL. 910 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 911 | /// 912 | /// NOTE: 32 bit instruction! 913 | inline fn call(cpu: *Cpu, info: isa.opinfo.k6) void { 914 | const ext = cpu.fetchCode(); 915 | cpu.pushCodeLoc(cpu.pc); // PC already points to the next instruction 916 | cpu.pc = (@as(u24, info.k) << 16) | ext; 917 | } 918 | 919 | /// ICALL – Indirect Call to Subroutine 920 | /// Calls to a subroutine within the entire 4M (words) Program memory. The return address (to the instruction 921 | /// after the CALL) will be stored onto the Stack. See also RCALL. The Stack Pointer uses a post-decrement 922 | /// scheme during CALL. 923 | /// 924 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 925 | inline fn icall(cpu: *Cpu) void { 926 | // PC(15:0) ← Z(15:0) 927 | // PC(21:16) ← 0 928 | cpu.pushCodeLoc(cpu.pc); // PC already points to the next instruction 929 | cpu.pc = cpu.readWideReg(.z, .raw); 930 | } 931 | 932 | /// IJMP – Indirect Jump 933 | /// Indirect jump to the address pointed to by the Z (16 bits) Pointer Register in the Register File. The Z- 934 | /// pointer Register is 16 bits wide and allows jump within the lowest 64K words (128KB) section of Program 935 | /// memory. 936 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 937 | inline fn ijmp(cpu: *Cpu) void { 938 | // PC(15:0) ← Z(15:0) 939 | // PC(21:16) ← 0 940 | cpu.pc = cpu.readWideReg(.z, .raw); 941 | } 942 | 943 | /// EICALL – Extended Indirect Call to Subroutine 944 | /// Indirect call of a subroutine pointed to by the Z (16 bits) Pointer Register in the Register File and the EIND 945 | /// Register in the I/O space. This instruction allows for indirect calls to the entire 4M (words) Program 946 | /// memory space. See also ICALL. The Stack Pointer uses a post-decrement scheme during EICALL. 947 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 948 | inline fn eicall(cpu: *Cpu) void { 949 | // PC(15:0) ← Z(15:0) 950 | // PC(21:16) ← EIND 951 | cpu.pushCodeLoc(cpu.pc); // PC already points to the next instruction 952 | cpu.pc = cpu.readWideReg(.z, .eind); 953 | } 954 | 955 | /// EIJMP – Extended Indirect Jump 956 | /// Indirect jump to the address pointed to by the Z (16 bits) Pointer Register in the Register File and the 957 | /// EIND Register in the I/O space. This instruction allows for indirect jumps to the entire 4M (words) 958 | /// Program memory space. See also IJMP. 959 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 960 | inline fn eijmp(cpu: *Cpu) void { 961 | // PC(15:0) ← Z(15:0) 962 | // PC(21:16) ← EIND 963 | cpu.pc = cpu.readWideReg(.z, .eind); 964 | } 965 | 966 | // Comparisons 967 | 968 | /// This instruction performs a compare between two registers Rd and Rr. 969 | /// None of the registers are changed. 970 | /// All conditional branches can be used after this instruction. 971 | inline fn cp(cpu: *Cpu, info: isa.opinfo.d5r5) void { 972 | // (1) Rd - Rr 973 | return generic_sub_cp(cpu, info.d, cpu.regs[info.r.num()], SubCpConfig.cp); 974 | } 975 | 976 | /// This instruction performs a compare between two registers Rd and Rr and also takes into account the 977 | /// previous carry. 978 | /// None of the registers are changed. 979 | /// All conditional branches can be used after this instruction. 980 | inline fn cpc(cpu: *Cpu, info: isa.opinfo.d5r5) void { 981 | // (1) Rd - Rr - C 982 | return generic_sub_cp(cpu, info.d, cpu.regs[info.r.num()], SubCpConfig.cpc); 983 | } 984 | 985 | /// This instruction performs a compare between register Rd and a constant. 986 | /// The register is not changed. 987 | /// All conditional branches can be used after this instruction. 988 | inline fn cpi(cpu: *Cpu, info: isa.opinfo.d4k8) void { 989 | // (1) Rd - K 990 | return generic_sub_cp(cpu, info.d.reg(), info.k, SubCpConfig.cp); 991 | } 992 | 993 | // Loads 994 | 995 | /// LDI – Load Immediate 996 | /// Loads an 8-bit constant directly to register 16 to 31. 997 | inline fn ldi(cpu: *Cpu, bits: isa.opinfo.d4k8) void { 998 | // Rd ← K 999 | cpu.regs[bits.d.num()] = bits.k; 1000 | } 1001 | 1002 | /// XCH – Exchange 1003 | /// Exchanges one byte indirect between register and data space. 1004 | /// The data location is pointed to by the Z (16 bits) Pointer Register in the Register File. Memory access is 1005 | /// limited to the current data segment of 64KB. To access another data segment in devices with more than 1006 | /// 64KB data space, the RAMPZ in register in the I/O area has to be changed. 1007 | /// The Z-pointer Register is left unchanged by the operation. This instruction is especially suited for writing/ 1008 | /// reading status bits stored in SRAM. 1009 | inline fn xch(cpu: *Cpu, info: isa.opinfo.r5) void { 1010 | // (Z) ← Rd, Rd ← (Z) 1011 | const z = cpu.readWideReg(.z, .ramp); 1012 | 1013 | const Rd = cpu.regs[info.r.num()]; 1014 | const mem = cpu.sram.read(z); 1015 | cpu.sram.write(z, Rd); 1016 | cpu.regs[info.r.num()] = mem; 1017 | } 1018 | 1019 | /// Load one byte indirect from data space to register and stores and clear the bits in data space specified by 1020 | /// the register. The instruction can only be used towards internal SRAM. 1021 | /// The data location is pointed to by the Z (16 bits) Pointer Register in the Register File. Memory access is 1022 | /// limited to the current data segment of 64KB. To access another data segment in devices with more than 1023 | /// 64KB data space, the RAMPZ in register in the I/O area has to be changed. 1024 | /// The Z-pointer Register is left unchanged by the operation. This instruction is especially suited for clearing 1025 | /// status bits stored in SRAM. 1026 | /// 1027 | /// (Z) ← ($FF – Rd) • (Z), Rd ← (Z) 1028 | inline fn lac(cpu: *Cpu, info: isa.opinfo.r5) void { 1029 | // (Z) ← ($FF – Rd) • (Z), Rd ← (Z) 1030 | const z = cpu.readWideReg(.z, .ramp); 1031 | 1032 | const Rd = cpu.regs[info.r.num()]; 1033 | const mem = cpu.sram.read(z); 1034 | cpu.sram.write(z, (0xFF - Rd) & mem); 1035 | cpu.regs[info.r.num()] = mem; 1036 | } 1037 | 1038 | /// LAS – Load and Set 1039 | /// 1040 | /// Load one byte indirect from data space to register and set bits in data space specified by the register. The 1041 | /// instruction can only be used towards internal SRAM. 1042 | /// The data location is pointed to by the Z (16 bits) Pointer Register in the Register File. Memory access is 1043 | /// limited to the current data segment of 64KB. To access another data segment in devices with more than 1044 | /// 64KB data space, the RAMPZ in register in the I/O area has to be changed. 1045 | /// The Z-pointer Register is left unchanged by the operation. This instruction is especially suited for setting 1046 | /// status bits stored in SRAM. 1047 | inline fn las(cpu: *Cpu, info: isa.opinfo.r5) void { 1048 | // (Z) ← Rd v (Z), Rd ← (Z) 1049 | const z = cpu.readWideReg(.z, .ramp); 1050 | 1051 | const Rd = cpu.regs[info.r.num()]; 1052 | const mem = cpu.sram.read(z); 1053 | cpu.sram.write(z, Rd | mem); 1054 | cpu.regs[info.r.num()] = mem; 1055 | } 1056 | 1057 | /// LAT – Load and Toggle 1058 | /// Load one byte indirect from data space to register and toggles bits in the data space specified by the 1059 | /// register. The instruction can only be used towards SRAM. 1060 | /// The data location is pointed to by the Z (16 bits) Pointer Register in the Register File. Memory access is 1061 | /// limited to the current data segment of 64KB. To access another data segment in devices with more than 1062 | /// 64KB data space, the RAMPZ in register in the I/O area has to be changed. 1063 | /// The Z-pointer Register is left unchanged by the operation. This instruction is especially suited for 1064 | /// changing status bits stored in SRAM. 1065 | inline fn lat(cpu: *Cpu, info: isa.opinfo.r5) void { 1066 | // (Z) ← Rd ⊕ (Z), Rd ← (Z) 1067 | const z = cpu.readWideReg(.z, .ramp); 1068 | 1069 | const Rd = cpu.regs[info.r.num()]; 1070 | const mem = cpu.sram.read(z); 1071 | cpu.sram.write(z, Rd ^ mem); 1072 | cpu.regs[info.r.num()] = mem; 1073 | } 1074 | 1075 | /// LDS – Load Direct from Data Space 1076 | /// 1077 | /// Loads one byte from the data space to a register. For parts with SRAM, the data space consists of the 1078 | /// Register File, I/O memory, and internal SRAM (and external SRAM if applicable). For parts without 1079 | /// SRAM, the data space consists of the register file only. The EEPROM has a separate address space. 1080 | /// A 16-bit address must be supplied. Memory access is limited to the current data segment of 64KB. The 1081 | /// LDS instruction uses the RAMPD Register to access memory above 64KB. To access another data 1082 | /// segment in devices with more than 64KB data space, the RAMPD in register in the I/O area has to be 1083 | /// changed. 1084 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 1085 | /// 1086 | /// NOTE: 32 bit instruction! 1087 | inline fn lds(cpu: *Cpu, info: isa.opinfo.d5) void { 1088 | // Rd ← (k) 1089 | const addr = cpu.extendDirectAddress(cpu.fetchCode()); 1090 | cpu.regs[info.d.num()] = cpu.sram.read(addr); 1091 | } 1092 | 1093 | const IndexOpMode = enum { none, post_incr, pre_decr, displace }; 1094 | const IndexOpConfig = struct { 1095 | op: IndexOpMode, 1096 | wb: IndexRegWriteMode, 1097 | rd: IndexRegReadMode, 1098 | }; 1099 | inline fn compute_and_mutate_index(cpu: *Cpu, comptime wr: WideReg, q: u6, comptime config: IndexOpConfig) u24 { 1100 | const raw = cpu.readWideReg(wr, config.rd); 1101 | const address = switch (config.op) { 1102 | .none => raw, 1103 | .displace => raw +% q, 1104 | .pre_decr => blk: { 1105 | const index = raw -% 1; 1106 | cpu.writeWideReg(wr, index, config.wb); 1107 | break :blk index; 1108 | }, 1109 | .post_incr => blk: { 1110 | cpu.writeWideReg(wr, raw +% 1, config.wb); 1111 | break :blk raw; 1112 | }, 1113 | }; 1114 | return address; 1115 | } 1116 | 1117 | inline fn generic_indexed_load(cpu: *Cpu, comptime wr: WideReg, d: isa.Register, q: u6, comptime mode: IndexOpMode) void { 1118 | const address = compute_and_mutate_index(cpu, wr, q, .{ 1119 | .op = mode, 1120 | .rd = .ramp, 1121 | .wb = .ramp, 1122 | }); 1123 | cpu.regs[d.num()] = cpu.sram.read(address); 1124 | } 1125 | 1126 | /// LD – Load Indirect from Data Space to Register using Index X 1127 | inline fn ldx_i(cpu: *Cpu, info: isa.opinfo.d5) void { 1128 | // Rd ← (X) 1129 | return generic_indexed_load(cpu, .x, info.d, 0, .none); 1130 | } 1131 | 1132 | /// LD – Load Indirect from Data Space to Register using Index X 1133 | inline fn ldx_ii(cpu: *Cpu, info: isa.opinfo.d5) void { 1134 | // Rd ← (X) X ← X + 1 1135 | return generic_indexed_load(cpu, .x, info.d, 0, .post_incr); 1136 | } 1137 | 1138 | /// LD – Load Indirect from Data Space to Register using Index X 1139 | inline fn ldx_iii(cpu: *Cpu, info: isa.opinfo.d5) void { 1140 | // X ← X - 1 Rd ← (X) 1141 | return generic_indexed_load(cpu, .x, info.d, 0, .pre_decr); 1142 | } 1143 | 1144 | // ldy_i is equivalent to ldy_iv with q=0 1145 | 1146 | /// LD (LDD) – Load Indirect from Data Space to Register using Index Y 1147 | inline fn ldy_ii(cpu: *Cpu, info: isa.opinfo.d5) void { 1148 | // Rd ← (Y), Y ← Y + 1 1149 | return generic_indexed_load(cpu, .y, info.d, 0, .post_incr); 1150 | } 1151 | 1152 | /// LD (LDD) – Load Indirect from Data Space to Register using Index Y 1153 | inline fn ldy_iii(cpu: *Cpu, info: isa.opinfo.d5) void { 1154 | // Y ← Y - 1, Rd ← (Y) 1155 | return generic_indexed_load(cpu, .y, info.d, 0, .pre_decr); 1156 | } 1157 | 1158 | /// LD (LDD) – Load Indirect from Data Space to Register using Index Y 1159 | inline fn ldy_iv(cpu: *Cpu, info: isa.opinfo.d5q6) void { 1160 | // Rd ← (Y+q) 1161 | return generic_indexed_load(cpu, .y, info.d, info.q, .displace); 1162 | } 1163 | 1164 | // ldz_i is equivalent to ldz_iv with q=0 1165 | 1166 | /// LD (LDD) – Load Indirect From Data Space to Register using Index Z 1167 | inline fn ldz_ii(cpu: *Cpu, info: isa.opinfo.d5) void { 1168 | // Rd ← (Z), Z ← Z + 1 1169 | return generic_indexed_load(cpu, .z, info.d, 0, .post_incr); 1170 | } 1171 | 1172 | /// LD (LDD) – Load Indirect From Data Space to Register using Index Z 1173 | inline fn ldz_iii(cpu: *Cpu, info: isa.opinfo.d5) void { 1174 | // Z ← Z - 1, Rd ← (Z) 1175 | return generic_indexed_load(cpu, .z, info.d, 0, .pre_decr); 1176 | } 1177 | 1178 | /// LD (LDD) – Load Indirect From Data Space to Register using Index Z 1179 | inline fn ldz_iv(cpu: *Cpu, info: isa.opinfo.d5q6) void { 1180 | // Rd ← (Z+q) 1181 | return generic_indexed_load(cpu, .z, info.d, info.q, .displace); 1182 | } 1183 | 1184 | /// LPM – Load Program Memory 1185 | /// 1186 | /// Loads one byte pointed to by the Z-register into the destination register Rd. This instruction features a 1187 | /// 100% space effective constant initialization or constant data fetch. The Program memory is organized in 1188 | /// 16-bit words while the Z-pointer is a byte address. Thus, the least significant bit of the Z-pointer selects 1189 | /// either low byte (ZLSB = 0) or high byte (ZLSB = 1). This instruction can address the first 64KB (32K words) 1190 | /// of Program memory. The Z-pointer Register can either be left unchanged by the operation, or it can be 1191 | /// incremented. The incrementation does not apply to the RAMPZ Register. 1192 | /// Devices with Self-Programming capability can use the LPM instruction to read the Fuse and Lock bit 1193 | /// values. Refer to the device documentation for a detailed description. 1194 | /// The LPM instruction is not available in all devices. Refer to the device specific instruction set summary. 1195 | /// The result of these combinations is undefined: 1196 | /// LPM r30, Z+ 1197 | /// LPM r31, Z+ 1198 | /// 1199 | /// ELPM – Extended Load Program Memory 1200 | /// 1201 | /// Loads one byte pointed to by the Z-register and the RAMPZ Register in the I/O space, and places this 1202 | /// byte in the destination register Rd. This instruction features a 100% space effective constant initialization 1203 | /// or constant data fetch. The Program memory is organized in 16-bit words while the Z-pointer is a byte 1204 | /// address. Thus, the least significant bit of the Z-pointer selects either low byte (ZLSB = 0) or high byte (ZLSB 1205 | /// = 1). This instruction can address the entire Program memory space. The Z-pointer Register can either be 1206 | /// left unchanged by the operation, or it can be incremented. The incrementation applies to the entire 24-bit 1207 | /// concatenation of the RAMPZ and Z-pointer Registers. 1208 | /// Devices with Self-Programming capability can use the ELPM instruction to read the Fuse and Lock bit 1209 | /// value. Refer to the device documentation for a detailed description. 1210 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 1211 | /// The result of these combinations is undefined: 1212 | /// ELPM r30, Z+ 1213 | /// ELPM r31, Z+ 1214 | inline fn generic_lpm(cpu: *Cpu, d: isa.Register, mode: IndexOpMode, size: enum { regular, extended }) void { 1215 | const addr = compute_and_mutate_index(cpu, .z, 0, .{ 1216 | .op = mode, 1217 | .wb = switch (size) { 1218 | .regular => .raw, 1219 | .extended => .ramp, 1220 | }, 1221 | .rd = switch (size) { 1222 | .regular => .raw, 1223 | .extended => .ramp, 1224 | }, 1225 | }); 1226 | const word = cpu.flash.read(addr >> 1); 1227 | cpu.regs[d.num()] = if ((addr & 1) != 0) 1228 | @truncate(word >> 8) 1229 | else 1230 | @truncate(word >> 0); 1231 | } 1232 | 1233 | inline fn lpm_i(cpu: *Cpu) void { 1234 | // R0 ← (Z) 1235 | return generic_lpm(cpu, .r0, .none, .regular); 1236 | } 1237 | 1238 | inline fn lpm_ii(cpu: *Cpu, info: isa.opinfo.d5) void { 1239 | // Rd ← (Z) 1240 | return generic_lpm(cpu, info.d, .none, .regular); 1241 | } 1242 | 1243 | inline fn lpm_iii(cpu: *Cpu, info: isa.opinfo.d5) void { 1244 | // Rd ← (Z) Z ← Z + 1 1245 | return generic_lpm(cpu, info.d, .post_incr, .regular); 1246 | } 1247 | 1248 | inline fn elpm_i(cpu: *Cpu) void { 1249 | // R0 ← (RAMPZ:Z) 1250 | return generic_lpm(cpu, .r0, .none, .extended); 1251 | } 1252 | 1253 | inline fn elpm_ii(cpu: *Cpu, info: isa.opinfo.d5) void { 1254 | // Rd ← (RAMPZ:Z) 1255 | return generic_lpm(cpu, info.d, .none, .extended); 1256 | } 1257 | 1258 | inline fn elpm_iii(cpu: *Cpu, info: isa.opinfo.d5) void { 1259 | // Rd ← (RAMPZ:Z), (RAMPZ:Z) ← (RAMPZ:Z) + 1 1260 | return generic_lpm(cpu, info.d, .post_incr, .extended); 1261 | } 1262 | 1263 | /// POP – Pop Register from Stack 1264 | /// This instruction loads register Rd with a byte from the STACK. The Stack Pointer is pre-incremented by 1 1265 | /// before the POP. 1266 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 1267 | inline fn pop(cpu: *Cpu, info: isa.opinfo.d5) void { 1268 | // Rd ← STACK 1269 | cpu.regs[info.d.num()] = cpu.pop(); 1270 | } 1271 | 1272 | // Stores: 1273 | 1274 | inline fn generic_indexed_store(cpu: *Cpu, comptime wr: WideReg, r: isa.Register, q: u6, comptime mode: IndexOpMode) void { 1275 | const address = compute_and_mutate_index(cpu, wr, q, .{ 1276 | .op = mode, 1277 | .rd = .ramp, 1278 | .wb = .ramp, 1279 | }); 1280 | cpu.sram.write(address, cpu.regs[r.num()]); 1281 | } 1282 | 1283 | /// ST – Store Indirect From Register to Data Space using Index X 1284 | inline fn stx_i(cpu: *Cpu, info: isa.opinfo.r5) void { 1285 | // (X) ← Rr 1286 | generic_indexed_store(cpu, .x, info.r, 0, .none); 1287 | } 1288 | 1289 | /// ST – Store Indirect From Register to Data Space using Index X 1290 | inline fn stx_ii(cpu: *Cpu, info: isa.opinfo.r5) void { 1291 | // (X) ← Rr, X ← X+1 1292 | generic_indexed_store(cpu, .x, info.r, 0, .post_incr); 1293 | } 1294 | 1295 | /// ST – Store Indirect From Register to Data Space using Index X 1296 | inline fn stx_iii(cpu: *Cpu, info: isa.opinfo.r5) void { 1297 | // (iii) X ← X - 1, (X) ← Rr 1298 | generic_indexed_store(cpu, .x, info.r, 0, .pre_decr); 1299 | } 1300 | 1301 | // sty_i is sty_iv with q=0 1302 | 1303 | /// ST (STD) – Store Indirect From Register to Data Space using Index Y 1304 | inline fn sty_ii(cpu: *Cpu, info: isa.opinfo.r5) void { 1305 | // (Y) ← Rr, Y ← Y+1 1306 | generic_indexed_store(cpu, .y, info.r, 0, .none); 1307 | } 1308 | 1309 | /// ST (STD) – Store Indirect From Register to Data Space using Index Y 1310 | inline fn sty_iii(cpu: *Cpu, info: isa.opinfo.r5) void { 1311 | // (iii) Y ← Y - 1, (Y) ← Rr 1312 | generic_indexed_store(cpu, .y, info.r, 0, .post_incr); 1313 | } 1314 | 1315 | /// ST (STD) – Store Indirect From Register to Data Space using Index Y 1316 | inline fn sty_iv(cpu: *Cpu, info: isa.opinfo.q6r5) void { 1317 | // (iv) (Y+q) ← Rr 1318 | generic_indexed_store(cpu, .y, info.r, info.q, .displace); 1319 | } 1320 | 1321 | // stz_i is stz_iv with q=0 1322 | 1323 | /// ST (STD) – Store Indirect From Register to Data Space using Index Z 1324 | inline fn stz_ii(cpu: *Cpu, info: isa.opinfo.r5) void { 1325 | // (Z) ← Rr, Z ← Z+1 1326 | generic_indexed_store(cpu, .z, info.r, 0, .none); 1327 | } 1328 | 1329 | /// ST (STD) – Store Indirect From Register to Data Space using Index Z 1330 | inline fn stz_iii(cpu: *Cpu, info: isa.opinfo.r5) void { 1331 | // (iii) Z ← Z - 1, (Z) ← Rr 1332 | generic_indexed_store(cpu, .z, info.r, 0, .post_incr); 1333 | } 1334 | 1335 | /// ST (STD) – Store Indirect From Register to Data Space using Index Z 1336 | inline fn stz_iv(cpu: *Cpu, info: isa.opinfo.q6r5) void { 1337 | // (iv) (Z+q) ← Rr 1338 | generic_indexed_store(cpu, .z, info.r, info.q, .displace); 1339 | } 1340 | 1341 | /// SPM – Store Program Memory 1342 | /// 1343 | /// SPM can be used to erase a page in the Program memory, to write a page in the Program memory (that 1344 | /// is already erased), and to set Boot Loader Lock bits. In some devices, the Program memory can be 1345 | /// written one word at a time, in other devices an entire page can be programmed simultaneously after first 1346 | /// filling a temporary page buffer. In all cases, the Program memory must be erased one page at a time. 1347 | /// When erasing the Program memory, the RAMPZ and Z-register are used as page address. When writing 1348 | /// the Program memory, the RAMPZ and Z-register are used as page or word address, and the R1:R0 1349 | /// register pair is used as data(1). When setting the Boot Loader Lock bits, the R1:R0 register pair is used as 1350 | /// data. Refer to the device documentation for detailed description of SPM usage. This instruction can 1351 | /// address the entire Program memory. 1352 | /// The SPM instruction is not available in all devices. Refer to the device specific instruction set summary. 1353 | /// 1354 | /// **NOTE:** 1. R1 determines the instruction high byte, and R0 determines the instruction low byte. 1355 | /// 1356 | ///! TODO! (implement much later, we don't really need it for emulating everything execpt bootloaders) 1357 | inline fn spm_i(cpu: *Cpu) void { 1358 | _ = cpu; 1359 | @panic("spm (i) is not supported."); 1360 | } 1361 | 1362 | /// SPM #2 – Store Program Memory 1363 | /// 1364 | /// SPM can be used to erase a page in the Program memory and to write a page in the Program memory 1365 | /// (that is already erased). An entire page can be programmed simultaneously after first filling a temporary 1366 | /// page buffer. The Program memory must be erased one page at a time. When erasing the Program 1367 | /// memory, the RAMPZ and Z-register are used as page address. When writing the Program memory, the 1368 | /// RAMPZ and Z-register are used as page or word address, and the R1:R0 register pair is used as data(1). 1369 | /// Refer to the device documentation for detailed description of SPM usage. This instruction can address 1370 | /// the entire Program memory. 1371 | /// 1372 | /// **NOTE:** 1. R1 determines the instruction high byte, and R0 determines the instruction low byte. 1373 | /// 1374 | ///! TODO! (implement much later, we don't really need it for emulating everything execpt bootloaders) 1375 | inline fn spm_ii(cpu: *Cpu) void { 1376 | _ = cpu; 1377 | @panic("spm #2 is not supported."); 1378 | } 1379 | 1380 | /// STS – Store Direct to Data Space 1381 | /// 1382 | /// Stores one byte from a Register to the data space. For parts with SRAM, the data space consists of the 1383 | /// Register File, I/O memory, and internal SRAM (and external SRAM if applicable). For parts without 1384 | /// SRAM, the data space consists of the Register File only. The EEPROM has a separate address space. 1385 | /// A 16-bit address must be supplied. Memory access is limited to the current data segment of 64KB. The 1386 | /// STS instruction uses the RAMPD Register to access memory above 64KB. To access another data 1387 | /// segment in devices with more than 64KB data space, the RAMPD in register in the I/O area has to be 1388 | /// changed. 1389 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 1390 | /// 1391 | /// NOTE: 32 bit instruction! 1392 | inline fn sts(cpu: *Cpu, info: isa.opinfo.d5) void { 1393 | // (k) ← Rr 1394 | const addr = cpu.extendDirectAddress(cpu.fetchCode()); 1395 | cpu.sram.write(addr, cpu.regs[info.d.num()]); 1396 | } 1397 | 1398 | /// PUSH – Push Register on Stack 1399 | /// This instruction stores the contents of register Rr on the STACK. The Stack Pointer is post-decremented 1400 | /// by 1 after the PUSH. 1401 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 1402 | inline fn push(cpu: *Cpu, info: isa.opinfo.d5) void { 1403 | // STACK ← Rr 1404 | cpu.push(cpu.regs[info.d.num()]); 1405 | } 1406 | 1407 | // Status Register 1408 | 1409 | /// BCLR – Bit Clear in SREG 1410 | /// Clears a single Flag in SREG. 1411 | inline fn bclr(cpu: *Cpu, info: isa.opinfo.s3) void { 1412 | // SREG(s) ← 0 1413 | cpu.sreg.writeBit(info.s, false); 1414 | } 1415 | 1416 | /// BSET – Bit Set in SREG 1417 | /// Sets a single Flag or bit in SREG. 1418 | inline fn bset(cpu: *Cpu, info: isa.opinfo.s3) void { 1419 | // SREG(s) ← 1 1420 | cpu.sreg.writeBit(info.s, true); 1421 | } 1422 | 1423 | /// BLD – Bit Load from the T Flag in SREG to a Bit in Register 1424 | /// Copies the T Flag in the SREG (Status Register) to bit b in register Rd. 1425 | inline fn bld(cpu: *Cpu, info: isa.opinfo.b3d5) void { 1426 | // Rd(b) ← T 1427 | changeBit(&cpu.regs[info.d.num()], info.b.num(), cpu.sreg.t); 1428 | } 1429 | 1430 | /// BST – Bit Store from Bit in Register to T Flag in SREG 1431 | /// Stores bit b from Rd to the T Flag in SREG (Status Register). 1432 | inline fn bst(cpu: *Cpu, info: isa.opinfo.b3d5) void { 1433 | // T ← Rd(b) 1434 | cpu.sreg.t = (cpu.regs[info.d.num()] & info.b.mask()) != 0; 1435 | } 1436 | 1437 | // Others: 1438 | 1439 | /// BREAK – Break 1440 | /// The BREAK instruction is used by the On-chip Debug system, and is normally not used in the application 1441 | /// software. When the BREAK instruction is executed, the AVR CPU is set in the Stopped Mode. This gives 1442 | /// the On-chip Debugger access to internal resources. 1443 | /// If any Lock bits are set, or either the JTAGEN or OCDEN Fuses are unprogrammed, the CPU will treat 1444 | /// the BREAK instruction as a NOP and will not enter the Stopped mode. 1445 | /// This instruction is not available in all devices. Refer to the device specific instruction set summary. 1446 | inline fn @"break"(cpu: *Cpu) void { 1447 | // On-chip Debug system break. 1448 | cpu.instr_effect = .breakpoint; 1449 | } 1450 | 1451 | /// DES – Data Encryption Standard 1452 | /// 1453 | /// Tshe module is an instruction set extension to the AVR CPU, performing DES iterations. The 64-bit data 1454 | /// block (plaintext or ciphertext) is placed in the CPU register file, registers R0-R7, where LSB of data is 1455 | /// placed in LSB of R0 and MSB of data is placed in MSB of R7. The full 64-bit key (including parity bits) is 1456 | /// placed in registers R8-R15, organized in the register file with LSB of key in LSB of R8 and MSB of key in 1457 | /// MSB of R15. Executing one DES instruction performs one round in the DES algorithm. Sixteen rounds 1458 | /// must be executed in increasing order to form the correct DES ciphertext or plaintext. Intermediate results 1459 | /// are stored in the register file (R0-R15) after each DES instruction. The instruction's operand (K) 1460 | /// determines which round is executed, and the half carry flag (H) determines whether encryption or 1461 | /// decryption is performed. 1462 | /// The DES algorithm is described in “Specifications for the Data Encryption Standard” (Federal Information 1463 | /// Processing Standards Publication 46). Intermediate results in this implementation differ from the standard 1464 | /// because the initial permutation and the inverse initial permutation are performed in each iteration. This 1465 | /// does not affect the result in the final ciphertext or plaintext, but reduces the execution time. 1466 | /// 1467 | /// TODO! (Not necessarily required for implementation, very weird use case) 1468 | inline fn des(cpu: *Cpu, info: isa.opinfo.k4) void { 1469 | _ = cpu; 1470 | _ = info; 1471 | @panic("TODO: Implement DES instruction!"); 1472 | } 1473 | 1474 | /// NOP – No Operation 1475 | /// This instruction performs a single cycle No Operation. 1476 | inline fn nop(cpu: *Cpu) void { 1477 | _ = cpu; 1478 | } 1479 | 1480 | /// SLEEP 1481 | /// This instruction sets the circuit in sleep mode defined by the MCU Control Register. 1482 | inline fn sleep(cpu: *Cpu) void { 1483 | // Refer to the device documentation for detailed description of SLEEP usage. 1484 | cpu.instr_effect = .sleep; 1485 | } 1486 | 1487 | /// WDR – Watchdog Reset 1488 | /// This instruction resets the Watchdog Timer. This instruction must be executed within a limited time given 1489 | /// by the WD prescaler. See the Watchdog Timer hardware specification. 1490 | inline fn wdr(cpu: *Cpu) void { 1491 | // WD timer restart. 1492 | cpu.instr_effect = .watchdog_reset; 1493 | } 1494 | }; 1495 | 1496 | pub const SREG = packed struct(u8) { 1497 | /// Carry Flag 1498 | /// This flag is set when there is a carry in an arithmetic or logic operation, and is cleared otherwise. 1499 | c: bool, 1500 | 1501 | /// Zero Flag 1502 | /// This flag is set when there is a zero result in an arithmetic or logic operation, and is cleared otherwise. 1503 | z: bool, 1504 | 1505 | /// Negative Flag 1506 | /// This flag is set when there is a negative result in an arithmetic or logic operation, and is cleared otherwise. 1507 | n: bool, 1508 | 1509 | /// Two’s Complement Overflow Flag 1510 | /// This flag is set when there is an overflow in arithmetic operations that support this, and is cleared otherwise 1511 | v: bool, 1512 | 1513 | /// Sign Flag 1514 | /// This flag is always an Exclusive Or (XOR) between the Negative flag (N) and the Two’s Complement Overflow flag (V). 1515 | s: bool, 1516 | 1517 | /// Half Carry Flag 1518 | /// This flag is set when there is a half carry in arithmetic operations that support this, and is cleared otherwise. Half 1519 | /// carry is useful in BCD arithmetic. 1520 | h: bool, 1521 | 1522 | /// Transfer Bit 1523 | /// The bit copy instructions, Bit Load (BLD) and Bit Store (BST), use the T bit as source or destination for the operated 1524 | /// bit. 1525 | t: bool, 1526 | 1527 | /// Global Interrupt Enable 1528 | /// Writing a ‘1’ to this bit enables interrupts on the device. 1529 | /// Writing a ‘0’ to this bit disables interrupts on the device, independent of the individual interrupt enable settings of the 1530 | /// peripherals. 1531 | /// This bit is not cleared by hardware while entering an Interrupt Service Routine (ISR) or set when the RETI instruction 1532 | /// is executed. 1533 | /// This bit can be set and cleared by software with the SEI and CLI instructions. 1534 | /// Changing the I bit through the I/O register results in a one-cycle Wait state on the access. 1535 | i: bool, 1536 | 1537 | pub fn readBit(sreg: SREG, bit: isa.StatusRegisterBit) bool { 1538 | const val: u8 = @bitCast(sreg); 1539 | return (bit.mask() & val) != 0; 1540 | } 1541 | 1542 | pub fn writeBit(sreg: *SREG, bit: isa.StatusRegisterBit, value: bool) void { 1543 | var val: u8 = @bitCast(sreg.*); 1544 | changeBit(&val, bit.num(), value); 1545 | sreg.* = @bitCast(val); 1546 | } 1547 | 1548 | pub fn format(sreg: SREG, fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { 1549 | _ = opt; 1550 | _ = fmt; 1551 | try writer.print("[{c}{c}{c}{c}{c}{c}{c}{c}]", .{ 1552 | if (sreg.c) @as(u8, 'C') else '-', 1553 | if (sreg.z) @as(u8, 'Z') else '-', 1554 | if (sreg.n) @as(u8, 'N') else '-', 1555 | if (sreg.v) @as(u8, 'V') else '-', 1556 | if (sreg.s) @as(u8, 'S') else '-', 1557 | if (sreg.h) @as(u8, 'H') else '-', 1558 | if (sreg.t) @as(u8, 'T') else '-', 1559 | if (sreg.i) @as(u8, 'I') else '-', 1560 | }); 1561 | } 1562 | }; 1563 | 1564 | const Bits8 = packed struct(u8) { 1565 | b0: bool, 1566 | b1: bool, 1567 | b2: bool, 1568 | b3: bool, 1569 | b4: bool, 1570 | b5: bool, 1571 | b6: bool, 1572 | b7: bool, 1573 | }; 1574 | 1575 | inline fn bval(b: u3) u8 { 1576 | return @as(u8, 1) << b; 1577 | } 1578 | 1579 | inline fn changeBit(dst: *u8, bit: u3, val: bool) void { 1580 | if (val) { 1581 | dst.* |= bval(bit); 1582 | } else { 1583 | dst.* &= ~bval(bit); 1584 | } 1585 | } 1586 | 1587 | pub const SpecialIoRegisters = struct { 1588 | /// Register concatenated with the X-registers enabling indirect addressing of the whole data 1589 | /// space on MCUs with more than 64KB data space, and constant data fetch on MCUs with more than 1590 | /// 64KB program space. 1591 | ramp_x: ?IO.Address, 1592 | 1593 | /// Register concatenated with the Y-registers enabling indirect addressing of the whole data 1594 | /// space on MCUs with more than 64KB data space, and constant data fetch on MCUs with more than 1595 | /// 64KB program space. 1596 | ramp_y: ?IO.Address, 1597 | 1598 | /// Register concatenated with the Z-registers enabling indirect addressing of the whole data 1599 | /// space on MCUs with more than 64KB data space, and constant data fetch on MCUs with more than 1600 | /// 64KB program space. 1601 | ramp_z: ?IO.Address, 1602 | 1603 | /// Register concatenated with the Z-register enabling direct addressing of the whole data space on MCUs 1604 | /// with more than 64KB data space. 1605 | ramp_d: ?IO.Address, 1606 | 1607 | /// Register concatenated with the Z-register enabling indirect jump and call to the whole program space on 1608 | /// MCUs with more than 64K words (128KB) program space. 1609 | e_ind: ?IO.Address, 1610 | 1611 | sp_l: IO.Address, // SP[7:0] 1612 | sp_h: IO.Address, // SP[15:8] 1613 | 1614 | sreg: IO.Address, 1615 | }; 1616 | 1617 | fn extendDirectAddress(cpu: *Cpu, value: u16) u24 { 1618 | return value | if (cpu.sio.ramp_d) |ramp_d| 1619 | @as(u24, cpu.io.read(ramp_d)) << 16 1620 | else 1621 | 0; 1622 | } 1623 | 1624 | fn getSP(cpu: *Cpu) u16 { 1625 | const lo = cpu.io.read(cpu.sio.sp_l); 1626 | const hi = cpu.io.read(cpu.sio.sp_h); 1627 | 1628 | return (@as(u16, hi) << 8) | lo; 1629 | } 1630 | 1631 | fn setSP(cpu: *Cpu, value: u16) void { 1632 | const lo: u8 = @truncate(value >> 0); 1633 | const hi: u8 = @truncate(value >> 8); 1634 | 1635 | cpu.io.write(cpu.sio.sp_l, lo); 1636 | cpu.io.write(cpu.sio.sp_h, hi); 1637 | } 1638 | 1639 | fn compose24(hi: u8, mid: u8, lo: u8) u24 { 1640 | return (@as(u24, hi) << 16) | 1641 | (@as(u24, mid) << 8) | 1642 | (@as(u24, lo) << 0); 1643 | } 1644 | 1645 | fn decompose24(value: u24) [3]u8 { 1646 | return [3]u8{ 1647 | @truncate(value >> 0), 1648 | @truncate(value >> 8), 1649 | @truncate(value >> 16), 1650 | }; 1651 | } 1652 | 1653 | fn compose16(hi: u8, lo: u8) u16 { 1654 | return (@as(u16, hi) << 8) | (@as(u16, lo) << 0); 1655 | } 1656 | 1657 | fn decompose16(value: u16) [2]u8 { 1658 | return [2]u8{ 1659 | @truncate(value >> 0), 1660 | @truncate(value >> 8), 1661 | }; 1662 | } 1663 | 1664 | fn fmtInstruction(inst: isa.Instruction) std.fmt.Formatter(formatInstruction) { 1665 | return .{ .data = inst }; 1666 | } 1667 | 1668 | fn formatInstruction(inst: isa.Instruction, fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { 1669 | _ = opt; 1670 | _ = fmt; 1671 | try writer.print(" {s: <8}", .{@tagName(inst)}); 1672 | 1673 | switch (inst) { 1674 | inline else => |args| { 1675 | const T = @TypeOf(args); 1676 | if (T != void) { 1677 | const info = @typeInfo(T).@"struct"; 1678 | 1679 | inline for (info.fields, 0..) |fld, i| { 1680 | if (i > 0) { 1681 | try writer.writeAll(", "); 1682 | } 1683 | try writer.print("{s}={}", .{ fld.name, @field(args, fld.name) }); 1684 | } 1685 | } 1686 | }, 1687 | } 1688 | } 1689 | -------------------------------------------------------------------------------- /src/lib/aviron.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const io = @import("io.zig"); 4 | const isa = @import("isa"); 5 | 6 | pub const Cpu = @import("Cpu.zig"); 7 | 8 | pub const Flash = io.Flash; 9 | pub const RAM = io.RAM; 10 | pub const EEPROM = io.EEPROM; 11 | pub const IO = io.IO; 12 | 13 | pub const Register = isa.Register; 14 | -------------------------------------------------------------------------------- /src/lib/decoder.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const isa = @import("isa"); 3 | const tables = @import("autogen-tables"); 4 | 5 | pub const Opcode = isa.Opcode; 6 | pub const Instruction = tables.Instruction; 7 | pub const opinfo = isa.opinfo; 8 | 9 | pub const Register4 = isa.Register4; 10 | pub const Register = isa.Register; 11 | pub const DataBit = isa.DataBit; 12 | pub const StatusRegisterBit = isa.StatusRegisterBit; 13 | 14 | /// Only decodes one instruction; make sure to decode the second word for lds, sts, jmp, call 15 | pub fn decode(inst_val: u16) !Instruction { 16 | const lookup: []const isa.Opcode = &tables.lookup; 17 | const opcode = lookup[inst_val]; 18 | switch (opcode) { 19 | inline else => |tag| { 20 | @setEvalBranchQuota(10_000); 21 | 22 | const Result = std.meta.FieldType(Instruction, tag); 23 | if (Result == void) { 24 | return @unionInit(Instruction, @tagName(tag), {}); 25 | } 26 | 27 | const positionals = @field(tables.positionals, @tagName(tag)); 28 | const Bitsets = std.meta.Tuple(types: { 29 | var bs_types: [positionals.len]type = undefined; 30 | inline for (positionals, 0..) |pos, i| { 31 | bs_types[i] = std.bit_set.IntegerBitSet(pos[1].len); 32 | } 33 | break :types &bs_types; 34 | }); 35 | 36 | var bitsets: Bitsets = undefined; 37 | inline for (&bitsets) |*bs| { 38 | bs.* = @TypeOf(bs.*).initEmpty(); 39 | } 40 | 41 | var bitset = std.bit_set.IntegerBitSet(16).initEmpty(); 42 | bitset.mask = @bitReverse(inst_val); 43 | 44 | var result: Result = undefined; 45 | inline for (positionals, 0..) |pos, i| { 46 | inline for (pos[1], 0..) |p, ii| { 47 | bitsets[i].setValue(ii, bitset.isSet(p)); 48 | } 49 | const name = &[1]u8{pos[0]}; 50 | 51 | const raw = @bitReverse(bitsets[i].mask); 52 | 53 | const Dst = @TypeOf(@field(result, name)); 54 | @field(result, name) = switch (@typeInfo(Dst)) { 55 | .@"enum" => @enumFromInt(raw), 56 | else => raw, 57 | }; 58 | } 59 | 60 | return @unionInit(Instruction, @tagName(tag), result); 61 | }, 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/lib/io.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const isa = @import("decoder.zig"); 3 | 4 | pub const Flash = struct { 5 | pub const Address = u24; 6 | 7 | ctx: ?*anyopaque, 8 | vtable: *const VTable, 9 | 10 | /// Size of the flash memory in bytes. Is always 2-aligned. 11 | size: usize, 12 | 13 | pub fn read(mem: Flash, addr: Address) u16 { 14 | std.debug.assert(addr < mem.size); 15 | return mem.vtable.readFn(mem.ctx, addr); 16 | } 17 | 18 | pub const VTable = struct { 19 | readFn: *const fn (ctx: ?*anyopaque, addr: Address) u16, 20 | }; 21 | 22 | pub const empty = Flash{ 23 | .ctx = null, 24 | .size = 0, 25 | .vtable = &VTable{ .readFn = emptyRead }, 26 | }; 27 | 28 | fn emptyRead(ctx: ?*anyopaque, addr: Address) u16 { 29 | _ = addr; 30 | _ = ctx; 31 | return 0; 32 | } 33 | 34 | pub fn Static(comptime size: comptime_int) type { 35 | if ((size & 1) != 0) 36 | @compileError("size must be a multiple of two!"); 37 | return struct { 38 | const Self = @This(); 39 | 40 | data: [size]u8 align(2) = .{0} ** size, 41 | 42 | pub fn memory(self: *Self) Flash { 43 | return Flash{ 44 | .ctx = self, 45 | .vtable = &vtable, 46 | .size = @divExact(size, 2), 47 | }; 48 | } 49 | 50 | pub const vtable = VTable{ .readFn = memRead }; 51 | 52 | fn memRead(ctx: ?*anyopaque, addr: Address) u16 { 53 | const mem: *Self = @ptrCast(@alignCast(ctx.?)); 54 | return std.mem.bytesAsSlice(u16, &mem.data)[addr]; 55 | } 56 | }; 57 | } 58 | }; 59 | 60 | pub const RAM = struct { 61 | pub const Address = u24; 62 | 63 | ctx: ?*anyopaque, 64 | vtable: *const VTable, 65 | 66 | /// Size of the RAM memory space in bytes. 67 | size: usize, 68 | 69 | pub fn read(mem: RAM, addr: Address) u8 { 70 | std.debug.assert(addr < mem.size); 71 | return mem.vtable.readFn(mem.ctx, addr); 72 | } 73 | 74 | pub fn write(mem: RAM, addr: Address, value: u8) void { 75 | std.debug.assert(addr < mem.size); 76 | return mem.vtable.writeFn(mem.ctx, addr, value); 77 | } 78 | 79 | pub const VTable = struct { 80 | readFn: *const fn (ctx: ?*anyopaque, addr: Address) u8, 81 | writeFn: *const fn (ctx: ?*anyopaque, addr: Address, value: u8) void, 82 | }; 83 | 84 | pub const empty = RAM{ 85 | .ctx = null, 86 | .size = 0, 87 | .vtable = &VTable{ .readFn = emptyRead, .writeFn = emptyWrite }, 88 | }; 89 | 90 | fn emptyRead(ctx: ?*anyopaque, addr: u16) u8 { 91 | _ = addr; 92 | _ = ctx; 93 | return 0; 94 | } 95 | 96 | fn emptyWrite(ctx: ?*anyopaque, addr: u16, value: u8) void { 97 | _ = value; 98 | _ = addr; 99 | _ = ctx; 100 | } 101 | 102 | pub fn Static(comptime size: comptime_int) type { 103 | return struct { 104 | const Self = @This(); 105 | 106 | data: [size]u8 align(2) = .{0} ** size, 107 | 108 | pub fn memory(self: *Self) RAM { 109 | return RAM{ 110 | .ctx = self, 111 | .vtable = &vtable, 112 | .size = size, 113 | }; 114 | } 115 | 116 | pub const vtable = VTable{ 117 | .readFn = memRead, 118 | .writeFn = memWrite, 119 | }; 120 | 121 | fn memRead(ctx: ?*anyopaque, addr: Address) u8 { 122 | const mem: *Self = @ptrCast(@alignCast(ctx.?)); 123 | return mem.data[addr]; 124 | } 125 | 126 | fn memWrite(ctx: ?*anyopaque, addr: Address, value: u8) void { 127 | const mem: *Self = @ptrCast(@alignCast(ctx.?)); 128 | mem.data[addr] = value; 129 | } 130 | }; 131 | } 132 | }; 133 | 134 | pub const EEPROM = RAM; // actually the same interface *shrug* 135 | 136 | pub const IO = struct { 137 | pub const Address = u6; 138 | 139 | ctx: ?*anyopaque, 140 | 141 | /// Size of the EEPROM in bytes. 142 | vtable: *const VTable, 143 | 144 | pub fn read(mem: IO, addr: Address) u8 { 145 | return mem.vtable.readFn(mem.ctx, addr); 146 | } 147 | 148 | pub fn write(mem: IO, addr: Address, value: u8) void { 149 | return mem.writeMasked(addr, 0xFF, value); 150 | } 151 | 152 | /// `mask` determines which bits of `value` are written. To write everything, use `0xFF` for `mask`. 153 | pub fn writeMasked(mem: IO, addr: Address, mask: u8, value: u8) void { 154 | return mem.vtable.writeFn(mem.ctx, addr, mask, value); 155 | } 156 | 157 | pub const VTable = struct { 158 | readFn: *const fn (ctx: ?*anyopaque, addr: Address) u8, 159 | writeFn: *const fn (ctx: ?*anyopaque, addr: Address, mask: u8, value: u8) void, 160 | }; 161 | 162 | pub const empty = IO{ 163 | .ctx = null, 164 | .vtable = &VTable{ .readFn = emptyRead, .writeFn = emptyWrite }, 165 | }; 166 | 167 | fn emptyRead(ctx: ?*anyopaque, addr: Address) u8 { 168 | _ = addr; 169 | _ = ctx; 170 | return 0; 171 | } 172 | 173 | fn emptyWrite(ctx: ?*anyopaque, addr: Address, mask: u8, value: u8) void { 174 | _ = mask; 175 | _ = value; 176 | _ = addr; 177 | _ = ctx; 178 | } 179 | }; 180 | -------------------------------------------------------------------------------- /src/libtestsuite/lib.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub inline fn exit(code: u8) noreturn { 4 | asm volatile ( 5 | \\out 0x00, %[code] 6 | : 7 | : [code] "r" (code), 8 | ); 9 | 10 | unreachable; 11 | } 12 | 13 | pub inline fn assert(b: bool) void { 14 | if (!b) 15 | exit(1); 16 | } 17 | 18 | pub const Channel = enum(u8) { 19 | stdout = 0x01, 20 | stderr = 0x02, 21 | }; 22 | 23 | pub inline fn write(comptime chan: Channel, string: []const u8) void { 24 | for (string) |c| { 25 | asm volatile ( 26 | \\out %[port], %[code] 27 | : 28 | : [code] "r" (c), 29 | [port] "i" (@intFromEnum(chan)), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const aviron = @import("aviron"); 4 | const args_parser = @import("args"); 5 | const ihex = @import("ihex"); 6 | 7 | pub fn main() !u8 { 8 | const allocator = std.heap.page_allocator; 9 | 10 | var cli = args_parser.parseForCurrentProcess(Cli, allocator, .print) catch return 1; 11 | defer cli.deinit(); 12 | 13 | if (cli.options.help or (cli.positionals.len == 0 and !cli.options.info)) { 14 | var stderr = std.io.getStdErr(); 15 | 16 | try args_parser.printHelp( 17 | Cli, 18 | cli.executable_name orelse "aviron", 19 | stderr.writer(), 20 | ); 21 | 22 | return if (cli.options.help) @as(u8, 0) else 1; 23 | } 24 | 25 | // Emulate Atmega382p device size: 26 | 27 | // TODO: Add support for more MCUs! 28 | std.debug.assert(cli.options.mcu == .atmega328p); 29 | 30 | var flash_storage = aviron.Flash.Static(32768){}; 31 | var sram = aviron.RAM.Static(2048){}; 32 | var eeprom = aviron.EEPROM.Static(1024){}; 33 | var io = IO{ 34 | .sreg = undefined, 35 | .sp = sram.data.len - 1, 36 | }; 37 | 38 | var cpu = aviron.Cpu{ 39 | .trace = cli.options.trace, 40 | 41 | .flash = flash_storage.memory(), 42 | .sram = sram.memory(), 43 | .eeprom = eeprom.memory(), 44 | .io = io.memory(), 45 | 46 | .code_model = .code16, 47 | .instruction_set = .avr5, 48 | 49 | .sio = .{ 50 | .ramp_x = null, 51 | .ramp_y = null, 52 | .ramp_z = null, 53 | .ramp_d = null, 54 | .e_ind = null, 55 | 56 | .sp_l = @intFromEnum(IO.Register.sp_l), 57 | .sp_h = @intFromEnum(IO.Register.sp_h), 58 | 59 | .sreg = @intFromEnum(IO.Register.sreg), 60 | }, 61 | }; 62 | 63 | io.sreg = &cpu.sreg; 64 | 65 | if (cli.options.info) { 66 | var stdout = std.io.getStdOut().writer(); 67 | try stdout.print("Information for {s}:\n", .{@tagName(cli.options.mcu)}); 68 | try stdout.print(" Generation: {s: >11}\n", .{@tagName(cpu.instruction_set)}); 69 | try stdout.print(" Code Model: {s: >11}\n", .{@tagName(cpu.code_model)}); 70 | try stdout.print(" Flash: {d: >5} bytes\n", .{cpu.flash.size}); 71 | try stdout.print(" RAM: {d: >5} bytes\n", .{cpu.sram.size}); 72 | try stdout.print(" EEPROM: {d: >5} bytes\n", .{cpu.eeprom.size}); 73 | return 0; 74 | } 75 | 76 | // Load all provided executables: 77 | for (cli.positionals) |file_path| { 78 | var file = try std.fs.cwd().openFile(file_path, .{}); 79 | defer file.close(); 80 | 81 | switch (cli.options.format) { 82 | .elf => { 83 | var source = std.io.StreamSource{ .file = file }; 84 | var header = try std.elf.Header.read(&source); 85 | 86 | var pheaders = header.program_header_iterator(&source); 87 | while (try pheaders.next()) |phdr| { 88 | if (phdr.p_type != std.elf.PT_LOAD) 89 | continue; // Header isn't lodead 90 | 91 | const dest_mem = if (phdr.p_vaddr >= 0x0080_0000) 92 | &sram.data 93 | else 94 | &flash_storage.data; 95 | 96 | const addr_masked: u24 = @intCast(phdr.p_vaddr & 0x007F_FFFF); 97 | 98 | try source.seekTo(phdr.p_offset); 99 | try source.reader().readNoEof(dest_mem[addr_masked..][0..phdr.p_filesz]); 100 | @memset(dest_mem[addr_masked + phdr.p_filesz ..][0 .. phdr.p_memsz - phdr.p_filesz], 0); 101 | } 102 | }, 103 | .binary, .bin => { 104 | const size = try file.readAll(&flash_storage.data); 105 | @memset(flash_storage.data[size..], 0); 106 | }, 107 | .ihex, .hex => { 108 | const ihex_processor = struct { 109 | fn process(flash: *@TypeOf(flash_storage), offset: u32, data: []const u8) !void { 110 | @memcpy(flash.data[offset .. offset + data.len], data); 111 | } 112 | }; 113 | _ = try ihex.parseData(file.reader(), .{ .pedantic = true }, &flash_storage, anyerror, ihex_processor.process); 114 | }, 115 | } 116 | } 117 | 118 | const result = try cpu.run(null); 119 | 120 | std.debug.print("STOP: {s}\n", .{@tagName(result)}); 121 | 122 | return 0; 123 | } 124 | 125 | // not actually marvel cinematic universe, but microcontroller unit ; 126 | pub const MCU = enum { 127 | atmega328p, 128 | }; 129 | 130 | pub const FileFormat = enum { 131 | elf, 132 | bin, 133 | binary, 134 | ihex, 135 | hex, 136 | }; 137 | 138 | const Cli = struct { 139 | help: bool = false, 140 | trace: bool = false, 141 | mcu: MCU = .atmega328p, 142 | info: bool = false, 143 | format: FileFormat = .elf, 144 | 145 | pub const shorthands = .{ 146 | .h = "help", 147 | .t = "trace", 148 | .m = "mcu", 149 | .I = "info", 150 | .f = "format", 151 | }; 152 | pub const meta = .{ 153 | .summary = "[-h] [-t] [-m ] ...", 154 | .full_text = 155 | \\AViRon is a simulator for the AVR cpu architecture as well as an basic emulator for several microcontrollers from Microchip/Atmel. 156 | \\ 157 | \\Loads at least a single file into the memory of the system and executes it with the provided MCU. 158 | \\ 159 | \\The code can use certain special registers to perform I/O and exit the emulator. 160 | , 161 | .option_docs = .{ 162 | .help = "Prints this help text.", 163 | .trace = "Trace all executed instructions.", 164 | .mcu = "Selects the emulated MCU.", 165 | .info = "Prints information about the given MCUs memory.", 166 | .format = "Specify file format.", 167 | }, 168 | }; 169 | }; 170 | 171 | const IO = struct { 172 | scratch_regs: [16]u8 = @splat(0), 173 | 174 | sp: u16, 175 | sreg: *aviron.Cpu.SREG, 176 | 177 | pub fn memory(self: *IO) aviron.IO { 178 | return aviron.IO{ 179 | .ctx = self, 180 | .vtable = &vtable, 181 | }; 182 | } 183 | 184 | pub const vtable = aviron.IO.VTable{ 185 | .readFn = read, 186 | .writeFn = write, 187 | }; 188 | 189 | // This is our own "debug" device with it's own debug addresses: 190 | const Register = enum(u6) { 191 | exit = 0, // read: 0, write: os.exit() 192 | stdio = 1, // read: stdin, write: print to stdout 193 | stderr = 2, // read: 0, write: print to stderr 194 | 195 | scratch_0 = 0x10, // scratch register 196 | scratch_1 = 0x11, // scratch register 197 | scratch_2 = 0x12, // scratch register 198 | scratch_3 = 0x13, // scratch register 199 | scratch_4 = 0x14, // scratch register 200 | scratch_5 = 0x15, // scratch register 201 | scratch_6 = 0x16, // scratch register 202 | scratch_7 = 0x17, // scratch register 203 | scratch_8 = 0x18, // scratch register 204 | scratch_9 = 0x19, // scratch register 205 | scratch_a = 0x1a, // scratch register 206 | scratch_b = 0x1b, // scratch register 207 | scratch_c = 0x1c, // scratch register 208 | scratch_d = 0x1d, // scratch register 209 | scratch_e = 0x1e, // scratch register 210 | scratch_f = 0x1f, // scratch register 211 | 212 | sp_l = 0x3D, // ATmega328p 213 | sp_h = 0x3E, // ATmega328p 214 | sreg = 0x3F, // ATmega328p 215 | 216 | _, 217 | }; 218 | 219 | fn read(ctx: ?*anyopaque, addr: u6) u8 { 220 | const io: *IO = @ptrCast(@alignCast(ctx.?)); 221 | const reg: Register = @enumFromInt(addr); 222 | return switch (reg) { 223 | .exit => 0, 224 | .stdio => std.io.getStdIn().reader().readByte() catch 0xFF, // 0xFF = EOF 225 | .stderr => 0, 226 | 227 | .scratch_0 => io.scratch_regs[0x0], 228 | .scratch_1 => io.scratch_regs[0x1], 229 | .scratch_2 => io.scratch_regs[0x2], 230 | .scratch_3 => io.scratch_regs[0x3], 231 | .scratch_4 => io.scratch_regs[0x4], 232 | .scratch_5 => io.scratch_regs[0x5], 233 | .scratch_6 => io.scratch_regs[0x6], 234 | .scratch_7 => io.scratch_regs[0x7], 235 | .scratch_8 => io.scratch_regs[0x8], 236 | .scratch_9 => io.scratch_regs[0x9], 237 | .scratch_a => io.scratch_regs[0xa], 238 | .scratch_b => io.scratch_regs[0xb], 239 | .scratch_c => io.scratch_regs[0xc], 240 | .scratch_d => io.scratch_regs[0xd], 241 | .scratch_e => io.scratch_regs[0xe], 242 | .scratch_f => io.scratch_regs[0xf], 243 | 244 | .sreg => @bitCast(io.sreg.*), 245 | 246 | .sp_l => @truncate(io.sp >> 0), 247 | .sp_h => @truncate(io.sp >> 8), 248 | 249 | _ => std.debug.panic("illegal i/o read from undefined register 0x{X:0>2}", .{addr}), 250 | }; 251 | } 252 | 253 | /// `mask` determines which bits of `value` are written. To write everything, use `0xFF` for `mask`. 254 | fn write(ctx: ?*anyopaque, addr: u6, mask: u8, value: u8) void { 255 | const io: *IO = @ptrCast(@alignCast(ctx.?)); 256 | const reg: Register = @enumFromInt(addr); 257 | switch (reg) { 258 | .exit => std.process.exit(value & mask), 259 | .stdio => std.io.getStdOut().writer().writeByte(value & mask) catch @panic("i/o failure"), 260 | .stderr => std.io.getStdErr().writer().writeByte(value & mask) catch @panic("i/o failure"), 261 | 262 | .scratch_0 => writeMasked(&io.scratch_regs[0x0], mask, value), 263 | .scratch_1 => writeMasked(&io.scratch_regs[0x1], mask, value), 264 | .scratch_2 => writeMasked(&io.scratch_regs[0x2], mask, value), 265 | .scratch_3 => writeMasked(&io.scratch_regs[0x3], mask, value), 266 | .scratch_4 => writeMasked(&io.scratch_regs[0x4], mask, value), 267 | .scratch_5 => writeMasked(&io.scratch_regs[0x5], mask, value), 268 | .scratch_6 => writeMasked(&io.scratch_regs[0x6], mask, value), 269 | .scratch_7 => writeMasked(&io.scratch_regs[0x7], mask, value), 270 | .scratch_8 => writeMasked(&io.scratch_regs[0x8], mask, value), 271 | .scratch_9 => writeMasked(&io.scratch_regs[0x9], mask, value), 272 | .scratch_a => writeMasked(&io.scratch_regs[0xa], mask, value), 273 | .scratch_b => writeMasked(&io.scratch_regs[0xb], mask, value), 274 | .scratch_c => writeMasked(&io.scratch_regs[0xc], mask, value), 275 | .scratch_d => writeMasked(&io.scratch_regs[0xd], mask, value), 276 | .scratch_e => writeMasked(&io.scratch_regs[0xe], mask, value), 277 | .scratch_f => writeMasked(&io.scratch_regs[0xf], mask, value), 278 | 279 | .sp_l => writeMasked(lobyte(&io.sp), mask, value), 280 | .sp_h => writeMasked(hibyte(&io.sp), mask, value), 281 | .sreg => writeMasked(@ptrCast(io.sreg), mask, value), 282 | 283 | _ => std.debug.panic("illegal i/o write to undefined register 0x{X:0>2} with value=0x{X:0>2}, mask=0x{X:0>2}", .{ addr, value, mask }), 284 | } 285 | } 286 | 287 | fn lobyte(val: *u16) *u8 { 288 | const bits: *[2]u8 = @ptrCast(val); 289 | return switch (comptime builtin.cpu.arch.endian()) { 290 | .big => return &bits[1], 291 | .little => return &bits[0], 292 | }; 293 | } 294 | 295 | fn hibyte(val: *u16) *u8 { 296 | const bits: *[2]u8 = @ptrCast(val); 297 | return switch (comptime builtin.cpu.arch.endian()) { 298 | .big => return &bits[0], 299 | .little => return &bits[1], 300 | }; 301 | } 302 | 303 | fn writeMasked(dst: *u8, mask: u8, val: u8) void { 304 | dst.* &= ~mask; 305 | dst.* |= (val & mask); 306 | } 307 | }; 308 | -------------------------------------------------------------------------------- /src/shared/isa.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const Opcode = enum(u8) { 4 | @"and", 5 | @"break", 6 | @"or", 7 | adc, 8 | add, 9 | adiw, 10 | andi, 11 | asr, 12 | bclr, 13 | bld, 14 | brbc, 15 | brbs, 16 | bset, 17 | bst, 18 | call, 19 | cbi, 20 | com, 21 | cp, 22 | cpc, 23 | cpi, 24 | cpse, 25 | dec, 26 | des, 27 | eicall, 28 | eijmp, 29 | elpm_i, 30 | elpm_ii, 31 | elpm_iii, 32 | eor, 33 | fmul, 34 | fmuls, 35 | fmulsu, 36 | icall, 37 | ijmp, 38 | in, 39 | inc, 40 | jmp, 41 | lac, 42 | las, 43 | lat, 44 | ldi, 45 | lds, 46 | ldx_i, 47 | ldx_ii, 48 | ldx_iii, 49 | // ldy_i is the same as ldy_iv 50 | ldy_ii, 51 | ldy_iii, 52 | ldy_iv, 53 | // ldz_i is the same as ldz_iv 54 | ldz_ii, 55 | ldz_iii, 56 | ldz_iv, 57 | lpm_i, 58 | lpm_ii, 59 | lpm_iii, 60 | lsr, 61 | mov, 62 | movw, 63 | mul, 64 | muls, 65 | mulsu, 66 | neg, 67 | nop, 68 | ori, 69 | out, 70 | pop, 71 | push, 72 | rcall, 73 | ret, 74 | reti, 75 | rjmp, 76 | ror, 77 | sbc, 78 | sbci, 79 | sbi, 80 | sbic, 81 | sbis, 82 | sbiw, 83 | sbrc, 84 | sbrs, 85 | sleep, 86 | spm_i, 87 | spm_ii, 88 | sts, 89 | stx_i, 90 | stx_ii, 91 | stx_iii, 92 | sty_ii, 93 | sty_iii, 94 | sty_iv, 95 | stz_ii, 96 | stz_iii, 97 | stz_iv, 98 | sub, 99 | subi, 100 | swap, 101 | wdr, 102 | xch, 103 | 104 | unknown, 105 | }; 106 | 107 | pub const opinfo = struct { 108 | pub const a6d5 = struct { a: u6, d: Register }; 109 | pub const a6r5 = struct { a: u6, r: Register }; 110 | pub const a5b3 = struct { a: u5, b: DataBit }; 111 | pub const b3d5 = struct { b: DataBit, d: Register }; 112 | pub const b3r5 = struct { b: DataBit, r: Register }; 113 | pub const d5 = struct { d: Register }; 114 | pub const d5k16 = struct { d: Register, k: u16 }; 115 | pub const d5q6 = struct { d: Register, q: u6 }; 116 | pub const d5r5 = struct { d: Register, r: Register }; 117 | pub const d4k8 = struct { d: Register4, k: u8 }; 118 | pub const d4r4 = struct { d: Register4, r: Register4 }; 119 | pub const D4R4 = struct { D: Register4_pair, R: Register4_pair }; 120 | pub const d3r3 = struct { d: Register3, r: Register3 }; 121 | pub const d2k6 = struct { d: u2, k: u6 }; 122 | pub const k4 = struct { k: u4 }; 123 | pub const k6 = struct { k: u6 }; 124 | pub const k12 = struct { k: u12 }; 125 | pub const k22 = struct { k: u22 }; 126 | pub const k7s3 = struct { k: u7, s: StatusRegisterBit }; 127 | pub const q6r5 = struct { q: u6, r: Register }; 128 | pub const r5 = struct { r: Register }; 129 | pub const s3 = struct { s: StatusRegisterBit }; 130 | }; 131 | 132 | pub const Register3 = enum(u3) { 133 | r16 = 0, 134 | r17 = 1, 135 | r18 = 2, 136 | r19 = 3, 137 | r20 = 4, 138 | r21 = 5, 139 | r22 = 6, 140 | r23 = 7, 141 | 142 | pub fn format(r: Register3, fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { 143 | _ = opt; 144 | _ = fmt; 145 | try writer.print("r{}", .{ 146 | 16 + @as(u32, @intFromEnum(r)), 147 | }); 148 | } 149 | 150 | /// Returns the numeric value of this value. 151 | pub fn int(r: Register3) u4 { 152 | return @intFromEnum(r); 153 | } 154 | 155 | /// Returns the register number. 156 | pub fn num(r: Register3) u5 { 157 | return 16 + @as(u5, @intFromEnum(r)); 158 | } 159 | 160 | pub fn reg(r: Register3) Register { 161 | return @enumFromInt(r.num()); 162 | } 163 | }; 164 | 165 | pub const Register4 = enum(u4) { 166 | r16 = 0, 167 | r17 = 1, 168 | r18 = 2, 169 | r19 = 3, 170 | r20 = 4, 171 | r21 = 5, 172 | r22 = 6, 173 | r23 = 7, 174 | r24 = 8, 175 | r25 = 9, 176 | r26 = 10, 177 | r27 = 11, 178 | r28 = 12, 179 | r29 = 13, 180 | r30 = 14, 181 | r31 = 15, 182 | 183 | pub fn format(reg4: Register4, fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { 184 | _ = opt; 185 | _ = fmt; 186 | try writer.print("r{}", .{ 187 | 16 + @as(u32, @intFromEnum(reg4)), 188 | }); 189 | } 190 | 191 | /// Returns the numeric value of this value. 192 | pub fn int(r4: Register4) u4 { 193 | return @intFromEnum(r4); 194 | } 195 | 196 | /// Returns the register number. 197 | pub fn num(r4: Register4) u5 { 198 | return 16 + @as(u5, @intFromEnum(r4)); 199 | } 200 | 201 | pub fn reg(r4: Register4) Register { 202 | return @enumFromInt(r4.num()); 203 | } 204 | }; 205 | 206 | pub const Register4_pair = enum(u4) { 207 | r1_r0 = 0, 208 | r3_r2 = 1, 209 | r5_r4 = 2, 210 | r7_r6 = 3, 211 | r9_r8 = 4, 212 | r11_r10 = 5, 213 | r13_r12 = 6, 214 | r15_r14 = 7, 215 | r17_r16 = 8, 216 | r19_r18 = 9, 217 | r21_r20 = 10, 218 | r23_r22 = 11, 219 | r25_r24 = 12, 220 | r27_r26 = 13, 221 | r29_r28 = 14, 222 | r31_r30 = 15, 223 | 224 | pub fn format(reg4: Register4_pair, fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { 225 | _ = opt; 226 | _ = fmt; 227 | try writer.print("r{}:r{}", .{ 228 | @as(u32, @intFromEnum(reg4)) * 2 + 1, 229 | @as(u32, @intFromEnum(reg4)) * 2, 230 | }); 231 | } 232 | 233 | /// Returns the numeric value of this value. 234 | pub fn int(r4: Register4_pair) u4 { 235 | return @intFromEnum(r4); 236 | } 237 | 238 | /// Returns the lower register number of the pair 239 | pub fn num(r4: Register4_pair) u5 { 240 | return @as(u5, @intFromEnum(r4)) * 2; 241 | } 242 | 243 | pub fn reg(r4: Register4_pair) Register { 244 | return @enumFromInt(r4.num()); 245 | } 246 | }; 247 | 248 | pub const Register = enum(u5) { 249 | r0 = 0, 250 | r1 = 1, 251 | r2 = 2, 252 | r3 = 3, 253 | r4 = 4, 254 | r5 = 5, 255 | r6 = 6, 256 | r7 = 7, 257 | r8 = 8, 258 | r9 = 9, 259 | r10 = 10, 260 | r11 = 11, 261 | r12 = 12, 262 | r13 = 13, 263 | r14 = 14, 264 | r15 = 15, 265 | r16 = 16, 266 | r17 = 17, 267 | r18 = 18, 268 | r19 = 19, 269 | r20 = 20, 270 | r21 = 21, 271 | r22 = 22, 272 | r23 = 23, 273 | r24 = 24, 274 | r25 = 25, 275 | r26 = 26, 276 | r27 = 27, 277 | r28 = 28, 278 | r29 = 29, 279 | r30 = 30, 280 | r31 = 31, 281 | 282 | pub fn format(reg5: Register, fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { 283 | _ = opt; 284 | _ = fmt; 285 | try writer.print("r{}", .{@intFromEnum(reg5)}); 286 | } 287 | 288 | /// Returns the numeric value of this value. 289 | pub fn int(r5: Register) u5 { 290 | return @intFromEnum(r5); 291 | } 292 | 293 | /// Returns the register number. 294 | pub fn num(r5: Register) u5 { 295 | return @intFromEnum(r5); 296 | } 297 | pub fn reg(r: Register) Register { 298 | return r; 299 | } 300 | }; 301 | 302 | pub const StatusRegisterBit = enum(u3) { 303 | C = 0, 304 | Z = 1, 305 | N = 2, 306 | V = 3, 307 | S = 4, 308 | H = 5, 309 | T = 6, 310 | I = 7, 311 | 312 | pub fn format(bit: StatusRegisterBit, fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { 313 | _ = opt; 314 | _ = fmt; 315 | try writer.print("{s}", .{@tagName(bit)}); 316 | } 317 | 318 | /// Returns the bit index. 319 | pub fn num(bit: StatusRegisterBit) u3 { 320 | return @intFromEnum(bit); 321 | } 322 | 323 | /// Returns the bit mask. 324 | pub fn mask(bit: StatusRegisterBit) u8 { 325 | return @as(u8, 1) << @intFromEnum(bit); 326 | } 327 | }; 328 | 329 | pub const DataBit = enum(u3) { 330 | _, 331 | 332 | pub fn format(bit: DataBit, fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { 333 | _ = opt; 334 | _ = fmt; 335 | try writer.print("{}", .{@intFromEnum(bit)}); 336 | } 337 | 338 | /// Returns the bit index. 339 | pub fn num(bit: DataBit) u3 { 340 | return @intFromEnum(bit); 341 | } 342 | 343 | /// Returns the bit mask. 344 | pub fn mask(bit: DataBit) u8 { 345 | return @as(u8, 1) << @intFromEnum(bit); 346 | } 347 | }; 348 | -------------------------------------------------------------------------------- /src/testconfig.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const ExitType = enum { 4 | breakpoint, 5 | enter_sleep_mode, 6 | reset_watchdog, 7 | out_of_gas, 8 | system_exit, 9 | }; 10 | 11 | pub const SystemCondition = struct { 12 | sreg: struct { 13 | c: ?bool = null, 14 | z: ?bool = null, 15 | n: ?bool = null, 16 | v: ?bool = null, 17 | s: ?bool = null, 18 | h: ?bool = null, 19 | t: ?bool = null, 20 | i: ?bool = null, 21 | } = .{}, 22 | 23 | r0: ?u8 = null, 24 | r1: ?u8 = null, 25 | r2: ?u8 = null, 26 | r3: ?u8 = null, 27 | r4: ?u8 = null, 28 | r5: ?u8 = null, 29 | r6: ?u8 = null, 30 | r7: ?u8 = null, 31 | r8: ?u8 = null, 32 | r9: ?u8 = null, 33 | r10: ?u8 = null, 34 | r11: ?u8 = null, 35 | r12: ?u8 = null, 36 | r13: ?u8 = null, 37 | r14: ?u8 = null, 38 | r15: ?u8 = null, 39 | r16: ?u8 = null, 40 | r17: ?u8 = null, 41 | r18: ?u8 = null, 42 | r19: ?u8 = null, 43 | r20: ?u8 = null, 44 | r21: ?u8 = null, 45 | r22: ?u8 = null, 46 | r23: ?u8 = null, 47 | r24: ?u8 = null, 48 | r25: ?u8 = null, 49 | r26: ?u8 = null, 50 | r27: ?u8 = null, 51 | r28: ?u8 = null, 52 | r29: ?u8 = null, 53 | r30: ?u8 = null, 54 | r31: ?u8 = null, 55 | }; 56 | 57 | pub const TestSuiteConfig = struct { 58 | exit: ExitType = .system_exit, 59 | exit_code: u8 = 0, 60 | 61 | stdout: []const u8 = "", 62 | stderr: []const u8 = "", 63 | stdin: []const u8 = "", 64 | precondition: SystemCondition = .{}, 65 | postcondition: SystemCondition = .{}, 66 | 67 | mileage: ?u32 = 0, 68 | 69 | cpu: ?[]const u8 = null, 70 | optimize: std.builtin.OptimizeMode = .ReleaseSmall, 71 | 72 | gcc_flags: []const []const u8 = &.{ 73 | "-g", 74 | "-O2", 75 | "-nostdlib", 76 | "-nostdinc", 77 | "-ffreestanding", 78 | }, 79 | 80 | pub fn toString(config: TestSuiteConfig, b: *std.Build) []const u8 { 81 | return std.json.stringifyAlloc(b.allocator, config, .{ 82 | .whitespace = .indent_2, 83 | .emit_null_optional_fields = true, 84 | .emit_strings_as_arrays = false, 85 | .escape_unicode = true, 86 | }) catch @panic("oom"); 87 | } 88 | 89 | pub fn load(allocator: std.mem.Allocator, file: std.fs.File) !TestSuiteConfig { 90 | var reader = std.json.reader(allocator, file.reader()); 91 | defer reader.deinit(); 92 | 93 | return try std.json.parseFromTokenSourceLeaky(TestSuiteConfig, allocator, &reader, .{ 94 | .allocate = .alloc_always, 95 | }); 96 | } 97 | }; 98 | -------------------------------------------------------------------------------- /src/testrunner.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const aviron = @import("aviron"); 4 | const args_parser = @import("args"); 5 | const testconfig = @import("testconfig.zig"); 6 | 7 | const Cli = struct { 8 | help: bool = false, 9 | trace: bool = false, 10 | 11 | name: []const u8 = "", 12 | config: ?[]const u8 = null, 13 | }; 14 | 15 | const SystemState = struct { 16 | cpu: aviron.Cpu, 17 | 18 | // Emulate Atmega382p device size: 19 | flash_storage: aviron.Flash.Static(32768) = .{}, 20 | sram: aviron.RAM.Static(2048) = .{}, 21 | eeprom: aviron.EEPROM.Static(1024) = .{}, 22 | io: IO, 23 | 24 | config: testconfig.TestSuiteConfig, 25 | options: Cli, 26 | }; 27 | 28 | var test_system: SystemState = undefined; 29 | 30 | const ExitMode = union(testconfig.ExitType) { 31 | breakpoint, 32 | enter_sleep_mode, 33 | reset_watchdog, 34 | out_of_gas, 35 | system_exit: u8, 36 | }; 37 | 38 | fn validateReg(ok: *bool, ts: *SystemState, comptime reg: aviron.Register) void { 39 | const expected = @field(ts.config.postcondition, @tagName(reg)) orelse return; 40 | 41 | const actual = ts.cpu.regs[reg.num()]; 42 | if (expected != actual) { 43 | std.debug.print("Invalid register value for register {s}: Expected {}, but got {}.\n", .{ 44 | @tagName(reg), 45 | expected, 46 | actual, 47 | }); 48 | ok.* = false; 49 | } 50 | } 51 | 52 | fn validateSystemAndExit(exit_mode: ExitMode) noreturn { 53 | var ok = true; 54 | const ts = &test_system; 55 | 56 | if (exit_mode != ts.config.exit) { 57 | std.debug.print("Invalid exit type: Expected {s}, but got {s}.\n", .{ 58 | @tagName(ts.config.exit), 59 | @tagName(exit_mode), 60 | }); 61 | ok = false; 62 | } else if (exit_mode == .system_exit and exit_mode.system_exit != ts.config.exit_code) { 63 | std.debug.print("Invalid exit code: Expected {}, but got {}.\n", .{ 64 | ts.config.exit_code, 65 | exit_mode.system_exit, 66 | }); 67 | ok = false; 68 | } 69 | 70 | if (!std.mem.eql(u8, ts.io.stdout.items, ts.config.stdout)) { 71 | std.debug.print("stdout mismatch:\n", .{}); 72 | std.debug.print("expected: '{}'\n", .{std.fmt.fmtSliceEscapeLower(ts.config.stdout)}); 73 | std.debug.print("actual: '{}'\n", .{std.fmt.fmtSliceEscapeLower(ts.io.stdout.items)}); 74 | ok = false; 75 | } 76 | 77 | if (!std.mem.eql(u8, ts.io.stderr.items, ts.config.stderr)) { 78 | std.debug.print("stderr mismatch:\n", .{}); 79 | std.debug.print("expected: '{}'\n", .{std.fmt.fmtSliceEscapeLower(ts.config.stderr)}); 80 | std.debug.print("actual: '{}'\n", .{std.fmt.fmtSliceEscapeLower(ts.io.stderr.items)}); 81 | ok = false; 82 | } 83 | 84 | inline for (comptime std.enums.values(aviron.Register)) |reg| { 85 | validateReg(&ok, ts, reg); 86 | } 87 | 88 | inline for (comptime std.meta.fields(aviron.Cpu.SREG)) |fld| { 89 | if (@field(test_system.config.postcondition.sreg, fld.name)) |expected_value| { 90 | const actual_value = @field(test_system.cpu.sreg, fld.name); 91 | if (actual_value != expected_value) { 92 | std.debug.print("Invalid register value for SREG.{s}: Expected {}, but got {}.\n", .{ 93 | fld.name, 94 | expected_value, 95 | actual_value, 96 | }); 97 | ok = false; 98 | } 99 | } 100 | } 101 | 102 | if (!ok and ts.options.name.len > 0) { 103 | std.debug.print("test {s} failed.\n", .{ts.options.name}); 104 | } 105 | 106 | std.process.exit(if (ok) 0x00 else 0x01); 107 | } 108 | 109 | pub fn main() !u8 { 110 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 111 | defer arena.deinit(); 112 | 113 | const allocator = arena.allocator(); 114 | 115 | var cli = args_parser.parseForCurrentProcess(Cli, allocator, .print) catch return 1; 116 | defer cli.deinit(); 117 | 118 | const config_path = cli.options.config orelse @panic("missing configuration path!"); 119 | 120 | const config = blk: { 121 | var file = try std.fs.cwd().openFile(config_path, .{}); 122 | defer file.close(); 123 | 124 | break :blk try testconfig.TestSuiteConfig.load(allocator, file); 125 | }; 126 | 127 | if (cli.positionals.len == 0) 128 | @panic("usage: aviron [--trace] "); 129 | 130 | var stdout = std.ArrayList(u8).init(allocator); 131 | defer stdout.deinit(); 132 | 133 | var stderr = std.ArrayList(u8).init(allocator); 134 | defer stderr.deinit(); 135 | 136 | test_system = SystemState{ 137 | .options = cli.options, 138 | .config = config, 139 | 140 | .io = IO{ 141 | .sreg = &test_system.cpu.sreg, 142 | .sp = 2047, 143 | .config = config, 144 | 145 | .stdin = config.stdin, 146 | .stdout = &stdout, 147 | .stderr = &stderr, 148 | }, 149 | 150 | .cpu = aviron.Cpu{ 151 | .trace = cli.options.trace, 152 | 153 | .instruction_set = .avr5, 154 | 155 | .flash = test_system.flash_storage.memory(), 156 | .sram = test_system.sram.memory(), 157 | .eeprom = test_system.eeprom.memory(), 158 | .io = test_system.io.memory(), 159 | 160 | .code_model = .code16, 161 | 162 | .sio = .{ 163 | .ramp_x = null, 164 | .ramp_y = null, 165 | .ramp_z = null, 166 | .ramp_d = null, 167 | .e_ind = null, 168 | 169 | .sp_l = @intFromEnum(IO.Register.sp_l), 170 | .sp_h = @intFromEnum(IO.Register.sp_h), 171 | 172 | .sreg = @intFromEnum(IO.Register.sreg), 173 | }, 174 | }, 175 | }; 176 | 177 | // Initialize CPU state: 178 | inline for (comptime std.meta.fields(aviron.Cpu.SREG)) |fld| { 179 | if (@field(test_system.config.precondition.sreg, fld.name)) |init_value| { 180 | @field(test_system.cpu.sreg, fld.name) = init_value; 181 | } 182 | } 183 | inline for (comptime std.enums.values(aviron.Register)) |reg| { 184 | if (@field(test_system.config.precondition, @tagName(reg))) |init_value| { 185 | test_system.cpu.regs[reg.num()] = init_value; 186 | } 187 | } 188 | 189 | for (cli.positionals) |file_path| { 190 | var elf_file = try std.fs.cwd().openFile(file_path, .{}); 191 | defer elf_file.close(); 192 | 193 | var source = std.io.StreamSource{ .file = elf_file }; 194 | var header = try std.elf.Header.read(&source); 195 | 196 | var pheaders = header.program_header_iterator(&source); 197 | while (try pheaders.next()) |phdr| { 198 | if (phdr.p_type != std.elf.PT_LOAD) 199 | continue; // Header isn't lodead 200 | 201 | const dest_mem = if (phdr.p_vaddr >= 0x0080_0000) 202 | &test_system.sram.data 203 | else 204 | &test_system.flash_storage.data; 205 | 206 | const addr_masked: u24 = @intCast(phdr.p_vaddr & 0x007F_FFFF); 207 | 208 | if (phdr.p_filesz > 0) { 209 | try source.seekTo(phdr.p_offset); 210 | try source.reader().readNoEof(dest_mem[addr_masked..][0..phdr.p_filesz]); 211 | } 212 | if (phdr.p_memsz > phdr.p_filesz) { 213 | @memset(dest_mem[addr_masked + phdr.p_filesz ..][0 .. phdr.p_memsz - phdr.p_filesz], 0); 214 | } 215 | } 216 | } 217 | 218 | const result = try test_system.cpu.run(null); 219 | validateSystemAndExit(switch (result) { 220 | inline else => |tag| @unionInit(ExitMode, @tagName(tag), {}), 221 | }); 222 | } 223 | 224 | const IO = struct { 225 | config: testconfig.TestSuiteConfig, 226 | 227 | scratch_regs: [16]u8 = @splat(0), 228 | 229 | sp: u16, 230 | sreg: *aviron.Cpu.SREG, 231 | 232 | stdout: *std.ArrayList(u8), 233 | stderr: *std.ArrayList(u8), 234 | 235 | stdin: []const u8, 236 | 237 | pub fn memory(self: *IO) aviron.IO { 238 | return aviron.IO{ 239 | .ctx = self, 240 | .vtable = &vtable, 241 | }; 242 | } 243 | 244 | pub const vtable = aviron.IO.VTable{ 245 | .readFn = read, 246 | .writeFn = write, 247 | }; 248 | 249 | // This is our own "debug" device with it's own debug addresses: 250 | const Register = enum(u6) { 251 | exit = 0, // read: 0, write: os.exit() 252 | stdio = 1, // read: stdin, write: print to stdout 253 | stderr = 2, // read: 0, write: print to stderr 254 | 255 | scratch_0 = 0x10, // scratch register 256 | scratch_1 = 0x11, // scratch register 257 | scratch_2 = 0x12, // scratch register 258 | scratch_3 = 0x13, // scratch register 259 | scratch_4 = 0x14, // scratch register 260 | scratch_5 = 0x15, // scratch register 261 | scratch_6 = 0x16, // scratch register 262 | scratch_7 = 0x17, // scratch register 263 | scratch_8 = 0x18, // scratch register 264 | scratch_9 = 0x19, // scratch register 265 | scratch_a = 0x1a, // scratch register 266 | scratch_b = 0x1b, // scratch register 267 | scratch_c = 0x1c, // scratch register 268 | scratch_d = 0x1d, // scratch register 269 | scratch_e = 0x1e, // scratch register 270 | scratch_f = 0x1f, // scratch register 271 | 272 | sp_l = 0x3D, // ATmega328p 273 | sp_h = 0x3E, // ATmega328p 274 | sreg = 0x3F, // ATmega328p 275 | 276 | _, 277 | }; 278 | 279 | fn read(ctx: ?*anyopaque, addr: u6) u8 { 280 | const io: *IO = @ptrCast(@alignCast(ctx.?)); 281 | const reg: Register = @enumFromInt(addr); 282 | return switch (reg) { 283 | .exit => 0, 284 | .stdio => if (io.stdin.len > 0) blk: { 285 | const byte = io.stdin[0]; 286 | io.stdin = io.stdin[1..]; 287 | break :blk byte; 288 | } else 0xFF, // EOF 289 | .stderr => 0, 290 | 291 | .scratch_0 => io.scratch_regs[0x0], 292 | .scratch_1 => io.scratch_regs[0x1], 293 | .scratch_2 => io.scratch_regs[0x2], 294 | .scratch_3 => io.scratch_regs[0x3], 295 | .scratch_4 => io.scratch_regs[0x4], 296 | .scratch_5 => io.scratch_regs[0x5], 297 | .scratch_6 => io.scratch_regs[0x6], 298 | .scratch_7 => io.scratch_regs[0x7], 299 | .scratch_8 => io.scratch_regs[0x8], 300 | .scratch_9 => io.scratch_regs[0x9], 301 | .scratch_a => io.scratch_regs[0xa], 302 | .scratch_b => io.scratch_regs[0xb], 303 | .scratch_c => io.scratch_regs[0xc], 304 | .scratch_d => io.scratch_regs[0xd], 305 | .scratch_e => io.scratch_regs[0xe], 306 | .scratch_f => io.scratch_regs[0xf], 307 | 308 | .sreg => @bitCast(io.sreg.*), 309 | 310 | .sp_l => @truncate(io.sp >> 0), 311 | .sp_h => @truncate(io.sp >> 8), 312 | 313 | _ => std.debug.panic("illegal i/o read from undefined register 0x{X:0>2}", .{addr}), 314 | }; 315 | } 316 | 317 | /// `mask` determines which bits of `value` are written. To write everything, use `0xFF` for `mask`. 318 | fn write(ctx: ?*anyopaque, addr: u6, mask: u8, value: u8) void { 319 | const io: *IO = @ptrCast(@alignCast(ctx.?)); 320 | const reg: Register = @enumFromInt(addr); 321 | switch (reg) { 322 | .exit => validateSystemAndExit(.{ 323 | .system_exit = (value & mask), 324 | }), 325 | 326 | .stdio => io.stdout.append(value & mask) catch @panic("out of memory"), 327 | .stderr => io.stderr.append(value & mask) catch @panic("out of memory"), 328 | 329 | .scratch_0 => writeMasked(&io.scratch_regs[0x0], mask, value), 330 | .scratch_1 => writeMasked(&io.scratch_regs[0x1], mask, value), 331 | .scratch_2 => writeMasked(&io.scratch_regs[0x2], mask, value), 332 | .scratch_3 => writeMasked(&io.scratch_regs[0x3], mask, value), 333 | .scratch_4 => writeMasked(&io.scratch_regs[0x4], mask, value), 334 | .scratch_5 => writeMasked(&io.scratch_regs[0x5], mask, value), 335 | .scratch_6 => writeMasked(&io.scratch_regs[0x6], mask, value), 336 | .scratch_7 => writeMasked(&io.scratch_regs[0x7], mask, value), 337 | .scratch_8 => writeMasked(&io.scratch_regs[0x8], mask, value), 338 | .scratch_9 => writeMasked(&io.scratch_regs[0x9], mask, value), 339 | .scratch_a => writeMasked(&io.scratch_regs[0xa], mask, value), 340 | .scratch_b => writeMasked(&io.scratch_regs[0xb], mask, value), 341 | .scratch_c => writeMasked(&io.scratch_regs[0xc], mask, value), 342 | .scratch_d => writeMasked(&io.scratch_regs[0xd], mask, value), 343 | .scratch_e => writeMasked(&io.scratch_regs[0xe], mask, value), 344 | .scratch_f => writeMasked(&io.scratch_regs[0xf], mask, value), 345 | 346 | .sp_l => writeMasked(lobyte(&io.sp), mask, value), 347 | .sp_h => writeMasked(hibyte(&io.sp), mask, value), 348 | .sreg => writeMasked(@ptrCast(io.sreg), mask, value), 349 | 350 | _ => std.debug.panic("illegal i/o write to undefined register 0x{X:0>2} with value=0x{X:0>2}, mask=0x{X:0>2}", .{ addr, value, mask }), 351 | } 352 | } 353 | 354 | fn lobyte(val: *u16) *u8 { 355 | const bits: *[2]u8 = @ptrCast(val); 356 | return switch (comptime builtin.cpu.arch.endian()) { 357 | .big => return &bits[1], 358 | .little => return &bits[0], 359 | }; 360 | } 361 | 362 | fn hibyte(val: *u16) *u8 { 363 | const bits: *[2]u8 = @ptrCast(val); 364 | return switch (comptime builtin.cpu.arch.endian()) { 365 | .big => return &bits[0], 366 | .little => return &bits[1], 367 | }; 368 | } 369 | 370 | fn writeMasked(dst: *u8, mask: u8, val: u8) void { 371 | dst.* &= ~mask; 372 | dst.* |= (val & mask); 373 | } 374 | }; 375 | -------------------------------------------------------------------------------- /testsuite.avr-gcc/README.md: -------------------------------------------------------------------------------- 1 | # Testsuite Sources 2 | 3 | This folder contains all files that cannot be compiled with Zig as-is and require the `avr-gcc` + `avr-binutils` toolchain. 4 | 5 | Run `zig build update-testsuite` from the main folder to update all files. This requires `avr-gcc` to be in the `PATH`! 6 | -------------------------------------------------------------------------------- /testsuite.avr-gcc/instructions/mul.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit": "breakpoint", 3 | //! "cpu": "attiny816", 4 | //! "precondition": { "r16": 80, "r17": 130 }, 5 | //! "postcondition": { 6 | //! "r16": 80, 7 | //! "r17": 130, 8 | //! "r0": 160, 9 | //! "r1": 40, 10 | //! "sreg": { "c": false, "z": false } } 11 | //! } 12 | 13 | mul r16, 17 ; => 10400, 0x28A0 14 | break 15 | -------------------------------------------------------------------------------- /testsuite.avr-gcc/instructions/muls.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit": "breakpoint", 3 | //! "cpu": "attiny816", 4 | //! "precondition": { "r16": 248, "r17": 243 }, 5 | //! "postcondition": { 6 | //! "r16": 248, 7 | //! "r17": 243, 8 | //! "r0": 104, 9 | //! "r1": 0, 10 | //! "sreg": { "c": false, "z": false } } 11 | //! } 12 | 13 | muls r16, 17 14 | break 15 | -------------------------------------------------------------------------------- /testsuite.avr-gcc/instructions/mulsu.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit": "breakpoint", 3 | //! "cpu": "attiny816", 4 | //! "precondition": { "r16": 248, "r17": 243 }, 5 | //! "postcondition": { 6 | //! "r16": 248, 7 | //! "r17": 243, 8 | //! "r0": 104, 9 | //! "r1": 248, 10 | //! "sreg": { "c": true, "z": false } } 11 | //! } 12 | 13 | mulsu r16, 17 14 | break 15 | -------------------------------------------------------------------------------- /testsuite/dummy.zig: -------------------------------------------------------------------------------- 1 | //! 2 | //! This file is just a blank file which will be compiled as the application 'root' 3 | //! 4 | -------------------------------------------------------------------------------- /testsuite/instructions/add.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 0, 3 | //! "stdout": "", 4 | //! "stderr": "Hi" 5 | //! } 6 | 7 | #include "../regs.inc" 8 | 9 | .global _start 10 | _start: 11 | 12 | ; '\' + '!' = 'H' 13 | ldi r16, '\'' 14 | ldi r18, '!' 15 | add r16, r18 16 | out IO_STDERR, r16 17 | ldi r16, 0x80 18 | ldi r18, 0x80 19 | add r16, r18 20 | ; '4' + '4' + carry = 'i' 21 | ldi r17, '4' 22 | ldi r19, '4' 23 | adc r17, r19 24 | out IO_STDERR, r17 25 | 26 | clr r16 27 | out IO_EXIT, r16 28 | -------------------------------------------------------------------------------- /testsuite/instructions/cbi.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 85 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | ldi r16, 0xFF 10 | out IO_SCRATCH_A, r16 11 | 12 | cbi IO_SCRATCH_A, 1 13 | cbi IO_SCRATCH_A, 3 14 | cbi IO_SCRATCH_A, 5 15 | cbi IO_SCRATCH_A, 7 16 | 17 | in r15, IO_SCRATCH_A 18 | out IO_EXIT, r15 19 | -------------------------------------------------------------------------------- /testsuite/instructions/in-stdio.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 0, 3 | //! "stdin": "14325", 4 | //! "stdout": "14325" 5 | //! } 6 | 7 | #include "../regs.inc" 8 | 9 | .global _start 10 | _start: 11 | 12 | in r16, IO_STDIN 13 | out IO_STDOUT, r16 14 | 15 | in r16, IO_STDIN 16 | out IO_STDOUT, r16 17 | 18 | in r16, IO_STDIN 19 | out IO_STDOUT, r16 20 | 21 | in r16, IO_STDIN 22 | out IO_STDOUT, r16 23 | 24 | in r16, IO_STDIN 25 | out IO_STDOUT, r16 26 | 27 | clr r16 28 | out IO_EXIT, r16 29 | -------------------------------------------------------------------------------- /testsuite/instructions/mul.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZigEmbeddedGroup/aviron/92cb11a3c8897fc9b3465475461c472569912006/testsuite/instructions/mul.elf -------------------------------------------------------------------------------- /testsuite/instructions/mul.elf.json: -------------------------------------------------------------------------------- 1 | { 2 | "exit": "breakpoint", 3 | "exit_code": 0, 4 | "stdout": "", 5 | "stderr": "", 6 | "stdin": "", 7 | "precondition": { 8 | "sreg": { 9 | "c": null, 10 | "z": null, 11 | "n": null, 12 | "v": null, 13 | "s": null, 14 | "h": null, 15 | "t": null, 16 | "i": null 17 | }, 18 | "r0": null, 19 | "r1": null, 20 | "r2": null, 21 | "r3": null, 22 | "r4": null, 23 | "r5": null, 24 | "r6": null, 25 | "r7": null, 26 | "r8": null, 27 | "r9": null, 28 | "r10": null, 29 | "r11": null, 30 | "r12": null, 31 | "r13": null, 32 | "r14": null, 33 | "r15": null, 34 | "r16": 80, 35 | "r17": 130, 36 | "r18": null, 37 | "r19": null, 38 | "r20": null, 39 | "r21": null, 40 | "r22": null, 41 | "r23": null, 42 | "r24": null, 43 | "r25": null, 44 | "r26": null, 45 | "r27": null, 46 | "r28": null, 47 | "r29": null, 48 | "r30": null, 49 | "r31": null 50 | }, 51 | "postcondition": { 52 | "sreg": { 53 | "c": false, 54 | "z": false, 55 | "n": null, 56 | "v": null, 57 | "s": null, 58 | "h": null, 59 | "t": null, 60 | "i": null 61 | }, 62 | "r0": 160, 63 | "r1": 40, 64 | "r2": null, 65 | "r3": null, 66 | "r4": null, 67 | "r5": null, 68 | "r6": null, 69 | "r7": null, 70 | "r8": null, 71 | "r9": null, 72 | "r10": null, 73 | "r11": null, 74 | "r12": null, 75 | "r13": null, 76 | "r14": null, 77 | "r15": null, 78 | "r16": 80, 79 | "r17": 130, 80 | "r18": null, 81 | "r19": null, 82 | "r20": null, 83 | "r21": null, 84 | "r22": null, 85 | "r23": null, 86 | "r24": null, 87 | "r25": null, 88 | "r26": null, 89 | "r27": null, 90 | "r28": null, 91 | "r29": null, 92 | "r30": null, 93 | "r31": null 94 | }, 95 | "mileage": 0, 96 | "cpu": "attiny816", 97 | "optimize": "ReleaseSmall", 98 | "gcc_flags": [ 99 | "-g", 100 | "-O2", 101 | "-nostdlib", 102 | "-nostdinc", 103 | "-ffreestanding" 104 | ] 105 | } -------------------------------------------------------------------------------- /testsuite/instructions/muls.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZigEmbeddedGroup/aviron/92cb11a3c8897fc9b3465475461c472569912006/testsuite/instructions/muls.elf -------------------------------------------------------------------------------- /testsuite/instructions/muls.elf.json: -------------------------------------------------------------------------------- 1 | { 2 | "exit": "breakpoint", 3 | "exit_code": 0, 4 | "stdout": "", 5 | "stderr": "", 6 | "stdin": "", 7 | "precondition": { 8 | "sreg": { 9 | "c": null, 10 | "z": null, 11 | "n": null, 12 | "v": null, 13 | "s": null, 14 | "h": null, 15 | "t": null, 16 | "i": null 17 | }, 18 | "r0": null, 19 | "r1": null, 20 | "r2": null, 21 | "r3": null, 22 | "r4": null, 23 | "r5": null, 24 | "r6": null, 25 | "r7": null, 26 | "r8": null, 27 | "r9": null, 28 | "r10": null, 29 | "r11": null, 30 | "r12": null, 31 | "r13": null, 32 | "r14": null, 33 | "r15": null, 34 | "r16": 248, 35 | "r17": 243, 36 | "r18": null, 37 | "r19": null, 38 | "r20": null, 39 | "r21": null, 40 | "r22": null, 41 | "r23": null, 42 | "r24": null, 43 | "r25": null, 44 | "r26": null, 45 | "r27": null, 46 | "r28": null, 47 | "r29": null, 48 | "r30": null, 49 | "r31": null 50 | }, 51 | "postcondition": { 52 | "sreg": { 53 | "c": false, 54 | "z": false, 55 | "n": null, 56 | "v": null, 57 | "s": null, 58 | "h": null, 59 | "t": null, 60 | "i": null 61 | }, 62 | "r0": 104, 63 | "r1": 0, 64 | "r2": null, 65 | "r3": null, 66 | "r4": null, 67 | "r5": null, 68 | "r6": null, 69 | "r7": null, 70 | "r8": null, 71 | "r9": null, 72 | "r10": null, 73 | "r11": null, 74 | "r12": null, 75 | "r13": null, 76 | "r14": null, 77 | "r15": null, 78 | "r16": 248, 79 | "r17": 243, 80 | "r18": null, 81 | "r19": null, 82 | "r20": null, 83 | "r21": null, 84 | "r22": null, 85 | "r23": null, 86 | "r24": null, 87 | "r25": null, 88 | "r26": null, 89 | "r27": null, 90 | "r28": null, 91 | "r29": null, 92 | "r30": null, 93 | "r31": null 94 | }, 95 | "mileage": 0, 96 | "cpu": "attiny816", 97 | "optimize": "ReleaseSmall", 98 | "gcc_flags": [ 99 | "-g", 100 | "-O2", 101 | "-nostdlib", 102 | "-nostdinc", 103 | "-ffreestanding" 104 | ] 105 | } -------------------------------------------------------------------------------- /testsuite/instructions/mulsu.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZigEmbeddedGroup/aviron/92cb11a3c8897fc9b3465475461c472569912006/testsuite/instructions/mulsu.elf -------------------------------------------------------------------------------- /testsuite/instructions/mulsu.elf.json: -------------------------------------------------------------------------------- 1 | { 2 | "exit": "breakpoint", 3 | "exit_code": 0, 4 | "stdout": "", 5 | "stderr": "", 6 | "stdin": "", 7 | "precondition": { 8 | "sreg": { 9 | "c": null, 10 | "z": null, 11 | "n": null, 12 | "v": null, 13 | "s": null, 14 | "h": null, 15 | "t": null, 16 | "i": null 17 | }, 18 | "r0": null, 19 | "r1": null, 20 | "r2": null, 21 | "r3": null, 22 | "r4": null, 23 | "r5": null, 24 | "r6": null, 25 | "r7": null, 26 | "r8": null, 27 | "r9": null, 28 | "r10": null, 29 | "r11": null, 30 | "r12": null, 31 | "r13": null, 32 | "r14": null, 33 | "r15": null, 34 | "r16": 248, 35 | "r17": 243, 36 | "r18": null, 37 | "r19": null, 38 | "r20": null, 39 | "r21": null, 40 | "r22": null, 41 | "r23": null, 42 | "r24": null, 43 | "r25": null, 44 | "r26": null, 45 | "r27": null, 46 | "r28": null, 47 | "r29": null, 48 | "r30": null, 49 | "r31": null 50 | }, 51 | "postcondition": { 52 | "sreg": { 53 | "c": true, 54 | "z": false, 55 | "n": null, 56 | "v": null, 57 | "s": null, 58 | "h": null, 59 | "t": null, 60 | "i": null 61 | }, 62 | "r0": 104, 63 | "r1": 248, 64 | "r2": null, 65 | "r3": null, 66 | "r4": null, 67 | "r5": null, 68 | "r6": null, 69 | "r7": null, 70 | "r8": null, 71 | "r9": null, 72 | "r10": null, 73 | "r11": null, 74 | "r12": null, 75 | "r13": null, 76 | "r14": null, 77 | "r15": null, 78 | "r16": 248, 79 | "r17": 243, 80 | "r18": null, 81 | "r19": null, 82 | "r20": null, 83 | "r21": null, 84 | "r22": null, 85 | "r23": null, 86 | "r24": null, 87 | "r25": null, 88 | "r26": null, 89 | "r27": null, 90 | "r28": null, 91 | "r29": null, 92 | "r30": null, 93 | "r31": null 94 | }, 95 | "mileage": 0, 96 | "cpu": "attiny816", 97 | "optimize": "ReleaseSmall", 98 | "gcc_flags": [ 99 | "-g", 100 | "-O2", 101 | "-nostdlib", 102 | "-nostdinc", 103 | "-ffreestanding" 104 | ] 105 | } -------------------------------------------------------------------------------- /testsuite/instructions/out-exit-0.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 0, 3 | //! "stdout": "", 4 | //! "stderr": "" 5 | //! } 6 | 7 | #include "../regs.inc" 8 | 9 | .global _start 10 | _start: 11 | clr r16 12 | out IO_EXIT, r16 13 | -------------------------------------------------------------------------------- /testsuite/instructions/out-exit-42.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 42, 3 | //! "stdout": "", 4 | //! "stderr": "" 5 | //! } 6 | 7 | #include "../regs.inc" 8 | 9 | .global _start 10 | _start: 11 | ldi r16, 42 12 | out IO_EXIT, r16 13 | -------------------------------------------------------------------------------- /testsuite/instructions/out-stderr.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 0, 3 | //! "stdout": "", 4 | //! "stderr": "Hello" 5 | //! } 6 | 7 | #include "../regs.inc" 8 | 9 | .global _start 10 | _start: 11 | 12 | ldi r16, 'H' 13 | out IO_STDERR, r16 14 | ldi r16, 'e' 15 | out IO_STDERR, r16 16 | ldi r16, 'l' 17 | out IO_STDERR, r16 18 | out IO_STDERR, r16 19 | ldi r16, 'o' 20 | out IO_STDERR, r16 21 | 22 | clr r16 23 | out IO_EXIT, r16 24 | -------------------------------------------------------------------------------- /testsuite/instructions/out-stdout.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 0, 3 | //! "stdout": "Hello", 4 | //! "stderr": "" 5 | //! } 6 | 7 | #include "../regs.inc" 8 | 9 | .global _start 10 | _start: 11 | 12 | ldi r16, 'H' 13 | out IO_STDOUT, r16 14 | ldi r16, 'e' 15 | out IO_STDOUT, r16 16 | ldi r16, 'l' 17 | out IO_STDOUT, r16 18 | out IO_STDOUT, r16 19 | ldi r16, 'o' 20 | out IO_STDOUT, r16 21 | 22 | clr r16 23 | out IO_EXIT, r16 24 | -------------------------------------------------------------------------------- /testsuite/instructions/sbi.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 170 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | clr r16 10 | out IO_SCRATCH_A, r16 11 | 12 | sbi IO_SCRATCH_A, 1 13 | sbi IO_SCRATCH_A, 3 14 | sbi IO_SCRATCH_A, 5 15 | sbi IO_SCRATCH_A, 7 16 | 17 | in r15, IO_SCRATCH_A 18 | out IO_EXIT, r15 19 | -------------------------------------------------------------------------------- /testsuite/instructions/sub.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 0, 3 | //! "stdout": "", 4 | //! "stderr": "Hi" 5 | //! } 6 | 7 | #include "../regs.inc" 8 | 9 | .global _start 10 | _start: 11 | 12 | ; 'q' - ')' = 'H' 13 | ldi r16, 'q' 14 | ldi r18, ')' 15 | sub r16, r18 16 | out IO_STDERR, r16 17 | ldi r16, 0x80 18 | ldi r18, 0x80 19 | add r16, r18 20 | ; '~' - 0x14 - carry = 'i' 21 | ldi r17, '~' 22 | ldi r19, 0x14 23 | sbc r17, r19 24 | out IO_STDERR, r17 25 | 26 | clr r16 27 | out IO_EXIT, r16 28 | -------------------------------------------------------------------------------- /testsuite/lib/write-chan.zig: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "stdout": "hello", 3 | //! "stderr": "world" 4 | //! } 5 | const testsuite = @import("testsuite"); 6 | 7 | export fn _start() callconv(.C) noreturn { 8 | testsuite.write(.stdout, "hello"); 9 | testsuite.write(.stderr, "world"); 10 | testsuite.exit(0); 11 | } 12 | -------------------------------------------------------------------------------- /testsuite/regs.inc: -------------------------------------------------------------------------------- 1 | 2 | #define IO_EXIT 0x00 3 | #define IO_STDIN 0x01 4 | #define IO_STDOUT 0x01 5 | #define IO_STDERR 0x02 6 | 7 | #define IO_SCRATCH_0 0x10 8 | #define IO_SCRATCH_1 0x11 9 | #define IO_SCRATCH_2 0x12 10 | #define IO_SCRATCH_3 0x13 11 | #define IO_SCRATCH_4 0x14 12 | #define IO_SCRATCH_5 0x15 13 | #define IO_SCRATCH_6 0x16 14 | #define IO_SCRATCH_7 0x17 15 | #define IO_SCRATCH_8 0x18 16 | #define IO_SCRATCH_9 0x19 17 | #define IO_SCRATCH_A 0x1a 18 | #define IO_SCRATCH_B 0x1b 19 | #define IO_SCRATCH_C 0x1c 20 | #define IO_SCRATCH_D 0x1d 21 | #define IO_SCRATCH_E 0x1e 22 | #define IO_SCRATCH_F 0x1f 23 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-reg0.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_0, r16 12 | 13 | in r15, IO_SCRATCH_0 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-reg1.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_1, r16 12 | 13 | in r15, IO_SCRATCH_1 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-reg2.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_2, r16 12 | 13 | in r15, IO_SCRATCH_2 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-reg3.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_3, r16 12 | 13 | in r15, IO_SCRATCH_3 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-reg4.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_4, r16 12 | 13 | in r15, IO_SCRATCH_4 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-reg5.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_5, r16 12 | 13 | in r15, IO_SCRATCH_5 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-reg6.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_6, r16 12 | 13 | in r15, IO_SCRATCH_6 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-reg7.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_7, r16 12 | 13 | in r15, IO_SCRATCH_7 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-reg8.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_8, r16 12 | 13 | in r15, IO_SCRATCH_8 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-reg9.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_9, r16 12 | 13 | in r15, IO_SCRATCH_9 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-rega.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_A, r16 12 | 13 | in r15, IO_SCRATCH_A 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-regb.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_B, r16 12 | 13 | in r15, IO_SCRATCH_B 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-regc.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_C, r16 12 | 13 | in r15, IO_SCRATCH_C 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-regd.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_D, r16 12 | 13 | in r15, IO_SCRATCH_D 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-rege.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_E, r16 12 | 13 | in r15, IO_SCRATCH_E 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /testsuite/simulator/scratch-regf.S: -------------------------------------------------------------------------------- 1 | //! { 2 | //! "exit_code": 11 3 | //! } 4 | 5 | #include "../regs.inc" 6 | 7 | .global _start 8 | _start: 9 | 10 | ldi r16, 11 11 | out IO_SCRATCH_F, r16 12 | 13 | in r15, IO_SCRATCH_F 14 | out IO_EXIT, r15 15 | -------------------------------------------------------------------------------- /tools/generate-tables.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const isa_def = @embedFile("isa.txt"); 3 | const isa = @import("isa"); 4 | 5 | fn stringToEnum(comptime T: type, str: []const u8) ?T { 6 | inline for (@typeInfo(T).@"enum".fields) |enumField| { 7 | if (std.mem.eql(u8, str, enumField.name)) { 8 | return @field(T, enumField.name); 9 | } 10 | } 11 | return null; 12 | } 13 | 14 | pub fn main() !void { 15 | const allocator = std.heap.page_allocator; 16 | 17 | const argv = try std.process.argsAlloc(allocator); 18 | defer std.process.argsFree(allocator, argv); 19 | 20 | if (argv.len != 2) @panic("usage: aviron-table-gen "); 21 | 22 | var out = try std.fs.cwd().createFile(argv[1], .{}); 23 | defer out.close(); 24 | 25 | var buf = std.ArrayList(u8).init(allocator); 26 | defer buf.deinit(); 27 | 28 | const writer = buf.writer(); 29 | 30 | var lut = [_]isa.Opcode{.unknown} ** (std.math.maxInt(u16) + 1); 31 | 32 | var base_number_bit_set = std.bit_set.IntegerBitSet(16).initEmpty(); 33 | var unknown_indices = try std.BoundedArray(u8, 16).init(0); 34 | var unknown_indices_bit_set = std.bit_set.IntegerBitSet(16).initEmpty(); 35 | var result_bit_set = std.bit_set.IntegerBitSet(16).initEmpty(); 36 | 37 | var positionals = std.enums.EnumArray(isa.Opcode, std.AutoArrayHashMapUnmanaged(u8, std.BoundedArray(u8, 16))).initFill(.{}); 38 | defer for (&positionals.values) |*map| { 39 | map.deinit(allocator); 40 | }; 41 | 42 | var lit = std.mem.splitScalar(u8, isa_def, '\n'); 43 | while (lit.next()) |line| { 44 | var pit = std.mem.tokenizeScalar(u8, line, ' '); 45 | 46 | const op_name = pit.next() orelse continue; 47 | const opcode = stringToEnum(isa.Opcode, op_name) orelse @panic(op_name); 48 | 49 | base_number_bit_set.mask = 0; 50 | try unknown_indices.resize(0); 51 | 52 | var index: usize = 0; 53 | for (pit.rest()) |r| { 54 | if (r == ' ') 55 | continue; 56 | if (index >= 16) 57 | break; 58 | 59 | switch (r) { 60 | '0' => {}, 61 | '1' => base_number_bit_set.set(index), 62 | else => { 63 | const gop = try positionals.getPtr(opcode).getOrPut(allocator, r); 64 | if (!gop.found_existing) { 65 | gop.value_ptr.* = try std.BoundedArray(u8, 16).init(0); 66 | } 67 | try gop.value_ptr.*.append(@intCast(index)); 68 | 69 | try unknown_indices.append(@intCast(index)); 70 | }, 71 | } 72 | 73 | index += 1; 74 | } 75 | 76 | const max_int = (try std.math.powi(usize, 2, unknown_indices.len)) - 1; 77 | index = 0; 78 | while (index <= max_int) : (index += 1) { 79 | unknown_indices_bit_set.mask = @intCast(index); 80 | result_bit_set.mask = base_number_bit_set.mask; 81 | 82 | for (unknown_indices.slice(), 0..) |v, i| { 83 | result_bit_set.setValue(v, unknown_indices_bit_set.isSet(i)); 84 | } 85 | 86 | const result = @bitReverse(result_bit_set.mask); 87 | 88 | if (lut[result] != .unknown) { 89 | std.log.err("Overlap ({b}): {s} vs {s}", .{ result, @tagName(lut[result]), @tagName(opcode) }); 90 | return; 91 | } 92 | lut[result] = opcode; 93 | } 94 | } 95 | 96 | try writer.writeAll("//! AUTOGENERATED CODE. DO NOT MODIFY!\n\n"); 97 | 98 | try writer.writeAll("const isa = @import(\"isa\");\n\n"); 99 | 100 | try writer.writeAll("pub const Instruction = union(isa.Opcode) {"); 101 | 102 | for (positionals.values, 0..) |map, i| { 103 | const opcode = std.enums.EnumIndexer(isa.Opcode).keyForIndex(i); 104 | 105 | try writer.print("{}: ", .{std.zig.fmtId(@tagName(opcode))}); 106 | 107 | const BitSet = struct { 108 | name: u8, 109 | count: usize, 110 | 111 | pub fn lt(ctx: void, lh: @This(), rh: @This()) bool { 112 | _ = ctx; 113 | return lh.name < rh.name; 114 | } 115 | }; 116 | 117 | var items = std.ArrayList(BitSet).init(allocator); 118 | defer items.deinit(); 119 | 120 | var it = map.iterator(); 121 | while (it.next()) |entry| { 122 | try items.append(.{ 123 | .name = entry.key_ptr.*, 124 | .count = entry.value_ptr.*.slice().len, 125 | }); 126 | } 127 | 128 | std.sort.block(BitSet, items.items, {}, BitSet.lt); 129 | 130 | if (items.items.len == 0) { 131 | try writer.writeAll("void"); 132 | } else { 133 | try writer.writeAll("isa.opinfo."); 134 | for (items.items) |key| { 135 | try writer.print("{c}{d}", .{ key.name, key.count }); 136 | } 137 | } 138 | 139 | try writer.writeAll(",\n"); 140 | } 141 | 142 | try writer.writeAll("};\n\n"); 143 | 144 | try writer.writeAll("pub const lookup = [65536]isa.Opcode {"); 145 | 146 | for (lut, 0..) |v, i| { 147 | try writer.print(".{},", .{std.zig.fmtId(@tagName(v))}); 148 | if ((i + 1) % 16 == 0) { 149 | try writer.print("\n", .{}); 150 | } 151 | } 152 | 153 | try writer.writeAll("};\n\npub const positionals = .{"); 154 | 155 | for (positionals.values, 0..) |map, i| { 156 | try writer.print(".{} = .{{", .{std.zig.fmtId(@tagName(std.enums.EnumIndexer(isa.Opcode).keyForIndex(i)))}); 157 | var it = map.iterator(); 158 | while (it.next()) |entry| { 159 | try writer.print(".{{'{c}', .{{", .{entry.key_ptr.*}); 160 | 161 | const slice = entry.value_ptr.*.slice(); 162 | for (slice, 0..) |val, ii| { 163 | try writer.print("{d}", .{val}); 164 | if (ii != slice.len - 1) { 165 | try writer.writeAll(","); 166 | } 167 | } 168 | 169 | try writer.writeAll("}}"); 170 | 171 | if (it.index != it.len) { 172 | try writer.writeAll(","); 173 | } 174 | } 175 | try writer.writeAll("},"); 176 | if ((i + 1) % 16 == 0) { 177 | try writer.print("\n", .{}); 178 | } 179 | } 180 | 181 | try writer.writeAll("};"); 182 | 183 | const txt = try buf.toOwnedSliceSentinel(0); 184 | defer allocator.free(txt); 185 | 186 | var tree = try std.zig.Ast.parse(allocator, txt, .zig); 187 | defer tree.deinit(allocator); 188 | 189 | if (tree.errors.len != 0) { 190 | for (tree.errors) |err| { 191 | try tree.renderError(err, std.io.getStdErr().writer()); 192 | } 193 | try out.writer().writeAll(txt); 194 | } else { 195 | const render_result = try tree.render(allocator); 196 | defer allocator.free(render_result); 197 | 198 | try out.writer().writeAll(render_result); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /tools/isa.txt: -------------------------------------------------------------------------------- 1 | movw 0000 0001 DDDD RRRR 2 | muls 0000 0010 dddd rrrr 3 | nop 0000 0000 0000 0000 4 | mulsu 0000 0011 0ddd 0rrr 5 | fmul 0000 0011 0ddd 1rrr 6 | fmuls 0000 0011 1ddd 0rrr 7 | fmulsu 0000 0011 1ddd 1rrr 8 | cpc 0000 01rd dddd rrrr 9 | sbc 0000 10rd dddd rrrr 10 | add 0000 11rd dddd rrrr 11 | cpse 0001 00rd dddd rrrr 12 | cp 0001 01rd dddd rrrr 13 | sub 0001 10rd dddd rrrr 14 | adc 0001 11rd dddd rrrr 15 | and 0010 00rd dddd rrrr 16 | eor 0010 01rd dddd rrrr 17 | or 0010 10rd dddd rrrr 18 | mov 0010 11rd dddd rrrr 19 | cpi 0011 kkkk dddd kkkk 20 | sbci 0100 kkkk dddd kkkk 21 | subi 0101 kkkk dddd kkkk 22 | ori 0110 kkkk dddd kkkk 23 | andi 0111 kkkk dddd kkkk 24 | lds 1001 000d dddd 0000 kkkk kkkk kkkk kkkk 25 | ldz_ii 1001 000d dddd 0001 26 | ldz_iii 1001 000d dddd 0010 27 | lpm_ii 1001 000d dddd 0100 28 | lpm_iii 1001 000d dddd 0101 29 | elpm_ii 1001 000d dddd 0110 30 | elpm_iii 1001 000d dddd 0111 31 | ldy_ii 1001 000d dddd 1001 32 | ldy_iii 1001 000d dddd 1010 33 | ldy_iv 10q0 qq0d dddd 1qqq 34 | ldx_i 1001 000d dddd 1100 35 | ldx_ii 1001 000d dddd 1101 36 | ldx_iii 1001 000d dddd 1110 37 | pop 1001 000d dddd 1111 38 | sts 1001 001d dddd 0000 kkkk kkkk kkkk kkkk 39 | push 1001 001d dddd 1111 40 | stz_ii 1001 001r rrrr 0001 41 | stz_iii 1001 001r rrrr 0010 42 | xch 1001 001r rrrr 0100 43 | las 1001 001r rrrr 0101 44 | lac 1001 001r rrrr 0110 45 | lat 1001 001r rrrr 0111 46 | sty_ii 1001 001r rrrr 1001 47 | sty_iii 1001 001r rrrr 1010 48 | stx_i 1001 001r rrrr 1100 49 | stx_ii 1001 001r rrrr 1101 50 | stx_iii 1001 001r rrrr 1110 51 | ijmp 1001 0100 0000 1001 52 | eijmp 1001 0100 0001 1001 53 | bset 1001 0100 0sss 1000 54 | bclr 1001 0100 1sss 1000 55 | des 1001 0100 kkkk 1011 56 | ret 1001 0101 0000 1000 57 | icall 1001 0101 0000 1001 58 | reti 1001 0101 0001 1000 59 | eicall 1001 0101 0001 1001 60 | sleep 1001 0101 1000 1000 61 | wdr 1001 0101 1010 1000 62 | lpm_i 1001 0101 1100 1000 63 | elpm_i 1001 0101 1101 1000 64 | spm_i 1001 0101 1110 1000 65 | spm_ii 1001 0101 1111 1000 66 | com 1001 010d dddd 0000 67 | neg 1001 010d dddd 0001 68 | swap 1001 010d dddd 0010 69 | inc 1001 010d dddd 0011 70 | asr 1001 010d dddd 0101 71 | lsr 1001 010d dddd 0110 72 | ror 1001 010d dddd 0111 73 | dec 1001 010d dddd 1010 74 | jmp 1001 010k kkkk 110k kkkk kkkk kkkk kkkk 75 | call 1001 010k kkkk 111k kkkk kkkk kkkk kkkk 76 | adiw 1001 0110 kkdd kkkk 77 | sbiw 1001 0111 kkdd kkkk 78 | cbi 1001 1000 aaaa abbb 79 | sbic 1001 1001 aaaa abbb 80 | sbi 1001 1010 aaaa abbb 81 | sbis 1001 1011 aaaa abbb 82 | mul 1001 11rd dddd rrrr 83 | in 1011 0aad dddd aaaa 84 | out 1011 1aar rrrr aaaa 85 | ldz_iv 10q0 qq0d dddd 0qqq 86 | stz_iv 10q0 qq1r rrrr 0qqq 87 | sty_iv 10q0 qq1r rrrr 1qqq 88 | rcall 1101 kkkk kkkk kkkk 89 | ldi 1110 kkkk dddd kkkk 90 | brbs 1111 00kk kkkk ksss 91 | brbc 1111 01kk kkkk ksss 92 | bld 1111 100d dddd 0bbb 93 | bst 1111 101d dddd 0bbb 94 | sbrc 1111 110r rrrr 0bbb 95 | sbrs 1111 111r rrrr 0bbb 96 | rjmp 1100 kkkk kkkk kkkk 97 | break 1001 0101 1001 1000 98 | -------------------------------------------------------------------------------- /tools/no-avr-gcc.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn main() !u8 { 4 | try std.io.getStdErr().writeAll("avr-gcc not found. Please install avr-gcc!\n"); 5 | return 1; 6 | } 7 | --------------------------------------------------------------------------------