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

"""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} ===")