Add zig example

This commit is contained in:
daurnimator 2020-01-22 23:47:43 +11:00
parent 53cffe1761
commit 6a60a7a83d
No known key found for this signature in database
GPG Key ID: 45B429A8F9D9D22A
12 changed files with 710 additions and 0 deletions

3
riscv-zig-blink/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
zig-cache/
riscv-zig-blink
riscv-zig-blink.bin

14
riscv-zig-blink/README.md Normal file
View File

@ -0,0 +1,14 @@
# riscv-zig-blink
Written against zig 0.5.0+518dbd30c
You can obtain the zig compiler via https://ziglang.org/download/
e.g. a linux user might run:
```
curl -L https://ziglang.org/builds/zig-linux-x86_64-0.5.0+518dbd30c.tar.xz | tar -xJf -
alias zig=./zig-linux-x86_64-0.5.0+518dbd30c/zig
```
Run `zig build --help` from this directory for usage and options.
`zig build run -Drelease` will compile the demo and and run it on your connected FOMU.

46
riscv-zig-blink/build.zig Normal file
View File

@ -0,0 +1,46 @@
const std = @import("std");
const Builder = std.build.Builder;
pub fn build(b: *Builder) void {
b.setPreferredReleaseMode(.ReleaseSmall);
const mode = b.standardReleaseOptions();
const elf = b.addExecutable("riscv-zig-blink", "src/main.zig");
elf.setTheTarget(.{
.Cross = .{
.arch = .riscv32,
.os = .freestanding,
.abi = .none,
.cpu_features = .{
.cpu = &std.Target.riscv.cpu.generic_rv32,
.features = std.Target.Cpu.Feature.Set.empty,
},
},
});
elf.setLinkerScriptPath("ld/linker.ld");
elf.setBuildMode(mode);
// The ELF file contains debug symbols and can be passed to gdb for remote debugging
if (b.option(bool, "emit-elf", "Should an ELF file be emitted in the current directory?") orelse false) {
elf.setOutputDir(".");
}
const binary = b.addSystemCommand(&[_][]const u8{
b.option([]const u8, "objcopy", "objcopy executable to use (defaults to riscv64-unknown-elf-objcopy)") orelse "riscv64-unknown-elf-objcopy",
"-I",
"elf32-littleriscv",
"-O",
"binary",
});
binary.addArtifactArg(elf);
binary.addArg("riscv-zig-blink.bin");
b.default_step.dependOn(&binary.step);
const run_cmd = b.addSystemCommand(&[_][]const u8{
"dfu-util",
"-D",
"riscv-zig-blink.bin",
});
run_cmd.step.dependOn(&binary.step);
const run_step = b.step("run", "Upload and run the app on your FOMU");
run_step.dependOn(&run_cmd.step);
}

View File

@ -0,0 +1,60 @@
ENTRY(_start)
__DYNAMIC = 0;
MEMORY {
sram : ORIGIN = 0x10000000, LENGTH = 0x00020000
rom : ORIGIN = 0x20040000, LENGTH = 0x100000 /* 1 MBit */
}
SECTIONS
{
.text :
{
_ftext = .;
*(.text.start)
*(.text .stub .text.* .gnu.linkonce.t.*)
_etext = .;
} > rom
.rodata :
{
. = ALIGN(4);
_frodata = .;
*(.rodata .rodata.* .gnu.linkonce.r.*)
*(.rodata1)
*(.srodata)
. = ALIGN(4);
_erodata = .;
} > rom
.data : AT (ADDR(.rodata) + SIZEOF (.rodata))
{
. = ALIGN(4);
_fdata = .;
*(.data .data.* .gnu.linkonce.d.*)
*(.data1)
*(.ramtext .ramtext.*)
_gp = ALIGN(16);
*(.sdata .sdata.* .gnu.linkonce.s.* .sdata2 .sdata2.*)
_edata = ALIGN(16); /* Make sure _edata is >= _gp. */
} > sram
.bss :
{
. = ALIGN(4);
_fbss = .;
*(.dynsbss)
*(.sbss .sbss.* .gnu.linkonce.sb.*)
*(.scommon)
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} > sram
_end = .;
}
PROVIDE(_fstack = ORIGIN(sram) + LENGTH(sram) - 4);

View File

@ -0,0 +1,75 @@
const builtin = @import("builtin");
const std = @import("std");
pub const MESSIBLE = @import("./fomu/messible.zig").MESSIBLE;
pub const REBOOT = @import("./fomu/reboot.zig").REBOOT;
pub const RGB = @import("./fomu/rgb.zig").RGB;
pub const TIMER0 = @import("./fomu/timer0.zig").TIMER0;
pub const TOUCH = @import("./fomu/touch.zig").TOUCH;
pub const SYSTEM_CLOCK_FREQUENCY = 12000000;
pub const start = @import("./fomu/start.zig");
// This forces start.zig file to be imported
comptime {
_ = start;
}
/// Panic function that sets LED to red and flashing + prints the panic message over messible
pub fn panic(message: []const u8, stack_trace: ?*builtin.StackTrace) noreturn {
@setCold(true);
// Put LED into non-raw flashing mode
RGB.CTRL.* = .{
.EXE = true,
.CURREN = true,
.RGBLEDEN = true,
.RRAW = false,
.GRAW = false,
.BRAW = false,
};
// Set colour to red
RGB.setColour(255, 0, 0);
// Enable the LED driver, and set 250 Hz mode.
RGB.setControlRegister(.{
.enable = true,
.fr = .@"250",
.quick_stop = false,
.outskew = false,
.output_polarity = .active_high,
.pwm_mode = .linear,
.BRMSBEXT = 0,
});
messibleOutstream.print("PANIC: {}\r\n", .{message}) catch void;
while (true) {
// TODO: Use @breakpoint() once https://reviews.llvm.org/D69390 is available
asm volatile ("ebreak");
}
}
const OutStream = std.io.OutStream(error{});
pub const messibleOutstream = &OutStream{
.writeFn = struct {
pub fn messibleWrite(self: *const OutStream, bytes: []const u8) error{}!void {
var left: []const u8 = bytes;
while (left.len > 0) {
const bytes_written = MESSIBLE.write(left);
left = left[bytes_written..];
}
}
}.messibleWrite,
};
const InStream = std.io.InStream(error{});
pub const messibleInstream = &InStream{
.writeFn = struct {
pub fn messibleRead(self: *const InStream, buffer: []u8) error{}!usize {
while (true) {
const bytes_read = MESSIBLE.read(buffer);
if (bytes_read != 0) return bytes_read;
}
}
}.messibleRead,
};

View File

@ -0,0 +1,52 @@
/// Messible: An Ansible for Messages
/// https://rm.fomu.im/messible.html
///
/// An Ansible is a system for instant communication across vast distances,
/// from a small portable device to a huge terminal far away. A Messible is
/// a message- passing system from embedded devices to a host system. You can
/// use it to get very simple printf()-style support over a debug channel.
///
/// The Messible assumes the host has a way to peek into the devices memory
/// space. This is the case with the Wishbone bridge, which allows both the
/// device and the host to access the same memory.
///
/// At its core, a Messible is a FIFO. As long as the STATUS.FULL bit is 0,
/// the device can write data into the Messible by writing into the IN.
/// However, if this value is 1, you need to decide if you want to wait for it
/// to empty (if the other side is just slow), or if you want to drop the
/// message. From the host side, you need to read STATUS.HAVE to see if there
/// is data in the FIFO. If there is, read OUT to get the most recent byte,
/// which automatically advances the READ pointer.
pub const MESSIBLE = struct {
const base = 0xe0008000;
/// Write half of the FIFO to send data out the Messible. Writing to this register advances the write pointer automatically.
pub const IN = @intToPtr(*volatile u8, base + 0x0);
/// Read half of the FIFO to receive data on the Messible. Reading from this register advances the read pointer automatically.
pub const OUT = @intToPtr(*volatile u8, base + 0x4);
pub const STATUS = @intToPtr(*volatile packed struct {
/// if more data can fit into the IN FIFO.
FULL: bool,
/// if data can be read from the OUT FIFO.
HAVE: bool,
}, base + 0x8);
pub fn write(data: []const u8) usize {
for (data) |c, i| {
if (STATUS.*.FULL) return i;
IN.* = c;
}
return data.len;
}
pub fn read(dst: []u8) usize {
for (dst) |*c, i| {
if (!STATUS.*.HAVE) return i;
c.* = OUT.*;
}
return dst.len;
}
};

View File

@ -0,0 +1,22 @@
/// https://rm.fomu.im/reboot.html
pub const REBOOT = struct {
const base = 0xe0006000;
/// Provides support for rebooting the FPGA.
/// You can select which of the four images to reboot to.
pub const CTRL = @intToPtr(*volatile packed struct {
/// Which image to reboot to. SB_WARMBOOT supports four images that are configured at FPGA startup.
/// The bootloader is image 0, so set these bits to 0 to reboot back into the bootloader.
image: u2,
/// A reboot key used to prevent accidental reboots when writing to random areas of memory.
/// To initiate a reboot, set this to 0b101011.
key: u6,
}, base + 0x0);
/// This sets the reset vector for the VexRiscv.
/// This address will be used whenever the CPU is reset, for example
/// through a debug bridge. You should update this address whenever
/// you load a new program, to enable the debugger to run `mon reset`
pub const ADDR = @intToPtr(*volatile u32, base + 0x4);
};

View File

@ -0,0 +1,140 @@
/// https://rm.fomu.im/rgb.html
pub const RGB = struct {
const base = 0xe0006800;
/// This is the value for the SB_LEDDA_IP.DAT register.
/// It is directly written into the SB_LEDDA_IP hardware block, so you
/// should refer to http://www.latticesemi.com/view_document?document_id=50668.
/// The contents of this register are written to the address specified
/// in ADDR immediately upon writing this register.
pub const DAT = @intToPtr(*volatile u8, base + 0x0);
/// This register is directly connected to SB_LEDDA_IP.ADDR.
/// This register controls the address that is updated whenever DAT is written.
/// Writing to this register has no immediate effect data isnt written until the DAT register is written.
pub const ADDR = @intToPtr(*volatile u4, base + 0x4);
pub const Register = enum {
PWRR = 1,
PWRG = 2,
PWRB = 3,
BCRR = 5,
BCFR = 6,
CR0 = 8,
BR = 9,
ONR = 10,
OFR = 11,
};
pub fn setRegister(reg: Register, value: u8) void {
ADDR.* = @enumToInt(reg);
DAT.* = value;
}
const CR0 = packed struct {
BRMSBEXT: u2,
pwm_mode: enum(u1) {
linear = 0,
/// The Polynomial for the LFSR is X^(8) + X^(5) + X^3 + X + 1
LFSR = 1,
},
quick_stop: bool,
outskew: bool,
/// PWM output polarity
output_polarity: enum(u1) {
active_high = 0,
active_low = 1,
},
/// Flick rate for PWM (in Hz)
fr: enum(u1) {
@"125" = 0,
@"250" = 1,
},
/// LED Driver enabled?
enable: bool,
};
pub fn setControlRegister(value: CR0) void {
setRegister(.CR0, @bitCast(u8, value));
}
pub const Breathe = packed struct {
/// Breathe rate is in 128 ms increments
rate: u4,
_pad: u1 = 0,
mode: enum(u1) {
fixed = 0,
modulate = 1,
},
pwm_range_extend: bool,
enable: bool,
};
pub fn setBreatheRegister(reg: enum {
On,
Off,
}, value: Breathe) void {
setRegister(switch (reg) {
.On => .BCRR,
.Off => .BCFR,
}, @bitCast(u8, value));
}
/// Control logic for the RGB LED and LEDDA hardware PWM LED block.
pub const CTRL = @intToPtr(*volatile packed struct {
/// Enable the fading pattern?
/// Connected to `SB_LEDDA_IP.LEDDEXE`.
EXE: bool,
/// Enable the current source?
/// Connected to `SB_RGBA_DRV.CURREN`.
CURREN: bool,
/// Enable the RGB PWM control logic?
/// Connected to `SB_RGBA_DRV.RGBLEDEN`.
RGBLEDEN: bool,
/// Enable raw control of the red LED via the RAW.R register.
RRAW: bool,
/// Enable raw control of the green LED via the RAW.G register.
GRAW: bool,
/// Enable raw control of the blue LED via the RAW.B register.
BRAW: bool,
}, base + 0x8);
/// Normally the hardware SB_LEDDA_IP block controls the brightness of the
/// LED, creating a gentle fading pattern.
/// However, by setting the appropriate bit in CTRL, it is possible to
/// manually control the three individual LEDs.
pub const RAW = @intToPtr(*volatile packed struct {
/// Red
R: bool,
/// Green
G: bool,
/// Blue
B: bool,
}, base + 0xc);
pub fn setColour(r: u8, g: u8, b: u8) void {
setRegister(.PWRR, r);
setRegister(.PWRG, g);
setRegister(.PWRB, b);
}
};

View File

@ -0,0 +1,63 @@
const root = @import("root");
const std = @import("std");
extern var _ftext: u8;
extern var _etext: u8;
extern var _frodata: u8 align(4);
extern var _erodata: u8 align(4);
extern var _fdata: u8 align(4);
extern var _gp: u8 align(16);
extern var _edata: u8 align(16);
extern var _fbss: u8 align(4);
extern var _ebss: u8 align(4);
extern var _end: u8;
extern var _fstack: u8;
export fn _start() linksection(".text.start") callconv(.Naked) noreturn {
asm volatile (
\\ j over_magic
\\ .word 0xb469075a // Magic value for config flags
\\ .word 0x00000020 // USB_NO_RESET flag so we can attach the debugger
\\over_magic:
);
// set the stack pointer to `&_fstack`
asm volatile (
\\ la sp, _fstack + 4
);
// zero out bss
asm (
\\ la a0, %[fbss]
\\ la a1, %[ebss]
\\bss_loop:
\\ beq a0, a1, bss_done
\\ sw zero, 0(a0)
\\ add a0, a0,4
\\ j bss_loop
\\bss_done:
: [ret] "=" (-> void)
: [fbss] "m" (&_fbss),
[ebss] "m" (&_ebss)
);
// copy data from data rom (which is after rodata) to data
asm (
\\ la t0, %[erodata]
\\ la t1, %[fdata]
\\ la t2, %[edata]
\\data_loop:
\\ lw t3, 0(t0)
\\ sw t3, 0(t1)
\\ addi t0, t0, 4
\\ addi t1, t1, 4
\\ bltu t1, t2, data_loop
: [ret] "=" (-> void)
: [erodata] "m" (&_erodata),
[fdata] "m" (&_fdata),
[edata] "m" (&_edata)
);
// call user's main
root.main();
}

View File

@ -0,0 +1,109 @@
/// Provides a generic Timer core.
///
/// The Timer is implemented as a countdown timer that can be used in various modes:
/// - Polling: Returns current countdown value to software.
/// - One-Shot: Loads itself and stops when value reaches 0.
/// - Periodic: (Re-)Loads itself when value reaches 0.
///
/// *en* register allows the user to enable/disable the Timer. When the Timer
/// is enabled, it is automatically loaded with the value of load register.
///
/// When the Timer reaches 0, it is automatically reloaded with value of reload register.
///
/// The user can latch the current countdown value by writing to update_value
/// register, it will update value register with current countdown value.
///
/// To use the Timer in One-Shot mode, the user needs to:
/// - Disable the timer.
/// - Set the load register to the expected duration.
/// - (Re-)Enable the Timer.
///
/// To use the Timer in Periodic mode, the user needs to:
/// - Disable the Timer.
/// - Set the load register to 0.
/// - Set the reload register to the expected period.
/// - Enable the Timer.
///
/// For both modes, the CPU can be advertised by an IRQ that the
/// duration/period has elapsed. (The CPU can also do software polling with
/// update_value and value to know the elapsed duration)
pub const TIMER0 = struct {
const base = 0xe0002800;
pub const LOAD3 = @intToPtr(*volatile u8, base + 0x0);
pub const LOAD2 = @intToPtr(*volatile u8, base + 0x4);
pub const LOAD1 = @intToPtr(*volatile u8, base + 0x8);
pub const LOAD0 = @intToPtr(*volatile u8, base + 0xc);
/// Load value when Timer is (re-)enabled.
/// In One-Shot mode, the value written to this register specify the
/// Timers duration in clock cycles.
pub fn load(x: u32) void {
LOAD3.* = @truncate(u8, x >> 24);
LOAD2.* = @truncate(u8, x >> 16);
LOAD1.* = @truncate(u8, x >> 8);
LOAD0.* = @truncate(u8, x);
}
pub const RELOAD3 = @intToPtr(*volatile u8, base + 0x10);
pub const RELOAD2 = @intToPtr(*volatile u8, base + 0x14);
pub const RELOAD1 = @intToPtr(*volatile u8, base + 0x18);
pub const RELOAD0 = @intToPtr(*volatile u8, base + 0x1c);
/// Reload value when Timer reaches 0.
/// In Periodic mode, the value written to this register specify the
/// Timers period in clock cycles.
pub fn reload(x: u32) void {
RELOAD3.* = @truncate(u8, x >> 24);
RELOAD2.* = @truncate(u8, x >> 16);
RELOAD1.* = @truncate(u8, x >> 8);
RELOAD0.* = @truncate(u8, x);
}
/// Enable of the Timer.
/// Set if to 1 to enable/start the Timer and 0 to disable the Timer
pub const EN = @intToPtr(*volatile bool, base + 0x20);
pub fn start() void {
EN.* = true;
}
pub fn stop() void {
EN.* = false;
}
/// Update of the current countdown value.
/// A write to this register latches the current countdown value to value
/// register.
pub const UPDATE_VALUE = @intToPtr(*volatile bool, base + 0x24);
pub const VALUE3 = @intToPtr(*volatile u8, base + 0x28);
pub const VALUE2 = @intToPtr(*volatile u8, base + 0x2c);
pub const VALUE1 = @intToPtr(*volatile u8, base + 0x30);
pub const VALUE0 = @intToPtr(*volatile u8, base + 0x34);
pub fn latchedValue() u32 {
return (@as(u32, VALUE3.*) << 24) |
(@as(u32, VALUE2.*) << 16) |
(@as(u32, VALUE1.*) << 8) |
(@as(u32, VALUE0.*));
}
pub fn value() u32 {
UPDATE_VALUE.* = true;
return latchedValue();
}
/// This register contains the current raw level of the Event trigger.
/// Writes to this register have no effect.
pub const EV_STATUS = @intToPtr(*volatile bool, base + 0x38);
/// When an Event occurs, the corresponding bit will be set in this
/// register. To clear the Event, set the corresponding bit in this
/// register.
pub const EV_PENDING = @intToPtr(*volatile bool, base + 0x3c);
/// This register enables the corresponding Events. Write a 0 to this
/// register to disable individual events.
pub const EV_ENABLE = @intToPtr(*volatile bool, base + 0x40);
};

View File

@ -0,0 +1,13 @@
/// https://rm.fomu.im/touch.html
pub const TOUCH = struct {
const base = 0xe0005800;
/// Output values for pads 1-4
pub const TOUCH_O = @intToPtr(*volatile u4, base + 0x0);
/// Output enable control for pads 1-4
pub const TOUCH_OE = @intToPtr(*volatile u4, base + 0x4);
/// Input value for pads 1-4
pub const TOUCH_1 = @intToPtr(*volatile u4, base + 0x8);
};

View File

@ -0,0 +1,113 @@
const builtin = @import("builtin");
const std = @import("std");
const fomu = @import("./fomu.zig");
pub fn panic(message: []const u8, stack_trace: ?*builtin.StackTrace) noreturn {
@setCold(true);
fomu.panic(message, stack_trace);
}
fn rgb_init() void {
// Turn on the RGB block and current enable, as well as enabling led control
fomu.RGB.CTRL.* = .{
.EXE = true,
.CURREN = true,
.RGBLEDEN = true,
.RRAW = false,
.GRAW = false,
.BRAW = false,
};
// Enable the LED driver, and set 250 Hz mode.
// Also set quick stop, which we'll use to switch patterns quickly.
fomu.RGB.setControlRegister(.{
.enable = true,
.fr = .@"250",
.quick_stop = true,
.outskew = false,
.output_polarity = .active_high,
.pwm_mode = .linear,
.BRMSBEXT = 0,
});
// Set clock register to 12 MHz / 64 kHz - 1
fomu.RGB.setRegister(.BR, (fomu.SYSTEM_CLOCK_FREQUENCY / 64000) - 1);
// Blink on/off time is in 32 ms increments
fomu.RGB.setRegister(.ONR, 1); // Amount of time to stay "on"
fomu.RGB.setRegister(.OFR, 0); // Amount of time to stay "off"
fomu.RGB.setBreatheRegister(.On, .{
.enable = true,
.pwm_range_extend = false,
.mode = .fixed,
.rate = 1,
});
fomu.RGB.setBreatheRegister(.Off, .{
.enable = true,
.pwm_range_extend = false,
.mode = .fixed,
.rate = 1,
});
}
const Colour = struct {
r: u8,
g: u8,
b: u8,
};
/// Input a value 0 to 255 to get a colour value.
/// The colours are a transition r - g - b - back to r.
fn colourWheel(wheelPos: u8) Colour {
var c: Colour = undefined;
var wp = 255 - wheelPos;
switch (wp) {
0...84 => {
c = .{
.r = 255 - wp * 3,
.g = 0,
.b = wp * 3,
};
},
85...169 => {
wp -= 85;
c = .{
.r = 0,
.g = wp * 3,
.b = 255 - wp * 3,
};
},
170...255 => {
wp -= 170;
c = .{
.r = wp * 3,
.g = 255 - wp * 3,
.b = 0,
};
},
}
return c;
}
fn msleep(ms: usize) void {
fomu.TIMER0.stop();
fomu.TIMER0.reload(0);
fomu.TIMER0.load(fomu.SYSTEM_CLOCK_FREQUENCY / 1000 * ms);
fomu.TIMER0.start();
while (fomu.TIMER0.value() != 0) {}
}
pub fn main() noreturn {
rgb_init();
var i: u8 = 0;
while (true) : (i +%= 1) {
const colour = colourWheel(i);
fomu.RGB.setColour(colour.r, colour.g, colour.b);
msleep(80);
}
unreachable;
}