Skip to content

Commit 8829db4

Browse files
committed
Add testbench generation example
1 parent e8eeba2 commit 8829db4

6 files changed

Lines changed: 266 additions & 115 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
3+
// You can obtain one at http://mozilla.org/MPL/2.0/.
4+
//
5+
// Copyright (c) 2026, Lukas Scheller lukasscheller@icloud.com
6+
7+
//! Walks a parsed VHDL design and emits a minimal testbench skeleton for every
8+
//! entity it finds. For each entity the generic and port names are extracted
9+
//! from the interface lists, and the testbench instantiates the unit under
10+
//! test as `entity work.<name>` with `name => name` associations.
11+
//!
12+
//! The example only reads the syntax tree (no builder API), so it stays small
13+
//! and works on syntactically valid input.
14+
15+
use vhdl_syntax::parser;
16+
use vhdl_syntax::syntax::node::SyntaxNode;
17+
use vhdl_syntax::syntax::visitor::WalkEvent;
18+
use vhdl_syntax::syntax::{
19+
AstNode, EntityDeclarationSyntax, InterfaceDeclarationSyntax, InterfaceListSyntax,
20+
InterfaceObjectDeclarationSyntax,
21+
};
22+
23+
fn main() {
24+
let vhdl = "\
25+
entity adder is
26+
generic (WIDTH : positive := 8);
27+
port (
28+
a, b : in std_logic_vector(WIDTH - 1 downto 0);
29+
sum : out std_logic_vector(WIDTH - 1 downto 0)
30+
);
31+
end entity adder;
32+
33+
entity counter is
34+
port (
35+
clk : in std_logic;
36+
q : out std_logic_vector(7 downto 0)
37+
);
38+
end entity counter;
39+
40+
entity blinky is
41+
end entity blinky;
42+
";
43+
44+
let (design, diagnostics) = parser::parse(vhdl);
45+
assert!(
46+
diagnostics.is_empty(),
47+
"Did not expect diagnostics for correct VHDL: {diagnostics:?}"
48+
);
49+
50+
for entity in design.walk().filter_map(extract_entity_declaration) {
51+
let name = extract_entity_name(&entity);
52+
let generics = extract_generics(&entity);
53+
let ports = extract_ports(&entity);
54+
55+
println!("{}", build_testbench(&name, &generics, &ports));
56+
}
57+
}
58+
59+
fn extract_entity_declaration(event: WalkEvent<SyntaxNode>) -> Option<EntityDeclarationSyntax> {
60+
match event {
61+
WalkEvent::Enter(node) => EntityDeclarationSyntax::cast(node),
62+
WalkEvent::Leave(_) => None,
63+
}
64+
}
65+
66+
fn extract_entity_name(entity: &EntityDeclarationSyntax) -> String {
67+
entity
68+
.entity_declaration_preamble()
69+
.and_then(|p| p.name_token())
70+
.map(|t| t.text().to_string())
71+
.expect("Invalid VHDL")
72+
}
73+
74+
fn extract_generics(entity: &EntityDeclarationSyntax) -> Vec<String> {
75+
entity
76+
.entity_header()
77+
.and_then(|h| h.generic_clause())
78+
.and_then(|c| c.interface_list())
79+
.map(|l| interface_names(&l))
80+
.unwrap_or_default()
81+
}
82+
83+
fn extract_ports(entity: &EntityDeclarationSyntax) -> Vec<String> {
84+
entity
85+
.entity_header()
86+
.and_then(|h| h.port_clause())
87+
.and_then(|c| c.interface_list())
88+
.map(|l| interface_names(&l))
89+
.unwrap_or_default()
90+
}
91+
92+
/// Collect the identifier names declared by a generic- or port-clause's
93+
/// interface list. Subprogram/package/type interfaces are skipped for the simple example.
94+
fn interface_names(list: &InterfaceListSyntax) -> Vec<String> {
95+
let mut names = Vec::new();
96+
for decl in list.interface_declarations() {
97+
let object = match decl {
98+
InterfaceDeclarationSyntax::InterfaceObjectDeclaration(o) => o,
99+
_ => continue,
100+
};
101+
let id_list = match object {
102+
InterfaceObjectDeclarationSyntax::InterfaceConstantDeclaration(d) => {
103+
d.identifier_list()
104+
}
105+
InterfaceObjectDeclarationSyntax::InterfaceSignalDeclaration(d) => d.identifier_list(),
106+
InterfaceObjectDeclarationSyntax::InterfaceVariableDeclaration(d) => {
107+
d.identifier_list()
108+
}
109+
InterfaceObjectDeclarationSyntax::InterfaceFileDeclaration(d) => d.identifier_list(),
110+
};
111+
if let Some(id_list) = id_list {
112+
for token in id_list.identifier_token() {
113+
names.push(token.text().to_string());
114+
}
115+
}
116+
}
117+
names
118+
}
119+
120+
fn build_testbench(name: &str, generics: &[String], ports: &[String]) -> String {
121+
let generic_map = format_map("generic", generics);
122+
let port_map = format_map("port", ports);
123+
format!(
124+
"
125+
entity tb_{name} is
126+
end entity tb_{name};
127+
128+
architecture arch of tb_{name} is
129+
begin
130+
uut: entity work.{name}{generic_map}{port_map};
131+
end architecture arch;
132+
"
133+
)
134+
}
135+
136+
/// Formats a generic or port map associating each name by formal (`name => name`)
137+
fn format_map(kind: &str, names: &[String]) -> String {
138+
if names.is_empty() {
139+
return String::default();
140+
}
141+
let assocs: Vec<String> = names.iter().map(|n| format!("{n} => {n}")).collect();
142+
format!(
143+
"
144+
{kind} map (
145+
{}
146+
)",
147+
assocs.join(",\n ")
148+
)
149+
}

0 commit comments

Comments
 (0)