Mimic new stdlib build API (#111)

* start on new docs to match api

* tweak the MicroZig dependency tree

* use microzig_options

* remove empty.zig

* add test programs

* keep it to one arch, bump flash for debug
wch-ch32v003
Matt Knight 2 years ago committed by GitHub
parent 11214ed8ba
commit b6fc3abbf7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,7 @@
steps:
- group: Build and Test
steps:
- label: Debug
command: zig build test
- label: ReleaseSmall
command: zig build test -Doptimize=ReleaseSmall

@ -29,4 +29,4 @@ jobs:
version: master version: master
- name: Build tests - name: Build tests
run: zig build -Doptimize=ReleaseSmall run: zig build test -Doptimize=ReleaseSmall

@ -12,94 +12,89 @@ toc::[]
== Contributing == 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. 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.
There will be issues marked as `good first issue`, or drafts for larger ideas that need scoping/breaking ground on.
== Introduction == 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. This repo contains the infrastructure for getting started in an embedded Zig project, it "gets you to main()". Specifically it offers:
Specifically it offers:
* a single easy-to-use builder function that: * a single easy-to-use builder function that:
** generates your linker script ** generates your linker script
** sets up packages and start code ** sets up packages and startup code
* generalized interfaces for common devices, such as UART. * generalized interfaces for common devices, such as UART.
* device drivers for interacting with external hardware * device drivers for interacting with external hardware
* an uncomplicated method to define xref:interrupts[interrupts] * 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). 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].
Then in your `build.zig`:
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] [source,zig]
---- ----
const std = @import("std"); 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 { pub fn build(b: *std.build.Builder) !void {
const backing = .{ const optimize = b.standardOptimizeOption(.{});
.board = microzig.boards.arduino_nano, var exe = microzig.addEmbeddedExecutable( b, .{
.name = "my-executable",
// if you don't have one of the boards, but do have one of the .root_source_file = .{
// "supported" chips: .path = "src/main.zig",
// .chip = microzig.chips.atmega328p, },
}; .backing = .{
.board = atmega.boards.arduino_nano,
var exe = microzig.addEmbeddedExecutable(
b, // instead of a board, you can use the raw chip as well
"my-executable", // .chip = atmega.chips.atmega328p,
"src/main.zig",
backing,
.{
// optional slice of packages that can be imported into your app:
// .packages = &my_packages,
}, },
); .optimize = optimize,
exe.setBuildMode(.ReleaseSmall); });
exe.install(); exe.install();
} }
---- ----
`zig build` and now you have an executable for an Arduino Nano. `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:
In your application you can import `microzig` in order to interact with the hardware:
[source,zig] [source,zig]
---- ----
const microzig = @import("microzig"); const microzig = @import("microzig");
// `microzig.chip.registers`: access to register definitions
// `microzig.config`: comptime access to configuration // `microzig.config`: comptime access to configuration
// - chip_name: name of the chip // `microzig.chip`: access to register definitions, generated code
// - has_board: true if board is configured // `microzig.board`: access to board information
// - board_name: name of the board is has_board is true // `microzig.hal`: access to hand-written code for interacting with the hardware
// - cpu_name: name of the CPU // `microzig.cpu`: access to AVR5 specific functions
pub fn main() !void { pub fn main() !void {
// your program here // 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! If you have a board/chip that isn't defined in microzig, you can set it up yourself! You need to have:
You need to have:
* SVD or ATDF file defining registers * SVD or ATDF file defining registers
* flash and ram address and sizes * 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. 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:
Then your `build.zig` is going to be the same, but you'll define the chip yourself:
[source,zig] [source,zig]
---- ----
const nrf52832 = Chip{ const nrf52832 = Chip{
.name = "nRF52832", .name = "nRF52832",
.path = "path/to/generated/file.zig", .source = .{
.path = "path/to/generated/file.zig",
},
.cpu = cpus.cortex_m4, .cpu = cpus.cortex_m4,
.memory_regions = &.{ .memory_regions = &.{
MemoryRegion{ .offset = 0x00000000, .length = 0x80000, .kind = .flash }, MemoryRegion{ .offset = 0x00000000, .length = 0x80000, .kind = .flash },
@ -112,27 +107,48 @@ const backing = .{
}; };
---- ----
[NOTE] It's important that the chip name actually match one of the entries under `devices` in the generated code.
`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. === Optional: JSON Register Schema
You can also invoke `regz` to generate a JSON representation of the hardware:
[source]
----
regz --json <path to svd/atdf>
----
=== 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] [source,zig]
---- ----
pub const interrupts = struct { const nrf52832 = Chip{
pub fn PCINT0() void { .name = "nRF52832",
// interrupt handling code .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 { pub fn main() !void {
// my application // my application
} }
---- ----
We're using compile-time checks along with the generated code to determine the list of interrupts. 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.
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.

@ -5,6 +5,7 @@
const std = @import("std"); const std = @import("std");
const LibExeObjStep = std.build.LibExeObjStep; const LibExeObjStep = std.build.LibExeObjStep;
const Module = std.build.Module; const Module = std.build.Module;
const FileSource = std.build.FileSource;
// alias for packages // alias for packages
pub const LinkerScriptStep = @import("src/modules/LinkerScriptStep.zig"); 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 { pub const EmbeddedExecutable = struct {
inner: *LibExeObjStep, inner: *LibExeObjStep,
@ -70,19 +67,25 @@ fn root_dir() []const u8 {
return std.fs.path.dirname(@src().file) orelse unreachable; return std.fs.path.dirname(@src().file) orelse unreachable;
} }
pub fn addEmbeddedExecutable( pub const EmbeddedExecutableOptions = struct {
builder: *std.build.Builder,
name: []const u8, name: []const u8,
source: []const u8, source_file: std.build.FileSource,
backing: Backing, backing: Backing,
options: BuildOptions, optimize: std.builtin.OptimizeMode = .Debug,
linkerscript_source_file: ?FileSource = null,
};
pub fn addEmbeddedExecutable(
builder: *std.build.Builder,
opts: EmbeddedExecutableOptions,
) *EmbeddedExecutable { ) *EmbeddedExecutable {
const has_board = (backing == .board); const has_board = (opts.backing == .board);
const chip = switch (backing) { const chip = switch (opts.backing) {
.chip => |c| c, .chip => |c| c,
.board => |b| b.chip, .board => |b| b.chip,
}; };
const has_hal = chip.hal != null;
const config_file_name = blk: { const config_file_name = blk: {
const hash = hash_blk: { const hash = hash_blk: {
var hasher = std.hash.SipHash128(1, 2).init("abcdefhijklmnopq"); 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.name);
hasher.update(chip.cpu.source.getPath(builder)); hasher.update(chip.cpu.source.getPath(builder));
if (backing == .board) { if (opts.backing == .board) {
hasher.update(backing.board.name); hasher.update(opts.backing.board.name);
// TODO: see above // TODO: see above
hasher.update(backing.board.source.getPath(builder)); hasher.update(opts.backing.board.source.getPath(builder));
} }
var mac: [16]u8 = undefined; var mac: [16]u8 = undefined;
@ -133,9 +136,11 @@ pub fn addEmbeddedExecutable(
defer config_file.close(); defer config_file.close();
var writer = config_file.writer(); 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; writer.print("pub const has_board = {};\n", .{has_board}) catch unreachable;
if (has_board) 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 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; writer.print("pub const cpu_name = \"{}\";\n", .{std.fmt.fmtSliceEscapeUpper(chip.cpu.name)}) catch unreachable;
@ -164,17 +169,16 @@ pub fn addEmbeddedExecutable(
}, },
})) catch unreachable; })) catch unreachable;
microzig_module.dependencies.put("hal", builder.createModule(.{ if (chip.hal) |hal_module_source| {
.source_file = if (chip.hal) |hal_module_path| microzig_module.dependencies.put("hal", builder.createModule(.{
hal_module_path .source_file = hal_module_source,
else .dependencies = &.{
.{ .path = comptime std.fmt.comptimePrint("{s}/src/core/empty.zig", .{root_dir()}) }, .{ .name = "microzig", .module = microzig_module },
.dependencies = &.{ },
.{ .name = "microzig", .module = microzig_module }, })) catch unreachable;
}, }
})) catch unreachable;
switch (backing) { switch (opts.backing) {
.board => |board| { .board => |board| {
microzig_module.dependencies.put("board", builder.createModule(.{ microzig_module.dependencies.put("board", builder.createModule(.{
.source_file = board.source, .source_file = board.source,
@ -186,20 +190,13 @@ pub fn addEmbeddedExecutable(
else => {}, 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; const exe = builder.allocator.create(EmbeddedExecutable) catch unreachable;
exe.* = EmbeddedExecutable{ exe.* = EmbeddedExecutable{
.inner = builder.addExecutable(.{ .inner = builder.addExecutable(.{
.name = name, .name = opts.name,
.root_source_file = .{ .path = comptime std.fmt.comptimePrint("{s}/src/start.zig", .{root_dir()}) }, .root_source_file = .{ .path = comptime std.fmt.comptimePrint("{s}/src/start.zig", .{root_dir()}) },
.target = chip.cpu.target, .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) // for the HAL it's true (it doesn't know the concept of threading)
exe.inner.single_threaded = true; exe.inner.single_threaded = true;
const linkerscript = LinkerScriptStep.create(builder, chip) catch unreachable; if (opts.linkerscript_source_file) |linkerscript_source_file| {
exe.inner.setLinkerScriptPath(.{ .generated = &linkerscript.generated_file }); exe.inner.setLinkerScriptPath(linkerscript_source_file);
} else {
const linkerscript = LinkerScriptStep.create(builder, chip) catch unreachable;
exe.inner.setLinkerScriptPath(.{ .generated = &linkerscript.generated_file });
}
// TODO: // TODO:
// - Generate the linker scripts from the "chip" or "board" module instead of using hardcoded ones. // - 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. // - 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 // - 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("microzig", microzig_module);
exe.addModule("app", builder.createModule(.{
.source_file = opts.source_file,
.dependencies = &.{
.{ .name = "microzig", .module = microzig_module },
},
}));
return exe; return exe;
} }
/// This build script validates usage patterns we expect from MicroZig
pub fn build(b: *std.build.Builder) !void { pub fn build(b: *std.build.Builder) !void {
const backings = @import("test/backings.zig");
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const test_step = b.step("test", "Builds and runs the library test suite");
_ = optimize; const minimal = addEmbeddedExecutable(b, .{
_ = test_step; .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);
} }

@ -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

@ -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.

@ -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"]
}

@ -0,0 +1,171 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.50.0 (0)
-->
<!-- Pages: 1 -->
<svg width="546pt" height="299pt"
viewBox="0.00 0.00 546.00 299.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 295)">
<polygon fill="white" stroke="transparent" points="-4,4 -4,-295 542,-295 542,4 -4,4"/>
<g id="clust1" class="cluster">
<title>cluster_0</title>
<polygon fill="none" stroke="black" stroke-dasharray="1,5" points="8,-8 8,-155 168,-155 168,-8 8,-8"/>
<text text-anchor="middle" x="88" y="-15.8" font-family="Times,serif" font-size="14.00">Application Dependencies</text>
</g>
<!-- root -->
<g id="node1" class="node">
<title>root</title>
<ellipse fill="none" stroke="black" cx="209" cy="-273" rx="27" ry="18"/>
<text text-anchor="middle" x="209" y="-269.3" font-family="Times,serif" font-size="14.00">root</text>
</g>
<!-- app -->
<g id="node2" class="node">
<title>app</title>
<ellipse fill="none" stroke="black" cx="182" cy="-201" rx="27" ry="18"/>
<text text-anchor="middle" x="182" y="-197.3" font-family="Times,serif" font-size="14.00">app</text>
</g>
<!-- root&#45;&gt;app -->
<g id="edge1" class="edge">
<title>root&#45;&gt;app</title>
<path fill="none" stroke="black" d="M202.6,-255.41C199.49,-247.34 195.67,-237.43 192.17,-228.35"/>
<polygon fill="black" stroke="black" points="195.4,-227.03 188.54,-218.96 188.87,-229.55 195.4,-227.03"/>
</g>
<!-- microzig -->
<g id="node3" class="node">
<title>microzig</title>
<ellipse fill="none" stroke="black" cx="287" cy="-129" rx="42.49" ry="18"/>
<text text-anchor="middle" x="287" y="-125.3" font-family="Times,serif" font-size="14.00">microzig</text>
</g>
<!-- root&#45;&gt;microzig -->
<g id="edge2" class="edge">
<title>root&#45;&gt;microzig</title>
<path fill="none" stroke="black" d="M217.95,-255.71C231.38,-231.26 256.88,-184.83 272.85,-155.76"/>
<polygon fill="black" stroke="black" points="275.93,-157.43 277.67,-146.98 269.79,-154.06 275.93,-157.43"/>
</g>
<!-- app&#45;&gt;microzig -->
<g id="edge3" class="edge">
<title>app&#45;&gt;microzig</title>
<path fill="none" stroke="black" d="M200.77,-187.49C216.28,-177.14 238.61,-162.26 256.66,-150.23"/>
<polygon fill="black" stroke="black" points="258.74,-153.04 265.12,-144.59 254.86,-147.22 258.74,-153.04"/>
</g>
<!-- A -->
<g id="node4" class="node">
<title>A</title>
<ellipse fill="none" stroke="black" cx="133" cy="-129" rx="27" ry="18"/>
<text text-anchor="middle" x="133" y="-125.3" font-family="Times,serif" font-size="14.00">A</text>
</g>
<!-- app&#45;&gt;A -->
<g id="edge4" class="edge">
<title>app&#45;&gt;A</title>
<path fill="none" stroke="black" d="M171.13,-184.46C164.84,-175.49 156.81,-164.02 149.74,-153.92"/>
<polygon fill="black" stroke="black" points="152.45,-151.68 143.84,-145.49 146.71,-155.69 152.45,-151.68"/>
</g>
<!-- B -->
<g id="node5" class="node">
<title>B</title>
<ellipse fill="none" stroke="black" cx="61" cy="-129" rx="27" ry="18"/>
<text text-anchor="middle" x="61" y="-125.3" font-family="Times,serif" font-size="14.00">B</text>
</g>
<!-- app&#45;&gt;B -->
<g id="edge5" class="edge">
<title>app&#45;&gt;B</title>
<path fill="none" stroke="black" d="M160.65,-189.59C143.22,-180.88 118.11,-167.85 97,-155 93.75,-153.02 90.41,-150.87 87.12,-148.68"/>
<polygon fill="black" stroke="black" points="88.89,-145.65 78.66,-142.89 84.94,-151.43 88.89,-145.65"/>
</g>
<!-- config -->
<g id="node8" class="node">
<title>config</title>
<ellipse fill="none" stroke="black" cx="209" cy="-57" rx="33.29" ry="18"/>
<text text-anchor="middle" x="209" y="-53.3" font-family="Times,serif" font-size="14.00">config</text>
</g>
<!-- microzig&#45;&gt;config -->
<g id="edge9" class="edge">
<title>microzig&#45;&gt;config</title>
<path fill="none" stroke="black" d="M269.69,-112.46C258.97,-102.84 245.04,-90.34 233.23,-79.75"/>
<polygon fill="black" stroke="black" points="235.24,-76.85 225.46,-72.77 230.57,-82.06 235.24,-76.85"/>
</g>
<!-- cpu -->
<g id="node9" class="node">
<title>cpu</title>
<ellipse fill="none" stroke="black" cx="287" cy="-57" rx="27" ry="18"/>
<text text-anchor="middle" x="287" y="-53.3" font-family="Times,serif" font-size="14.00">cpu</text>
</g>
<!-- microzig&#45;&gt;cpu -->
<g id="edge10" class="edge">
<title>microzig&#45;&gt;cpu</title>
<path fill="none" stroke="black" d="M287,-100.67C287,-95.69 287,-90.49 287,-85.51"/>
<polygon fill="black" stroke="black" points="283.5,-100.7 287,-110.7 290.5,-100.7 283.5,-100.7"/>
<polygon fill="black" stroke="black" points="290.5,-85.1 287,-75.1 283.5,-85.1 290.5,-85.1"/>
</g>
<!-- chip -->
<g id="node10" class="node">
<title>chip</title>
<ellipse fill="none" stroke="black" cx="359" cy="-57" rx="27" ry="18"/>
<text text-anchor="middle" x="359" y="-53.3" font-family="Times,serif" font-size="14.00">chip</text>
</g>
<!-- microzig&#45;&gt;chip -->
<g id="edge11" class="edge">
<title>microzig&#45;&gt;chip</title>
<path fill="none" stroke="black" d="M310.65,-105.01C319.12,-96.78 328.62,-87.54 336.94,-79.44"/>
<polygon fill="black" stroke="black" points="308.07,-102.64 303.34,-112.12 312.95,-107.65 308.07,-102.64"/>
<polygon fill="black" stroke="black" points="339.47,-81.87 344.2,-72.39 334.59,-76.85 339.47,-81.87"/>
</g>
<!-- board -->
<g id="node11" class="node">
<title>board</title>
<ellipse fill="none" stroke="black" cx="435" cy="-57" rx="30.59" ry="18"/>
<text text-anchor="middle" x="435" y="-53.3" font-family="Times,serif" font-size="14.00">board</text>
</g>
<!-- microzig&#45;&gt;board -->
<g id="edge12" class="edge">
<title>microzig&#45;&gt;board</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M323.49,-110.74C347.78,-99.25 379.41,-84.29 402.77,-73.24"/>
<polygon fill="black" stroke="black" points="321.69,-107.72 314.15,-115.16 324.68,-114.05 321.69,-107.72"/>
<polygon fill="black" stroke="black" points="404.37,-76.36 411.92,-68.92 401.38,-70.03 404.37,-76.36"/>
</g>
<!-- hal -->
<g id="node12" class="node">
<title>hal</title>
<ellipse fill="none" stroke="black" cx="511" cy="-57" rx="27" ry="18"/>
<text text-anchor="middle" x="511" y="-53.3" font-family="Times,serif" font-size="14.00">hal</text>
</g>
<!-- microzig&#45;&gt;hal -->
<g id="edge13" class="edge">
<title>microzig&#45;&gt;hal</title>
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M333.14,-117.27C371.63,-107.79 427.62,-92.72 475,-75 476.84,-74.31 478.73,-73.56 480.61,-72.78"/>
<polygon fill="black" stroke="black" points="332.15,-113.91 323.26,-119.68 333.81,-120.71 332.15,-113.91"/>
<polygon fill="black" stroke="black" points="482.2,-75.91 489.93,-68.66 479.37,-69.5 482.2,-75.91"/>
</g>
<!-- C -->
<g id="node6" class="node">
<title>C</title>
<ellipse fill="none" stroke="black" cx="131" cy="-57" rx="27" ry="18"/>
<text text-anchor="middle" x="131" y="-53.3" font-family="Times,serif" font-size="14.00">C</text>
</g>
<!-- A&#45;&gt;C -->
<g id="edge6" class="edge">
<title>A&#45;&gt;C</title>
<path fill="none" stroke="black" d="M132.51,-110.7C132.29,-102.98 132.02,-93.71 131.77,-85.11"/>
<polygon fill="black" stroke="black" points="135.27,-85 131.49,-75.1 128.28,-85.2 135.27,-85"/>
</g>
<!-- B&#45;&gt;C -->
<g id="edge7" class="edge">
<title>B&#45;&gt;C</title>
<path fill="none" stroke="black" d="M75.5,-113.5C85.27,-103.73 98.31,-90.69 109.3,-79.7"/>
<polygon fill="black" stroke="black" points="111.92,-82.03 116.51,-72.49 106.97,-77.08 111.92,-82.03"/>
</g>
<!-- D -->
<g id="node7" class="node">
<title>D</title>
<ellipse fill="none" stroke="black" cx="59" cy="-57" rx="27" ry="18"/>
<text text-anchor="middle" x="59" y="-53.3" font-family="Times,serif" font-size="14.00">D</text>
</g>
<!-- B&#45;&gt;D -->
<g id="edge8" class="edge">
<title>B&#45;&gt;D</title>
<path fill="none" stroke="black" d="M60.51,-110.7C60.29,-102.98 60.02,-93.71 59.77,-85.11"/>
<polygon fill="black" stroke="black" points="63.27,-85 59.49,-75.1 56.28,-85.2 63.27,-85"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

@ -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;
}
----

@ -15,6 +15,9 @@ pub const app = @import("app");
/// and so on. /// and so on.
pub const config = @import("config"); 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. /// Provides access to the low level features of the current microchip.
pub const chip = struct { pub const chip = struct {
const inner = @import("chip"); const inner = @import("chip");
@ -22,16 +25,14 @@ pub const chip = struct {
pub usingnamespace @field(inner.devices, config.chip_name); 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. /// Provides access to board features or is `void` when no board is present.
pub const board = if (config.has_board) @import("board") else void; 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 mmio = @import("mmio.zig");
pub const interrupt = @import("interrupt.zig"); pub const interrupt = @import("interrupt.zig");
pub const hal = @import("hal");
pub const core = @import("core.zig"); pub const core = @import("core.zig");
pub const drivers = @import("drivers.zig"); pub const drivers = @import("drivers.zig");

@ -54,11 +54,6 @@ fn make(step: *Step) !void {
defer file.close(); defer file.close();
const target = linkerscript.chip.cpu.target; 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(); const writer = file.writer();
try writer.print( try writer.print(
\\/* \\/*
@ -122,14 +117,15 @@ fn make(step: *Step) !void {
\\ \\
); );
if (target.cpu_arch.? == .arm or target.cpu_arch.? == .thumb) { switch (target.getCpuArch()) {
try writer.writeAll( .arm, .thumb => try writer.writeAll(
\\ .ARM.exidx : { \\ .ARM.exidx : {
\\ *(.ARM.exidx* .gnu.linkonce.armexidx.*) \\ *(.ARM.exidx* .gnu.linkonce.armexidx.*)
\\ } >flash0 \\ } >flash0
\\ \\
\\ \\
); ),
else => {},
} }
try writer.writeAll( try writer.writeAll(

@ -1,5 +1,6 @@
const std = @import("std"); const std = @import("std");
const microzig = @import("microzig"); const microzig = @import("microzig");
const root = @import("root");
pub inline fn sei() void { pub inline fn sei() void {
asm volatile ("sei"); 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)); 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"; 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 (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"); @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)) { 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"; 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| { 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| { inline for (std.meta.fields(microzig.chip.VectorTable)[1..]) |field| {
const new_insn = if (has_interrupts) overload: { const new_insn = if (has_interrupts) overload: {
if (@hasDecl(microzig.app.interrupts, field.name)) { const interrupts = root.microzig_options.interrupts;
const handler = @field(microzig.app.interrupts, field.name); if (@hasDecl(interrupts, field.name)) {
const handler = @field(interrupts, field.name);
const isr = make_isr_handler(field.name, handler); const isr = make_isr_handler(field.name, handler);

@ -1,6 +1,6 @@
const std = @import("std"); const std = @import("std");
const microzig = @import("microzig"); const microzig = @import("microzig");
const app = microzig.app; const root = @import("root");
pub fn sei() void { pub fn sei() void {
asm volatile ("cpsie i"); asm volatile ("cpsie i");
@ -82,8 +82,8 @@ fn is_valid_field(field_name: []const u8) bool {
!std.mem.eql(u8, field_name, "reset"); !std.mem.eql(u8, field_name, "reset");
} }
const VectorTable = if (@hasDecl(microzig.app, "VectorTable")) const VectorTable = if (@hasDecl(root, "microzig_options") and @hasDecl(root.microzig_options, "VectorTable"))
microzig.app.VectorTable root.microzig_options.VectorTable
else if (@hasDecl(microzig.hal, "VectorTable")) else if (@hasDecl(microzig.hal, "VectorTable"))
microzig.hal.VectorTable microzig.hal.VectorTable
else else
@ -95,12 +95,13 @@ pub var vector_table: VectorTable = blk: {
.initial_stack_pointer = microzig.config.end_of_stack, .initial_stack_pointer = microzig.config.end_of_stack,
.Reset = .{ .C = microzig.cpu.startup_logic._start }, .Reset = .{ .C = microzig.cpu.startup_logic._start },
}; };
if (@hasDecl(app, "interrupts")) { if (@hasDecl(root, "microzig_options") and @hasDecl(root.microzig_options, "interrupts")) {
if (@typeInfo(app.interrupts) != .Struct) const interrupts = root.microzig_options.interrupts;
if (@typeInfo(interrupts) != .Struct)
@compileLog("root.interrupts must be a struct"); @compileLog("root.interrupts must be a struct");
inline for (@typeInfo(app.interrupts).Struct.decls) |decl| { inline for (@typeInfo(interrupts).Struct.decls) |decl| {
const function = @field(app.interrupts, decl.name); const function = @field(interrupts, decl.name);
if (!@hasField(VectorTable, 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"; 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; 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( fn create_interrupt_vector(
comptime function: anytype, comptime function: anytype,
) InterruptVector { ) microzig.interrupt.Handler {
const calling_convention = @typeInfo(@TypeOf(function)).Fn.calling_convention; const calling_convention = @typeInfo(@TypeOf(function)).Fn.calling_convention;
return switch (calling_convention) { return switch (calling_convention) {
.C => .{ .C = function }, .C => .{ .C = function },

@ -1,7 +1,6 @@
const std = @import("std"); const std = @import("std");
const root = @import("root"); const root = @import("root");
const microzig = @import("microzig"); const microzig = @import("microzig");
const app = microzig.app;
pub fn sei() void { pub fn sei() void {
// asm volatile ("sei"); // asm volatile ("sei");

@ -1,17 +1,24 @@
const std = @import("std"); const std = @import("std");
const microzig = @import("microzig"); const microzig = @import("microzig");
const app = microzig.app; const app = @import("app");
const options_override = if (@hasDecl(app, "std_options")) app.std_options else struct {}; pub usingnamespace app;
pub const std_options = struct {
pub usingnamespace options_override; // Use microzig panic handler if not defined by an application
pub usingnamespace if (!@hasDecl(app, "panic"))
// Conditionally provide a default no-op logFn if app does not have one struct {
// defined. Parts of microzig use the stdlib logging facility and pub const panic = microzig.panic;
// compilations will now fail on freestanding systems that use it but do }
// not explicitly set `root.std_options.logFn` else
pub usingnamespace if (!@hasDecl(options_override, "logFn")) struct {};
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( pub fn logFn(
comptime message_level: std.log.Level, comptime message_level: std.log.Level,
comptime scope: @Type(.EnumLiteral), comptime scope: @Type(.EnumLiteral),
@ -23,22 +30,18 @@ pub const std_options = struct {
_ = format; _ = format;
_ = args; _ = 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 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: // Startup logic:
comptime { comptime {
@ -102,7 +105,7 @@ export fn microzig_main() noreturn {
// function. // function.
if (@hasDecl(app, "init")) if (@hasDecl(app, "init"))
app.init() app.init()
else if (@hasDecl(microzig.hal, "init")) else if (microzig.hal != void and @hasDecl(microzig.hal, "init"))
microzig.hal.init(); microzig.hal.init();
if (@typeInfo(return_type) == .ErrorUnion) { if (@typeInfo(return_type) == .ErrorUnion) {

@ -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",
},
},
};

@ -0,0 +1,6 @@
pub const types = struct {};
pub const devices = struct {
pub const minimal_chip = struct {};
pub const chip_with_hal = struct {};
};

@ -0,0 +1,3 @@
pub const startup_logic = struct {};
pub fn cli() void {}

@ -6,17 +6,17 @@ const led_pin = if (micro.config.has_board)
.@"Arduino Nano" => micro.Pin("D13"), .@"Arduino Nano" => micro.Pin("D13"),
.@"Arduino Uno" => micro.Pin("D13"), .@"Arduino Uno" => micro.Pin("D13"),
.@"mbed LPC1768" => micro.Pin("LED-1"), .@"mbed LPC1768" => micro.Pin("LED-1"),
.@"STM32F3DISCOVERY" => micro.Pin("LD3"), .STM32F3DISCOVERY => micro.Pin("LD3"),
.@"STM32F4DISCOVERY" => micro.Pin("LD5"), .STM32F4DISCOVERY => micro.Pin("LD5"),
.@"STM32F429IDISCOVERY" => micro.Pin("LD4"), .STM32F429IDISCOVERY => micro.Pin("LD4"),
.@"Longan Nano" => micro.Pin("PA2"), .@"Longan Nano" => micro.Pin("PA2"),
else => @compileError("unknown board"), else => @compileError("unknown board"),
} }
else switch (micro.config.chip_name) { else switch (micro.config.chip_name) {
.@"ATmega328p" => micro.Pin("PB5"), .ATmega328p => micro.Pin("PB5"),
.@"NXP LPC1768" => micro.Pin("P1.18"), .@"NXP LPC1768" => micro.Pin("P1.18"),
.@"STM32F103x8" => micro.Pin("PC13"), .STM32F103x8 => micro.Pin("PC13"),
.@"GD32VF103x8" => micro.Pin("PA2"), .GD32VF103x8 => micro.Pin("PA2"),
else => @compileError("unknown chip"), else => @compileError("unknown chip"),
}; };

@ -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 {}

@ -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 {}

@ -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 { comptime {
// This function will contain the application logic. assert(!microzig.config.has_board);
assert(!microzig.config.has_hal);
} }
pub fn main() void {}

Loading…
Cancel
Save