kivikakk.ee

zxxrtl

I’ve been getting back into using CXXRTL and Zig together, so I’ve extracted and rendered somewhat reusable the bindings I made to use them together!

zxxrtl uses CXXRTL’s C API to provide a somewhat idiomatic way to access, manipulate, and respond to events happening in the design. Its README covers the setup — it’s a bit involved as it’s necessarily something of a build system, but once you’re done it’s good to go and flexible enough to be instrumented from a higher build system.

I’m going to paste the example usage here; this doesn’t use the Sample API for edge detection, and just drives the simulation while optionally recording VCD:

const Cxxrtl = @import("zxxrtl");

// Initialise the design.
const cxxrtl = Cxxrtl.init();

// Optionally start recording VCD. Assume `vcd_out` is `?[]const u8` representing an
// optional output filename.
var vcd: ?Cxxrtl.Vcd = null;
if (vcd_out != null) vcd = Cxxrtl.Vcd.init(cxxrtl);

defer {
    if (vcd) |*vcdh| vcdh.deinit();
    cxxrtl.deinit();
}

// Get handles to the clock and reset lines.
const clk = cxxrtl.get(bool, "clk");
const rst = cxxrtl.get(bool, "rst");  // These are of type `Cxxrtl.Object(bool)`.

// Reset for a tick.
rst.next(true);

clk.next(false);
cxxrtl.step();
if (vcd) |*vcdh| vcdh.sample();

clk.next(true);
cxxrtl.step();
if (vcd) |*vcdh| vcdh.sample();

rst.next(false);

// Play out 10 cycles.
for (0..10) |_| {
    clk.next(false);
    cxxrtl.step();
    if (vcd) |*vcdh| vcdh.sample();

    clk.next(true);
    cxxrtl.step();
    if (vcd) |*vcdh| vcdh.sample();
}

if (vcd) |*vcdh| {
    // Assume `alloc` exists.
    const buffer = try vcdh.read(alloc);
    defer alloc.free(buffer);

    var file = try std.fs.cwd().createFile(vcd_out.?, .{});
    defer file.close();

    try file.writeAll(buffer);
}

Hopefully this is useful to someone else!