import numpy as np
import matplotlib.pyplot as plt
from scipy.sparse import diags
from scipy.sparse.linalg import spsolve
import matplotlib.colors as colors
from matplotlib.widgets import Slider, Button
from matplotlib.patches import Rectangle
import time
class NMOSIonDiffusionSimulator:
def __init__(self):
# 物理参数设置
self.D0 = 5e-15 # 基础扩散系数 (m²/s)
self.activation_energy = 1.2 # 激活能量 (eV)
self.dose = 1e20 # 注入剂量 (ions/cm²)
self.Rp = 100e-9 # 投影射程 (100 nm)
self.ΔRp = 30e-9 # 投影射程偏差 (30 nm)
self.substrate_temp = 1000 # 基底温度 (K)
# 数值模拟参数
self.depth = 500e-9 # 模拟深度 (500 nm)
self.dz = 1e-9 # 空间步长 (1 nm)
self.dt = 0.1 # 时间步长 (0.1 s)
self.simulation_time = 3600 # 总模拟时间 (3600 s = 1小时)
# 计算网格
self.z = np.arange(0, self.depth, self.dz)
self.nz = len(self.z)
self.nt = int(self.simulation_time / self.dt)
# 初始化离子浓度(高斯分布)
self.C = self.dose * np.exp(-(self.z - self.Rp) ** 2 / (2 * self.ΔRp ** 2))
# 结果存储
self.time_points = []
self.concentration_profiles = []
# 热力学参数
self.k_boltzmann = 8.617333e-5 # 玻尔兹曼常数 (eV/K)
# 计算温度依赖的扩散系数
self.D = self.D0 * np.exp(-self.activation_energy / (self.k_boltzmann * self.substrate_temp))
print(f"计算扩散系数: {self.D:.2e} m²/s")
# 设置扩散矩阵
self.setup_diffusion_matrix()
def setup_diffusion_matrix(self):
"""设置用于求解扩散方程的系数矩阵(隐式格式)"""
r = self.D * self.dt / self.dz ** 2
# 创建三对角矩阵
diagonals = [
-r * np.ones(self.nz - 1), # 下对角线
(1 + 2 * r) * np.ones(self.nz), # 主对角线
-r * np.ones(self.nz - 1) # 上对角线
]
offsets = [-1, 0, 1]
# 创建稀疏矩阵
self.A = diags(diagonals, offsets, shape=(self.nz, self.nz), format='csc')
# 边界条件(表面无扩散)
self.A[0, 0] = 1
self.A[0, 1] = 0
self.A[-1, -1] = 1
self.A[-1, -2] = 0
def solve_diffusion(self):
"""求解扩散方程"""
self.time_points = []
self.concentration_profiles = [self.C.copy()]
current_C = self.C.copy()
start_time = time.time()
for t in range(self.nt):
# 创建右侧向量
b = current_C.copy()
b[0] = 0 # 表面边界条件:浓度固定为0
b[-1] = 0 # 底部边界条件:浓度固定为0
# 求解线性系统
new_C = spsolve(self.A, b)
# 更新浓度
current_C = new_C
# 每10秒保存一次结果 # 保存结果时同时记录时间点
if t % 100 == 0:
current_time = t * self.dt
self.time_points.append(current_time) # 同时保存时间点
self.concentration_profiles.append(new_C.copy())
end_time = time.time()
print(f"扩散模拟完成! 耗时: {end_time - start_time:.2f} 秒")
print(f"存储了 {len(self.concentration_profiles)} 个时间点的浓度分布")
def draw_nmos_structure(self, ax):
"""绘制NMOS器件结构示意图"""
# 清除原有内容
ax.clear()
# 绘制衬底
substrate = plt.Rectangle((0, 0), 100, 40, facecolor='sandybrown', alpha=0.6)
ax.add_patch(substrate)
# 绘制栅氧化层
gate_oxide = plt.Rectangle((30, 40), 40, 5, facecolor='gray', alpha=0.7)
ax.add_patch(gate_oxide)
# 绘制多晶硅栅极
gate = plt.Rectangle((35, 45), 30, 10, facecolor='silver', alpha=0.8)
ax.add_patch(gate)
# 绘制源极
source = plt.Rectangle((10, 40), 15, 15, facecolor='blue', alpha=0.5)
ax.add_patch(source)
# 绘制漏极
drain = plt.Rectangle((75, 40), 15, 15, facecolor='blue', alpha=0.5)
ax.add_patch(drain)
# 添加文本标签
ax.text(15, 47, "Source", fontsize=10, ha='center')
ax.text(85, 47, "Drain", fontsize=10, ha='center')
ax.text(50, 50, "Gate", fontsize=10, ha='center')
ax.text(50, 20, "Substrate", fontsize=10, ha='center')
# 设置坐标范围
ax.set_xlim(0, 100)
ax.set_ylim(0, 60)
ax.set_aspect('equal')
ax.axis('off') # 隐藏坐标轴
# 添加标题
ax.set_title('NMOS Device Structure', fontsize=12)
def interactive_visualization(self):
"""创建交互式可视化界面"""
# 创建更大的图形,包含三个子图
fig = plt.figure(figsize=(16, 10))
gs = fig.add_gridspec(2, 2, width_ratios=[2, 1], height_ratios=[1, 1])
# 创建三个轴
ax1 = fig.add_subplot(gs[0, 0]) # 浓度深度曲线
ax2 = fig.add_subplot(gs[1, 0]) # 时间热图
ax3 = fig.add_subplot(gs[:, 1]) # NMOS器件图
plt.subplots_adjust(bottom=0.15, left=0.1, right=0.95, top=0.95,
hspace=0.3, wspace=0.2)
# 绘制初始器件结构
self.draw_nmos_structure(ax3)
# 在器件图中添加扩散效果可视化
diffusion_line = ax3.axvline(x=50, ymin=0.67, ymax=1.0, color='red',
linewidth=3, alpha=0.7, visible=False)
diffusion_text = ax3.text(50, 55, '', fontsize=10, ha='center',
bbox=dict(facecolor='white', alpha=0.7))
# 主图设置
ax1.set_title('Ion Concentration Profile', fontsize=14)
ax1.set_xlabel('Depth from Surface (nm)', fontsize=12)
ax1.set_ylabel('Ion Concentration (ions/cm³)', fontsize=12)
ax1.grid(True)
# 转换为nm单位
z_nm = self.z * 1e9
# 绘制初始浓度分布
line, = ax1.plot(z_nm, self.concentration_profiles[0], 'b-', lw=2)
# 添加浓度标签
text = ax1.text(0.05, 0.95, '', transform=ax1.transAxes,
bbox=dict(facecolor='white', alpha=0.8))
# 添加深度标记
depth_line = ax1.axvline(x=0, color='r', linestyle='--', alpha=0)
depth_text = ax1.text(0.05, 0.85, '', transform=ax1.transAxes,
bbox=dict(facecolor='white', alpha=0.8))
# 创建时间滑块
ax_time = plt.axes([0.25, 0.05, 0.5, 0.03])
time_slider = Slider(
ax_time, 'Time (s)', 0, self.simulation_time,
valinit=0, valstep=self.dt * 100
)
# 在下方创建热图
time_arr = np.array(self.time_points) # 时间点数组形状
conc_arr = np.array(self.concentration_profiles).T
# 确保维度匹配
print(f"时间点数组形状: {time_arr.shape}") # 应该是 (361,)
print(f"浓度数组形状: {conc_arr.shape}") # 应该是 (500, 361)
# 创建网格坐标 - 确保使用正确的维度
Z_nm = self.z * 1e9 # 深度转换为nm
T, Z = np.meshgrid(time_arr, Z_nm)
# 对浓度取对数以更好显示
log_conc = np.log10(conc_arr + 1e10) # 加上小值避免log(0)
# 创建更全面的热图显示
fig = plt.figure(figsize=(16, 12)) # 增加图形大小
gs = fig.add_gridspec(3, 2, width_ratios=[2, 1], height_ratios=[1, 1, 0.2])
# 创建四个轴 (增加一个颜色条专用轴)
ax1 = fig.add_subplot(gs[0, 0]) # 浓度深度曲线
ax2 = fig.add_subplot(gs[1, 0]) # 时间热图
cax = fig.add_subplot(gs[2, 0]) # 颜色条专用轴
ax3 = fig.add_subplot(gs[:, 1]) # NMOS器件图
# 创建热图
# 改进2: 使用更有表现力的颜色映射和归一化
norm = colors.LogNorm(vmin=1e15, vmax=conc_arr.max())
im = ax2.pcolormesh(T, Z, conc_arr, shading='auto',
cmap='viridis', norm=norm) # 使用Viridis颜色映射
# 添加更全面的颜色条
cbar = fig.colorbar(im, cax=cax, orientation='horizontal')
cbar.set_label('Ion Concentration (ions/cm³)', fontsize=10)
cax.xaxis.set_ticks_position('top')
cax.xaxis.set_label_position('top')
# 添加等值线增强可读性
# X, Y = np.meshgrid(time_arr, self.z * 1e9)
levels = np.logspace(np.log10(1e16), np.log10(conc_arr.max()), 10)
ax2.contour(T, Z, conc_arr, levels=levels,
colors='white', alpha=0.3, linewidths=0.5)
# 添加时间标记线
time_line = ax2.axvline(x=0, color='cyan', linewidth=2, alpha=0.7)
time_text = ax2.text(0.05, 0.95, '', transform=ax2.transAxes,
bbox=dict(facecolor='white', alpha=0.8))
# ❓要删吗
# ax2.set_title('Concentration Evolution Over Time', fontsize=14)
# ax2.set_xlabel('Time (s)', fontsize=12)
# ax2.set_ylabel('Depth (nm)', fontsize=12)
# 添加颜色条
# cbar = fig.colorbar(im, ax=ax2)
# cbar.set_label('log10(Concentration) (ions/cm³)', fontsize=10)
# 添加时间标记线
# time_line = ax2.axvline(x=0, color='cyan', linewidth=2, alpha=0.7)
# time_text = ax2.text(0.05, 0.95, '', transform=ax2.transAxes,
# bbox=dict(facecolor='white', alpha=0.8))
# 添加半导体结构示意图
# gate_oxide = Rectangle((0.7, 0.05), 0.2, 0.1, transform=fig.transFigure,
# facecolor='gray', alpha=0.5)
# source = Rectangle((0.6, 0.05), 0.1, 0.1, transform=fig.transFigure,
# facecolor='blue', alpha=0.3)
# drain = Rectangle((0.9, 0.05), 0.1, 0.1, transform=fig.transFigure,
# facecolor='blue', alpha=0.3)
# substrate = Rectangle((0.6, 0), 0.4, 0.05, transform=fig.transFigure,
# facecolor='sandybrown', alpha=0.5)
#
# fig.patches.extend([gate_oxide, source, drain, substrate])
#
# # 添加文本标签
# fig.text(0.75, 0.13, 'Gate Oxide', ha='center')
# fig.text(0.65, 0.13, 'Source', ha='center')
# fig.text(0.95, 0.13, 'Drain', ha='center')
# fig.text(0.8, 0.025, 'Substrate', ha='center')
# 浓度分布更新函数
def update(val):
time_val = time_slider.val
idx = min(int(time_val / (self.dt * 100)), len(self.concentration_profiles) - 1)
# 更新浓度曲线
line.set_ydata(self.concentration_profiles[idx])
text.set_text(
f'Time: {time_val:.1f} s\nMax Concentration: {self.concentration_profiles[idx].max():.2e} ions/cm³')
# 更新热图时间线
time_line.set_xdata([time_val, time_val])
time_text.set_text(f"Time: {time_val:.1f} s")
# 更新器件图中的扩散效果 - 显示扩散深度
# 计算最大浓度的深度位置
max_conc_idx = np.argmax(self.concentration_profiles[idx])
max_conc_depth = z_nm[max_conc_idx]
# 计算扩散深度(浓度降至最大浓度1%的深度)
threshold = self.concentration_profiles[idx].max() * 0.01
above_threshold = np.where(self.concentration_profiles[idx] > threshold)[0]
if above_threshold.size > 0:
diffusion_depth = z_nm[above_threshold[-1]]
else:
diffusion_depth = 0
# 更新扩散线位置和文本
# 计算在器件图中的扩散深度(像素高度)
# 500nm对应40像素(衬底高度)
h_pixels = (diffusion_depth * 40) / 500
# 清除之前的扩散区域
for patch in ax3.patches:
if patch.get_facecolor() == (1., 0., 0., 0.3):
patch.remove()
# 创建渐变扩散效果(多个矩形叠加)
if diffusion_depth > 0:
# 创建5个渐变矩形
steps = 5
for i in range(steps):
step_depth = h_pixels * (i + 1) / steps
alpha = 0.3 * (1 - i / steps) # 顶部透明度高,底部透明度低
diffusion_area = Rectangle(
(30, 40 - step_depth), 40, step_depth,
facecolor='red', alpha=alpha
)
ax3.add_patch(diffusion_area)
diffusion_text.set_text(f'Diffusion Depth: {diffusion_depth:.1f} nm')
fig.canvas.draw_idle()
time_slider.on_changed(update)
# 点击事件处理
def onclick(event):
if event.inaxes == ax1:
depth_nm = event.xdata
depth_m = depth_nm * 1e-9
# 找到时间点
time_idx = min(int(time_slider.val / (self.dt * 100)), len(self.concentration_profiles) - 1)
# 找到深度对应的浓度
z_idx = int(depth_nm) # 1nm分辨率
if 0 <= z_idx < len(self.concentration_profiles[time_idx]):
conc = self.concentration_profiles[time_idx][z_idx]
# 更新深度线和文本
depth_line.set_xdata([depth_nm, depth_nm])
depth_line.set_alpha(1)
depth_text.set_text(f'Depth: {depth_nm:.1f} nm\nConcentration: {conc:.2e} ions/cm³')
# 更新热图时间线
time_line.set_xdata([time_slider.val, time_slider.val])
fig.canvas.draw_idle()
fig.canvas.mpl_connect('button_press_event', onclick)
# 添加重置按钮
resetax = plt.axes([0.8, 0.01, 0.1, 0.04])
button = Button(resetax, 'Reset', color='lightgoldenrodyellow', hovercolor='0.975')
def reset(event):
time_slider.reset()
# 重置器件图中的扩散效果
diffusion_line.set_visible(False)
diffusion_text.set_text('')
# 清除扩散区域
for patch in ax3.patches:
if patch.get_facecolor() == (1., 0., 0., 0.3):
patch.remove()
self.draw_nmos_structure(ax3) # 重绘器件结构
fig.canvas.draw_idle()
button.on_clicked(reset)
# 添加截图按钮(模拟电镜观察)
screenshotax = plt.axes([0.65, 0.01, 0.1, 0.04])
screenshot_btn = Button(screenshotax, 'Take SEM Image', color='lightblue', hovercolor='0.975')
def take_sem_image(event):
"""模拟电镜下观察离子浓度"""
# 获取当前时间点的浓度分布
time_val = time_slider.val
idx = min(int(time_val / (self.dt * 100)), len(self.concentration_profiles) - 1)
# 创建新的电镜观察图
sem_fig, sem_ax = plt.subplots(figsize=(10, 6))
# 计算扩散深度
threshold = self.concentration_profiles[idx].max() * 0.01
diffusion_depth = z_nm[np.where(self.concentration_profiles[idx] > threshold)[0][-1]]
# 显示离子分布
sem_ax.plot(z_nm, self.concentration_profiles[idx], 'b-', lw=2)
sem_ax.axvline(x=diffusion_depth, color='r', linestyle='--',
label=f'Diffusion Depth: {diffusion_depth:.1f} nm')
sem_ax.set_title(f'SEM Observation at {time_val:.1f} s', fontsize=14)
sem_ax.set_xlabel('Depth from Surface (nm)', fontsize=12)
sem_ax.set_ylabel('Ion Concentration (ions/cm³)', fontsize=12)
sem_ax.grid(True)
sem_ax.legend()
# 添加NMOS器件示意图
sem_ax.text(0.95, 0.95, 'NMOS Structure', transform=sem_ax.transAxes,
ha='right', va='top', fontsize=12,
bbox=dict(facecolor='white', alpha=0.7))
plt.tight_layout()
plt.show()
screenshot_btn.on_clicked(take_sem_image)
plt.show()
def run(self):
"""运行完整模拟和可视化"""
self.solve_diffusion()
self.interactive_visualization()
# 运行模拟
if __name__ == "__main__":
simulator = NMOSIonDiffusionSimulator()
simulator.run()
为什么报错:
Traceback (most recent call last):
File "D:\Users\x00708\SRAM_Classification-242终极版\test_code\ion.py", line 419, in <module>
simulator.run()
File "D:\Users\x00708\SRAM_Classification-242终极版\test_code\ion.py", line 413, in run
self.interactive_visualization()
File "D:\Users\x00708\SRAM_Classification-242终极版\test_code\ion.py", line 221, in interactive_visualization
im = ax2.pcolormesh(T, Z, conc_arr, shading='auto',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Users\x00708\SRAM_Classification\.venv\Lib\site-packages\matplotlib\__init__.py", line 1524, in inner
return func(
^^^^^
File "D:\Users\x00708\SRAM_Classification\.venv\Lib\site-packages\matplotlib\axes\_axes.py", line 6528, in pcolormesh
X, Y, C, shading = self._pcolorargs('pcolormesh', *args,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Users\x00708\SRAM_Classification\.venv\Lib\site-packages\matplotlib\axes\_axes.py", line 6060, in _pcolorargs
raise TypeError(f"Dimensions of C {C.shape} should"
TypeError: Dimensions of C (500, 361) should be one smaller than X(360) and Y(500) while using shading='flat' see help(pcolormesh)
最新发布