Add console tetris demo

This commit is contained in:
Toby Jaffey 2025-12-07 21:02:22 +00:00
parent cd95e63af5
commit c0711213ae
9 changed files with 261 additions and 0 deletions

View file

@ -25,11 +25,13 @@ Although based on a fully fledged CPU emulator, uvm32 is intended for executing
* [apps/sketch](apps/sketch) C Arduino/Wiring/Processing type program in `setup()` and `loop()` style * [apps/sketch](apps/sketch) C Arduino/Wiring/Processing type program in `setup()` and `loop()` style
* [apps/rust-hello](apps/rust-hello) Rust hello world program (note, the version of rust installed by brew on mac has issues, use the official rust installer from https://rust-lang.org/learn/get-started/) * [apps/rust-hello](apps/rust-hello) Rust hello world program (note, the version of rust installed by brew on mac has issues, use the official rust installer from https://rust-lang.org/learn/get-started/)
* [apps/zig-mandel](apps/zig-mandel) Zig ASCII mandelbrot generator program * [apps/zig-mandel](apps/zig-mandel) Zig ASCII mandelbrot generator program
* [apps/zigtris](apps/zigtris) Zig Tetris (https://github.com/ringtailsoftware/zigtris) WASD+space to play
## Quickstart ## Quickstart
make make
host/host precompiled/mandel.bin host/host precompiled/mandel.bin
host/host precompiled/zigtris.bin
Build one of the sample apps (requires docker for C, or Zig, or Rust) Build one of the sample apps (requires docker for C, or Zig, or Rust)

View file

@ -5,6 +5,7 @@ all:
(cd sketch && make) (cd sketch && make)
(cd helloworld && make) (cd helloworld && make)
(cd zig-mandel && make) (cd zig-mandel && make)
(cd zigtris && make)
(cd rust-hello && make) (cd rust-hello && make)
(cd hello-asm && make) (cd hello-asm && make)
@ -12,6 +13,7 @@ clean:
(cd sketch && make clean) (cd sketch && make clean)
(cd helloworld && make clean) (cd helloworld && make clean)
(cd zig-mandel && make clean) (cd zig-mandel && make clean)
(cd zigtris && make clean)
(cd rust-hello && make clean) (cd rust-hello && make clean)
(cd hello-asm && make clean) (cd hello-asm && make clean)

12
apps/zigtris/Makefile Normal file
View file

@ -0,0 +1,12 @@
all:
# zig's objcopy is broken, so use external tool
# https://ziggit.dev/t/addobjcopy-producing-zero-padding-at-start-of-binary/13384
zig build && docker run -v `pwd`:/data -w /data --rm riscv-dev riscv64-unknown-elf-objcopy zig-out/bin/zigtris -O binary zigtris.bin
clean:
rm -rf zigtris.bin zig-out .zig-cache
test: all
../../host/host zigtris.bin

66
apps/zigtris/build.zig Normal file
View file

@ -0,0 +1,66 @@
const std = @import("std");
const CrossTarget = @import("std").zig.CrossTarget;
const Target = @import("std").Target;
const Feature = @import("std").Target.Cpu.Feature;
pub fn build(b: *std.Build) void {
const features = Target.riscv.Feature;
var disabled_features = Feature.Set.empty;
var enabled_features = Feature.Set.empty;
// disable all CPU extensions
disabled_features.addFeature(@intFromEnum(features.a));
disabled_features.addFeature(@intFromEnum(features.c));
disabled_features.addFeature(@intFromEnum(features.d));
disabled_features.addFeature(@intFromEnum(features.e));
disabled_features.addFeature(@intFromEnum(features.f));
// except multiply
enabled_features.addFeature(@intFromEnum(features.m));
const target = b.resolveTargetQuery(.{
.cpu_arch = Target.Cpu.Arch.riscv32,
.os_tag = Target.Os.Tag.freestanding,
.abi = Target.Abi.none,
.cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32},
.cpu_features_sub = disabled_features,
.cpu_features_add = enabled_features
});
const exe = b.addExecutable(.{
.name = "zigtris",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = .ReleaseSmall,
}),
});
// add mibu for zigtris event generation
const mibu = b.dependency("mibu", .{
.target = target,
.optimize = .ReleaseSmall,
});
exe.root_module.addImport("mibu", mibu.module("mibu"));
const zigtris_dep = b.dependency("zigtris", .{
.target = target,
.optimize = .ReleaseSmall,
});
const zigtris_mod = zigtris_dep.module("zigtris");
exe.root_module.addImport("zigtris", zigtris_mod);
b.installArtifact(exe);
exe.addAssemblyFile(b.path("../crt0.s"));
exe.setLinkerScript(b.path("../linker.ld"));
exe.addIncludePath(b.path("../../common"));
const bin = b.addObjCopy(exe.getEmittedBin(), .{
.format = .bin,
});
bin.step.dependOn(&exe.step);
const copy_bin = b.addInstallBinFile(bin.getOutput(), "zigtris.bin");
b.default_step.dependOn(&copy_bin.step);
}

View file

@ -0,0 +1,16 @@
.{
.name = .zigtris,
.version = "0.15.2",
.dependencies = .{
.mibu = .{
.url = "git+https://github.com/xyaman/mibu.git#be3713ff04e7db3584669e679253ef388e426dc6",
.hash = "mibu-0.0.1-dev-Ddr1riTEAAAYAe1TWBT--W8fPUL8UyBE7dy-yNNZ3z5V",
},
.zigtris = .{
.url = "git+https://github.com/ringtailsoftware/zigtris.git#1dc9fc4c5dae709279bfe0708e3fae5c810837f0",
.hash = "zigtris-0.0.1-of0vIotsAACy777-kWnBDCH2tYIprpqXLpidQgvRF6r1",
},
},
.paths = .{""},
.fingerprint = 0x1f86de49aac15bec,
}

View file

@ -0,0 +1,49 @@
const std = @import("std");
extern fn console_write(data: [*]const u8, len: usize) void;
var wbuf:[4096]u8 = undefined;
var cw = ConsoleWriter.init(&wbuf);
const uvm = @import("uvm.zig");
pub const WriteError = error{ Unsupported, NotConnected };
pub const ConsoleWriter = struct {
interface: std.Io.Writer,
err: ?WriteError = null,
fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize {
var ret: usize = 0;
const b = w.buffered();
uvm.print(b);
_ = w.consume(b.len);
for (data) |d| {
uvm.print(d);
ret += d.len;
}
const pattern = data[data.len - 1];
for (0..splat) |_| {
uvm.print(pattern);
ret += pattern.len;
}
return ret;
}
pub fn init(buf: []u8) ConsoleWriter {
return ConsoleWriter{
.interface = .{
.buffer = buf,
.vtable = &.{
.drain = drain,
},
},
};
}
};
pub fn getWriter() *std.Io.Writer {
return &cw.interface;
}

41
apps/zigtris/src/main.zig Normal file
View file

@ -0,0 +1,41 @@
const uvm = @import("uvm.zig");
const mibu = @import("mibu");
const zigtris = @import("zigtris");
var nextEvent:mibu.events.Event = .none;
const console = @import("console.zig").getWriter();
export fn main() void {
var gameRunning = true;
var lastUpdate:u32 = 0;
zigtris.gamesetup(console, uvm.millis()) catch |err| {
_ = console.print("err {any}\n", .{err}) catch 0;
_ = console.flush() catch 0;
return;
};
while(gameRunning) {
const now = uvm.millis();
if (uvm.getch()) |key| {
switch(key) {
' ' => nextEvent = mibu.events.Event{.key = .{.code = .{.char = ' '}}},
'a' => nextEvent = mibu.events.Event{.key = .{.code = .left}},
'd' => nextEvent = mibu.events.Event{.key = .{.code = .right}},
'w' => nextEvent = mibu.events.Event{.key = .{.code = .up}},
's' => nextEvent = mibu.events.Event{.key = .{.code = .down}},
'q' => gameRunning = false,
else => {},
}
}
if (now > lastUpdate + 100 or nextEvent != .none) {
lastUpdate = now;
gameRunning = zigtris.gameloop(console, now, nextEvent) catch false;
nextEvent = .none;
_ = console.flush() catch 0;
}
}
}

73
apps/zigtris/src/uvm.zig Normal file
View file

@ -0,0 +1,73 @@
const uvm32 = @cImport({
@cDefine("USE_MAIN", "1");
@cInclude("uvm32_target.h");
});
const std = @import("std");
// dupeZ would be better, but want to avoid using an allocator
var new_buf:[128]u8 = undefined;
pub inline fn print(m: []const u8) void {
@memcpy(new_buf[0..m.len], m);
new_buf[m.len] = 0;
const s = new_buf[0..m.len :0];
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINT}) ++ ", %[arg1]"
:
: [arg1] "r" (s.ptr),
);
}
pub inline fn println(val: [:0]const u8) void {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINTLN}) ++ ", %[arg1]"
:
: [arg1] "r" (val.ptr),
);
}
pub inline fn printd(val: u32) void {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINTD}) ++ ", %[arg1]"
:
: [arg1] "r" (val),
);
}
pub inline fn printx(val: u32) void {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINTX}) ++ ", %[arg1]"
:
: [arg1] "r" (val),
);
}
pub inline fn printc(val: u32) void {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINTC}) ++ ", %[arg1]"
:
: [arg1] "r" (val),
);
}
pub inline fn yield() void {
asm volatile (std.fmt.comptimePrint("csrwi 0x{x}, 0", .{uvm32.IOREQ_YIELD}));
}
var millis_storage:u32 = 0;
pub inline fn millis() u32 {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_MILLIS}) ++ ", %[arg1]"
:
: [arg1] "r" (&millis_storage),
);
return millis_storage;
}
var getch_storage:u32 = 0;
pub inline fn getch() ?u8 {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_GETC}) ++ ", %[arg1]"
:
: [arg1] "r" (&getch_storage),
);
if (getch_storage <= 0xFF) {
return @truncate(getch_storage);
} else {
return null;
}
}

BIN
precompiled/zigtris.bin Executable file

Binary file not shown.