# New Verilog Project

> 新建 Verilog 仿真项目，搭建环境。触发词包括：新建项目、建立Verilog环境、初始化工程、搭建仿真环境、新建Verilog工程

- Skill: `ljj05170930/new-verilog-project` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ljj05170930/new-verilog-project`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ljj05170930/new-verilog-project/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: ljj05170930 (https://skillmd.com/u/ljj05170930)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ljj05170930/new-verilog-project

---


在**当前工作目录**下创建完整的 Verilog 仿真项目结构。

## 创建以下目录结构

```
./
├── RTL/               （仅在不存在时创建；已存在则保留原内容不动）
├── TB/
│   └── tb_top.sv      （仅在 TB/ 内无仿真文件时由 tb-creator skill 生成）
├── .vscode/
│   └── tasks.json
├── .gitignore
├── gen_filelist.py
├── gen_dumpvars.py
├── clean.py
└── CLAUDE.md
```

## 各文件内容

### .vscode/tasks.json

```json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Generate Filelist",
            "type": "shell",
            "command": "python",
            "args": ["gen_filelist.py"],
            "group": "build"
        },
        {
            "label": "iverilog compile",
            "type": "shell",
            "command": "iverilog",
            "args": [
                "-o", "sim.vvp",
                "-g2012",
                "-f", "filelist.f"
            ],
            "group": {
                "kind": "build",
                "isDefault": false
            },
            "problemMatcher": []
        },
        {
            "label": "vvp run",
            "type": "shell",
            "command": "vvp sim.vvp -fst",
            "group": {
                "kind": "build",
                "isDefault": false
            },
            "problemMatcher": []
        },
        {
            "label": "Gen Dumpvars",
            "type": "shell",
            "command": "python",
            "args": ["gen_dumpvars.py", "--patch"],
            "group": "build",
            "problemMatcher": []
        },
        {
            "label": "clean",
            "type": "shell",
            "command": "python",
            "args": ["clean.py"],
            "group": "build",
            "problemMatcher": []
        },
        {
            "label": "compile and run",
            "dependsOn": ["Generate Filelist","Gen Dumpvars","iverilog compile", "vvp run"],
            "dependsOrder": "sequence",
            "group": {
                "kind": "build",
                "isDefault": false
            },
            "problemMatcher": []
        }
    ]
}
```

### gen_filelist.py

```python
import os

extensions = ('.v', '.sv')
dirs = ['RTL', 'TB']

with open('filelist.f', 'w') as f:
    for d in dirs:
        if not os.path.exists(d):
            continue
        for root, _, files in os.walk(d):
            for file in files:
                if file.endswith(extensions):
                    path = os.path.join(root, file).replace('', '/')
                    f.write(path + '\n')

print("Generated filelist.f")
```

### gen_dumpvars.py

```python
#!/usr/bin/env python3
"""
gen_dumpvars.py
自动扫描 RTL 文件中的非压缩数组，结合实例化层次，
生成 DUMP_ARRAY 宏调用，可选地直接更新 TB 的波形 initial 块。

用法:
  python gen_dumpvars.py           # 仅打印生成结果
  python gen_dumpvars.py --patch   # 同时更新 TB 文件
"""

import re, sys, argparse
from pathlib import Path

FILELIST = "filelist.f"

SV_KEYWORDS = {
    'module','endmodule','input','output','inout','wire','reg','logic',
    'integer','genvar','begin','end','if','else','for','always','initial',
    'assign','parameter','localparam','generate','endgenerate','task',
    'function','endtask','endfunction','case','casex','casez','endcase',
    'posedge','negedge','repeat','while','fork','join','string','bit',
    'byte','int','real','time','signed','unsigned','automatic',
    'supply0','supply1','tri','triand','trior','wand','wor',
}

# ── 工具 ──────────────────────────────────────────────────────────

def strip_comments(src):
    src = re.sub(r'//[^\n]*', '', src)
    src = re.sub(r'/\*.*?\*/', ' ', src, flags=re.S)
    return src

def read_filelist(path=FILELIST):
    files = []
    with open(path) as f:
        for line in f:
            line = line.strip().replace('', '/')
            if line and not line.startswith('//'):
                files.append(line)
    return files

# ── 解析 ──────────────────────────────────────────────────────────

def extract_params(body):
    """提取 parameter / localparam 的字面量值"""
    params = {}
    for m in re.finditer(
        r'\b(?:parameter|localparam)\b\s*(?:\[[^\]]*\]\s*)?(\w+)\s*=\s*([^;,\n]+)',
        body
    ):
        name = m.group(1)
        expr = m.group(2).strip()
        try:
            val = eval(expr, {"__builtins__": {}}, dict(params))
            params[name] = int(val)
        except Exception:
            pass
    return params

def resolve(expr, params):
    try:
        return int(eval(expr.strip(), {"__builtins__": {}}, params))
    except Exception:
        return None

def parse_module(body):
    """
    解析模块体，返回:
      arrays    : [(arr_name, size)]
      reg_insts : [(mod_type, inst_name)]          -- generate 块之外
      gen_insts : [(gen_label, mod_type, inst_name)] -- generate 块内
    """
    params = extract_params(body)

    # ── 非压缩数组声明（reg / wire）──────────────────────────────
    arrays = []
    for a in re.finditer(
        r'\b(?:reg|wire)\b\s*(?:signed\s*)?(?:\[[^\]]*\]\s*)?'  # 类型 + 可选位宽
        r'(\w+)\s*'                                               # 数组名
        r'\[\s*([^\]:]+?)\s*:\s*([^\]]+?)\s*\]\s*;',            # [hi : lo];
        body
    ):
        name = a.group(1)
        if name in SV_KEYWORDS:
            continue
        hi = resolve(a.group(2), params)
        lo = resolve(a.group(3), params)
        if hi is not None and lo is not None:
            arrays.append((name, abs(hi - lo) + 1))

    # ── generate 块内的实例化 ─────────────────────────────────────
    gen_insts = []
    for gen in re.finditer(r'\bgenerate\b(.*?)\bendgenerate\b', body, re.S):
        for blk in re.finditer(
            r'\bbegin\s*:\s*(\w+)\b(.*?)\bend\b',
            gen.group(1), re.S
        ):
            label    = blk.group(1)
            blk_body = blk.group(2)
            for m in re.finditer(
                r'\b(\w+)\s*(?:#\s*\([^;]*?\)\s*)?(\w+)\s*\(', blk_body
            ):
                mtype, iname = m.group(1), m.group(2)
                if mtype not in SV_KEYWORDS and iname not in SV_KEYWORDS:
                    gen_insts.append((label, mtype, iname))

    # ── generate 块外的常规实例化 ─────────────────────────────────
    body_no_gen = re.sub(r'\bgenerate\b.*?\bendgenerate\b', '', body, flags=re.S)
    reg_insts = []
    for m in re.finditer(
        r'\b(\w+)\s*(?:#\s*\([^;]*?\)\s*)?(\w+)\s*\(', body_no_gen
    ):
        mtype, iname = m.group(1), m.group(2)
        if mtype not in SV_KEYWORDS and iname not in SV_KEYWORDS:
            reg_insts.append((mtype, iname))

    return arrays, reg_insts, gen_insts

def parse_all(files):
    """返回 {mod_name: (arrays, reg_insts, gen_insts)}"""
    all_mods = {}
    for f in files:
        try:
            src   = Path(f).read_text(encoding='utf-8', errors='replace')
            clean = strip_comments(src)
            for m in re.finditer(
                r'\bmodule\s+(\w+)\b(.*?)\bendmodule\b', clean, re.S
            ):
                name, body = m.group(1), m.group(2)
                all_mods[name] = parse_module(body)
        except Exception as e:
            print(f'[警告] 解析 {f} 失败: {e}', file=sys.stderr)
    return all_mods

# ── 层次遍历 ──────────────────────────────────────────────────────

def collect(all_mods, start_insts):
    """
    DFS 收集所有 DUT 内部非压缩数组。
    start_insts: [(mod_type, inst_name)]  — TB 的顶层 DUT 实例
    返回 [(path, size)]
    """
    results = []
    visited = set()

    def dfs(mod_name, prefix):
        key = (mod_name, prefix)
        if key in visited or mod_name not in all_mods:
            return
        visited.add(key)

        arrays, reg_insts, gen_insts = all_mods[mod_name]

        for arr_name, size in arrays:
            results.append((f'{prefix}.{arr_name}', size))

        for mtype, iname in reg_insts:
            if mtype in all_mods:
                dfs(mtype, f'{prefix}.{iname}')

        # generate 实例：波形只关注 [0]（其余通道结构相同）
        for label, mtype, iname in gen_insts:
            if mtype in all_mods:
                dfs(mtype, f'{prefix}.{label}[0].{iname}')

    for mtype, iname in start_insts:
        if mtype in all_mods:
            dfs(mtype, iname)

    return results

# ── 生成 initial 块文本 ───────────────────────────────────────────

def gen_block(tb_mod, entries):
    lines = [
        'initial begin',
        '    $dumpfile("wave.fst");',
        f'    $dumpvars(0, {tb_mod});',
    ]
    for path, size in entries:
        lines.append(f'    `DUMP_ARRAY({path}, {size})')
    lines.append('end')
    return '\n'.join(lines)

# ── 更新 TB 文件 ──────────────────────────────────────────────────

def patch_tb(tb_path, new_block):
    src   = Path(tb_path).read_text(encoding='utf-8')
    lines = src.split('\n')

    start_i = end_i = None
    for i, line in enumerate(lines):
        if re.search(r'\binitial\s+begin\b', line):
            window = '\n'.join(lines[i: i + 6])
            if '$dumpfile' in window:
                start_i = i
                depth = 0
                for j in range(i, len(lines)):
                    depth += len(re.findall(r'\bbegin\b', lines[j]))
                    depth -= len(re.findall(r'\bend\b',   lines[j]))
                    if depth == 0 and j > i:
                        end_i = j
                        break
                break

    if start_i is not None and end_i is not None:
        new_lines = (lines[:start_i]
                     + new_block.split('\n')
                     + lines[end_i + 1:])
        Path(tb_path).write_text('\n'.join(new_lines), encoding='utf-8')
        print(f'[已更新] {tb_path}')
    else:
        # 找到 endmodule 的位置，把 dump initial 块插入到它前面
        endmod_i = None
        for i, line in enumerate(lines):
            if re.search(r'\bendmodule\b', line):
                endmod_i = i
                break
        if endmod_i is not None:
            new_lines = (lines[:endmod_i]
                         + ['']
                         + new_block.split('\n')
                         + ['']
                         + lines[endmod_i:])
            Path(tb_path).write_text('\n'.join(new_lines), encoding='utf-8')
            print(f'[已插入] {tb_path}（在 endmodule 前新增 dump initial 块）')
        else:
            print('[警告] 未找到 endmodule，无法自动插入，请手动添加。', file=sys.stderr)

# ── main ──────────────────────────────────────────────────────────

def main():
    ap = argparse.ArgumentParser(description='自动生成 $dumpvars 波形 initial 块')
    ap.add_argument('--patch', action='store_true',
                    help='直接更新 TB 文件中的 dump initial 块')
    args = ap.parse_args()

    files    = read_filelist()
    tb_files = [f for f in files if Path(f).stem.startswith('tb_')]

    all_mods = parse_all(files)

    # 找 TB 顶层模块名 & DUT 实例列表
    tb_mod      = None
    start_insts = []
    for tf in tb_files:
        src   = Path(tf).read_text(encoding='utf-8', errors='replace')
        clean = strip_comments(src)
        m = re.search(r'\bmodule\s+(\w+)\b(.*?)\bendmodule\b', clean, re.S)
        if m:
            tb_mod = m.group(1)
            _, reg_insts, _ = parse_module(m.group(2))
            for mtype, iname in reg_insts:
                if mtype in all_mods and mtype != tb_mod:
                    start_insts.append((mtype, iname))
            break

    if not tb_mod:
        print('错误：找不到 TB 模块', file=sys.stderr)
        sys.exit(1)

    entries = collect(all_mods, start_insts)

    # 去重，保留首次出现
    seen, unique = set(), []
    for path, size in entries:
        if path not in seen:
            seen.add(path)
            unique.append((path, size))

    block = gen_block(tb_mod, unique)

    print('\n=== 生成的 dump initial 块 ===')
    print(block)
    print('==============================\n')

    if args.patch:
        for tf in tb_files:
            patch_tb(tf, block)

if __name__ == '__main__':
    main()
```

### clean.py

```python
import os
import glob
import shutil

for fst in glob.glob("*.fst"):
    base = os.path.splitext(fst)[0]
    for ext in [".fst", ".vcd"]:
        target = base + ext
        if os.path.exists(target):
            os.remove(target)
            print(f"Deleted: {target}")

for f in ["filelist.f", "sim.vvp"]:
    if os.path.exists(f):
        os.remove(f)
        print(f"Deleted: {f}")

for cache_dir in glob.glob("**/__pycache__", recursive=True):
    shutil.rmtree(cache_dir, ignore_errors=True)
    print(f"Deleted: {cache_dir}")

for pyc in glob.glob("**/*.pyc", recursive=True):
    os.remove(pyc)
    print(f"Deleted: {pyc}")

print("Clean done.")
```

### .gitignore

```
# 仿真产物
*.fst
*.vvp
filelist.f

# Python 缓存
__pycache__/
*.pyc
```

### CLAUDE.md

```markdown
## 回答规范
每次回答结束后，必须在末尾附上：
- 当前使用的模型名称
- 当前的effort 级别（如果有）
```

## 执行步骤

1. **RTL 目录**：使用 Glob 检查 `RTL/` 下是否有任意文件：
   - 若**不存在**：使用 Write 工具创建 `RTL/.gitkeep`（空文件，确保目录存在）
   - 若**已存在**：跳过，不对 `RTL/` 做任何操作

2. 使用 Write 工具依次创建以下文件（直接覆盖，不询问）：
   - `.vscode/tasks.json`
   - `.gitignore`
   - `gen_filelist.py`
   - `gen_dumpvars.py`
   - `clean.py`
   - `CLAUDE.md`
   - `.claude/settings.local.json`（内容如下，用于自动授权 iverilog 编译和仿真命令）

### .claude/settings.local.json

```json
{
  "permissions": {
    "allow": [
      "Bash(iverilog -o sim.vvp -g2012 -f filelist.f)",
      "Bash(vvp sim.vvp -fst)"
    ]
  }
}
```

3. **TB 目录**：使用 Glob 检查 `TB/` 下是否存在 `.v` 或 `.sv` 文件：
   - 若**不存在**：调用 `tb-creator` skill 生成 `TB/tb_top.sv`
   - 若**已存在**：跳过，不对 `TB/` 做任何操作

4. **tasks.json**：创建或覆盖 `.vscode/tasks.json`，不询问用户任何问题

5. 全部完成后，告知用户"Verilog 项目环境已就绪"，并列出创建/跳过的文件清单。

