You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
8.1 KiB
242 lines
8.1 KiB
"""Build the cmpp preprocessor and compile XSPICE .cm code model libraries."""
|
|
import subprocess
|
|
import os
|
|
import sys
|
|
import shutil
|
|
|
|
MINGW_BIN = r"C:\mingw64\bin"
|
|
NGSPICE_ROOT = r"C:\git\ngspice"
|
|
CMPP_SRC = os.path.join(NGSPICE_ROOT, "src", "xspice", "cmpp")
|
|
ICM_DIR = os.path.join(NGSPICE_ROOT, "src", "xspice", "icm")
|
|
INCLUDE_DIR = os.path.join(NGSPICE_ROOT, "src", "include")
|
|
CONFIG_H_DIR = os.path.join(NGSPICE_ROOT, "visualc", "src", "include")
|
|
BUILD_DIR = os.path.join(NGSPICE_ROOT, "build", "cmpp_build")
|
|
CM_OUTPUT_DIR = os.path.join(NGSPICE_ROOT, "pyngspice", "codemodels")
|
|
CMPP_EXE = os.path.join(BUILD_DIR, "cmpp.exe")
|
|
|
|
def get_env():
|
|
env = os.environ.copy()
|
|
env["PATH"] = MINGW_BIN + ";" + env["PATH"]
|
|
return env
|
|
|
|
def run(cmd, cwd=None):
|
|
print(f" > {' '.join(cmd)}")
|
|
r = subprocess.run(cmd, capture_output=True, text=True, cwd=cwd, env=get_env())
|
|
if r.stdout.strip():
|
|
for line in r.stdout.strip().split('\n')[:20]:
|
|
print(f" {line}")
|
|
if r.stderr.strip():
|
|
lines = r.stderr.strip().split('\n')
|
|
# Show first 10 and last 30 lines of errors
|
|
if len(lines) > 40:
|
|
for line in lines[:5]:
|
|
print(f" {line}")
|
|
print(f" ... ({len(lines) - 35} lines omitted) ...")
|
|
for line in lines[-30:]:
|
|
print(f" {line}")
|
|
else:
|
|
for line in lines:
|
|
print(f" {line}")
|
|
if r.returncode != 0:
|
|
print(f" FAILED (rc={r.returncode})")
|
|
return False
|
|
return True
|
|
|
|
def build_cmpp():
|
|
"""Build the cmpp preprocessor tool."""
|
|
print("=== Step 1: Building cmpp preprocessor ===")
|
|
os.makedirs(BUILD_DIR, exist_ok=True)
|
|
|
|
if os.path.exists(CMPP_EXE):
|
|
print(f" cmpp.exe already exists, skipping")
|
|
return True
|
|
|
|
cmpp_sources = [
|
|
"main.c", "file_buffer.c", "pp_ifs.c", "pp_lst.c", "pp_mod.c",
|
|
"read_ifs.c", "util.c", "writ_ifs.c", "ifs_yacc.c", "mod_yacc.c",
|
|
"ifs_lex.c", "mod_lex.c"
|
|
]
|
|
|
|
cmd = [
|
|
"gcc", "-o", CMPP_EXE,
|
|
] + [os.path.join(CMPP_SRC, f) for f in cmpp_sources] + [
|
|
f"-I{CMPP_SRC}", f"-I{INCLUDE_DIR}",
|
|
"-lshlwapi"
|
|
]
|
|
|
|
if not run(cmd):
|
|
return False
|
|
|
|
print(f" OK: {CMPP_EXE}")
|
|
return True
|
|
|
|
def preprocess_category(category):
|
|
"""Run cmpp on a code model category (analog, digital, etc.)."""
|
|
print(f"\n=== Step 2: Preprocessing '{category}' code models ===")
|
|
src_dir = os.path.join(ICM_DIR, category)
|
|
out_dir = os.path.join(BUILD_DIR, category)
|
|
|
|
# Copy the category directory to build dir for cmpp to work in
|
|
if os.path.exists(out_dir):
|
|
shutil.rmtree(out_dir)
|
|
shutil.copytree(src_dir, out_dir)
|
|
|
|
# Step 2a: Generate registration headers from modpath.lst
|
|
print(f" Generating registration headers...")
|
|
env = get_env()
|
|
env["CMPP_IDIR"] = src_dir
|
|
env["CMPP_ODIR"] = out_dir
|
|
r = subprocess.run([CMPP_EXE, "-lst"], capture_output=True, text=True,
|
|
cwd=out_dir, env=env)
|
|
if r.returncode != 0:
|
|
print(f" cmpp -lst FAILED: {r.stderr}")
|
|
return None
|
|
print(f" OK: cminfo.h, cmextrn.h generated")
|
|
|
|
# Step 2b: Read modpath.lst to get model names
|
|
modpath = os.path.join(src_dir, "modpath.lst")
|
|
with open(modpath) as f:
|
|
models = [line.strip() for line in f if line.strip()]
|
|
|
|
# Step 2c: Preprocess each model's .ifs and .mod files
|
|
for model in models:
|
|
model_src = os.path.join(src_dir, model)
|
|
model_out = os.path.join(out_dir, model)
|
|
|
|
if not os.path.isdir(model_src):
|
|
print(f" SKIP: {model} (directory not found)")
|
|
continue
|
|
|
|
# Preprocess ifspec.ifs -> ifspec.c
|
|
env_mod = get_env()
|
|
env_mod["CMPP_IDIR"] = model_src
|
|
env_mod["CMPP_ODIR"] = model_out
|
|
r = subprocess.run([CMPP_EXE, "-ifs"], capture_output=True, text=True,
|
|
cwd=model_out, env=env_mod)
|
|
if r.returncode != 0:
|
|
print(f" FAIL: {model}/ifspec.ifs: {r.stderr}")
|
|
return None
|
|
|
|
# Preprocess cfunc.mod -> cfunc.c
|
|
r = subprocess.run([CMPP_EXE, "-mod"], capture_output=True, text=True,
|
|
cwd=model_out, env=env_mod)
|
|
if r.returncode != 0:
|
|
print(f" FAIL: {model}/cfunc.mod: {r.stderr}")
|
|
return None
|
|
|
|
print(f" OK: {len(models)} models preprocessed")
|
|
return models
|
|
|
|
# Category-specific extra source files and include paths
|
|
CATEGORY_EXTRAS = {
|
|
"tlines": {
|
|
"extra_sources": [
|
|
os.path.join(NGSPICE_ROOT, "src", "xspice", "tlines", "tline_common.c"),
|
|
os.path.join(NGSPICE_ROOT, "src", "xspice", "tlines", "msline_common.c"),
|
|
],
|
|
"extra_includes": [
|
|
os.path.join(NGSPICE_ROOT, "src", "xspice", "tlines"),
|
|
],
|
|
},
|
|
}
|
|
|
|
def compile_cm(category, models):
|
|
"""Compile preprocessed code models into a .cm DLL."""
|
|
print(f"\n=== Step 3: Compiling '{category}.cm' DLL ===")
|
|
src_dir = os.path.join(ICM_DIR, category)
|
|
out_dir = os.path.join(BUILD_DIR, category)
|
|
os.makedirs(CM_OUTPUT_DIR, exist_ok=True)
|
|
|
|
# Collect all .c files to compile
|
|
c_files = []
|
|
|
|
# dlmain.c (the DLL entry point with exports)
|
|
dlmain = os.path.join(ICM_DIR, "dlmain.c")
|
|
c_files.append(dlmain)
|
|
|
|
# Each model's cfunc.c and ifspec.c
|
|
for model in models:
|
|
model_dir = os.path.join(out_dir, model)
|
|
cfunc_c = os.path.join(model_dir, "cfunc.c")
|
|
ifspec_c = os.path.join(model_dir, "ifspec.c")
|
|
if os.path.exists(cfunc_c) and os.path.exists(ifspec_c):
|
|
c_files.append(cfunc_c)
|
|
c_files.append(ifspec_c)
|
|
else:
|
|
print(f" SKIP: {model} (generated .c files missing)")
|
|
|
|
# UDN (user-defined node) types — udnfunc.c files from udnpath.lst
|
|
udnpath = os.path.join(src_dir, "udnpath.lst")
|
|
if os.path.exists(udnpath):
|
|
with open(udnpath) as f:
|
|
udns = [line.strip() for line in f if line.strip()]
|
|
for udn in udns:
|
|
udnfunc = os.path.join(src_dir, udn, "udnfunc.c")
|
|
if os.path.exists(udnfunc):
|
|
c_files.append(udnfunc)
|
|
print(f" UDN: {udn}/udnfunc.c")
|
|
|
|
# Also need dstring.c for some models that use DS_CREATE
|
|
dstring_c = os.path.join(NGSPICE_ROOT, "src", "misc", "dstring.c")
|
|
if os.path.exists(dstring_c):
|
|
c_files.append(dstring_c)
|
|
|
|
# Category-specific extra sources
|
|
extras = CATEGORY_EXTRAS.get(category, {})
|
|
for src in extras.get("extra_sources", []):
|
|
if os.path.exists(src):
|
|
c_files.append(src)
|
|
print(f" EXTRA: {os.path.basename(src)}")
|
|
|
|
cm_file = os.path.join(CM_OUTPUT_DIR, f"{category}.cm")
|
|
|
|
cmd = [
|
|
"gcc", "-shared", "-o", cm_file,
|
|
] + c_files + [
|
|
f"-I{INCLUDE_DIR}",
|
|
f"-I{CONFIG_H_DIR}",
|
|
f"-I{out_dir}", # For cminfo.h, cmextrn.h
|
|
f"-I{ICM_DIR}", # For dlmain.c includes
|
|
]
|
|
|
|
# Category-specific extra include paths
|
|
for inc in extras.get("extra_includes", []):
|
|
cmd.append(f"-I{inc}")
|
|
|
|
cmd += [
|
|
"-DHAS_PROGREP",
|
|
"-DXSPICE",
|
|
"-DSIMULATOR",
|
|
"-DNG_SHARED_BUILD",
|
|
"-DNGSPICEDLL",
|
|
"-Wall", "-Wno-unused-variable", "-Wno-unused-function",
|
|
"-Wno-sign-compare", "-Wno-maybe-uninitialized",
|
|
"-Wno-implicit-function-declaration", # GCC 15 makes this an error; some models missing stdlib.h
|
|
]
|
|
|
|
if not run(cmd):
|
|
return False
|
|
|
|
size_kb = os.path.getsize(cm_file) // 1024
|
|
print(f" OK: {cm_file} ({size_kb} KB)")
|
|
return True
|
|
|
|
def build_category(category):
|
|
"""Full pipeline: preprocess + compile a code model category."""
|
|
models = preprocess_category(category)
|
|
if models is None:
|
|
return False
|
|
return compile_cm(category, models)
|
|
|
|
if __name__ == "__main__":
|
|
categories = sys.argv[1:] if len(sys.argv) > 1 else ["analog"]
|
|
|
|
if not build_cmpp():
|
|
sys.exit(1)
|
|
|
|
for cat in categories:
|
|
if not build_category(cat):
|
|
print(f"\nFailed to build {cat}.cm")
|
|
sys.exit(1)
|
|
|
|
print(f"\n=== Done! Code models in: {CM_OUTPUT_DIR} ===")
|