diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml new file mode 100644 index 0000000..723f5a2 --- /dev/null +++ b/.buildkite/pipeline.yml @@ -0,0 +1,7 @@ +steps: + - group: Build and Test + steps: + - label: Debug + command: zig build test + - label: ReleaseSmall + command: zig build test -Doptimize=ReleaseSmall diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1026e82..57245c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,4 +29,4 @@ jobs: version: master - name: Build tests - run: zig build -Doptimize=ReleaseSmall + run: zig build test -Doptimize=ReleaseSmall diff --git a/README.adoc b/README.adoc index 8d093a4..dd8fcab 100644 --- a/README.adoc +++ b/README.adoc @@ -12,94 +12,89 @@ toc::[] == Contributing -Please see the https://github.com/orgs/ZigEmbeddedGroup/projects/1/views/1[project page], its used as a place to brainstorm and organize work in ZEG. -There will be issues marked as `good first issue`, or drafts for larger ideas that need scoping/breaking ground on. +Please see the https://github.com/orgs/ZigEmbeddedGroup/projects/1/views/1[project page], its used as a place to brainstorm and organize work in ZEG. There will be issues marked as `good first issue`, or drafts for larger ideas that need scoping/breaking ground on. == Introduction -This repo contains the infrastructure for getting started in an embedded Zig project, as well as some code to interact with some chips/boards. -Specifically it offers: +This repo contains the infrastructure for getting started in an embedded Zig project, it "gets you to main()". Specifically it offers: * a single easy-to-use builder function that: ** generates your linker script -** sets up packages and start code +** sets up packages and startup code * generalized interfaces for common devices, such as UART. * device drivers for interacting with external hardware * an uncomplicated method to define xref:interrupts[interrupts] -== How to +== Design -Here's a number of things you might be interested in doing, and how to achieve them with microzig and other ZEG tools. +For MicroZig internals please see the xref:docs/design.adoc[Design Document]. -=== Embedded project with "supported" chip/board +== Does MicroZig support X hardware? -Start with an empty Zig project by running `zig init-exe`, and add microzig as a git submodule (or your choice of package manager). -Then in your `build.zig`: +MicroZig is designed to cover as wide swath of hardware as possible. The https://github.com/ZigEmbeddedGroup[Zig Embedded Group] has some repositories that contain hardware-specific code. You will find them with the `hardware-support-package` label. If you can't find your specific device, it doesn't mean that you can't run Zig on it, it's likely you're just the first! In that case see xref:#getting-microzig-on-new-hardware[Getting MicroZig on New Hardare]. + +Start with an empty Zig project by running `zig init-exe`, and add the hardware support package as a submodule. We'll use `microchip-atmega` in our example: [source,zig] ---- const std = @import("std"); -const microzig = @import("path/to/microzig/src/main.zig"); +const atmega = @import("deps/microchip-atmega/build.zig"); + +// the hardware support package should have microzig as a dependency +const microzig = @import("deps/hardware_support_package/deps/microzig/build.zig"); pub fn build(b: *std.build.Builder) !void { - const backing = .{ - .board = microzig.boards.arduino_nano, - - // if you don't have one of the boards, but do have one of the - // "supported" chips: - // .chip = microzig.chips.atmega328p, - }; - - var exe = microzig.addEmbeddedExecutable( - b, - "my-executable", - "src/main.zig", - backing, - .{ - // optional slice of packages that can be imported into your app: - // .packages = &my_packages, + const optimize = b.standardOptimizeOption(.{}); + var exe = microzig.addEmbeddedExecutable( b, .{ + .name = "my-executable", + .root_source_file = .{ + .path = "src/main.zig", + }, + .backing = .{ + .board = atmega.boards.arduino_nano, + + // instead of a board, you can use the raw chip as well + // .chip = atmega.chips.atmega328p, }, - ); - exe.setBuildMode(.ReleaseSmall); + .optimize = optimize, + }); exe.install(); } ---- -`zig build` and now you have an executable for an Arduino Nano. -In your application you can import `microzig` in order to interact with the hardware: +`zig build` and now you have an executable for an Arduino Nano. In your application you can import `microzig` in order to interact with the hardware: [source,zig] ---- const microzig = @import("microzig"); -// `microzig.chip.registers`: access to register definitions // `microzig.config`: comptime access to configuration -// - chip_name: name of the chip -// - has_board: true if board is configured -// - board_name: name of the board is has_board is true -// - cpu_name: name of the CPU +// `microzig.chip`: access to register definitions, generated code +// `microzig.board`: access to board information +// `microzig.hal`: access to hand-written code for interacting with the hardware +// `microzig.cpu`: access to AVR5 specific functions pub fn main() !void { // your program here } ---- -=== Embedded project with "unsupported" chip +== Getting MicroZig on New Hardware -If you have a board/chip that isn't defined in microzig, you can set it up yourself! -You need to have: +If you have a board/chip that isn't defined in microzig, you can set it up yourself! You need to have: * SVD or ATDF file defining registers * flash and ram address and sizes -First use https://github.com/ZigEmbeddedGroup/regz[regz] to generate the register definitions for your chip and save them to a file. -Then your `build.zig` is going to be the same, but you'll define the chip yourself: +First use https://github.com/ZigEmbeddedGroup/regz[Regz] to generate the register definitions for your chip and save them to a file. Then define the chip: [source,zig] ---- const nrf52832 = Chip{ .name = "nRF52832", - .path = "path/to/generated/file.zig", + .source = .{ + .path = "path/to/generated/file.zig", + }, .cpu = cpus.cortex_m4, .memory_regions = &.{ MemoryRegion{ .offset = 0x00000000, .length = 0x80000, .kind = .flash }, @@ -112,27 +107,48 @@ const backing = .{ }; ---- -[NOTE] -`regz` is also still in development, and while it tends to generate code well, it's possible that there will be errors in the generated code! -Please create an issue if you run into anything fishy. +It's important that the chip name actually match one of the entries under `devices` in the generated code. + +=== Optional: JSON Register Schema + +You can also invoke `regz` to generate a JSON representation of the hardware: + +[source] +---- +regz --json +---- -=== Interrupts +This file could then be used by tooling. You can add it to a `Chip` like so: -The current supported architectures for interrupt vector generation are ARM and AVR. -To define the Interrupt Service Routine (ISR) for a given interrupt, you create a function with the same name in an `interrupts` namespace: [source,zig] ---- -pub const interrupts = struct { - pub fn PCINT0() void { - // interrupt handling code - } +const nrf52832 = Chip{ + .name = "nRF52832", + .json_register_schema = .{ + .path = "path/to.json", + }, + // ... }; +---- + +== Interrupts + +The current supported architectures for interrupt vector generation are ARM and AVR. To define the Interrupt Service Routine (ISR) for a given interrupt, you create a function with the same name in an `interrupts` namespace, which is nested in a `microzig_options` namespace: + +[source,zig] +---- +pub const microzig_options = struct { + pub const interrupts = struct { + pub fn PCINT0() void { + // interrupt handling code + } + }; +} pub fn main() !void { // my application } ---- -We're using compile-time checks along with the generated code to determine the list of interrupts. -If a function is defined whose name is not in this list, you'll get a compiler error with the list of interrupts/valid names. +We're using compile-time checks along with the generated code to determine the list of interrupts. If a function is defined whose name is not in this list, you'll get a compiler error with the list of interrupts/valid names. diff --git a/build.zig b/build.zig index d453660..5e48566 100644 --- a/build.zig +++ b/build.zig @@ -5,6 +5,7 @@ const std = @import("std"); const LibExeObjStep = std.build.LibExeObjStep; const Module = std.build.Module; +const FileSource = std.build.FileSource; // alias for packages pub const LinkerScriptStep = @import("src/modules/LinkerScriptStep.zig"); @@ -26,10 +27,6 @@ pub const Backing = union(enum) { } }; -pub const BuildOptions = struct { - optimize: std.builtin.OptimizeMode = .Debug, -}; - pub const EmbeddedExecutable = struct { inner: *LibExeObjStep, @@ -70,19 +67,25 @@ fn root_dir() []const u8 { return std.fs.path.dirname(@src().file) orelse unreachable; } -pub fn addEmbeddedExecutable( - builder: *std.build.Builder, +pub const EmbeddedExecutableOptions = struct { name: []const u8, - source: []const u8, + source_file: std.build.FileSource, backing: Backing, - options: BuildOptions, + optimize: std.builtin.OptimizeMode = .Debug, + linkerscript_source_file: ?FileSource = null, +}; + +pub fn addEmbeddedExecutable( + builder: *std.build.Builder, + opts: EmbeddedExecutableOptions, ) *EmbeddedExecutable { - const has_board = (backing == .board); - const chip = switch (backing) { + const has_board = (opts.backing == .board); + const chip = switch (opts.backing) { .chip => |c| c, .board => |b| b.chip, }; + const has_hal = chip.hal != null; const config_file_name = blk: { const hash = hash_blk: { var hasher = std.hash.SipHash128(1, 2).init("abcdefhijklmnopq"); @@ -94,10 +97,10 @@ pub fn addEmbeddedExecutable( hasher.update(chip.cpu.name); hasher.update(chip.cpu.source.getPath(builder)); - if (backing == .board) { - hasher.update(backing.board.name); + if (opts.backing == .board) { + hasher.update(opts.backing.board.name); // TODO: see above - hasher.update(backing.board.source.getPath(builder)); + hasher.update(opts.backing.board.source.getPath(builder)); } var mac: [16]u8 = undefined; @@ -133,9 +136,11 @@ pub fn addEmbeddedExecutable( defer config_file.close(); var writer = config_file.writer(); + + writer.print("pub const has_hal = {};\n", .{has_hal}) catch unreachable; writer.print("pub const has_board = {};\n", .{has_board}) catch unreachable; if (has_board) - writer.print("pub const board_name = \"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(backing.board.name)}) catch unreachable; + writer.print("pub const board_name = \"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(opts.backing.board.name)}) catch unreachable; writer.print("pub const chip_name = \"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(chip.name)}) catch unreachable; writer.print("pub const cpu_name = \"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(chip.cpu.name)}) catch unreachable; @@ -164,17 +169,16 @@ pub fn addEmbeddedExecutable( }, })) catch unreachable; - microzig_module.dependencies.put("hal", builder.createModule(.{ - .source_file = if (chip.hal) |hal_module_path| - hal_module_path - else - .{ .path = comptime std.fmt.comptimePrint("{s}/src/core/empty.zig", .{root_dir()}) }, - .dependencies = &.{ - .{ .name = "microzig", .module = microzig_module }, - }, - })) catch unreachable; + if (chip.hal) |hal_module_source| { + microzig_module.dependencies.put("hal", builder.createModule(.{ + .source_file = hal_module_source, + .dependencies = &.{ + .{ .name = "microzig", .module = microzig_module }, + }, + })) catch unreachable; + } - switch (backing) { + switch (opts.backing) { .board => |board| { microzig_module.dependencies.put("board", builder.createModule(.{ .source_file = board.source, @@ -186,20 +190,13 @@ pub fn addEmbeddedExecutable( else => {}, } - microzig_module.dependencies.put("app", builder.createModule(.{ - .source_file = .{ .path = source }, - .dependencies = &.{ - .{ .name = "microzig", .module = microzig_module }, - }, - })) catch unreachable; - const exe = builder.allocator.create(EmbeddedExecutable) catch unreachable; exe.* = EmbeddedExecutable{ .inner = builder.addExecutable(.{ - .name = name, + .name = opts.name, .root_source_file = .{ .path = comptime std.fmt.comptimePrint("{s}/src/start.zig", .{root_dir()}) }, .target = chip.cpu.target, - .optimize = options.optimize, + .optimize = opts.optimize, }), }; @@ -209,23 +206,63 @@ pub fn addEmbeddedExecutable( // for the HAL it's true (it doesn't know the concept of threading) exe.inner.single_threaded = true; - const linkerscript = LinkerScriptStep.create(builder, chip) catch unreachable; - exe.inner.setLinkerScriptPath(.{ .generated = &linkerscript.generated_file }); + if (opts.linkerscript_source_file) |linkerscript_source_file| { + exe.inner.setLinkerScriptPath(linkerscript_source_file); + } else { + const linkerscript = LinkerScriptStep.create(builder, chip) catch unreachable; + exe.inner.setLinkerScriptPath(.{ .generated = &linkerscript.generated_file }); + } // TODO: // - Generate the linker scripts from the "chip" or "board" module instead of using hardcoded ones. // - This requires building another tool that runs on the host that compiles those files and emits the linker script. // - src/tools/linkerscript-gen.zig is the source file for this - exe.inner.bundle_compiler_rt = (exe.inner.target.cpu_arch.? != .avr); // don't bundle compiler_rt for AVR as it doesn't compile right now + exe.inner.bundle_compiler_rt = (exe.inner.target.getCpuArch() != .avr); // don't bundle compiler_rt for AVR as it doesn't compile right now exe.addModule("microzig", microzig_module); + exe.addModule("app", builder.createModule(.{ + .source_file = opts.source_file, + .dependencies = &.{ + .{ .name = "microzig", .module = microzig_module }, + }, + })); return exe; } +/// This build script validates usage patterns we expect from MicroZig pub fn build(b: *std.build.Builder) !void { + const backings = @import("test/backings.zig"); const optimize = b.standardOptimizeOption(.{}); - const test_step = b.step("test", "Builds and runs the library test suite"); - _ = optimize; - _ = test_step; + const minimal = addEmbeddedExecutable(b, .{ + .name = "minimal", + .source_file = .{ + .path = comptime root_dir() ++ "/test/programs/minimal.zig", + }, + .backing = backings.minimal, + .optimize = optimize, + }); + + const has_hal = addEmbeddedExecutable(b, .{ + .name = "has_hal", + .source_file = .{ + .path = comptime root_dir() ++ "/test/programs/has_hal.zig", + }, + .backing = backings.has_hal, + .optimize = optimize, + }); + + const has_board = addEmbeddedExecutable(b, .{ + .name = "has_board", + .source_file = .{ + .path = comptime root_dir() ++ "/test/programs/has_board.zig", + }, + .backing = backings.has_board, + .optimize = optimize, + }); + + const test_step = b.step("test", "build test programs"); + test_step.dependOn(&minimal.inner.step); + test_step.dependOn(&has_hal.inner.step); + test_step.dependOn(&has_board.inner.step); } diff --git a/docs/design.adoc b/docs/design.adoc new file mode 100644 index 0000000..4e97a2d --- /dev/null +++ b/docs/design.adoc @@ -0,0 +1,103 @@ += MicroZig Design +:imagesdir: images +:toc: macro + +toc::[] + +== Dependency Tree + +The build portion of MicroZig sets up a dependency graph like the following. + +image::deps.svg[] + +Your application lives in `app`, that's where `main()` resides. `root` contains the entry point, and will set up [zero-initialized data] and [uninitialized data]. This is all encapsulated in an `EmbeddedExecutable` object. It has methods to add dependencies acquired from the package manager. + +The `microzig` module has different namespaces, some are static, but the nodes you see in the diagram above are switched out according to your configured hardware. + +== Configurable Modules under `microzig` + +The configurable modules, with the exception of `config`, are able to import `microzig`. This exists so that one module may access another through `microzig`. This allows us to have patterns like the `hal` grabbing the frequency of an external crystal oscillator from `board`. Information stays where it's relevant. Circular dependencies of declarations will result in a compile error. + +=== `cpu` + +This module models your specific CPU, and is important for initializing memory. Generally you shouldn't need to define this yourself, it's likely that MicroZig will have the definition for you. + +Further research is needed for SOCs with multiple, heterogenous CPUs. Likely it means patching together multiple `EmbeddedExecutable`s. + +=== `chip` + +This module is intended for generated code from https://github.com/ZigEmbeddedGroup/regz[Regz]. You can handwrite this code if you like, but needs to be structured as follows: + +[source,zig] +---- +pub const types = struct { + // type definitions for peripherals here +}; + +pub const devices = struct { + pub const chip_name = struct { + // peripherals and interrupt table here ... + }; +}; +---- + +This code generation has a `devices` namespace where your specific hardware will reside. When defining a `Chip`, which is ultimately used in the creation of an `EmbeddedExecutable`, the name must exactly match the name under the `devices` namespace. It's okay if the name has whitespace, for that we can use `@""` notation. + +Let's say we had a device with the name `STM32F103`. We'd define our chip as: + +[source,zig] +---- +pub const stm32f103 = microzig.Chip{ + .name = "STM32F103", + .cpu = microzig.cpus.cortex_m3, + .source = .{ + .path = "path/to/generated.zig", + }, + .json_register_schema = .{ + .path = "path/to/generated.json", + }, + .hal = .{ + .path = "path/to/hal.zig", + }, + .memory_regions = &.{ + MemoryRegion{ .offset = 0x08000000, .length = 64 * 1024, .kind = .flash }, + MemoryRegion{ .offset = 0x20000000, .length = 20 * 1024, .kind = .ram }, + }, +}; +---- + +As discussed, the `name` must match a namespace under `devices` in the `chip` source. + +TODO + +When making a package that defines chips for others, see xref:hardware_support_packages.adoc[Hardware Support Packages] and xref:tricks.adoc#packaging-and-paths[Packaging and Paths]. + +=== `hal` + +This module contains hand-written code for interracting with the chip. + +TODO + +=== `board` + +TODO + +=== `config` + +TODO + +== Static Namespaces under `microzig` + +TODO + +== Linkerscript Generation + +TODO + +== JSON register schema + +TODO + +== Interrupts + +TODO diff --git a/docs/hardware_support_packages.adoc b/docs/hardware_support_packages.adoc new file mode 100644 index 0000000..4b8605f --- /dev/null +++ b/docs/hardware_support_packages.adoc @@ -0,0 +1,7 @@ += Hardware Support Packages +:imagesdir: images +:toc: macro + +toc::[] + +This document goes over the _suggested_ way to structure a hardware support package. At the end of the day, MicroZig is designed for flexibility, we do not require all "supported" hardware be defined in one monorepo. Instead we make it so that you can define your hardware locally if that best suites your project. diff --git a/docs/images/deps.dot b/docs/images/deps.dot new file mode 100644 index 0000000..1577418 --- /dev/null +++ b/docs/images/deps.dot @@ -0,0 +1,23 @@ +digraph { + root -> app + root -> microzig + + app -> microzig + app -> A + app -> B + + subgraph cluster_0 { + style="dotted" + graph [labelloc=b] + A -> C + B -> C + B -> D + label = "Application Dependencies"; + } + + microzig -> config + microzig -> cpu [dir="both"] + microzig -> chip [dir="both"] + microzig -> board [dir="both", style="dashed"] + microzig -> hal [dir="both", style="dashed"] +} diff --git a/docs/images/deps.svg b/docs/images/deps.svg new file mode 100644 index 0000000..20fd6e1 --- /dev/null +++ b/docs/images/deps.svg @@ -0,0 +1,171 @@ + + + + + + + + +cluster_0 + +Application Dependencies + + + +root + +root + + + +app + +app + + + +root->app + + + + + +microzig + +microzig + + + +root->microzig + + + + + +app->microzig + + + + + +A + +A + + + +app->A + + + + + +B + +B + + + +app->B + + + + + +config + +config + + + +microzig->config + + + + + +cpu + +cpu + + + +microzig->cpu + + + + + + +chip + +chip + + + +microzig->chip + + + + + + +board + +board + + + +microzig->board + + + + + + +hal + +hal + + + +microzig->hal + + + + + + +C + +C + + + +A->C + + + + + +B->C + + + + + +D + +D + + + +B->D + + + + + diff --git a/docs/tricks.adoc b/docs/tricks.adoc new file mode 100644 index 0000000..11055a9 --- /dev/null +++ b/docs/tricks.adoc @@ -0,0 +1,14 @@ += Tips and Tricks + +This document has some `build.zig` tricks for other pages to reference. + +== Packaging and Paths + +TODO + +[source,zig] +---- +fn root_dir() []const u8 { + return std.fs.path.dirname(@src().file) orelse unreachable; +} +---- diff --git a/src/microzig.zig b/src/microzig.zig index 98969cc..a40ea53 100644 --- a/src/microzig.zig +++ b/src/microzig.zig @@ -15,6 +15,9 @@ pub const app = @import("app"); /// and so on. pub const config = @import("config"); +/// Provides access to the low level features of the CPU. +pub const cpu = @import("cpu"); + /// Provides access to the low level features of the current microchip. pub const chip = struct { const inner = @import("chip"); @@ -22,16 +25,14 @@ pub const chip = struct { pub usingnamespace @field(inner.devices, config.chip_name); }; +/// Provides higher level APIs for interacting with hardware +pub const hal = if (config.has_hal) @import("hal") else void; + /// Provides access to board features or is `void` when no board is present. pub const board = if (config.has_board) @import("board") else void; -/// Provides access to the low level features of the CPU. -pub const cpu = @import("cpu"); pub const mmio = @import("mmio.zig"); pub const interrupt = @import("interrupt.zig"); - -pub const hal = @import("hal"); - pub const core = @import("core.zig"); pub const drivers = @import("drivers.zig"); diff --git a/src/modules/LinkerScriptStep.zig b/src/modules/LinkerScriptStep.zig index eb6df1d..368b7a1 100644 --- a/src/modules/LinkerScriptStep.zig +++ b/src/modules/LinkerScriptStep.zig @@ -54,11 +54,6 @@ fn make(step: *Step) !void { defer file.close(); const target = linkerscript.chip.cpu.target; - if (target.cpu_arch == null) { - std.log.err("target does not have 'cpu_arch'", .{}); - return error.NoCpuArch; - } - const writer = file.writer(); try writer.print( \\/* @@ -122,14 +117,15 @@ fn make(step: *Step) !void { \\ ); - if (target.cpu_arch.? == .arm or target.cpu_arch.? == .thumb) { - try writer.writeAll( + switch (target.getCpuArch()) { + .arm, .thumb => try writer.writeAll( \\ .ARM.exidx : { \\ *(.ARM.exidx* .gnu.linkonce.armexidx.*) \\ } >flash0 \\ \\ - ); + ), + else => {}, } try writer.writeAll( diff --git a/src/modules/cpus/avr5.zig b/src/modules/cpus/avr5.zig index a7423dd..b24e7cb 100644 --- a/src/modules/cpus/avr5.zig +++ b/src/modules/cpus/avr5.zig @@ -1,5 +1,6 @@ const std = @import("std"); const microzig = @import("microzig"); +const root = @import("root"); pub inline fn sei() void { asm volatile ("sei"); @@ -29,12 +30,13 @@ pub const vector_table = blk: { std.debug.assert(std.mem.eql(u8, "RESET", std.meta.fields(microzig.chip.VectorTable)[0].name)); var asm_str: []const u8 = "jmp microzig_start\n"; - const has_interrupts = @hasDecl(microzig.app, "interrupts"); + const has_interrupts = @hasDecl(root, "microzig_options") and @hasDecl(root.microzig_options, "interrupts"); if (has_interrupts) { - if (@hasDecl(microzig.app.interrupts, "RESET")) + const interrupts = root.microzig_options.interrupts; + if (@hasDecl(interrupts, "RESET")) @compileError("Not allowed to overload the reset vector"); - inline for (std.meta.declarations(microzig.app.interrupts)) |decl| { + inline for (std.meta.declarations(interrupts)) |decl| { if (!@hasField(microzig.chip.VectorTable, decl.name)) { var msg: []const u8 = "There is no such interrupt as '" ++ decl.name ++ "'. ISRs the 'interrupts' namespace must be one of:\n"; inline for (std.meta.fields(microzig.chip.VectorTable)) |field| { @@ -50,8 +52,9 @@ pub const vector_table = blk: { inline for (std.meta.fields(microzig.chip.VectorTable)[1..]) |field| { const new_insn = if (has_interrupts) overload: { - if (@hasDecl(microzig.app.interrupts, field.name)) { - const handler = @field(microzig.app.interrupts, field.name); + const interrupts = root.microzig_options.interrupts; + if (@hasDecl(interrupts, field.name)) { + const handler = @field(interrupts, field.name); const isr = make_isr_handler(field.name, handler); diff --git a/src/modules/cpus/cortex-m.zig b/src/modules/cpus/cortex-m.zig index 3a1f540..4802e9d 100644 --- a/src/modules/cpus/cortex-m.zig +++ b/src/modules/cpus/cortex-m.zig @@ -1,6 +1,6 @@ const std = @import("std"); const microzig = @import("microzig"); -const app = microzig.app; +const root = @import("root"); pub fn sei() void { asm volatile ("cpsie i"); @@ -82,8 +82,8 @@ fn is_valid_field(field_name: []const u8) bool { !std.mem.eql(u8, field_name, "reset"); } -const VectorTable = if (@hasDecl(microzig.app, "VectorTable")) - microzig.app.VectorTable +const VectorTable = if (@hasDecl(root, "microzig_options") and @hasDecl(root.microzig_options, "VectorTable")) + root.microzig_options.VectorTable else if (@hasDecl(microzig.hal, "VectorTable")) microzig.hal.VectorTable else @@ -95,12 +95,13 @@ pub var vector_table: VectorTable = blk: { .initial_stack_pointer = microzig.config.end_of_stack, .Reset = .{ .C = microzig.cpu.startup_logic._start }, }; - if (@hasDecl(app, "interrupts")) { - if (@typeInfo(app.interrupts) != .Struct) + if (@hasDecl(root, "microzig_options") and @hasDecl(root.microzig_options, "interrupts")) { + const interrupts = root.microzig_options.interrupts; + if (@typeInfo(interrupts) != .Struct) @compileLog("root.interrupts must be a struct"); - inline for (@typeInfo(app.interrupts).Struct.decls) |decl| { - const function = @field(app.interrupts, decl.name); + inline for (@typeInfo(interrupts).Struct.decls) |decl| { + const function = @field(interrupts, decl.name); if (!@hasField(VectorTable, decl.name)) { var msg: []const u8 = "There is no such interrupt as '" ++ decl.name ++ "'. Declarations in 'interrupts' must be one of:\n"; @@ -122,16 +123,9 @@ pub var vector_table: VectorTable = blk: { break :blk tmp; }; -const InterruptVector = if (@hasDecl(microzig.app, "InterruptVector")) - microzig.app.InterruptVector -else if (@hasDecl(microzig.hal, "InterruptVector")) - microzig.hal.InterruptVector -else - microzig.chip.InterruptVector; - fn create_interrupt_vector( comptime function: anytype, -) InterruptVector { +) microzig.interrupt.Handler { const calling_convention = @typeInfo(@TypeOf(function)).Fn.calling_convention; return switch (calling_convention) { .C => .{ .C = function }, diff --git a/src/modules/cpus/riscv32.zig b/src/modules/cpus/riscv32.zig index 49b3636..200b554 100644 --- a/src/modules/cpus/riscv32.zig +++ b/src/modules/cpus/riscv32.zig @@ -1,7 +1,6 @@ const std = @import("std"); const root = @import("root"); const microzig = @import("microzig"); -const app = microzig.app; pub fn sei() void { // asm volatile ("sei"); diff --git a/src/start.zig b/src/start.zig index ef4de2f..42a76f1 100644 --- a/src/start.zig +++ b/src/start.zig @@ -1,17 +1,24 @@ const std = @import("std"); const microzig = @import("microzig"); -const app = microzig.app; - -const options_override = if (@hasDecl(app, "std_options")) app.std_options else struct {}; -pub const std_options = struct { - pub usingnamespace options_override; - - // Conditionally provide a default no-op logFn if app does not have one - // defined. Parts of microzig use the stdlib logging facility and - // compilations will now fail on freestanding systems that use it but do - // not explicitly set `root.std_options.logFn` - pub usingnamespace if (!@hasDecl(options_override, "logFn")) - struct { +const app = @import("app"); + +pub usingnamespace app; + +// Use microzig panic handler if not defined by an application +pub usingnamespace if (!@hasDecl(app, "panic")) + struct { + pub const panic = microzig.panic; + } +else + struct {}; + +// Conditionally provide a default no-op logFn if app does not have one +// defined. Parts of microzig use the stdlib logging facility and +// compilations will now fail on freestanding systems that use it but do +// not explicitly set `root.std_options.logFn` +pub usingnamespace if (!@hasDecl(app, "std_options")) + struct { + pub const std_options = struct { pub fn logFn( comptime message_level: std.log.Level, comptime scope: @Type(.EnumLiteral), @@ -23,22 +30,18 @@ pub const std_options = struct { _ = format; _ = args; } - } - else - struct {}; -}; - -// Allow app to override the os API layer -pub const os = if (@hasDecl(app, "os")) - app.os -else - struct {}; - -// Allow app to override the panic handler -pub const panic = if (@hasDecl(app, "panic")) - app.panic + }; + } else - microzig.panic; + struct { + comptime { + // Technically the compiler's errors should be good enough that we + // shouldn't include errors like this, but since we add default + // behavior we should clarify the situation for the user. + if (!@hasDecl(app.std_options, "logFn")) + @compileError("By default MicroZig provides a no-op logging function. Since you are exporting `std_options`, you must export the stdlib logging function yourself."); + } + }; // Startup logic: comptime { @@ -102,7 +105,7 @@ export fn microzig_main() noreturn { // function. if (@hasDecl(app, "init")) app.init() - else if (@hasDecl(microzig.hal, "init")) + else if (microzig.hal != void and @hasDecl(microzig.hal, "init")) microzig.hal.init(); if (@typeInfo(return_type) == .ErrorUnion) { diff --git a/test/backings.zig b/test/backings.zig new file mode 100644 index 0000000..0a08dc5 --- /dev/null +++ b/test/backings.zig @@ -0,0 +1,67 @@ +//! This file contains some backings for different test programs +const std = @import("std"); +const microzig = @import("../build.zig"); + +fn root_dir() []const u8 { + return std.fs.path.dirname(@src().file) orelse unreachable; +} + +const cpu = microzig.Cpu{ + .name = "my_cpu", + .source = .{ + .path = root_dir() ++ "/cpu.zig", + }, + + // this will actually make it a native target, this is still fine + .target = std.zig.CrossTarget{ + .cpu_arch = .thumb, + .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0 }, + .os_tag = .freestanding, + .abi = .none, + }, +}; + +const minimal_chip = microzig.Chip{ + .name = "minimal_chip", + .cpu = cpu, + .source = .{ + .path = root_dir() ++ "/chip.zig", + }, + .memory_regions = &.{ + .{ .offset = 0x00000, .length = 0x10000, .kind = .flash }, + .{ .offset = 0x10000, .length = 0x10000, .kind = .ram }, + }, +}; + +const chip_with_hal = microzig.Chip{ + .name = "chip_with_hal", + .cpu = cpu, + .source = .{ + .path = root_dir() ++ "/chip.zig", + }, + .hal = .{ + .path = root_dir() ++ "/hal.zig", + }, + .memory_regions = &.{ + .{ .offset = 0x00000, .length = 0x10000, .kind = .flash }, + .{ .offset = 0x10000, .length = 0x10000, .kind = .ram }, + }, +}; + +pub const minimal = microzig.Backing{ + .chip = minimal_chip, +}; + +pub const has_hal = microzig.Backing{ + .chip = chip_with_hal, +}; + +pub const has_board = microzig.Backing{ + .board = .{ + .name = "has_board", + .chip = minimal_chip, + .source = .{ + .path = root_dir() ++ "/board.zig", + }, + }, +}; diff --git a/src/core/empty.zig b/test/board.zig similarity index 100% rename from src/core/empty.zig rename to test/board.zig diff --git a/test/chip.zig b/test/chip.zig new file mode 100644 index 0000000..a3ad12e --- /dev/null +++ b/test/chip.zig @@ -0,0 +1,6 @@ +pub const types = struct {}; + +pub const devices = struct { + pub const minimal_chip = struct {}; + pub const chip_with_hal = struct {}; +}; diff --git a/test/cpu.zig b/test/cpu.zig new file mode 100644 index 0000000..7892a04 --- /dev/null +++ b/test/cpu.zig @@ -0,0 +1,3 @@ +pub const startup_logic = struct {}; + +pub fn cli() void {} diff --git a/test/hal.zig b/test/hal.zig new file mode 100644 index 0000000..e69de29 diff --git a/test/programs/blinky.zig b/test/programs/blinky.zig index d84cf96..16a841d 100644 --- a/test/programs/blinky.zig +++ b/test/programs/blinky.zig @@ -6,17 +6,17 @@ const led_pin = if (micro.config.has_board) .@"Arduino Nano" => micro.Pin("D13"), .@"Arduino Uno" => micro.Pin("D13"), .@"mbed LPC1768" => micro.Pin("LED-1"), - .@"STM32F3DISCOVERY" => micro.Pin("LD3"), - .@"STM32F4DISCOVERY" => micro.Pin("LD5"), - .@"STM32F429IDISCOVERY" => micro.Pin("LD4"), + .STM32F3DISCOVERY => micro.Pin("LD3"), + .STM32F4DISCOVERY => micro.Pin("LD5"), + .STM32F429IDISCOVERY => micro.Pin("LD4"), .@"Longan Nano" => micro.Pin("PA2"), else => @compileError("unknown board"), } else switch (micro.config.chip_name) { - .@"ATmega328p" => micro.Pin("PB5"), + .ATmega328p => micro.Pin("PB5"), .@"NXP LPC1768" => micro.Pin("P1.18"), - .@"STM32F103x8" => micro.Pin("PC13"), - .@"GD32VF103x8" => micro.Pin("PA2"), + .STM32F103x8 => micro.Pin("PC13"), + .GD32VF103x8 => micro.Pin("PA2"), else => @compileError("unknown chip"), }; diff --git a/test/programs/has_board.zig b/test/programs/has_board.zig new file mode 100644 index 0000000..8141376 --- /dev/null +++ b/test/programs/has_board.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const assert = std.debug.assert; +const microzig = @import("microzig"); + +comptime { + assert(microzig.config.has_board); + assert(!microzig.config.has_hal); +} + +pub fn main() void {} diff --git a/test/programs/has_dependencies.zig b/test/programs/has_dependencies.zig new file mode 100644 index 0000000..e69de29 diff --git a/test/programs/has_hal.zig b/test/programs/has_hal.zig new file mode 100644 index 0000000..eed6b0c --- /dev/null +++ b/test/programs/has_hal.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const assert = std.debug.assert; +const microzig = @import("microzig"); + +comptime { + assert(microzig.config.has_hal); + assert(!microzig.config.has_board); +} + +pub fn main() void {} diff --git a/test/programs/minimal.zig b/test/programs/minimal.zig index 5258ce3..3ef20f7 100644 --- a/test/programs/minimal.zig +++ b/test/programs/minimal.zig @@ -1,5 +1,11 @@ -const micro = @import("microzig"); +const std = @import("std"); +const assert = std.debug.assert; +const microzig = @import("microzig"); +const peripherals = microzig.chip.peripherals; -pub fn main() void { - // This function will contain the application logic. +comptime { + assert(!microzig.config.has_board); + assert(!microzig.config.has_hal); } + +pub fn main() void {}