
简介集成电路仿真大作业Python代码资源以参数调整和神经网络设计训练为主线面向微电子、集成电路专业学生及需要借助Python完成电路仿真优化与建模的开发者。压缩包共70个文件大小约1.69MB包含核心Python源码.py、电路仿真过程数据.sp/.st0/.lis/.mat/.mt0等、训练数据.csv、模型文件.mdl以及项目说明文档.pdf/.doc能够还原从电路元参数设定、优化算法到神经网络训练验证的完整过程。已有138人学习适合课程设计参考、实验复现或作为进一步研究的起点。代码覆盖使用PySpice等库建模、调用SciPy优化参数、利用TensorFlow/Keras搭建网络等环节通过配套数据与说明可深入理解数据预处理、超参数调节和模型评估的具体实现为后续科研或工程实践提供可迁移的框架。1. 拿到这个集成电路仿真 Python 大作业的 .rar 时第一感觉是乱拿到的压缩包顶层是__MACOSX和.DS_Store里面夹着iccad/、main.py、Two_stage_opamp.py、DE.py、nn_test_xmh.py还有x.mat、data.csv和penalty.mat。如果你以为解压后直接python main.py就能出结果大概率会卡在路径、依赖或者调用顺序上。这个集成电路仿真大作业的真实主线是用 SPICE 类工具搭两级运算放大器跑出不同参数组合下的性能数据再通过差分进化DE算法自动调参最后训练神经网络替代重复仿真。它解决的是一整条“仿真-优化-拟合”链路而不是某一段脚本。适合正在上模拟 IC 课程、需要交大作业或者刚接触 Python 做电路代理建模的同学。你需要分清楚哪些文件是数据生成器、哪些是优化器、哪些是训练入口下面按实际执行顺序拆开讲。2. 两级运放仿真与数据生成PySpice 建模和 SPICE 参数提取在开始调参之前得先让仿真器稳定地产出数据。项目里的Two_stage_opamp.py就是这个入口它把两级运放网表封装成 Python 函数输入是几个设计变量输出是低频增益、单位增益带宽GBW和相位裕度PM。2.1 为什么是两级运放两级运放是模拟电路课程设计的“标配”第一级提供高增益的差分放大第二级提供更大的输出摆幅和驱动能力。和单级放大器相比它的性能指标更丰富适合做成优化问题和三级结构相比它又没有那么多稳定性风险适合在一个学期内完成。设计变量一般包括输入对管宽度wp、尾电流itail、第二级负载管宽度、密勒补偿电容Cc、调零电阻Rz。如果只调晶体管尺寸而不动补偿网络相位裕度往往连 45 度都到不了后续 DE 算法的惩罚值会一直居高不下。所以数据生成脚本里一定要把Cc和Rz也作为变量参与扫描。2.2 用 PySpice 搭网表并做 AC 扫描常见做法是直接用 PySpice 在 Python 里建网表而不是手写 SPICE 文本因为每个管子的宽长比都是 Python 变量后面接优化器非常方便。下面是simulate_opamp的核心骨架整体思路和原项目中的Two_stage_opamp.py一致只是做了函数化封装# simulate_opamp.py 核心片段 import numpy as np from PySpice.Spice.Netlist import Circuit from PySpice.Unit import * def simulate_opamp(wp60e-6, L0.5e-6, cc2e-12, rz3e3, itail50e-6): circuit Circuit(two_stage_opamp) # 0.18um 工艺简化模型Level 1足够做趋势研究 circuit.model(nch, nmos, level1, kn180e-6, vto0.45, lambda_0.02) circuit.model(pch, pmos, level1, kp60e-6, vto-0.45, lambda_0.03) vdd 1.8 circuit.V(vdd, vdd, circuit.gnd, vdd) # 尾电流源 M5栅极接偏置电压 circuit.MOSFET(M5, tail, bias, vss, vss, modelnch, width10e-6, lengthL) circuit.V(bias, bias, circuit.gnd, 0.8) # 输入对管 M1/M2差分正负输入 vip/vin circuit.MOSFET(M1, d1, vin, tail, tail, modelnch, widthwp, lengthL) circuit.MOSFET(M2, d2, vip, tail, tail, modelnch, widthwp, lengthL) # PMOS 有源负载 M3/M4 circuit.MOSFET(M3, d1, d1, vdd, vdd, modelpch, widthwp, lengthL) circuit.MOSFET(M4, d2, d1, vdd, vdd, modelpch, widthwp, lengthL) # 第二级放大管 M6 和补偿网络 circuit.MOSFET(M6, out, d2, vss, vss, modelnch, width120e-6, lengthL) circuit.C(cc, out, d2, cc) circuit.R(rz, out, d2, rz) circuit.R(load, out, vdd, 50e3) # 交流激励从 vip 注入 1V 小信号 circuit.SinusoidalVoltageSource(vin_ac, vip, circuit.gnd, amplitude1) circuit.V(vicm, vin, circuit.gnd, 0.9) simulator circuit.simulator(temperature25, nominal_temperature25) analysis simulator.ac(start_frequency1u_Hz, stop_frequency1u_GHz, number_of_points101, variationdec) return analysis这段代码里的关键参数wp是输入对管的沟道宽度决定第一级跨导和增益L是沟道长度长度增加会让输出阻抗变大但带宽下降Cc是密勒补偿电容直接影响主极点和相位裕度Rz是调零电阻用于消除右半平面零点。跑完 AC 扫描后analysis[out]是复数电压增益序列用20 * np.log10(np.abs(vout))能算增益曲线再插值找到增益下降 3dB 处的频率就是 GBW。注意 Level 1 模型精度有限但作为大作业和训练神经网络的样本来源已经足够真正流片前还需要换成 BSIM 模型。2.3 批量采样并导出 data.csv要喂给神经网络必须把“参数 - 性能指标”组织成结构化表格。我一般会在外层做 300500 次随机采样每次从合理范围内抽取一组参数调用上面的函数把结果写进同一个 DataFrame。如果仿真不收敛就把该行性能值置为None后续统一处理。# 批量生成训练数据 import pandas as pd rows [] rng np.random.default_rng(42) for i in range(500): wp rng.uniform(40e-6, 120e-6) cc rng.uniform(1e-12, 5e-12) rz rng.uniform(0, 5e3) itail rng.uniform(20e-6, 80e-6) try: analysis simulate_opamp(wpwp, cccc, rzrz, itailitail) gain_db compute_gain_db(analysis) # 由 util.py 提供 gbw_mhz compute_gbw(analysis) # 由 util.py 提供 pm_deg compute_pm(analysis) # 由 util.py 提供 rows.append({wp: wp, cc: cc, rz: rz, itail: itail, gain_db: gain_db, gbw_mhz: gbw_mhz, pm_deg: pm_deg}) except Exception: rows.append({wp: wp, cc: cc, rz: rz, itail: itail, gain_db: None, gbw_mhz: None, pm_deg: None}) df pd.DataFrame(rows) df.to_csv(data.csv, indexFalse)这里用均匀随机采样而不是网格采样是因为 5 个变量做网格扫描会指数爆炸而随机 500 组已经能覆盖设计空间的主要区域。保留失败行也是有意义的训练时剔除它们同时观察失败比例是否过高如果超过 30%说明抽样范围里有很多不收敛的参数组合需要收窄范围。util.py里通常是这些后处理函数包括从 AC 数据里提取增益、带宽、相位裕度的插值计算。参数范围影响趋势wp40~120 µm增大 gm提升增益但寄生电容增大带宽下降L0.3~0.8 µm沟长增大输出阻抗上升增益上升带宽下降Cc1~5 pF主极点内推相位裕度改善但 GBW 下降Rz0~5 kΩ调右半平面零点过大引入低频零极点itail20~80 µA增大偏置电流提升 GBW但功耗上升3. DE 算法调参把仿真目标函数和约束写成可优化问题有仿真器之后手动调参数会非常累。项目中的DE.py使用了差分进化算法这是一种对非光滑、非连续目标函数很友好的全局优化方法。相比遗传算法DE 不涉及二进制编码直接在实数域上做变异和交叉实现成本低收敛速度通常也更快。3.1 为什么选择差分进化模拟电路参数优化常见的方案有网格搜索、随机搜索、贝叶斯优化和遗传算法。网格搜索在高维空间里点数量爆炸随机搜索无法利用历史搜索信息贝叶斯优化需要高斯过程拟合对陡峭区域和仿真失败点比较敏感。而差分进化只需要给定参数范围就能用种群迭代逼近全局最优非常适合“每个个体都要完整跑一次 SPICE 仿真”的场景。scipy 里自带differential_evolution函数控制参数少基本只需要设置边界、种群大小和最大迭代次数。3.2 定义目标函数和惩罚值在 DE 中目标函数返回一个标量越小越好。我们不能直接写“相位裕度大于 60 度”而是要把约束转成惩罚。项目中的penalty.mat应该就是记录迭代过程中惩罚值的矩阵方便事后画收敛曲线。下面是我会用的目标函数设计# objective.py 目标函数定义 import numpy as np def evaluate_design(x): # x [wp, cc, rz, itail] wp, cc, rz, itail x gain_db, gbw_mhz, pm_deg run_simulate_and_extract( wp, cc, rz, itail) if gain_db is None: return 1e6 # 仿真失败时给一个巨大的惩罚 # 目标指标 gain_ref 60.0 # dB gbw_ref 50.0 # MHz pm_ref 60.0 # deg penalty 0.0 # 未达标部分按相对偏差平方累加达标不再奖励 penalty max(0.0, (gain_ref - gain_db) / gain_ref) ** 2 penalty max(0.0, (gbw_ref - gbw_mhz) / gbw_ref) ** 2 penalty max(0.0, (pm_ref - pm_deg) / pm_ref) ** 2 return penalty逻辑说明max(0, ...)保证达标项不贡献惩罚未达标项按相对偏差平方叠加。这样避免量纲差异——增益是 dB 量级带宽是 MHz 量级如果不归一化带宽项会被吃掉。仿真失败时直接返回1e6DE 算法因为惩罚值巨大而自动丢弃该个体。注意这里每个个体都要跑一次仿真所以目标函数要尽量精简run_simulate_and_extract里不应该再打印调试信息或重复读文件。3.3 运行差分进化并保存惩罚历史# DE.py 主程序 from scipy.optimize import differential_evolution from scipy.io import savemat import numpy as np bounds [(40e-6, 120e-6), # wp (1e-12, 5e-12), # cc (0, 5e3), # rz (20e-6, 80e-6)] # itail result differential_evolution( evaluate_design, bounds, seed7, maxiter80, popsize25, updatingdeferred, workers-1, dispFalse ) print(最优参数:, result.x) print(最小惩罚:, result.fun) # 保存最终种群的惩罚值便于分析收敛情况 savemat(penalty.mat, {penalty: result.population_energies})参数说明popsize25表示每个维度有 25 个种群个体4 维就是 100 个maxiter80是最大迭代代数总仿真次数约 100×808000 次需要根据单次仿真时长调整如果一次仿真 2 秒这就要 4 个多小时所以通常会把 AC 扫描点数减少到 50或先把仿真脚本并行化updatingdeferred让种群在每代结束后统一更新配合workers-1使用多核并行效果更好。result.population_energies是最终种群所有个体对应的惩罚值存成.mat文件后可以用scipy.io.loadmat读回结合 matplotlib 绘制惩罚值分布柱状图直观看到有多少个体逼近零惩罚。方法需要梯度处理失败点高维表现实现复杂度网格搜索否容易直接跳过差低随机搜索否容易一般低贝叶斯优化否需要额外处理较好中差分进化否自然处理好低-中4. 神经网络替代仿真x.mat 与 data.csv 驱动的训练流程当 DE 找到一组候选参数之后我们还想知道“如果参数稍微偏一点性能会不会崩”或者想在全设计空间快速筛选。这时最合适的做法是训练一个前馈神经网络作为代理模型。项目里的nn_test_xmh.py和erfenlei.py就是在做这件事x.mat很可能是从 MATLAB 导出的输入特征矩阵data.csv则是上面生成的仿真数据表。4.1 数据清洗与归一化训练之前要检查三点是否存在全为空的行、输入输出是否都做了标准化、训练/验证/测试集合是否按同一种方式切分。下面以data.csv为例import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler df pd.read_csv(data.csv) df df.dropna(subset[gain_db, gbw_mhz, pm_deg]) # 输入特征和输出指标 X df[[wp, cc, rz, itail]].values y df[[gain_db, gbw_mhz, pm_deg]].values scaler_x StandardScaler().fit(X) scaler_y StandardScaler().fit(y) X_s scaler_x.transform(X) y_s scaler_y.transform(y) X_train, X_val, y_train, y_val train_test_split( X_s, y_s, test_size0.2, random_state42)这里用dropna剔除仿真失败的行如果失败行太多就要回到第 2 章收窄参数范围。StandardScaler把输入输出都变成零均值单位方差否则wp是微米量级、cc是皮法量级数值相差太多梯度下降会非常不稳定。random_state42保证每次切分一致方便复现实验。4.2 用 Keras 搭一个回归网络对于这种表格型特征不需要卷积神经网络最简单的三层全连接网络也就是经典的 BP 网络结构就能达到不错的效果。输入是 4 个设计参数输出是 3 个性能指标中间层用 ReLU 激活输出层不接激活函数from tensorflow import keras from tensorflow.keras import layers model keras.Sequential([ layers.Input(shape(4,)), layers.Dense(64, activationrelu), layers.Dropout(0.1), layers.Dense(64, activationrelu), layers.Dense(3) ]) model.compile( optimizerkeras.optimizers.Adam(learning_rate3e-3), lossmse, metrics[mae] ) history model.fit( X_train, y_train, epochs300, batch_size32, validation_data(X_val, y_val), callbacks[keras.callbacks.EarlyStopping(patience30)] )为什么要两层 64 神经元因为输入只有 4 个特征输出 3 个问题并不复杂层数太多反而容易过拟合。Dropout(0.1)是轻量正则EarlyStopping在验证 loss 连续 30 个 epoch 不下降时停止避免白跑。学习率初始设为3e-3如果训练震荡明显降到1e-3如果收敛太慢试着调大 batch size。训练完成后用model.predict对新参数组合做预测时要记得先对输入做scaler_x.transform对输出做scaler_y.inverse_transform才能得到真实量纲的增益和带宽。4.3 超参数选择的几种常见组合我整理了一个适用于这个数据规模的经验表可以在自己的机器上先跑这几个组合学习率神经元数量batch_size结果表现3e-364-6432收敛快可能有震荡1e-364-6432稳定推荐默认1e-332-3264参数少方差大1e-4128-12816收敛慢但精度略好实际训练时我习惯先跑1e-3 64-64 batch32看验证 loss 曲线如果连续 50 轮没有明显下降就换成1e-4做 fine-tune。不要把 epoch 设成固定 3000一定要配合早停。4.4 二分类版本erfenlei.py 在做什么除了回归预测连续性能erfenlei.py通常是把问题转化为“该组参数是否满足所有指标”。这个脚本会计算每个样本是否达标例如增益大于 60dB 且相位裕度大于 60°生成 0/1 标签然后用同样的输入特征训练一个二分类器。这样做的好处是 DE 可以先快速过滤掉大概率不达标的区域再用精确仿真验证保留点# erfenlei.py 示例二分类标签生成与逻辑回归 from sklearn.linear_model import LogisticRegression meet (df[gain_db] 60) (df[gbw_mhz] 50) (df[pm_deg] 60) y_cls meet.astype(int).values clf LogisticRegression(max_iter500) clf.fit(scaler_x.transform(X), y_cls) # 预测时使用 clf.predict_proba() 取正类概率这里二分类的好处是可以理解参数空间里“合格区域”的大小。如果正类样本占比非常低说明抽样范围离目标方向偏了需要把 DE 的最优解附近作为新的采样中心继续生成数据形成“仿真 - 训练 - 再采样”的主动学习闭环。5. 验证与排错确保仿真和预测对得上训练完神经网络最重要的不是 loss 有多低而是预测值和 SPICE 仿真结果是否一致。这一章讲几个我实际踩过的坑和验证手段。5.1 用散点图和 R2 检查回归效果一个合格代理模型在验证集上的 R2 应该在 0.95 以上。如果低于这个数要么数据量太少要么某个特征没参与扫描。下面这段代码对每个输出画“真实值 vs 预测值”import matplotlib.pyplot as plt from sklearn.metrics import r2_score y_pred_s model.predict(X_val) # 反标准化回真实量纲 y_pred scaler_y.inverse_transform(y_pred_s) y_true scaler_y.inverse_transform(y_val) labels [Gain (dB), GBW (MHz), Phase Margin (deg)] for i in range(3): plt.figure(figsize(4, 4)) plt.scatter(y_true[:, i], y_pred[:, i], s8, alpha0.5) plt.plot([y_true[:, i].min(), y_true[:, i].max()], [y_true[:, i].min(), y_true[:, i].max()], r--) plt.xlabel(SPICE Simulated labels[i]) plt.ylabel(NN Predicted labels[i]) print(f{labels[i]}: R2 {r2_score(y_true[:, i], y_pred[:, i]):.3f}) plt.savefig(fcheck_{i}.png, dpi150)如果散点图出现弯曲的“弓形”大概率是输出没有真正覆盖两端要检查数据生成时参数边界是否太窄。如果某个指标 R2 特别差优先看它在数据生成时是否因为仿真失败被剔除太多导致样本量不足。5.2 几个容易踩的坑首先是路径问题.rar里有__MACOSX和.DS_Store在 Windows 上解压后目录结构有时会多出一层导致open(x.mat)失败。我建议把所有数据文件直接放到项目根目录用os.path.join(os.path.dirname(__file__), data.csv)这种绝对路径写法避免终端工作目录不一致。第二个坑是读取x.mat时scipy.io.loadmat读出来的通常是字典里面可能有__header__和__globals__等键要先用.keys()查看实际矩阵名再决定用哪个键当输入数据。第三个坑是 DE 和神经网络都要固定seed否则每次跑出来的参数和结果都不同最后报告里无法复现。最后一个实用技巧是在神经网络训练结束后挑 1020 组在训练中没有见过的参数重新跑一遍simulate_opamp对比预测和仿真。因为随机切分和标准化已经保证了数据隔离但重新采样能验证模型在真实设计空间外推时的可靠性。把这 20 个点的平均绝对误差写进大作业的结论页比贴训练 loss 曲线更有说服力。本文还有配套的精品资源点击获取