How to Build a Custom Verilog Testbench Generator in Python

Written by

in

How to Build a Custom Verilog Testbench Generator in Python Writing Verilog testbenches by hand is repetitive and error-prone. Every time a module interface changes, you must manually update port declarations, signal instantiations, and wire connections. Automating this process with a custom Python script saves time and eliminates typos.

This guide will show you how to build a lightweight, custom Verilog testbench generator using Python. Why Build a Custom Generator?

While commercial tools exist, a custom Python script offers specific advantages:

Customization: Tailor the output to your team’s specific coding guidelines and headers.

Automation: Integrate the script directly into your continuous integration (CI/CD) pipelines.

Flexibility: Easily add features like automatic clock generation, reset sequences, or file I/O logging. Architecture of the Generator A testbench generator relies on three core steps:

[ Verilog Source File ] —> [ 1. Parser (Regex) ] —> [ 2. Data Structure ] —> [ 3. Template Engine ] —> [ Generated Testbench ]

The Parser: Extracts the module name and port list (inputs and outputs) from your source code.

The Intermediate Representation: Stores this extracted data cleanly in Python dictionaries or objects.

The Renderer: Uses the extracted data to populate a standardized testbench template. Step 1: Parsing the Verilog Module

To generate a testbench, the script must understand the target module’s interface. We can use Python’s built-in re (regular expression) module to find the module name and parse its ports. Here is the parser implementation:

import re def parse_verilog_module(file_path): with open(file_path, ‘r’) as f: content = f.read() # Remove multi-line and single-line comments to avoid false matches content = re.sub(r’//.’, “, content) content = re.sub(r’/*.?*/‘, “, content, flags=re.DOTALL) # Extract module name module_match = re.search(r’\bmodule\s+(\w+)‘, content) if not module_match: raise ValueError(“No module definition found in the file.”) module_name = module_match.group(1) # Extract port declarations # Matches patterns like: input wire [7:0] data_in, output reg out port_pattern = r’\b(input|output)\s+(?:wire|reg)?\s([[^]]+])?\s(\w+)’ ports = re.findall(port_pattern, content) parsed_ports = [] for direction, size, name in ports: parsed_ports.append({ ‘direction’: direction, ‘size’: size.strip() if size else “, ‘name’: name }) return module_name, parsed_ports Use code with caution. Step 2: Creating the Testbench Template

Instead of concatenating raw strings in Python, use an explicit template string. This keeps the code readable and easy to modify.

We will use a format string that defines standard testbench components: Clock and reset generation logic. Driven reg types for Unit Under Test (UUT) inputs. Wire types for UUT outputs. A structured UUT instantiation block. An initial block for stimulus.

TB_TEMPLATE = “”“`timescale 1ns / 1ps module {tb_name}; // Inputs (driven as regs) {input_decls} // Outputs (monitored as wires) {output_decls} // Instantiate the Unit Under Test (UUT) {module_name} uut ( {uut_connections} ); // Clock generation (Optional - default example) initial begin clk = 0; forever #5 clk = ~clk; end // Stimulus block initial begin // Initialize Inputs // TODO: Add your initialization logic here $finish; end endmodule “”” Use code with caution. Step 3: Generating the Code

Now, combine the parsed data with the template. The script iterates through the ports to construct the signal declarations and port mapping code.

def generate_testbench(module_name, ports): tb_name = f”{module_name}_tb” input_decls = [] output_decls = [] uut_connections = [] for port in ports: size_space = f”{port[‘size’]} “ if port[‘size’] else “” # Format declarations if port[‘direction’] == ‘input’: input_decls.append(f” reg {size_space}{port[‘name’]};“) else: output_decls.append(f” wire {size_space}{port[‘name’]};“) # Format UUT connections uut_connections.append(f” .{port[‘name’]}({port[‘name’]})“) # Join lines with proper formatting input_decls_str = “\n”.join(input_decls) output_decls_str = “\n”.join(output_decls) uut_connections_str = “,\n”.join(uut_connections) # Render template return TB_TEMPLATE.format( tb_name=tb_name, module_name=module_name, input_decls=input_decls_str, output_decls=output_decls_str, uut_connections=uut_connections_str ) Use code with caution. Step 4: Building the Command Line Interface

To make the script reusable, wrap it in a simple command-line interface using sys.argv.

import sys def main(): if len(sys.argv) < 2: print(“Usage: python tb_gen.py ”) sys.exit(1) source_file = sys.argv[1] try: module_name, ports = parse_verilog_module(source_file) tb_code = generate_testbench(module_name, ports) output_file = f”{module_name}_tb.v” with open(output_file, ‘w’) as f: f.write(tb_code) print(f”Successfully generated testbench: {output_file}“) except Exception as e: print(f”Error: {e}“) if name == “main”: main() Use code with caution. Limitations and Next Steps

This lightweight regex approach works beautifully for clean, standard module declarations. However, regular expressions can struggle with complex ANSI vs. non-ANSI port lists or heavily nested macros.

If you want to scale this project up for industrial use, consider these upgrades:

Use a Real Parser: Replace regex with an Abstract Syntax Tree (AST) parser like pyverilog to reliably handle highly complex language features.

Template Engines: Use Jinja2 for your templates to gain cleaner loops, conditionals, and better template management.

Auto-Initialization: Check port names dynamically; if the script finds a port named clk or rst, automatically hook it up to the clock generation logic.

By spending an hour building this custom generator, you can eliminate thousands of lines of boilerplate code over your hardware design career. If you would like to expand this script, let me know:

Should we add automatic detection for specific clock and active-low reset ports? AI responses may include mistakes. Learn more

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *