Ruverta (/rʊˈvɛrtə/) is a library for easily creating IP generators in Rust.
- Flexible Generation : The abstraction of modules using SystemVerilog parameters is not very flexible. Create highly flexible IPs using Rust + Ruverta.
- Minimalist Syntax : Supports only simple subset of SystemVerilog which is enough for most cases.
- Variables: Only
logicis available. Noregorwire. - Combinational circuits: Only
always_combis available. Noassign. - Sequential circuits: Only
always_ffis available. Noalways.
- Variables: Only
- Human Friendly : Builder API is designed to be easy to use. Additionally, the generated SystemVerilog code is readable. You don't have to struggle with a bunch of meaningless variable names.
- Init rust project and add ruverta
$ cargo init
$ cargo add ruverta -F module- Write code
Parameter "div"
use ruverta::{ext::DFF, module::Module, stmt::Stmt};
fn main(){
let div: usize = 24;
let module = Module::new("blink", "clk", "rstn")
.logic("cnt", div, 1)
.add(DFF::sync(Stmt::assign("cnt", "0"), Stmt::assign("cnt", "cnt + 1")))
.input("clk", 1)
.input("rst", 1)
.output("led", 1)
.always_comb(Stmt::assign("led", format!("cnt[{}]", div - 1)));
println!("{}", module.verilog().join("\n"));
}- Generate Verilog
$ cargo run > blink.svmodule blink (
input logic clk,
input logic rst,
output logic led
);
logic [23:0] cnt;
always_ff @(posedge clk) begin
if (!rstn) cnt <= 0;
else cnt <= cnt + 1;
end
always_comb led = cnt[23];
endmoduleRuverta is aimed at generating branch modules.
- core: Only wrapper of system verilog.
- atom: Support API to generate single clock domain module.
- cros: Add support to generate cross clock domain module.
- top: Support API to generate top module. Multiple clocks and resets can be specified.
| Rust | SystemVerilog |
|---|---|
use ruverta::{module::{Module, Sens}, stmt::Stmt};
fn test_module() {
let m = Module::new("test_module", "clk", "rstn")
.param("BIT", Some("8"))
.input("clk", 1)
.input("rstn", 1)
.input("in0", 8)
.input("in1", 8)
.output("out", 8)
.always_comb(Stmt::assign("out", "in0 + in1"))
.always_ff(
Sens::new().posedge("clk"),
Stmt::begin().add(Stmt::assign("a", "b")).end(),
);
println!("{}", m.verilog().join("\n"));
} |
module test_module #(
parameter BIT = 8
) (
input logic clk,
input logic rstn,
input logic [ 7:0] in0,
input logic [ 7:0] in1,
output logic [ 7:0] out
);
always_comb
out = in0 + in1;
always_ff @(posedge clk)
begin
a <= b;
end
endmodule; |
.input(name, width).output(name, width).inout(name, width)
.param(name, default_value).lparam(name, value)
.logic(name, bit, len)
.instant(inst: Instant)
.always_comb(stmt: Stmt)
Stmt is a class representing a statement.
.always_ff(Sens, Stmt)
Sens is a class representing a sensitivity list.
.posedge(wire_name).negedge(wire_name).bothedge(wire_name)
Generate Verilog with .verilog(). Since it returns Vec<String>, use .join("\n") to concatenate.
Extend the builder methods of Module to easily construct various circuits.
When implementing sequential circuits, it is recommended to use the DFF extension instead of always_ff.
DFF has several usage patterns depending on the clock and reset settings.
- clock edge: posedge / negedge / bothedge
- reset edge: positive / negative
- reset timing: sync / async
Currently, only the following patterns are supported.
| clock edge | reset logic | reset timing | |
|---|---|---|---|
DFF::sync |
posedge | negative | sync |
DFF::async |
posedge | negative | async |
Clock and reset signals are taken from the module's default clock and reset.
use ruverta::{ext::DFF, module::Module, stmt::Stmt};
Module::new("example", "clk", "rstn")
.input("clk", 1)
.input("rstn", 1)
.input("in0", 8)
.input("in1", 8)
.output("out", 8)
.add(DFF::sync(
Stmt::begin().assign("out", "0").end(),
Stmt::begin().assign("out", "in0 + in1").end(),
));When implementing combinational circuits, it is recommended to use the Comb extension instead of always_comb.
Since it always requires a default, there are no omissions in case distinctions.
use ruverta::{ext::Comb, module::Module};
Module::new("example", "clk", "rstn")
.input("clk", 1)
.input("rstn", 1)
.input("in0", 1)
.input("in1", 1)
.output("out0", 1)
.output("out1", 1)
.add(
Comb::new()
.input("in0")
.input("in1")
.output("out0")
.output("out1")
.case("in0==0", vec!["0", "1"])
.default(vec!["in0", "in1"]),
);Construct a state machine with a single state variable.
use ruverta::{ext::StateMachine, module::Module};
const INIT: &str = "INIT";
const FUGA: &str = "FUGA";
Module::new("example", "clk", "rstn")
.input("clk", 1)
.input("rstn", 1)
.input("hoge", 1)
.add(
StateMachine::new("state")
.state(INIT)
.jump("hoge == 1", FUGA)
.r#else(INIT)
.state(FUGA)
.jump("hoge == 0", INIT)
.r#else(FUGA),
);| Rust | Verilog | Test | |
|---|---|---|---|
| AXILiteSlave | axi_lite_slave.rs | axi_lite_slave.sv | axi_lite_slave_tb.sv |
| PicoSlave | pico_slave.rs |
use ruverta::{bus::{AXILiteSlave, RegList}, module::Module};
Module::new("example", "clk", "rstn")
.input("clk", 1)
.input("rstn", 1)
.add(AXILiteSlave::new(
Some("cbus"),
"clk",
"rstn",
RegList::new()
.read_write("csr_rw", 8, 4)
.read_only("csr_ro", 8, 1)
.trigger("csr_tw")
.allocate_greedy(32, 8),
));- AXI Lite Slave
- Pico Slave
Tests are located under tests.
$ cargo testwill output sv files under tests/verilog/.
Running make will launch gtkwave.
ruverta/tests/verilog$ make ?????? is the name of the test case.