use std::collections::HashMap;
use std::rc::Rc;
use sv_lang::kinds::{SyntaxKind as K, TokenKind, TriviaKind};
use crate::preproc::{self, Elem, Node, Tok};
const MAX_FRAMES: usize = 1_000_000;
pub fn expand(src: &str, path: &str) -> Result<String, String> {
expand_with_includes(src, path, &[])
}
pub fn expand_with_includes(
src: &str,
path: &str,
include_dirs: &[String],
) -> Result<String, String> {
crate::protect::refuse_decryption_envelopes(src, path, include_dirs)?;
if !src.contains("randsequence") && !src.contains('`') {
return Ok(src.to_string());
}
let Some(root) = preproc::parse(src, path, include_dirs) else {
return Ok(src.to_string());
};
let tokens = root.tokens();
if !tokens
.iter()
.any(|t| t.kind == TokenKind::RandSequenceKeyword)
{
return Ok(src.to_string());
}
let main: Rc<str> = tokens
.first()
.map(|t| t.file.clone())
.unwrap_or_else(|| Rc::from(path));
let mut g = Gen {
w: Writer::new(main),
next_id: 0,
subst: HashMap::new(),
prev: None,
heads: Vec::new(),
variant: None,
};
g.elems(&root.members)?;
Ok(g.w.finish())
}
struct Writer {
text: String,
file: Rc<str>,
line: usize,
col: usize,
source_line: bool,
}
impl Writer {
fn new(main: Rc<str>) -> Self {
Writer {
text: String::new(),
file: main,
line: 1,
col: 1,
source_line: false,
}
}
fn finish(mut self) -> String {
self.text.push('\n');
self.text
}
fn goto(&mut self, file: &Rc<str>, line: usize, col: usize) {
if line == 0 {
self.space();
return;
}
if *file == self.file && line == self.line && col >= self.col {
self.pad(col - self.col);
} else if *file == self.file && line > self.line && line - self.line <= 32 {
for _ in self.line..line {
self.text.push('\n');
}
self.line = line;
self.col = 1;
self.source_line = false;
self.pad(col - 1);
} else {
let name = file.replace('\\', "\\\\").replace('"', "\\\"");
self.text
.push_str(&format!("\n`line {line} \"{name}\" 0\n"));
self.file = file.clone();
self.line = line;
self.col = 1;
self.source_line = false;
self.pad(col.saturating_sub(1));
}
}
fn pad(&mut self, n: usize) {
self.text.extend(std::iter::repeat_n(' ', n));
self.col += n;
}
fn space(&mut self) {
if !self.text.is_empty() && !self.text.ends_with([' ', '\n']) {
self.raw(" ");
}
}
fn raw(&mut self, s: &str) {
self.text.push_str(s);
self.col += s.chars().count();
}
fn glue(&mut self, s: &str) {
if self.source_line {
self.text.push('\n');
self.line += 1;
self.col = 1;
self.source_line = false;
}
self.space();
self.raw(s);
}
fn glue_at(&mut self, t: &Tok, s: &str) {
self.goto(&t.file, t.line, t.col);
self.raw(s);
self.source_line = true;
}
fn token(&mut self, t: &Tok, trivia: bool) {
if trivia {
self.directives(t);
}
if t.missing || t.text.is_empty() {
return;
}
self.goto(&t.file, t.line, t.col);
self.source_line = true;
match t.text.rfind('\n') {
None => self.raw(&t.text),
Some(last) => {
self.text.push_str(&t.text);
self.line += t.text.matches('\n').count();
self.col = t.text[last + 1..].chars().count() + 1;
}
}
}
fn directives(&mut self, t: &Tok) {
for (kind, text) in &t.trivia {
if *kind == TriviaKind::Directive && kept_directive(text) {
self.text.push('\n');
self.text.push_str(text.trim());
self.text.push('\n');
self.file = Rc::from("");
self.line = 0;
self.col = 1;
}
}
}
}
fn kept_directive(text: &str) -> bool {
let Some(rest) = text.trim_start().strip_prefix('`') else {
return false;
};
let name: String = rest
.chars()
.take_while(|c| c.is_ascii_alphanumeric() || *c == '_')
.collect();
matches!(
name.as_str(),
"timescale"
| "default_nettype"
| "celldefine"
| "endcelldefine"
| "resetall"
| "unconnected_drive"
| "nounconnected_drive"
| "begin_keywords"
| "end_keywords"
| "pragma"
| "default_decay_time"
| "default_trireg_strength"
| "delay_mode_distributed"
| "delay_mode_path"
| "delay_mode_unit"
| "delay_mode_zero"
| "protect"
| "endprotect"
)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Dir {
In,
Out,
InOut,
Ref,
}
#[derive(Clone, PartialEq, Eq, Debug)]
enum Piece {
Text(String),
Cap,
}
#[derive(Clone, PartialEq, Eq, Debug)]
struct Root {
pieces: Vec<Piece>,
head: Option<String>,
konst: bool,
}
impl Root {
fn caps(&self) -> usize {
self.pieces.iter().filter(|p| **p == Piece::Cap).count()
}
fn render(&self, cap: impl Fn(usize) -> String) -> String {
let mut out = String::new();
let mut i = 0;
for p in &self.pieces {
match p {
Piece::Text(t) => {
out.push_str(t);
out.push(' ');
}
Piece::Cap => {
out.push_str(&cap(i));
out.push(' ');
i += 1;
}
}
}
out.trim_end().to_string()
}
}
enum CapSrc<'a> {
Expr(&'a Node),
Text(String),
}
struct Port<'a> {
name: String,
dir: Dir,
konst: bool,
ty: Option<&'a Node>,
implicit: bool,
dims: Vec<&'a Tok>,
default: Option<&'a Node>,
}
struct Rule<'a> {
join: Option<&'a Node>,
prods: Vec<&'a Node>,
weight: Option<&'a Node>,
code: Option<&'a Node>,
uses: Vec<(usize, usize)>,
}
struct Prod<'a> {
name: String,
name_tok: &'a Tok,
ports: Vec<Port<'a>>,
value: Option<&'a Node>,
rules: Vec<Rule<'a>>,
joined: bool,
}
type Pc = i64;
type JoinItem<'a> = (usize, &'a Node, Option<(usize, usize)>);
type CallSite<'a> = (Option<(usize, usize)>, usize, Option<&'a Node>);
enum Arm<'a> {
Call {
caller: Option<(usize, usize)>,
callee: usize,
item: Option<&'a Node>,
cont: Pc,
},
Cont {
caller: (usize, usize),
callee: usize,
item: &'a Node,
slot: Option<(usize, usize)>,
next: Pc,
boundary: bool,
},
Entry {
prod: usize,
starts: Vec<Pc>,
},
RuleStart {
prod: usize,
rule: usize,
first: Pc,
},
Code {
prod: usize,
rule: usize,
block: &'a Node,
next: Pc,
},
If {
prod: usize,
rule: usize,
cond: &'a Node,
then_: Pc,
else_: Option<Pc>,
next: Pc,
},
Case {
prod: usize,
rule: usize,
node: &'a Node,
targets: Vec<Pc>,
next: Pc,
},
RepInit {
prod: usize,
rule: usize,
count: &'a Node,
site: usize,
check: Pc,
},
RepCheck {
prod: usize,
site: usize,
call: Pc,
next: Pc,
},
Exit,
JoinInit {
prod: usize,
rule: usize,
clause: &'a Node,
items: Vec<(usize, &'a Node)>,
entry: Pc,
exit: Pc,
},
JoinEntry {
n: usize,
entries: Vec<Pc>,
sched: Pc,
},
JoinSched {
prod: usize,
n: usize,
next: Pc,
},
JoinExit {
caller: (usize, usize),
items: Vec<JoinItem<'a>>,
entry: Pc,
sched: Pc,
},
}
struct Gen {
w: Writer,
next_id: usize,
subst: HashMap<String, String>,
prev: Option<TokenKind>,
heads: Vec<String>,
variant: Option<(usize, Vec<usize>)>,
}
struct Rs<'a> {
b: String,
prods: Vec<Prod<'a>>,
arms: Vec<(Pc, Arm<'a>)>,
next_pc: Pc,
exits: Vec<Pc>,
entries: Vec<Pc>,
reps: Vec<Vec<usize>>,
n_reps: usize,
has_join: bool,
roots: Vec<Vec<Vec<Root>>>,
}
impl<'a> Rs<'a> {
fn pc(&mut self) -> Pc {
self.next_pc += 1;
self.next_pc
}
fn find(&self, item: &Node) -> Result<usize, String> {
let t = item
.tok(0)
.ok_or("a randsequence production item with no name")?;
self.prods
.iter()
.position(|p| p.name == t.text)
.ok_or_else(|| {
format!(
"{}:{}:{}: error: unknown randsequence production '{}'",
t.file, t.line, t.col, t.text
)
})
}
}
fn items_of(prod: &Node) -> Vec<&Node> {
match prod.kind {
K::RsProdItem => vec![prod],
K::RsIfElse => {
let mut v: Vec<&Node> = prod.node(4).into_iter().collect();
v.extend(prod.node(5).and_then(|e| e.node(1)));
v
}
K::RsRepeat => prod.node(4).into_iter().collect(),
K::RsCase => prod
.list_nodes(4)
.into_iter()
.filter_map(|ci| ci.node(2))
.collect(),
_ => Vec::new(),
}
}
fn first_tok(n: &Node) -> Option<&Tok> {
n.tokens()
.into_iter()
.find(|t| !t.missing && !t.text.is_empty())
}
impl Gen {
fn fresh(&mut self) -> usize {
self.next_id += 1;
self.next_id
}
fn elems(&mut self, elems: &[Elem]) -> Result<(), String> {
for e in elems {
match e {
Elem::Node(n) if n.kind == K::RandSequenceStatement => self.randseq(n)?,
Elem::Node(n) => self.elems(&n.members)?,
Elem::Tok(t) => self.w.token(t, true),
Elem::List(v) => self.elems(v)?,
Elem::Absent => {}
}
}
Ok(())
}
fn chunk(&mut self, n: &Node) {
for t in n.tokens() {
self.emit(t);
}
}
fn emit(&mut self, t: &Tok) {
let member = matches!(self.prev, Some(TokenKind::Dot | TokenKind::DoubleColon));
self.prev = Some(t.kind);
if t.kind == TokenKind::Identifier
&& !member
&& let Some(to) = self.subst.get(&t.text)
{
let mut t2 = t.clone();
t2.text = to.clone();
self.w.token(&t2, false);
return;
}
self.w.token(t, false);
}
fn mapped(&self, name: &str) -> String {
self.subst
.get(name)
.cloned()
.unwrap_or_else(|| name.to_string())
}
fn region(
&mut self,
rs: &Rs,
prod: usize,
rule: Option<usize>,
body: &mut dyn FnMut(&mut Gen) -> Result<(), String>,
) -> Result<(), String> {
if !Self::has_scope(rs, prod, rule) {
return body(self);
}
let refs: Vec<usize> = rs.prods[prod]
.ports
.iter()
.enumerate()
.filter(|(_, p)| p.dir == Dir::Ref)
.map(|(j, _)| j)
.collect();
let mut choice = vec![0; rs.prods[prod].ports.len()];
self.region_at(rs, prod, rule, &refs, &mut choice, body)
}
fn region_at(
&mut self,
rs: &Rs,
prod: usize,
rule: Option<usize>,
refs: &[usize],
choice: &mut Vec<usize>,
body: &mut dyn FnMut(&mut Gen) -> Result<(), String>,
) -> Result<(), String> {
let b = rs.b.clone();
if let Some((&j, rest)) = refs.split_first() {
let roots = &rs.roots[prod][j];
if roots.is_empty() {
return Ok(());
}
self.w.glue(&format!("case ({b}_rk{prod}_{j}[{b}_fp])"));
for k in 0..roots.len() {
choice[j] = k;
self.w.glue(&format!("{k} : begin"));
self.region_at(rs, prod, rule, rest, choice, body)?;
self.w.glue("end");
}
self.w.glue("default : ; endcase");
return Ok(());
}
let saved = (
self.subst.clone(),
std::mem::take(&mut self.heads),
self.variant.take(),
);
let p = &rs.prods[prod];
for port in &p.ports {
self.subst.remove(&port.name);
}
if let Some(r) = rule {
for (q, _) in &p.rules[r].uses {
self.subst.remove(&rs.prods[*q].name);
}
}
for (j, port) in p.ports.iter().enumerate() {
if port.dir != Dir::Ref {
continue;
}
let root = &rs.roots[prod][j][choice[j]];
if let Some(h) = &root.head {
self.heads.push(h.clone());
}
let text = root.render(|i| format!("{b}_c{prod}_{j}_{i}[{b}_fp]"));
self.subst.insert(port.name.clone(), text);
}
let mut names: Vec<String> = p
.ports
.iter()
.filter(|port| port.dir != Dir::Ref)
.map(|port| port.name.clone())
.collect();
if let Some(r) = rule {
names.extend(
p.rules[r]
.uses
.iter()
.map(|(q, _)| rs.prods[*q].name.clone()),
);
}
for name in names {
if self.heads.contains(&name) {
let m = format!("{b}_m{}", self.fresh());
self.subst.insert(name, m);
}
}
self.variant = Some((prod, choice.clone()));
self.w.glue("begin");
self.bind(rs, prod, rule);
let r = body(self);
self.writeback(rs, prod, rule);
self.w.glue("end");
(self.subst, self.heads, self.variant) = saved;
r
}
fn randseq(&mut self, n: &Node) -> Result<(), String> {
for t in n.tokens() {
self.w.directives(t);
}
let k = self.fresh();
let b = format!("{}rs{k}", vj_sim::process::cfg::INTERNAL_NAME_PREFIX);
let mut rs = Rs {
b: b.clone(),
prods: Vec::new(),
arms: Vec::new(),
next_pc: 0,
exits: Vec::new(),
entries: Vec::new(),
reps: Vec::new(),
n_reps: 0,
has_join: false,
roots: Vec::new(),
};
for p in n.list_nodes(6) {
rs.prods.push(production(p)?);
}
if rs.prods.is_empty() {
return Err("a randsequence with no productions".to_string());
}
for i in 0..rs.prods.len() {
for r in 0..rs.prods[i].rules.len() {
let mut uses: Vec<(usize, usize)> = Vec::new();
let rule = &rs.prods[i].rules[r];
let mut named: Vec<&Node> = Vec::new();
for p in &rule.prods {
named.extend(items_of(p));
}
let mut joined = Vec::new();
for item in named {
let q = rs.find(item)?;
if rule.join.is_some() {
joined.push(q);
}
if rs.prods[q].value.is_some() {
match uses.iter_mut().find(|(u, _)| *u == q) {
Some((_, c)) => *c += 1,
None => uses.push((q, 1)),
}
}
}
for q in joined {
rs.prods[q].joined = true;
rs.has_join = true;
}
rs.prods[i].rules[r].uses = uses;
}
}
let top = match n.tok(4) {
Some(t) => rs
.prods
.iter()
.position(|p| p.name == t.text)
.ok_or_else(|| {
format!(
"{}:{}:{}: error: unknown randsequence production '{}'",
t.file, t.line, t.col, t.text
)
})?,
None => 0,
};
for _ in 0..rs.prods.len() {
let e = rs.pc();
let x = rs.pc();
rs.entries.push(e);
rs.exits.push(x);
rs.reps.push(Vec::new());
}
let start = rs.pc();
rs.arms.push((
start,
Arm::Call {
caller: None,
callee: top,
item: None,
cont: -1,
},
));
for p in 0..rs.prods.len() {
build_prod(&mut rs, p)?;
}
enumerate_roots(&mut rs)?;
let anchor = first_tok(n).ok_or("an empty randsequence")?;
match n.node(0).and_then(|l| l.tok(0)) {
Some(label) => self.w.glue_at(anchor, &format!("begin : {}", label.text)),
None => self.w.glue_at(anchor, "begin"),
}
self.decls(&rs)?;
self.w.glue(&format!(
"automatic int {b}_pc = {start}; automatic int {b}_fp = 0; automatic int {b}_nf = 0; \
automatic int {b}_ch = 0; automatic bit {b}_ab = 0; automatic bit {b}_bk = 0; \
automatic bit {b}_y = 0;"
));
self.w
.glue(&format!("while ({b}_pc >= 0) begin case ({b}_pc)"));
let arms = std::mem::take(&mut rs.arms);
for (pc, arm) in &arms {
self.w.glue(&format!("{pc} : begin"));
self.arm(&rs, arm)?;
self.w.glue("end");
}
self.w.glue("default : ;");
self.w.glue("endcase end");
match n.tok(7) {
Some(end) => self.w.glue_at(end, "end"),
None => self.w.glue("end"),
}
Ok(())
}
fn decls(&mut self, rs: &Rs) -> Result<(), String> {
let b = &rs.b;
for (i, p) in rs.prods.iter().enumerate() {
for (j, port) in p.ports.iter().enumerate() {
if port.dir == Dir::Ref {
self.w.glue(&format!("automatic int {b}_rk{i}_{j} [int];"));
for n in 0..max_caps(rs, i, j) {
self.w.glue(&format!(
"automatic logic signed [63:0] {b}_c{i}_{j}_{n} [int];"
));
}
continue;
}
self.w.glue("typedef");
match port.ty {
Some(t) if port.implicit => {
self.w.glue("logic");
self.chunk(t);
}
Some(t) => self.chunk(t),
None => self.w.glue("logic"),
}
self.w.glue(&format!("{b}_tp{i}_{j}"));
for d in &port.dims {
self.w.token(d, false);
}
self.w.glue(";");
self.w
.glue(&format!("automatic {b}_tp{i}_{j} {b}_p{i}_{j} [int];"));
}
if let Some(v) = p.value {
self.w.glue("typedef");
self.chunk(v);
self.w.glue(&format!("{b}_tv{i};"));
self.w
.glue(&format!("automatic {b}_tv{i} {b}_rv{i} [int];"));
}
}
for (i, p) in rs.prods.iter().enumerate() {
for (r, rule) in p.rules.iter().enumerate() {
for (q, count) in &rule.uses {
for o in 1..=*count {
self.w
.glue(&format!("automatic {b}_tv{q} {b}_h{i}_{r}_{q}_{o} [int];"));
}
}
}
}
for s in 0..rs.n_reps {
self.w
.glue(&format!("automatic longint unsigned {b}_rep{s} [int];"));
}
self.w.glue(&format!(
"automatic int {b}_ret [int]; automatic int {b}_up [int];"
));
if rs.has_join {
self.w.glue(&format!(
"automatic int {b}_jn [int]; automatic int {b}_res [int]; \
automatic int {b}_len [int]; automatic bit {b}_done [int]; \
automatic int {b}_ji [int]; automatic int {b}_jbase [int]; \
automatic int {b}_jidx [int]; automatic bit {b}_jph [int]; \
automatic real {b}_jbias [int];"
));
}
Ok(())
}
fn bind(&mut self, rs: &Rs, prod: usize, rule: Option<usize>) {
let b = &rs.b;
let p = &rs.prods[prod];
for (j, port) in p.ports.iter().enumerate() {
if port.dir == Dir::Ref {
continue;
}
self.w.glue(&format!(
"automatic {b}_tp{prod}_{j} {} = {b}_p{prod}_{j}[{b}_fp];",
self.mapped(&port.name)
));
}
let Some(r) = rule else {
return;
};
let mut assigns = String::new();
for (q, count) in &p.rules[r].uses {
let name = &self.mapped(&rs.prods[*q].name);
if *count == 1 {
self.w.glue(&format!(
"automatic {b}_tv{q} {name} = {b}_h{prod}_{r}_{q}_1[{b}_fp];"
));
} else {
self.w
.glue(&format!("automatic {b}_tv{q} {name} [1:{count}];"));
for o in 1..=*count {
assigns.push_str(&format!("{name}[{o}] = {b}_h{prod}_{r}_{q}_{o}[{b}_fp]; "));
}
}
}
if !assigns.is_empty() {
self.w.glue(assigns.trim_end());
}
}
fn writeback(&mut self, rs: &Rs, prod: usize, rule: Option<usize>) {
let b = &rs.b;
let p = &rs.prods[prod];
let mut out = String::new();
for (j, port) in p.ports.iter().enumerate() {
if port.dir == Dir::Ref {
continue;
}
out.push_str(&format!(
"{b}_p{prod}_{j}[{b}_fp] = {}; ",
self.mapped(&port.name)
));
}
if let Some(r) = rule {
for (q, count) in &p.rules[r].uses {
let name = &self.mapped(&rs.prods[*q].name);
if *count == 1 {
out.push_str(&format!("{b}_h{prod}_{r}_{q}_1[{b}_fp] = {name}; "));
} else {
for o in 1..=*count {
out.push_str(&format!("{b}_h{prod}_{r}_{q}_{o}[{b}_fp] = {name}[{o}]; "));
}
}
}
}
if !out.is_empty() {
self.w.glue(out.trim_end());
}
}
fn has_scope(rs: &Rs, prod: usize, rule: Option<usize>) -> bool {
!rs.prods[prod].ports.is_empty()
|| rule.is_some_and(|r| !rs.prods[prod].rules[r].uses.is_empty())
}
fn goto(&mut self, rs: &Rs, prod: usize, next: Pc, boundary: bool) {
let b = &rs.b;
if boundary && rs.prods[prod].joined {
self.w.glue(&format!(
"if ({b}_jn.exists({b}_fp)) begin {b}_len[{b}_fp] = {b}_len[{b}_fp] - 1; \
{b}_res[{b}_fp] = {next}; {b}_y = 1; {b}_pc = {b}_jn[{b}_fp]; end \
else {b}_pc = {next};"
));
} else {
self.w.glue(&format!("{b}_pc = {next};"));
}
}
fn free(&mut self, rs: &Rs, prod: usize, frame: &str) {
let b = &rs.b;
let p = &rs.prods[prod];
let mut out = String::new();
for (j, port) in p.ports.iter().enumerate() {
if port.dir == Dir::Ref {
out.push_str(&format!("{b}_rk{prod}_{j}.delete({frame}); "));
for n in 0..max_caps(rs, prod, j) {
out.push_str(&format!("{b}_c{prod}_{j}_{n}.delete({frame}); "));
}
} else {
out.push_str(&format!("{b}_p{prod}_{j}.delete({frame}); "));
}
}
if p.value.is_some() {
out.push_str(&format!("{b}_rv{prod}.delete({frame}); "));
}
for (r, rule) in p.rules.iter().enumerate() {
for (q, count) in &rule.uses {
for o in 1..=*count {
out.push_str(&format!("{b}_h{prod}_{r}_{q}_{o}.delete({frame}); "));
}
}
}
for s in &rs.reps[prod] {
out.push_str(&format!("{b}_rep{s}.delete({frame}); "));
}
if !out.is_empty() {
self.w.glue(out.trim_end());
}
}
fn arg_decls(&mut self, rs: &Rs, callee: usize, tag: usize) {
let b = rs.b.as_str();
let q = &rs.prods[callee];
for (j, port) in q.ports.iter().enumerate() {
if port.dir == Dir::Ref {
self.w.glue(&format!("automatic int {b}_xr{tag}_{j};"));
for n in 0..max_caps(rs, callee, j) {
self.w.glue(&format!(
"automatic logic signed [63:0] {b}_xc{tag}_{j}_{n};"
));
}
} else {
self.w
.glue(&format!("automatic {b}_tp{callee}_{j} {b}_x{tag}_{j};"));
}
}
}
fn args(
&mut self,
rs: &Rs,
caller: Option<(usize, usize)>,
callee: usize,
item: Option<&Node>,
tag: usize,
) -> Result<(), String> {
let b = rs.b.clone();
let b = b.as_str();
let q = &rs.prods[callee];
let actuals = actuals_of(item, q)?;
let given: Vec<(usize, &Node)> = actuals
.iter()
.enumerate()
.filter_map(|(j, a)| a.map(|a| (j, a)))
.collect();
let mut emit_given = |g: &mut Gen| -> Result<(), String> {
for (j, a) in &given {
match q.ports[*j].dir {
Dir::Out => {}
Dir::Ref => g.bind_ref(rs, caller, callee, *j, a, tag)?,
_ => {
g.w.glue(&format!("{b}_x{tag}_{j} ="));
g.chunk(a);
g.w.glue(";");
}
}
}
Ok(())
};
if !given.is_empty() {
match caller {
Some((cp, cr)) => self.region(rs, cp, Some(cr), &mut emit_given)?,
None => emit_given(self)?,
}
}
for (j, port) in q.ports.iter().enumerate() {
if actuals[j].is_some() || port.dir == Dir::Out {
continue;
}
match (port.default, port.dir) {
(Some(d), Dir::Ref) => self.bind_ref(rs, None, callee, j, d, tag)?,
(Some(d), _) => {
self.w.glue(&format!("{b}_x{tag}_{j} ="));
self.chunk(d);
self.w.glue(";");
}
(None, Dir::Ref) => {
return Err(format!(
"randsequence production '{}': no actual for ref argument '{}'",
q.name, port.name
));
}
(None, _) => {}
}
}
Ok(())
}
fn bind_ref(
&mut self,
rs: &Rs,
caller: Option<(usize, usize)>,
callee: usize,
j: usize,
a: &Node,
tag: usize,
) -> Result<(), String> {
let b = rs.b.clone();
let choice = match (caller, &self.variant) {
(Some((cp, _)), Some((vp, ch))) if *vp == cp => ch.clone(),
_ => Vec::new(),
};
let (mut root, caps) = derive(rs, caller, &choice, a)?;
root.konst = false;
let k = rs.roots[callee][j]
.iter()
.position(|r| *r == root)
.ok_or("randsequence: a ref argument's target was not enumerated")?;
self.w.glue(&format!("{b}_xr{tag}_{j} = {k};"));
for (n, c) in caps.iter().enumerate() {
self.w.glue(&format!("{b}_xc{tag}_{j}_{n} ="));
match c {
CapSrc::Expr(e) => self.chunk(e),
CapSrc::Text(t) => self.w.glue(t),
}
self.w.glue(";");
}
Ok(())
}
fn push_frame(&mut self, rs: &Rs, callee: usize, tag: usize) {
let b = &rs.b;
let q = &rs.prods[callee];
self.w.glue(&format!("{b}_nf = {b}_nf + 1;"));
for (j, port) in q.ports.iter().enumerate() {
if port.dir == Dir::Ref {
self.w
.glue(&format!("{b}_rk{callee}_{j}[{b}_nf] = {b}_xr{tag}_{j};"));
for n in 0..max_caps(rs, callee, j) {
self.w.glue(&format!(
"{b}_c{callee}_{j}_{n}[{b}_nf] = {b}_xc{tag}_{j}_{n};"
));
}
} else {
self.w
.glue(&format!("{b}_p{callee}_{j}[{b}_nf] = {b}_x{tag}_{j};"));
}
}
if q.value.is_some() {
self.w.glue(&format!(
"begin automatic {b}_tv{callee} d; {b}_rv{callee}[{b}_nf] = d; end"
));
}
}
fn copy_back(
&mut self,
rs: &Rs,
caller: (usize, usize),
callee: usize,
item: &Node,
slot: Option<(usize, usize)>,
) -> Result<(), String> {
let b = &rs.b;
let q = &rs.prods[callee];
let actuals = actuals_of(Some(item), q)?;
let outs: Vec<(usize, &Node)> = actuals
.iter()
.enumerate()
.filter(|(j, a)| a.is_some() && matches!(q.ports[*j].dir, Dir::Out | Dir::InOut))
.map(|(j, a)| (j, a.unwrap()))
.collect();
if !outs.is_empty() {
self.region(rs, caller.0, Some(caller.1), &mut |g| {
for (j, a) in &outs {
g.chunk(a);
g.w.glue(&format!("= {b}_p{callee}_{j}[{b}_ch];"));
}
Ok(())
})?;
}
if let Some((q, o)) = slot {
let (cp, cr) = caller;
self.w.glue(&format!(
"{b}_h{cp}_{cr}_{q}_{o}[{b}_fp] = {b}_rv{callee}[{b}_ch];"
));
}
Ok(())
}
fn arm(&mut self, rs: &Rs, arm: &Arm) -> Result<(), String> {
let b = rs.b.clone();
let b = b.as_str();
match arm {
Arm::Call {
caller,
callee,
item,
cont,
} => {
let tag = self.fresh();
self.arg_decls(rs, *callee, tag);
self.args(rs, *caller, *callee, *item, tag)?;
self.push_frame(rs, *callee, tag);
let entry = rs.entries[*callee];
self.w.glue(&format!(
"{b}_ret[{b}_nf] = {cont}; {b}_up[{b}_nf] = {b}_fp; {b}_fp = {b}_nf; \
{b}_pc = {entry};"
));
self.w.glue(&format!(
"if ({b}_ret.num() > {MAX_FRAMES}) \
$fatal(1, \"randsequence: production recursion deeper than {MAX_FRAMES} frames\");"
));
}
Arm::Cont {
caller,
callee,
item,
slot,
next,
boundary,
} => {
self.copy_back(rs, *caller, *callee, item, *slot)?;
self.free(rs, *callee, &format!("{b}_ch"));
self.goto(rs, caller.0, *next, *boundary);
}
Arm::Entry { prod, starts } => {
let p = &rs.prods[*prod];
let joined = p.joined;
let lens: Vec<usize> = p
.rules
.iter()
.map(|r| r.prods.iter().filter(|x| x.kind != K::RsCodeBlock).count())
.collect();
let set = |pc: Pc, len: usize| {
if joined {
format!("begin {b}_pc = {pc}; {b}_len[{b}_fp] = {len}; end")
} else {
format!("{b}_pc = {pc};")
}
};
if starts.len() == 1 {
self.w.glue(&set(starts[0], lens[0]));
} else {
self.region(rs, *prod, None, &mut |g| {
g.w.glue(&format!("{b}_pc = {};", rs.exits[*prod]));
g.w.glue_at(p.name_tok, "randcase");
for (r, rule) in p.rules.iter().enumerate() {
g.w.glue("(");
match rule.weight {
Some(wt) => g.chunk(wt),
None => g.w.glue("1"),
}
g.w.glue(&format!(") : {}", set(starts[r], lens[r])));
}
g.w.glue("endcase");
Ok(())
})?;
}
if joined {
self.w.glue(&format!(
"if ({b}_jn.exists({b}_fp)) begin {b}_res[{b}_fp] = {b}_pc; {b}_y = 1; \
{b}_pc = {b}_jn[{b}_fp]; end"
));
}
}
Arm::RuleStart { prod, rule, first } => {
let uses = rs.prods[*prod].rules[*rule].uses.clone();
for (q, count) in uses {
for o in 1..=count {
self.w.glue(&format!(
"begin automatic {b}_tv{q} d; {b}_h{prod}_{rule}_{q}_{o}[{b}_fp] = d; end"
));
}
}
self.w.glue(&format!("{b}_pc = {first};"));
}
Arm::Code {
prod,
rule,
block,
next,
} => {
self.w.glue(&format!("{b}_ab = 0;"));
self.region(rs, *prod, Some(*rule), &mut |g| {
let cb = format!("{b}_cb{}", g.fresh());
g.w.glue(&format!("begin : {cb}"));
let cx = CodeCx {
b: b.to_string(),
cb,
prod: *prod,
value: rs.prods[*prod].value.is_some(),
};
g.code(block.list(1), &cx, false)?;
g.w.glue("end");
Ok(())
})?;
self.w.glue(&format!(
"if ({b}_bk) {b}_pc = -1; else if ({b}_ab) {b}_pc = {};",
rs.exits[*prod]
));
self.w.glue("else begin");
self.goto(rs, *prod, *next, false);
self.w.glue("end");
}
Arm::If {
prod,
rule,
cond,
then_,
else_,
next,
} => {
self.w.glue(&format!("{b}_pc = -2;"));
self.region(rs, *prod, Some(*rule), &mut |g| {
g.w.glue("if (");
g.chunk(cond);
g.w.glue(&format!(") {b}_pc = {then_};"));
if let Some(e) = else_ {
g.w.glue(&format!("else {b}_pc = {e};"));
}
Ok(())
})?;
self.w.glue(&format!("if ({b}_pc == -2) begin"));
self.goto(rs, *prod, *next, true);
self.w.glue("end");
}
Arm::Case {
prod,
rule,
node,
targets,
next,
} => {
self.w.glue(&format!("{b}_pc = -2;"));
self.region(rs, *prod, Some(*rule), &mut |g| {
let kw = node.tok(0).ok_or("a case production with no keyword")?;
g.w.glue_at(kw, "case (");
g.chunk(node.node(2).ok_or("a case production with no selector")?);
g.w.glue(")");
for (ci, target) in node.list_nodes(4).into_iter().zip(targets) {
if ci.kind == K::DefaultRsCaseItem {
g.w.glue("default");
} else {
let mut first = true;
for e in ci.list_nodes(0) {
if !first {
g.w.glue(",");
}
first = false;
g.chunk(e);
}
}
g.w.glue(&format!(": {b}_pc = {target};"));
}
g.w.glue("endcase");
Ok(())
})?;
self.w.glue(&format!("if ({b}_pc == -2) begin"));
self.goto(rs, *prod, *next, true);
self.w.glue("end");
}
Arm::RepInit {
prod,
rule,
count,
site,
check,
} => {
self.w.glue(&format!("{b}_rep{site}[{b}_fp] = 0;"));
self.region(rs, *prod, Some(*rule), &mut |g| {
g.w.glue("repeat (");
g.chunk(count);
g.w.glue(&format!(
") {b}_rep{site}[{b}_fp] = {b}_rep{site}[{b}_fp] + 1;"
));
Ok(())
})?;
self.w.glue(&format!("{b}_pc = {check};"));
}
Arm::RepCheck {
prod,
site,
call,
next,
} => {
self.w.glue(&format!(
"if ({b}_rep{site}[{b}_fp] > 0) begin {b}_rep{site}[{b}_fp] = \
{b}_rep{site}[{b}_fp] - 1; {b}_pc = {call}; end else begin \
{b}_rep{site}.delete({b}_fp);"
));
self.goto(rs, *prod, *next, true);
self.w.glue("end");
}
Arm::Exit => {
self.w.glue(&format!(
"{b}_ch = {b}_fp; {b}_pc = {b}_ret[{b}_ch]; {b}_fp = {b}_up[{b}_ch]; \
{b}_ret.delete({b}_ch); {b}_up.delete({b}_ch);"
));
}
Arm::JoinInit {
prod,
rule,
clause,
items,
entry,
exit,
} => {
let n = items.len();
let tags: Vec<usize> = (0..n).map(|_| self.fresh()).collect();
self.w.glue(&format!("automatic real {b}_bias = 0.5;"));
for (i, (q, _)) in items.iter().enumerate() {
self.arg_decls(rs, *q, tags[i]);
}
for (i, (q, item)) in items.iter().enumerate() {
self.args(rs, Some((*prod, *rule)), *q, Some(item), tags[i])?;
}
if let Some(e) = clause.node(2) {
self.region(rs, *prod, Some(*rule), &mut |g| {
g.w.glue(&format!("{b}_bias ="));
g.chunk(e);
g.w.glue(";");
Ok(())
})?;
}
self.w.glue(&format!(
"{b}_jbase[{b}_fp] = {b}_nf + 1; {b}_jidx[{b}_fp] = 0; {b}_jph[{b}_fp] = 0; \
{b}_jbias[{b}_fp] = {b}_bias;"
));
for (i, (q, _)) in items.iter().enumerate() {
self.push_frame(rs, *q, tags[i]);
self.w.glue(&format!(
"{b}_ret[{b}_nf] = {exit}; {b}_up[{b}_nf] = {b}_fp; {b}_jn[{b}_nf] = {entry}; \
{b}_ji[{b}_nf] = {i}; {b}_done[{b}_nf] = 0; {b}_len[{b}_nf] = 0;"
));
}
self.w.glue(&format!("{b}_y = 0; {b}_pc = {entry};"));
}
Arm::JoinEntry { n, entries, sched } => {
self.w.glue(&format!(
"automatic int {b}_f; automatic int {b}_i; \
{b}_f = {b}_y ? {b}_up[{b}_fp] : {b}_fp; {b}_y = 0; {b}_i = {b}_jidx[{b}_f]; \
if ({b}_i < {n}) begin {b}_jidx[{b}_f] = {b}_i + 1; \
{b}_fp = {b}_jbase[{b}_f] + {b}_i; case ({b}_i)"
));
for (i, e) in entries.iter().enumerate() {
self.w.glue(&format!("{i} : {b}_pc = {e};"));
}
self.w.glue(&format!(
"default : ; endcase end else begin {b}_jph[{b}_f] = 1; \
for (int {b}_k = 0; {b}_k < {n}; {b}_k++) \
if ({b}_jn.exists({b}_jbase[{b}_f] + {b}_k)) {b}_jn[{b}_jbase[{b}_f] + {b}_k] = {sched}; \
{b}_fp = {b}_f; {b}_pc = {sched}; end"
));
}
Arm::JoinSched { prod, n, next } => {
let w = |c: &str| {
format!(
"((({b}_len[{c}] > 0) ? real'({b}_len[{c}]) : 1.0) ** \
(2.0 * {b}_jbias[{b}_f] - 1.0))"
)
};
let c = format!("{b}_jbase[{b}_f] + {b}_k");
let wc = w(&c);
self.w.glue(&format!(
"automatic int {b}_f; automatic real {b}_tot; automatic real {b}_r; \
automatic real {b}_w; automatic int {b}_pick; \
{b}_f = {b}_y ? {b}_up[{b}_fp] : {b}_fp; {b}_y = 0; {b}_tot = 0.0; \
{b}_pick = -1; \
for (int {b}_k = 0; {b}_k < {n}; {b}_k++) \
if (!{b}_done[{c}]) {b}_tot = {b}_tot + {wc}; \
if ({b}_tot == 0.0) begin \
for (int {b}_k = 0; {b}_k < {n}; {b}_k++) {b}_done.delete({c}); \
{b}_jbase.delete({b}_f); {b}_jidx.delete({b}_f); {b}_jph.delete({b}_f); \
{b}_jbias.delete({b}_f); {b}_fp = {b}_f;"
));
self.goto(rs, *prod, *next, true);
self.w.glue(&format!(
"end else begin \
{b}_r = real'($urandom) / 4294967296.0 * {b}_tot; \
for (int {b}_k = 0; {b}_k < {n}; {b}_k++) if (!{b}_done[{c}]) begin \
{b}_w = {wc}; \
if ({b}_pick < 0 && {b}_r < {b}_w) {b}_pick = {c}; else {b}_r = {b}_r - {b}_w; end \
if ({b}_pick < 0) for (int {b}_k = 0; {b}_k < {n}; {b}_k++) \
if (!{b}_done[{c}]) {b}_pick = {c}; \
{b}_fp = {b}_pick; {b}_pc = {b}_res[{b}_pick]; end"
));
}
Arm::JoinExit {
caller,
items,
entry,
sched,
} => {
self.w.glue(&format!("case ({b}_ji[{b}_ch])"));
for (i, (q, item, slot)) in items.iter().enumerate() {
self.w.glue(&format!("{i} : begin"));
self.copy_back(rs, *caller, *q, item, *slot)?;
self.free(rs, *q, &format!("{b}_ch"));
self.w.glue("end");
}
self.w.glue(&format!(
"default : ; endcase {b}_done[{b}_ch] = 1; {b}_jn.delete({b}_ch); \
{b}_len.delete({b}_ch); {b}_res.delete({b}_ch); {b}_ji.delete({b}_ch); \
{b}_y = 0; {b}_pc = {b}_jph[{b}_fp] ? {sched} : {entry};"
));
}
}
Ok(())
}
fn code(&mut self, elems: &[Elem], cx: &CodeCx, in_loop: bool) -> Result<(), String> {
for e in elems {
match e {
Elem::Node(n) => match n.kind {
K::RandSequenceStatement => self.randseq(n)?,
K::ReturnStatement => {
if let Some(label) = n.node(0) {
self.chunk(label);
}
let kw = n.tok(2).ok_or("a return with no keyword")?;
let (b, cb) = (&cx.b, &cx.cb);
match (n.node(3), cx.value) {
(Some(v), true) => {
self.w
.glue_at(kw, &format!("begin {b}_rv{}[{b}_fp] =", cx.prod));
self.chunk(v);
self.w.glue(&format!("; {b}_ab = 1; disable {cb}; end"));
}
(None, false) => {
self.w
.glue_at(kw, &format!("begin {b}_ab = 1; disable {cb}; end"));
}
(Some(_), false) => {
return Err(format!(
"{}:{}:{}: error: a void randsequence production cannot \
return a value",
kw.file, kw.line, kw.col
));
}
(None, true) => {
return Err(format!(
"{}:{}:{}: error: a randsequence production with a value \
must return one",
kw.file, kw.line, kw.col
));
}
}
}
K::JumpStatement if !in_loop => {
if let Some(label) = n.node(0) {
self.chunk(label);
}
let kw = n.tok(2).ok_or("a jump with no keyword")?;
if kw.kind == TokenKind::BreakKeyword {
let (b, cb) = (&cx.b, &cx.cb);
self.w
.glue_at(kw, &format!("begin {b}_bk = 1; disable {cb}; end"));
} else {
return Err(format!(
"{}:{}:{}: error: 'continue' outside a loop in a randsequence \
code block",
kw.file, kw.line, kw.col
));
}
}
K::DataDeclaration => {
for d in n.list_nodes(3) {
if let Some(t) = d.tok(0) {
self.declare(&cx.b, &t.text);
}
}
let lifetime = n.list(1).iter().any(|m| {
matches!(m, Elem::Tok(t)
if matches!(t.kind, TokenKind::StaticKeyword | TokenKind::AutomaticKeyword))
});
self.code(&n.members[..2.min(n.members.len())], cx, in_loop)?;
if !lifetime {
self.w.glue("automatic");
}
if n.members.len() > 2 {
self.code(&n.members[2..], cx, in_loop)?;
}
}
K::ForLoopStatement | K::ForeachLoopStatement => {
let saved = self.subst.clone();
if n.kind == K::ForLoopStatement {
for init in n.list_nodes(4) {
if init.kind == K::ForVariableDeclaration
&& let Some(t) = init.node(2).and_then(|d| d.tok(0))
{
self.declare(&cx.b, &t.text);
}
}
} else if let Some(list) = n.node(3) {
for v in list.list_nodes(3) {
if let Some(t) = v.tok(0) {
self.declare(&cx.b, &t.text);
}
}
}
let r = self.code(&n.members, cx, true);
self.subst = saved;
r?
}
K::LoopStatement | K::DoWhileStatement | K::ForeverStatement => {
self.code(&n.members, cx, true)?
}
K::SequentialBlockStatement | K::ParallelBlockStatement => {
let saved = self.subst.clone();
let r = self.code(&n.members, cx, in_loop);
self.subst = saved;
r?
}
_ => self.code(&n.members, cx, in_loop)?,
},
Elem::Tok(t) => self.emit(t),
Elem::List(v) => self.code(v, cx, in_loop)?,
Elem::Absent => {}
}
}
Ok(())
}
}
impl Gen {
fn declare(&mut self, b: &str, name: &str) {
if self.heads.iter().any(|h| h == name) {
let m = format!("{b}_m{}", self.fresh());
self.subst.insert(name.to_string(), m);
} else {
self.subst.remove(name);
}
}
}
fn max_caps(rs: &Rs, prod: usize, j: usize) -> usize {
rs.roots[prod][j].iter().map(Root::caps).max().unwrap_or(0)
}
fn derive<'a>(
rs: &Rs,
caller: Option<(usize, usize)>,
choice: &[usize],
a: &'a Node,
) -> Result<(Root, Vec<CapSrc<'a>>), String> {
let mut root = Root {
pieces: Vec::new(),
head: None,
konst: false,
};
let mut caps = Vec::new();
lvalue(rs, caller, choice, a, &mut root, &mut caps)?;
Ok((root, caps))
}
fn not_variable(a: &Node) -> String {
match first_tok(a) {
Some(t) => format!(
"{}:{}:{}: error: the actual of a randsequence ref argument must be a variable",
t.file, t.line, t.col
),
None => "the actual of a randsequence ref argument must be a variable".to_string(),
}
}
fn lvalue<'a>(
rs: &Rs,
caller: Option<(usize, usize)>,
choice: &[usize],
a: &'a Node,
root: &mut Root,
caps: &mut Vec<CapSrc<'a>>,
) -> Result<(), String> {
match a.kind {
K::SimplePropertyExpr | K::SimpleSequenceExpr if a.node(1).is_none() => lvalue(
rs,
caller,
choice,
a.node(0).ok_or_else(|| not_variable(a))?,
root,
caps,
),
K::IdentifierName => {
let t = a.tok(0).ok_or_else(|| not_variable(a))?;
head(rs, caller, choice, t, &[], root, caps)
}
K::IdentifierSelectName => {
let t = a.tok(0).ok_or_else(|| not_variable(a))?;
let sels = a.list_nodes(1);
head(rs, caller, choice, t, &sels, root, caps)
}
K::ElementSelectExpression => {
lvalue(
rs,
caller,
choice,
a.node(0).ok_or_else(|| not_variable(a))?,
root,
caps,
)?;
select(a.node(1).ok_or_else(|| not_variable(a))?, root, caps);
Ok(())
}
K::MemberAccessExpression => {
lvalue(
rs,
caller,
choice,
a.node(0).ok_or_else(|| not_variable(a))?,
root,
caps,
)?;
let name = a.tok(2).ok_or_else(|| not_variable(a))?;
root.pieces.push(Piece::Text(format!(".{}", name.text)));
Ok(())
}
K::ScopedName => {
let sep = a.tok(1).ok_or_else(|| not_variable(a))?;
let right = a.node(2).ok_or_else(|| not_variable(a))?;
if sep.kind == TokenKind::Dot {
lvalue(
rs,
caller,
choice,
a.node(0).ok_or_else(|| not_variable(a))?,
root,
caps,
)?;
root.pieces.push(Piece::Text(".".to_string()));
} else {
let left = a.node(0).ok_or_else(|| not_variable(a))?;
let text: Vec<String> = left.tokens().iter().map(|t| t.text.clone()).collect();
root.pieces
.push(Piece::Text(format!("{}{}", text.join(" "), sep.text)));
if root.head.is_none() {
root.head = Some(String::new());
}
}
member_name(right, root, caps)
}
_ => Err(not_variable(a)),
}
}
fn member_name<'a>(n: &'a Node, root: &mut Root, caps: &mut Vec<CapSrc<'a>>) -> Result<(), String> {
let t = n.tok(0).ok_or_else(|| not_variable(n))?;
root.pieces.push(Piece::Text(t.text.clone()));
if n.kind == K::IdentifierSelectName {
for s in n.list_nodes(1) {
select(s, root, caps);
}
} else if n.kind != K::IdentifierName {
return Err(not_variable(n));
}
Ok(())
}
fn head<'a>(
rs: &Rs,
caller: Option<(usize, usize)>,
choice: &[usize],
t: &Tok,
sels: &[&'a Node],
root: &mut Root,
caps: &mut Vec<CapSrc<'a>>,
) -> Result<(), String> {
let b = &rs.b;
let mut sels = sels.iter();
let frame = |root: &mut Root, caps: &mut Vec<CapSrc>, base: String| {
root.pieces.push(Piece::Text(format!("{base}[")));
root.pieces.push(Piece::Cap);
caps.push(CapSrc::Text(format!("{b}_fp")));
root.pieces.push(Piece::Text("]".to_string()));
};
let mut done = false;
if let Some((cp, cr)) = caller {
let c = &rs.prods[cp];
if let Some(k) = c.ports.iter().position(|p| p.name == t.text) {
if c.ports[k].dir == Dir::Ref {
let inner = &rs.roots[cp][k][choice.get(k).copied().unwrap_or(0)];
let mut i = 0;
for p in &inner.pieces {
if *p == Piece::Cap {
caps.push(CapSrc::Text(format!("{b}_c{cp}_{k}_{i}[{b}_fp]")));
i += 1;
}
root.pieces.push(p.clone());
}
root.head = inner.head.clone();
root.konst = c.ports[k].konst;
} else {
frame(root, caps, format!("{b}_p{cp}_{k}"));
}
done = true;
} else if let Some((q, count)) = c.rules[cr]
.uses
.iter()
.find(|(q, _)| rs.prods[*q].name == t.text)
{
let o = if *count == 1 {
1
} else {
let o = sels
.next()
.and_then(|s| s.node(1))
.filter(|s| s.kind == K::BitSelect)
.and_then(|s| s.node(0))
.and_then(|e| {
let toks = e.tokens();
(toks.len() == 1)
.then(|| toks[0].text.replace('_', "").parse::<usize>().ok())?
})
.filter(|o| (1..=*count).contains(o));
o.ok_or_else(|| {
format!(
"{}:{}:{}: error: a ref argument's actual names production value '{}' with a non-constant index",
t.file, t.line, t.col, t.text
)
})?
};
frame(root, caps, format!("{b}_h{cp}_{cr}_{q}_{o}"));
done = true;
}
}
if !done {
root.pieces.push(Piece::Text(t.text.clone()));
if root.head.is_none() {
root.head = Some(t.text.clone());
}
}
for s in sels {
select(s, root, caps);
}
Ok(())
}
fn select<'a>(s: &'a Node, root: &mut Root, caps: &mut Vec<CapSrc<'a>>) {
let text = |n: &Node| -> String {
n.tokens()
.iter()
.map(|t| t.text.clone())
.collect::<Vec<_>>()
.join(" ")
};
let constant = |n: &Node| {
!n.tokens()
.iter()
.any(|t| matches!(t.kind, TokenKind::Identifier | TokenKind::SystemIdentifier))
};
let mut cap_or_text = |n: &'a Node, root: &mut Root| {
if constant(n) {
root.pieces.push(Piece::Text(text(n)));
} else {
root.pieces.push(Piece::Cap);
caps.push(CapSrc::Expr(n));
}
};
root.pieces.push(Piece::Text("[".to_string()));
match s.node(1) {
Some(sel) if sel.kind == K::BitSelect => {
if let Some(e) = sel.node(0) {
cap_or_text(e, root);
}
}
Some(sel) => {
let indexed = sel
.tok(1)
.is_some_and(|t| matches!(t.kind, TokenKind::PlusColon | TokenKind::MinusColon));
if let Some(l) = sel.node(0) {
if indexed {
cap_or_text(l, root);
} else {
root.pieces.push(Piece::Text(text(l)));
}
}
if let Some(t) = sel.tok(1) {
root.pieces.push(Piece::Text(t.text.clone()));
}
if let Some(r) = sel.node(2) {
root.pieces.push(Piece::Text(text(r)));
}
}
None => {}
}
root.pieces.push(Piece::Text("]".to_string()));
}
fn enumerate_roots(rs: &mut Rs) -> Result<(), String> {
rs.roots = rs
.prods
.iter()
.map(|p| vec![Vec::new(); p.ports.len()])
.collect();
let mut sites: Vec<CallSite> = Vec::new();
for (_, arm) in &rs.arms {
match arm {
Arm::Call {
caller,
callee,
item,
..
} => sites.push((*caller, *callee, *item)),
Arm::JoinInit {
prod, rule, items, ..
} => {
for (q, item) in items {
sites.push((Some((*prod, *rule)), *q, Some(*item)));
}
}
_ => {}
}
}
if !rs
.prods
.iter()
.any(|p| p.ports.iter().any(|q| q.dir == Dir::Ref))
{
return Ok(());
}
loop {
let mut found: Vec<(usize, usize, Root)> = Vec::new();
for (caller, callee, item) in &sites {
let q = &rs.prods[*callee];
let actuals = actuals_of(*item, q)?;
for (j, port) in q.ports.iter().enumerate() {
if port.dir != Dir::Ref {
continue;
}
match (actuals[j], port.default) {
(Some(a), _) => {
for choice in choices(rs, caller.map(|c| c.0)) {
found.push((*callee, j, derive(rs, *caller, &choice, a)?.0));
}
}
(None, Some(d)) => found.push((*callee, j, derive(rs, None, &[], d)?.0)),
(None, None) => {
return Err(format!(
"randsequence production '{}': no actual for ref argument '{}'",
q.name, port.name
));
}
}
}
}
let mut changed = false;
for (p, j, mut root) in found {
if root.konst && !rs.prods[p].ports[j].konst {
return Err(format!(
"randsequence production '{}': a const ref argument cannot be the actual \
of ref argument '{}'",
rs.prods[p].name, rs.prods[p].ports[j].name
));
}
root.konst = false;
if !rs.roots[p][j].contains(&root) {
if rs.roots[p][j].len() >= 64 {
return Err(format!(
"randsequence production '{}': ref argument '{}' can denote more than 64 variables",
rs.prods[p].name, rs.prods[p].ports[j].name
));
}
rs.roots[p][j].push(root);
changed = true;
}
}
if !changed {
return Ok(());
}
}
}
fn choices(rs: &Rs, prod: Option<usize>) -> Vec<Vec<usize>> {
let Some(p) = prod else {
return vec![Vec::new()];
};
let mut out = vec![vec![0; rs.prods[p].ports.len()]];
for (j, port) in rs.prods[p].ports.iter().enumerate() {
if port.dir != Dir::Ref {
continue;
}
let n = rs.roots[p][j].len();
let mut next = Vec::new();
for c in &out {
for k in 0..n {
let mut c = c.clone();
c[j] = k;
next.push(c);
}
}
out = next;
}
out
}
struct CodeCx {
b: String,
cb: String,
prod: usize,
value: bool,
}
fn actuals_of<'a>(item: Option<&'a Node>, q: &Prod) -> Result<Vec<Option<&'a Node>>, String> {
let mut out: Vec<Option<&Node>> = vec![None; q.ports.len()];
let Some(list) = item.and_then(|i| i.node(1)) else {
return Ok(out);
};
let mut pos = 0;
for a in list.list_nodes(1) {
match a.kind {
K::OrderedArgument => {
if pos >= out.len() {
let t = first_tok(a);
return Err(match t {
Some(t) => format!(
"{}:{}:{}: error: too many arguments to randsequence production '{}'",
t.file, t.line, t.col, q.name
),
None => format!("too many arguments to production '{}'", q.name),
});
}
out[pos] = a.node(0);
pos += 1;
}
K::NamedArgument => {
let name = a.tok(1).map(|t| t.text.clone()).unwrap_or_default();
let j = q.ports.iter().position(|p| p.name == name).ok_or_else(|| {
format!(
"randsequence production '{}' has no argument '{name}'",
q.name
)
})?;
out[j] = a.node(3);
}
_ => pos += 1,
}
}
Ok(out)
}
fn production(p: &Node) -> Result<Prod<'_>, String> {
let name_tok = p.tok(1).ok_or("a randsequence production with no name")?;
let value = p.node(0).filter(|t| {
let toks = t.tokens();
!(toks.len() == 1 && toks[0].kind == TokenKind::VoidKeyword) && !toks.is_empty()
});
let mut ports = Vec::new();
if let Some(list) = p.node(2) {
let mut dir = Dir::In;
let mut ty: Option<&Node> = None;
let mut implicit = false;
for port in list.list_nodes(1) {
if port.kind != K::FunctionPort {
return Err(format!(
"randsequence production '{}': unsupported argument form",
name_tok.text
));
}
let konst = port.tok(1).is_some();
if let Some(d) = port.tok(2) {
dir = match d.kind {
TokenKind::InputKeyword => Dir::In,
TokenKind::OutputKeyword => Dir::Out,
TokenKind::RefKeyword => Dir::Ref,
_ => Dir::InOut,
};
}
if let Some(t) = port.node(5) {
if !t.tokens().is_empty() {
implicit = t.kind == K::ImplicitType;
ty = Some(t);
} else if port.tok(2).is_some() {
ty = None;
implicit = false;
}
}
let decl = port.node(6).ok_or("a production argument with no name")?;
let name = decl.tok(0).ok_or("a production argument with no name")?;
let dims = decl
.list(1)
.iter()
.flat_map(|e| match e {
Elem::Node(n) => n.tokens(),
Elem::Tok(t) => vec![t],
_ => Vec::new(),
})
.collect();
ports.push(Port {
name: name.text.clone(),
dir,
konst,
ty,
implicit,
dims,
default: decl.node(2).and_then(|init| init.node(1)),
});
}
}
for port in ports
.iter()
.filter(|port| port.konst && port.dir == Dir::Ref)
{
for r in p.list_nodes(4) {
if let Some(t) = written(r, &port.name) {
return Err(format!(
"{}:{}:{}: error: cannot assign to const ref argument '{}'",
t.file, t.line, t.col, port.name
));
}
}
}
let mut rules = Vec::new();
for r in p.list_nodes(4) {
let weight_clause = r.node(2);
rules.push(Rule {
join: r.node(0),
prods: r.list_nodes(1),
weight: weight_clause.and_then(|w| w.node(1)),
code: weight_clause.and_then(|w| w.node(2)),
uses: Vec::new(),
});
}
Ok(Prod {
name: name_tok.text.clone(),
name_tok,
ports,
value,
rules,
joined: false,
})
}
fn written<'a>(n: &'a Node, name: &str) -> Option<&'a Tok> {
let kind = format!("{:?}", n.kind);
let target = if kind.ends_with("AssignmentExpression") {
n.node(0)
} else if matches!(
n.kind,
K::UnaryPreincrementExpression
| K::UnaryPredecrementExpression
| K::PostincrementExpression
| K::PostdecrementExpression
) {
n.members.iter().find_map(|m| match m {
Elem::Node(x) => Some(x),
_ => None,
})
} else {
None
};
if let Some(t) = target.and_then(|t| base_name(t, name)) {
return Some(t);
}
for m in &n.members {
let found = match m {
Elem::Node(x) => written(x, name),
Elem::List(v) => v.iter().find_map(|e| match e {
Elem::Node(x) => written(x, name),
_ => None,
}),
_ => None,
};
if found.is_some() {
return found;
}
}
None
}
fn base_name<'a>(n: &'a Node, name: &str) -> Option<&'a Tok> {
match n.kind {
K::IdentifierName | K::IdentifierSelectName => n.tok(0).filter(|t| t.text == name),
K::ElementSelectExpression | K::MemberAccessExpression | K::ScopedName => {
n.node(0).and_then(|l| base_name(l, name))
}
K::ConcatenationExpression | K::StreamingConcatenationExpression => {
n.members.iter().find_map(|m| match m {
Elem::List(v) => v.iter().find_map(|e| match e {
Elem::Node(x) => base_name(x, name),
_ => None,
}),
Elem::Node(x) => base_name(x, name),
_ => None,
})
}
K::ParenthesizedExpression => n.node(1).and_then(|e| base_name(e, name)),
_ => None,
}
}
fn build_prod(rs: &mut Rs, p: usize) -> Result<(), String> {
let exit = rs.exits[p];
rs.arms.push((exit, Arm::Exit));
let mut starts = Vec::new();
let n_rules = rs.prods[p].rules.len();
for r in 0..n_rules {
let rule_prods = rs.prods[p].rules[r].prods.clone();
let join = rs.prods[p].rules[r].join;
let code = rs.prods[p].rules[r].code;
let n_items =
if join.is_some() { 1 } else { rule_prods.len() } + usize::from(code.is_some());
let item_pcs: Vec<Pc> = (0..n_items).map(|_| rs.pc()).collect();
let first = item_pcs.first().copied().unwrap_or(exit);
let start = if rs.prods[p].rules[r].uses.is_empty() {
first
} else {
let s = rs.pc();
rs.arms.push((
s,
Arm::RuleStart {
prod: p,
rule: r,
first,
},
));
s
};
starts.push(start);
let next_of = |i: usize| item_pcs.get(i + 1).copied().unwrap_or(exit);
let mut seen: HashMap<usize, usize> = HashMap::new();
let mut slot_of = |rs: &Rs, q: usize| -> Option<(usize, usize)> {
rs.prods[q].value?;
let o = seen.entry(q).or_insert(0);
*o += 1;
Some((q, *o))
};
if let Some(clause) = join {
let mut items = Vec::new();
for item in &rule_prods {
if item.kind != K::RsProdItem {
return Err("rand join takes production items only".to_string());
}
items.push((rs.find(item)?, *item));
}
let entry = rs.pc();
let sched = rs.pc();
let jexit = rs.pc();
let entries: Vec<Pc> = items.iter().map(|(q, _)| rs.entries[*q]).collect();
let with_slots: Vec<JoinItem> = items
.iter()
.map(|(q, item)| (*q, *item, slot_of(rs, *q)))
.collect();
let n = items.len();
rs.arms.push((
item_pcs[0],
Arm::JoinInit {
prod: p,
rule: r,
clause,
items,
entry,
exit: jexit,
},
));
rs.arms.push((entry, Arm::JoinEntry { n, entries, sched }));
rs.arms.push((
sched,
Arm::JoinSched {
prod: p,
n,
next: next_of(0),
},
));
rs.arms.push((
jexit,
Arm::JoinExit {
caller: (p, r),
items: with_slots,
entry,
sched,
},
));
} else {
for (i, prod) in rule_prods.iter().enumerate() {
let here = item_pcs[i];
let next = next_of(i);
match prod.kind {
K::RsProdItem => {
let q = rs.find(prod)?;
let slot = slot_of(rs, q);
let cont = rs.pc();
push_call(rs, here, cont, (p, r), q, prod, slot, next, true);
}
K::RsCodeBlock => rs.arms.push((
here,
Arm::Code {
prod: p,
rule: r,
block: prod,
next,
},
)),
K::RsIfElse => {
let cond = prod.node(2).ok_or("an if production with no condition")?;
let then_item = prod.node(4).ok_or("an if production with no item")?;
let tq = rs.find(then_item)?;
let tslot = slot_of(rs, tq);
let then_pc = rs.pc();
let then_cont = rs.pc();
push_call(
rs,
then_pc,
then_cont,
(p, r),
tq,
then_item,
tslot,
next,
true,
);
let else_pc = match prod.node(5).and_then(|e| e.node(1)) {
Some(else_item) => {
let eq = rs.find(else_item)?;
let eslot = slot_of(rs, eq);
let pc = rs.pc();
let cont = rs.pc();
push_call(rs, pc, cont, (p, r), eq, else_item, eslot, next, true);
Some(pc)
}
None => None,
};
rs.arms.push((
here,
Arm::If {
prod: p,
rule: r,
cond,
then_: then_pc,
else_: else_pc,
next,
},
));
}
K::RsRepeat => {
let count = prod.node(2).ok_or("a repeat production with no count")?;
let item = prod.node(4).ok_or("a repeat production with no item")?;
let q = rs.find(item)?;
let slot = slot_of(rs, q);
let site = rs.n_reps;
rs.n_reps += 1;
rs.reps[p].push(site);
let check = rs.pc();
let call = rs.pc();
let cont = rs.pc();
push_call(rs, call, cont, (p, r), q, item, slot, check, false);
rs.arms.push((
here,
Arm::RepInit {
prod: p,
rule: r,
count,
site,
check,
},
));
rs.arms.push((
check,
Arm::RepCheck {
prod: p,
site,
call,
next,
},
));
}
K::RsCase => {
let mut targets = Vec::new();
for ci in prod.list_nodes(4) {
let item = ci.node(2).ok_or("a case production item with no item")?;
let q = rs.find(item)?;
let slot = slot_of(rs, q);
let pc = rs.pc();
let cont = rs.pc();
push_call(rs, pc, cont, (p, r), q, item, slot, next, true);
targets.push(pc);
}
rs.arms.push((
here,
Arm::Case {
prod: p,
rule: r,
node: prod,
targets,
next,
},
));
}
other => {
return Err(format!("unsupported randsequence item {other:?}"));
}
}
}
}
if let Some(cb) = code {
let here = *item_pcs.last().expect("a code block has a step");
rs.arms.push((
here,
Arm::Code {
prod: p,
rule: r,
block: cb,
next: exit,
},
));
}
}
let entry = rs.entries[p];
rs.arms.push((entry, Arm::Entry { prod: p, starts }));
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn push_call<'a>(
rs: &mut Rs<'a>,
here: Pc,
cont: Pc,
caller: (usize, usize),
callee: usize,
item: &'a Node,
slot: Option<(usize, usize)>,
next: Pc,
boundary: bool,
) {
rs.arms.push((
here,
Arm::Call {
caller: Some(caller),
callee,
item: Some(item),
cont,
},
));
rs.arms.push((
cont,
Arm::Cont {
caller,
callee,
item,
slot,
next,
boundary,
},
));
}
#[cfg(test)]
mod tests {
use super::expand;
#[test]
fn source_without_randsequence_is_unchanged() {
let src = "module m;\n initial $display(\"x\");\nendmodule\n";
assert_eq!(expand(src, "m.sv").unwrap(), src);
}
#[test]
fn the_translation_has_no_randsequence_left() {
let src = "module m;\ninitial begin\nrandsequence (a)\n a : b c ;\n b : { $display(\"b\"); } ;\n c : b ;\nendsequence\nend\nendmodule\n";
let out = expand(src, "m.sv").unwrap();
assert!(!out.contains("randsequence ("), "{out}");
assert!(!out.contains("endsequence"), "{out}");
assert!(out.contains("__vj_rs1_pc"), "{out}");
}
#[test]
fn several_rules_become_a_randcase_on_their_weights() {
let src = "module m;\ninitial randsequence () t : x := 2 | y ; x : { } ; y : { } ; endsequence\nendmodule\n";
let out = expand(src, "m.sv").unwrap();
assert!(out.contains("randcase"), "{out}");
assert!(out.contains("( 1 )"), "the unweighted rule weighs 1: {out}");
}
#[test]
fn unknown_production_is_an_error_naming_it() {
let src = "module m;\ninitial randsequence (a) a : nope ; endsequence\nendmodule\n";
let err = expand(src, "m.sv").unwrap_err();
assert!(err.contains("nope") && err.contains(":2:"), "{err}");
}
#[test]
fn writing_a_const_ref_argument_is_an_error() {
let src = "module m;\nint v;\ninitial randsequence (a) a : p(v) ;\n\
p (const ref int c) : { c++; } ;\nendsequence\nendmodule\n";
let err = expand(src, "m.sv").unwrap_err();
assert!(
err.contains("const ref argument 'c'") && err.contains(":4:"),
"{err}"
);
}
#[test]
fn a_const_ref_argument_cannot_be_passed_to_a_ref_one() {
let src = "module m;\nint v;\ninitial randsequence (a) a : p(v) ;\n\
p (const ref int c) : q(c) ;\nq (ref int r) : { } ;\nendsequence\nendmodule\n";
let err = expand(src, "m.sv").unwrap_err();
assert!(err.contains("ref argument 'r'"), "{err}");
}
#[test]
fn a_ref_argument_is_rewritten_to_its_actual() {
let src = "module m;\nint v;\ninitial randsequence (a) a : p(v) ;\n\
p (ref int x) : { x = 1; } ;\nendsequence\nendmodule\n";
let out = expand(src, "m.sv").unwrap();
assert!(out.contains("v = 1"), "{out}");
}
}