- Notifications
You must be signed in to change notification settings - Fork1
Idiomatic CXXRTL bindings for Zig
License
kivikakk/zxxrtl
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
CXXRTL bindings for Zig.
ili9341spi uses this in combination withNiar to build a CXXRTL simulationwithAmaranth and Zig.
Note
This guide assumes you're driving the build from outside, and use Zig's buildsystem just to build the Zig parts and link the final object. This gives you alot of flexibility, but if you don't need it, you can simplify by bringing theCXXRTL object file building into yourbuild.zig
too. Refer tozxxrtl'sbuild.zig
for guidance.
Add zxxrtl to yourbuild.zig.zon
:
zig fetch --save https://github.com/kivikakk/zxxrtl/archive/<commit>.tar.gz
Now let's add the import to yourbuild.zig
. We'll take the CXXRTL object filelist as an option in thebuild()
function:
constcxxrtl_o_paths=b.option([][]constu8,"cxxrtl_o_path","path to .o file to link against")orelse &[_][]constu8{"../build/cxxrtl/ili9341spi.o"};
We supply a default value for the object file paths --- it should match yourdevelopment environment. This is to ensure ZLS still works.
Then add the dependency, and import the module into your executable:
constzxxrtl_mod=b.dependency("zxxrtl", .{ .target=target, .optimize=optimize,}).module("zxxrtl");exe.root_module.addImport("zxxrtl",zxxrtl_mod);
The last step is to link against the CXXRTL object files:
for (cxxrtl_o_paths)|cxxrtl_o_path| {exe.addObjectFile(b.path(cxxrtl_o_path));}
If you want to be able to specify the Yosys data dir from thezig build
line,you can specify it when adding the zxxrtl dependency. Here we add an option,with a default fallback to actually callingyosys-config --datdir
for ZLS orthe lazy:
constyosys_data_dir=b.option([]constu8,"yosys_data_dir","yosys data dir (per yosys-config --datdir)")orelse@import("zxxrtl").guessYosysDataDir(b);
Now adapt theb.dependency()
call:
constzxxrtl_mod=b.dependency("zxxrtl", .{ .target=target, .optimize=optimize, .yosys_data_dir=yosys_data_dir,}).module("zxxrtl");
constCxxrtl=@import("zxxrtl");// Initialise the design.constcxxrtl=Cxxrtl.init();// Optionally start recording VCD. Assume `vcd_out` is `?[]const u8` representing an// optional output filename.varvcd:?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.constclk=cxxrtl.get(bool,"clk");constrst=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.constbuffer=tryvcdh.read(alloc);deferalloc.free(buffer);varfile=trystd.fs.cwd().createFile(vcd_out.?, .{});deferfile.close();tryfile.writeAll(buffer);}
Cxxrtl.Object(T)
is the basic interface to CXXRTL objects. It exposes twomethods:curr(Self) T
, andnext(Self, T) void
, which get the current value,and set the next value respectively.
There's also a helper,Cxxrtl.Sample(T)
, which is used for change detection indriver loops: you call itstick(Self)
on every trigger edge, and then canquery itsprev
andcurr
values, and if it'sstable(Self)
. IfT == bool
,you can also ask whether it'sfalling(Self)
,rising(Self)
,stable_low(Self)
orstable_high(Self)
.
The following example is adapted from an SPI peripheral blackbox. Each byte ofpayload from the design is specified as data or command depending on thedc
line during the last bit. The module returns events to the caller on each tick.
conststd=@import("std");constCxxrtl=@import("zxxrtl");constSpiConnector=@This();clk:Cxxrtl.Sample(bool),res:Cxxrtl.Sample(bool),dc:Cxxrtl.Sample(bool),copi:Cxxrtl.Sample(bool),sr:u8=0,index:u8=0,constTick=union(enum) {Nop,Command:u8,Data:u8,};pubfninit(cxxrtl:Cxxrtl)SpiConnector {constclk=Cxxrtl.Sample(bool).init(cxxrtl,"spi_clk",false);constres=Cxxrtl.Sample(bool).init(cxxrtl,"spi_res",false);constdc=Cxxrtl.Sample(bool).init(cxxrtl,"spi_dc",false);constcopi=Cxxrtl.Sample(bool).init(cxxrtl,"spi_copi",false);return .{ .clk=clk, .res=res, .dc=dc, .copi=copi, };}pubfntick(self:*SpiConnector)Tick {constclk=self.clk.tick();constres=self.res.tick();constdc=self.dc.tick();constcopi=self.copi.tick();varresult:Tick=.Nop;if (res.curr) {self.sr=0;self.index=0; }if (clk.rising()) {self.sr= (self.sr<<1)|@as(u8, (if (copi.curr)1else0));if (self.index<7)self.index+=1elseif (dc.curr) {result= .{ .Command=self.sr };self.index=0; }else {result= .{ .Data=self.sr };self.index=0; } }returnresult;}
This is a very simple use case. For a relatively overcomplicated one, seesh1107'sI2CConnector
.
About
Idiomatic CXXRTL bindings for Zig