"""
Modern UVM Testbench Generator UI
Clean, Professional Dark Theme - Inspired by VS Code, GitHub, Modern React Dashboards
"""
import streamlit as st
import logging
import tempfile
import os
import zipfile
import io
from pathlib import Path
from datetime import datetime
import json
import pandas as pd
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("uvmgen-modern")
st.set_page_config(
page_title="UVM Generator",
page_icon="⚡",
layout="wide",
initial_sidebar_state="expanded"
)
st.markdown("""
""", unsafe_allow_html=True)
EXAMPLES = {
"UART": """design_name: uart
clock_reset:
clock: clk
reset: rst_n
interfaces:
- name: wb
signals:
- name: wb_cyc
direction: input
- name: wb_stb
direction: input
- name: wb_we
direction: input
- name: wb_addr
direction: input
width: 3
- name: wb_data_o
direction: output
width: 8
- name: wb_data_i
direction: input
width: 8
- name: wb_ack
direction: output
- name: uart
signals:
- name: uart_tx
direction: output
- name: uart_rx
direction: input
- name: cts_n
direction: input
- name: rts_n
direction: output
- name: uart_intr
direction: output
registers:
- name: RBR_THR
address: 0x0
fields:
- name: data
bits: 7:0
- name: IER
address: 0x1
fields:
- name: erbfi
bits: '0'
- name: etbei
bits: '1'
- name: LCR
address: 0x3
fields:
- name: wls
bits: 1:0
- name: dlab
bits: '7'
- name: LSR
address: 0x5
fields:
- name: dr
bits: '0'
- name: thre
bits: '5'
protocol: uart""",
"SPI": """design_name: spi_controller
clock_reset:
clock: clk
reset: rst_n
interfaces:
- name: apb
signals:
- name: psel
direction: input
- name: penable
direction: input
- name: pwrite
direction: input
- name: paddr
direction: input
width: 8
- name: pwdata
direction: input
width: 32
- name: prdata
direction: output
width: 32
- name: pready
direction: output
- name: spi
signals:
- name: sclk
direction: output
- name: mosi
direction: output
- name: miso
direction: input
- name: cs_n
direction: output
width: 4
registers:
- name: CTRL
address: 0x0
- name: TXDATA
address: 0x4
- name: RXDATA
address: 0x8
- name: STATUS
address: 0xC
protocol: spi""",
"I2C": """design_name: i2c_master
clock_reset:
clock: clk
reset: rst_n
interfaces:
- name: axi4lite
signals:
- name: awvalid
direction: input
- name: awready
direction: output
- name: awaddr
direction: input
width: 16
- name: wvalid
direction: input
- name: wready
direction: output
- name: wdata
direction: input
width: 32
- name: rvalid
direction: output
- name: rready
direction: input
- name: rdata
direction: output
width: 32
- name: i2c
signals:
- name: scl
direction: inout
- name: sda
direction: inout
registers:
- name: PRESCALE
address: 0x0
- name: CTRL
address: 0x4
- name: TX_RX
address: 0x8
- name: CMD_STATUS
address: 0xC
protocol: i2c"""
}
MODEL_CONFIGS = {
"v2": {
"name": "ML-Driven (Recommended)",
"desc": "Advanced RL with pattern recognition",
"features": ["Reinforcement Learning", "Experience Replay", "Pattern Mining", "UVM Compliance"]
},
"hybrid": {
"name": "Hybrid Retrieval",
"desc": "Similarity + templates",
"features": ["Similarity Search", "Template Matching"]
},
"template": {
"name": "Template-Based",
"desc": "Fast deterministic generation",
"features": ["Fastest", "Deterministic"]
}
}
RL_STRATEGIES = {
"ucb": "Upper Confidence Bound",
"softmax": "Softmax (Boltzmann)",
"epsilon_greedy": "Epsilon-Greedy",
"thompson": "Thompson Sampling"
}
if 'last_result' not in st.session_state:
st.session_state.last_result = None
if 'generated_files' not in st.session_state:
st.session_state.generated_files = {}
if 'log_output' not in st.session_state:
st.session_state.log_output = []
if 'ml_stats' not in st.session_state:
st.session_state.ml_stats = None
if 'run_id' not in st.session_state:
st.session_state.run_id = 0
with st.sidebar:
st.markdown("""
""", unsafe_allow_html=True)
with st.expander("Specification", expanded=True):
st.markdown('', unsafe_allow_html=True)
selected_protocol = st.selectbox(
"Protocol",
list(EXAMPLES.keys()),
index=0,
label_visibility="collapsed",
key="p_sel"
)
st.markdown('', unsafe_allow_html=True)
design_name = st.text_input(
"Design Name",
value=f"{selected_protocol.lower()}_controller",
label_visibility="collapsed",
key="d_nm"
)
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
col_c1, col_c2 = st.columns(2)
with col_c1:
clk_freq = st.number_input("Clock (MHz)", value=100.0, min_value=1.0, step=10.0, key="clk_f")
with col_c2:
rst_polarity = st.selectbox("Reset", ["Active Low", "Active High"], index=0, key="rst_p")
st.markdown('', unsafe_allow_html=True)
with st.expander("AI Configuration", expanded=True):
st.markdown('', unsafe_allow_html=True)
model_mode = st.radio(
"Mode",
list(MODEL_CONFIGS.keys()),
index=0,
format_func=lambda k: MODEL_CONFIGS[k]["name"],
label_visibility="collapsed",
key="m_mode"
)
st.caption(MODEL_CONFIGS[model_mode]["desc"])
for feat in MODEL_CONFIGS[model_mode]["features"]:
st.markdown(f'{feat}', unsafe_allow_html=True)
if model_mode == "v2":
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
rl_strategy = st.selectbox(
"Exploration Strategy",
list(RL_STRATEGIES.keys()),
index=0,
format_func=lambda k: RL_STRATEGIES[k],
key="rl_s"
)
enable_learning = st.checkbox("Continuous Learning", value=True, key="l_en")
strict_uvm = st.checkbox("Strict UVM Compliance", value=True, key="uvm_s")
st.markdown('', unsafe_allow_html=True)
with st.expander("Execution", expanded=True):
st.markdown('', unsafe_allow_html=True)
max_iterations = st.slider(
"Iterations",
min_value=1,
max_value=10,
value=1,
key="m_iter"
)
enable_sim = st.checkbox("Run Simulation", value=False, key="s_en")
enable_val = st.checkbox("Validate Output", value=True, key="v_en")
st.markdown('', unsafe_allow_html=True)
generate_btn = st.button(
"Generate Testbench",
type="primary",
use_container_width=True,
key="r_btn"
)
col_a, col_b = st.columns(2)
with col_a:
validate_btn = st.button("Validate", use_container_width=True, key="v_btn")
with col_b:
export_btn = st.button("Export", use_container_width=True, key="e_btn")
st.markdown('', unsafe_allow_html=True)
with st.expander("Resources", expanded=False):
st.markdown('', unsafe_allow_html=True)
st.markdown("""
""", unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
st.markdown("""
""", unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
st.caption("Sai Kumar Taraka")
st.markdown("""
Design:
uart_controller
Protocol:
UART
Engine:
V2
""", unsafe_allow_html=True)
tab_spec, tab_gen, tab_results, tab_analysis, tab_coverage, tab_logs = st.tabs([
"Specification",
"Generation",
"Results",
"Analysis",
"Coverage",
"Logs"
])
with tab_spec:
col_edit, col_summary = st.columns([2, 1])
with col_edit:
st.markdown('', unsafe_allow_html=True)
spec_text = st.text_area(
"Edit Specification",
value=EXAMPLES[selected_protocol],
height=520,
key="s_edit",
label_visibility="collapsed"
)
cols = st.columns(4)
with cols[0]:
st.markdown(f"""
{selected_protocol}
Protocol
""", unsafe_allow_html=True)
with cols[1]:
st.markdown(f"""
""", unsafe_allow_html=True)
with cols[2]:
st.markdown(f"""
{model_mode.upper()}
Engine
""", unsafe_allow_html=True)
with cols[3]:
st.markdown(f"""
""", unsafe_allow_html=True)
with col_summary:
st.markdown('', unsafe_allow_html=True)
import yaml
try:
spec_dict = yaml.safe_load(spec_text)
cols_s = st.columns(2)
with cols_s[0]:
st.metric("Interfaces", len(spec_dict.get('interfaces', [])))
total_sigs = sum(len(i.get('signals', [])) for i in spec_dict.get('interfaces', []))
st.metric("Signals", total_sigs)
with cols_s[1]:
st.metric("Registers", len(spec_dict.get('registers', [])))
total_fields = sum(len(r.get('fields', [])) for r in spec_dict.get('registers', []))
st.metric("Fields", total_fields)
st.markdown('', unsafe_allow_html=True)
st.markdown('Interface Configuration
', unsafe_allow_html=True)
for iface in spec_dict.get('interfaces', []):
with st.expander(f"🔌 {iface.get('name')}"):
for sig in iface.get('signals', []):
name = sig.get('name')
direction = sig.get('direction')
width = sig.get('width', 1)
dir_color = "#3fb950" if direction == "input" else "#58a6ff"
st.markdown(f"""
{name}
{direction} [{width}]
""", unsafe_allow_html=True)
except Exception as e:
st.error(f"Parse Error: {e}")
with tab_gen:
st.markdown('', unsafe_allow_html=True)
cols_g = st.columns(4)
with cols_g[0]:
st.markdown(f"""
{model_mode.upper()}
Engine
""", unsafe_allow_html=True)
with cols_g[1]:
st.markdown(f"""
{max_iterations}
Iterations
""", unsafe_allow_html=True)
with cols_g[2]:
st.markdown(f"""
{'ON' if enable_learning else 'OFF'}
Learning
""", unsafe_allow_html=True)
with cols_g[3]:
st.markdown(f"""
{'ON' if strict_uvm else 'OFF'}
UVM Strict
""", unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
pipeline_cols = st.columns(6)
pipeline_steps = [
("Spec Parse", True, "success"),
("Feature Ext", True, "success"),
("ML Generation", False, "pending"),
("UVM Validation", False, "pending"),
("Coverage Analysis", False, "pending"),
("Export", False, "pending")
]
for i, (step, is_done, status) in enumerate(pipeline_steps):
with pipeline_cols[i]:
dot_class = status
bg_color = "#1c2128" if is_done else "#0d1117"
border_color = "#3fb950" if is_done else "#21262d"
text_color = "#3fb950" if is_done else "#8b949e"
st.markdown(f"""
""", unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
col_info1, col_info2 = st.columns(2)
with col_info1:
st.markdown("""
Coverage Strategy
Recommended: Add directed tests for reset values, bus protocols, and register access patterns.
""", unsafe_allow_html=True)
with col_info2:
st.markdown("""
Assertion Generator
AI can generate protocol-specific assertions for signal timing, handshakes, and data integrity.
""", unsafe_allow_html=True)
with tab_results:
if st.session_state.last_result:
result = st.session_state.last_result
eval_metrics = result.get('evaluation', {})
st.markdown('', unsafe_allow_html=True)
cols_m = st.columns(6)
metrics_display = [
("Completeness", eval_metrics.get('completeness', 0) * 100, "%"),
("Signal Cov", eval_metrics.get('interface_signal_coverage', 0) * 100, "%"),
("Register Cov", eval_metrics.get('register_coverage', 0) * 100, "%"),
("Files", len(st.session_state.generated_files), ""),
("Iterations", result.get('auto_train_iterations', 0), ""),
("Status", "PASS" if result.get('passed') else "DONE", "")
]
for i, (label, value, suffix) in enumerate(metrics_display):
with cols_m[i]:
if isinstance(value, float):
display_val = f"{value:.1f}{suffix}"
else:
display_val = f"{value}{suffix}"
is_pass = (label == "Status" and value == "PASS") or (isinstance(value, (int, float)) and value >= 90)
success_class = "metric-success" if is_pass else ""
st.markdown(f"""
""", unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
if st.session_state.generated_files:
st.markdown('', unsafe_allow_html=True)
col_tree, col_code = st.columns([1, 3])
with col_tree:
file_names = sorted(st.session_state.generated_files.keys())
if 'selected_file' not in st.session_state:
st.session_state.selected_file = file_names[0] if file_names else None
st.markdown('', unsafe_allow_html=True)
for fn in file_names:
is_active = (fn == st.session_state.selected_file)
active_class = "active" if is_active else ""
if st.button(fn, key=f"f_{fn}", use_container_width=True):
st.session_state.selected_file = fn
st.markdown('
', unsafe_allow_html=True)
with col_code:
selected_file = st.session_state.selected_file
if selected_file and selected_file in st.session_state.generated_files:
file_path = st.session_state.generated_files[selected_file]
if os.path.exists(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
col_dl1, col_dl2, col_info = st.columns([1, 1, 3])
with col_dl1:
st.download_button(
"Download",
data=content,
file_name=selected_file,
mime="text/plain",
use_container_width=True
)
with col_dl2:
st.button("Copy", use_container_width=True, key="cp_btn")
with col_info:
st.caption(f"Lines: {len(content.splitlines())} | Size: {len(content)} bytes")
st.markdown(f"""
""", unsafe_allow_html=True)
except Exception as e:
st.warning(f"Could not read file: {e}")
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:
for name, path in st.session_state.generated_files.items():
if os.path.exists(path):
zipf.write(path, arcname=name)
zip_buffer.seek(0)
col_e1, col_e2, col_e3 = st.columns([2, 2, 3])
with col_e1:
st.download_button(
"Download UVM Package",
data=zip_buffer,
file_name=f"{design_name}_uvm_testbench.zip",
mime="application/zip",
use_container_width=True,
type="primary"
)
with col_e2:
st.button("Generate Simulation Script", use_container_width=True)
with col_e3:
st.button("Create Regression Suite", use_container_width=True)
else:
st.markdown("""
⚡
Ready to Generate
Configure your specification and click "Generate Testbench" in the sidebar.
""", unsafe_allow_html=True)
with tab_analysis:
if st.session_state.ml_stats:
stats = st.session_state.ml_stats
st.markdown('', unsafe_allow_html=True)
cols_a = st.columns(4)
with cols_a[0]:
st.markdown(f"""
{stats.get('total_generations', 0)}
Generations
""", unsafe_allow_html=True)
with cols_a[1]:
if 'rl_learner' in stats:
rl = stats['rl_learner']
st.markdown(f"""
{rl.get('episode_count', 0)}
RL Episodes
""", unsafe_allow_html=True)
with cols_a[2]:
if 'rl_learner' in stats:
rl = stats['rl_learner']
st.markdown(f"""
{rl.get('total_updates', 0)}
Updates
""", unsafe_allow_html=True)
with cols_a[3]:
if 'rl_learner' in stats:
rl = stats['rl_learner']
st.markdown(f"""
{rl.get('learning_rate', 0.1):.3f}
Learning Rate
""", unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
col_dist, col_weights = st.columns(2)
with col_dist:
st.markdown('', unsafe_allow_html=True)
if 'source_distribution' in stats:
dist = stats['source_distribution']
if dist:
df = pd.DataFrame({
'Source': list(dist.keys()),
'Count': list(dist.values())
})
st.bar_chart(df.set_index('Source'), color=["#58a6ff"])
else:
st.info("No distribution data yet")
with col_weights:
st.markdown('', unsafe_allow_html=True)
if 'strategy_weights' in stats:
weights = stats['strategy_weights']
for strategy, weight in weights.items():
percentage = weight * 100
st.markdown(f"""
{strategy}
{percentage:.1f}%
""", unsafe_allow_html=True)
else:
st.info("No strategy weights yet")
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
if 'rl_learner' in stats and 'state_stats' in stats['rl_learner']:
state_stats = stats['rl_learner']['state_stats']
if state_stats:
cols_state = st.columns(min(4, len(state_stats)))
for i, (state, info) in enumerate(list(state_stats.items())[:4]):
with cols_state[i]:
q_val = info.get('best_q_value', 0)
best_action = info.get('best_action', 'N/A')
visits = info.get('visit_count', 0)
st.markdown(f"""
{state[:20]}
{q_val:.3f}
Q-Value
Best: {best_action}
Visits: {visits}
""", unsafe_allow_html=True)
else:
st.info("No RL state data yet - run a generation to collect statistics")
else:
st.markdown("""
🔬
ML Analysis
Run a generation with the ML-Driven engine to see:
• Reinforcement Learning metrics
• Strategy weight distributions
• Q-value tracking
• Source distribution analysis
""", unsafe_allow_html=True)
with tab_coverage:
st.markdown('', unsafe_allow_html=True)
cols_c = st.columns(4)
with cols_c[0]:
st.markdown(f"""
""", unsafe_allow_html=True)
with cols_c[1]:
st.markdown(f"""
""", unsafe_allow_html=True)
with cols_c[2]:
st.markdown(f"""
""", unsafe_allow_html=True)
with cols_c[3]:
st.markdown(f"""
""", unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
st.markdown("""
🎯 AI Coverage Recommendations
1
Add directed tests for edge cases (all 0s, all 1s, max values)
2
Verify reset values for all registers
3
Test protocol handshakes with back-to-back transactions
4
Add concurrent stimulus for protocol validation
""", unsafe_allow_html=True)
with tab_logs:
st.markdown('', unsafe_allow_html=True)
if st.session_state.log_output:
log_html = "\n".join([
f'{line}'
for line in st.session_state.log_output
])
st.markdown(f"""
{log_html}
""", unsafe_allow_html=True)
else:
st.markdown(f"""
[{datetime.now().strftime('%H:%M:%S')}] System initialized
[{datetime.now().strftime('%H:%M:%S')}] Waiting for specification...
[{datetime.now().strftime('%H:%M:%S')}] AI engine ready
[{datetime.now().strftime('%H:%M:%S')}] UVM templates loaded
[{datetime.now().strftime('%H:%M:%S')}] All systems operational
# Click "Generate Testbench" to begin
""", unsafe_allow_html=True)
col_clear, col_export = st.columns([1, 5])
with col_clear:
st.button("Clear Logs", use_container_width=True)
if generate_btn:
st.session_state.run_id += 1
st.session_state.log_output = []
st.session_state.last_result = None
st.session_state.generated_files = {}
st.session_state.ml_stats = None
try:
from src.config import ConfigLoader, PipelineConfig, MLConfig, GenerationConfig, AutoTrainConfig
from src.pipeline import TBPipeline
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False, encoding='utf-8') as f:
f.write(spec_text)
spec_path = f.name
timestamp = datetime.now().strftime('%H:%M:%S')
st.session_state.log_output.append(f"[{timestamp}] Starting generation: {design_name}")
st.session_state.log_output.append(f"[{timestamp}] Engine: {model_mode}")
if model_mode == "v2":
st.session_state.log_output.append(f"[{timestamp}] RL Strategy: {rl_strategy}")
ml_cfg = MLConfig(
enabled=(model_mode != "template"),
model_type=model_mode,
use_llm=False,
use_semantic_encoder=False,
)
if model_mode == "v2":
ml_cfg.exploration_strategy = rl_strategy
ml_cfg.use_learning = enable_learning
ml_cfg.strict_validation = strict_uvm
pipeline_cfg = PipelineConfig(
ml=ml_cfg,
generation=GenerationConfig(
templates_dir=os.path.join(os.getcwd(), "src", "generation", "templates"),
output_dir=os.path.join(os.getcwd(), "output"),
overwrite=True
),
auto_train=AutoTrainConfig(
enabled=(max_iterations > 1),
max_iterations=max_iterations
)
)
pipeline = TBPipeline(pipeline_cfg)
st.session_state.log_output.append(f"[{timestamp}] Model: {type(pipeline.model).__name__}")
st.session_state.log_output.append(f"[{timestamp}] Processing specification...")
result = pipeline.run(spec_path)
try:
os.unlink(spec_path)
except:
pass
st.session_state.last_result = result
st.session_state.generated_files = result.get('generated_files', {})
try:
if hasattr(pipeline.model, 'get_learning_stats'):
st.session_state.ml_stats = pipeline.model.get_learning_stats()
except Exception as e:
logger.warning(f"Could not get ML stats: {e}")
timestamp = datetime.now().strftime('%H:%M:%S')
st.session_state.log_output.append(f"[{timestamp}] Generation complete")
st.session_state.log_output.append(f"[{timestamp}] Files generated: {len(st.session_state.generated_files)}")
if result.get('passed'):
st.session_state.log_output.append(f"[{timestamp}] Status: PASS")
else:
st.session_state.log_output.append(f"[{timestamp}] Status: COMPLETED")
st.rerun()
except Exception as e:
timestamp = datetime.now().strftime('%H:%M:%S')
st.session_state.log_output.append(f"[{timestamp}] Error: {str(e)}")
import traceback
st.session_state.log_output.append(traceback.format_exc())
st.rerun()