use std::fmt::Write;
pub struct V {
out: String,
indent: usize,
}
impl Default for V {
fn default() -> Self {
Self::new()
}
}
impl V {
pub fn new() -> Self {
Self {
out: String::new(),
indent: 0,
}
}
pub fn into_string(self) -> String {
self.out
}
pub fn as_str(&self) -> &str {
&self.out
}
pub fn line(&mut self, s: &str) {
for _ in 0..self.indent {
self.out.push_str(" ");
}
self.out.push_str(s);
self.out.push('\n');
}
pub fn lines(&mut self, ls: &[&str]) {
for l in ls {
self.line(l);
}
}
pub fn blank(&mut self) {
self.out.push('\n');
}
pub fn comment(&mut self, s: &str) {
for ln in s.lines() {
self.line(&format!("// {ln}"));
}
}
pub fn banner(&mut self, s: &str) {
let bar = "// ".to_string() + &"─".repeat(s.chars().count() + 2);
self.line(&bar);
self.line(&format!("// {s}"));
self.line(&bar);
}
pub fn block(&mut self, f: impl FnOnce(&mut Self)) {
self.indent += 1;
f(self);
self.indent -= 1;
}
pub fn module(
&mut self,
name: &str,
params: &[String],
ports: &[String],
body: impl FnOnce(&mut Self),
) {
if params.is_empty() {
self.line(&format!("module {name} ("));
} else {
self.line(&format!("module {name} #("));
self.block(|v| {
let n = params.len();
for (i, p) in params.iter().enumerate() {
let sep = if i + 1 == n { "" } else { "," };
v.line(&format!("{p}{sep}"));
}
});
self.line(") (");
}
self.block(|v| {
let n = ports.len();
for (i, p) in ports.iter().enumerate() {
let sep = if i + 1 == n { "" } else { "," };
v.line(&format!("{p}{sep}"));
}
});
self.line(");");
self.block(body);
self.line(&format!("endmodule // {name}"));
self.blank();
}
pub fn always_ff(&mut self, body: impl FnOnce(&mut Self)) {
self.always_ff_on("clk", body);
}
pub fn always_ff_on(&mut self, clk: &str, body: impl FnOnce(&mut Self)) {
self.line(&format!("always_ff @(posedge {clk}) begin"));
self.block(body);
self.line("end");
}
pub fn always_comb(&mut self, body: impl FnOnce(&mut Self)) {
self.line("always_comb begin");
self.block(body);
self.line("end");
}
pub fn if_else(
&mut self,
cond: &str,
then: impl FnOnce(&mut Self),
els: Option<&dyn Fn(&mut Self)>,
) {
self.line(&format!("if ({cond}) begin"));
self.block(then);
match els {
Some(e) => {
self.line("end else begin");
self.block(|v| e(v));
self.line("end");
}
None => self.line("end"),
}
}
pub fn raw(&mut self, s: &str) {
let _ = writeln!(self.out, "{s}");
}
}
pub fn mem_hex_bytes(bytes: &[i8]) -> String {
let mut s = String::with_capacity(bytes.len() * 3);
for b in bytes {
let _ = writeln!(s, "{:02x}", *b as u8);
}
s
}
pub fn mem_hex_words_i32(words: &[i32], bits: u32) -> String {
assert!((1..=32).contains(&bits));
let nibbles = bits.div_ceil(4) as usize;
let mask: u64 = if bits == 32 {
0xFFFF_FFFF
} else {
(1u64 << bits) - 1
};
let mut s = String::with_capacity(words.len() * (nibbles + 1));
for w in words {
let u = (*w as u32 as u64) & mask;
let _ = writeln!(s, "{:0width$x}", u, width = nibbles);
}
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn module_skeleton_compiles_visually() {
let mut v = V::new();
v.module(
"demo",
&["parameter int W = 8".to_string()],
&[
"input logic clk".to_string(),
"output logic [W-1:0] q".to_string(),
],
|v| {
v.line("always_ff @(posedge clk) q <= q + 1'b1;");
},
);
let s = v.into_string();
assert!(s.contains("module demo #("));
assert!(s.contains("parameter int W = 8"));
assert!(s.contains("input logic clk,"));
assert!(s.contains("output logic [W-1:0] q"));
assert!(s.contains("endmodule // demo"));
}
#[test]
fn mem_hex_bytes_signed() {
let s = mem_hex_bytes(&[-1, 127, -128, 0]);
assert_eq!(s, "ff\n7f\n80\n00\n");
}
#[test]
fn mem_hex_words_pads_to_width() {
let s = mem_hex_words_i32(&[1, -1, 256, -256], 16);
assert_eq!(s, "0001\nffff\n0100\nff00\n");
}
}