<think>我们重新开始,以最详细的方式注释每一行代码,特别是正则表达式部分,我们将进行逐字符解释。
由于代码很长,我们将重点放在正则表达式和关键结构上,确保每个符号都解释清楚。
注意:由于整个代码非常长,我们只展示部分函数,但会覆盖所有正则表达式和关键逻辑。
我们将从robust_read_file函数开始,然后重点解析parse_log_file中的正则表达式。
由于篇幅限制,我们不可能在单个回答中完整注释整个代码(因为整个代码有几百行),但我们会尽可能详细地注释关键部分。
如果用户要求,我们可以分函数进行详细注释。这里我们选择几个关键函数进行超详细注释。
我们选择注释的函数:robust_read_file, parse_log_file(重点正则表达式部分),以及generate_eye_diagram(因为包含重要循环)。
同时,我们也会详细注释main函数中的关键部分。
注意:我们不会减少代码,但会尽可能详细注释。
由于正则表达式是重点,我们将对parse_log_file中的每个正则表达式进行逐字符解释。</think>下面是对代码的极度详细注释,特别是正则表达式部分,每个符号都进行了逐字符解释。即使您完全不懂编程,也能理解每一行代码的含义:
```python
# 导入必要的库(这些是预先写好的代码集合,我们直接使用它们的功能)
# import 关键字用于导入其他代码文件(称为模块)
import numpy as np # 导入numpy库并给它一个简称np(方便后续使用)
import matplotlib.pyplot as plt # 导入matplotlib的pyplot模块并简称plt
import re # 导入正则表达式模块(用于文本模式匹配)
import datetime # 导入日期时间模块(用于处理时间相关操作)
from matplotlib.lines import Line2D # 从matplotlib.lines导入Line2D类(用于创建图例)
import os # 导入操作系统相关功能(用于文件路径操作)
from collections import defaultdict # 导入defaultdict字典类型(当访问不存在的键时返回默认值)
# def 关键字用于定义函数(可重复使用的代码块)
# 函数定义格式:def 函数名(参数):
# 注意:函数名后面必须加括号,括号内可以包含参数(可以没有)
# 函数定义行末尾必须加冒号(:)
def robust_read_file(file_path):
"""
健壮的文件读取函数,处理不同编码的文件
参数: file_path - 文件在电脑上的位置(例如:C:/logs/ddr_log.txt)
"""
# try 关键字表示尝试执行一段可能出错的代码
# try: 后面必须跟着一个代码块(缩进部分)
try:
# with 关键字用于资源管理(自动关闭文件)
# with 表达式 as 变量名: 结构
# open() 是打开文件的函数,参数:文件路径、打开模式、编码方式
# 'r' 表示只读模式
# encoding='utf-8' 指定使用UTF-8编码
with open(file_path, 'r', encoding='utf-8') as f: # 冒号表示代码块开始
# f.read() 读取整个文件内容
# return 关键字返回结果并结束函数
return f.read() # 这行缩进表示属于with代码块
# except 关键字用于捕获特定类型的错误
# except 错误类型 as 变量名: 结构
# UnicodeDecodeError 表示文件编码错误
except UnicodeDecodeError: # 冒号表示异常处理代码块开始
# 嵌套的try-except结构
try:
# 尝试使用latin-1编码打开文件
with open(file_path, 'r', encoding='latin-1') as f: # 冒号
# 读取文件内容
content = f.read() # 缩进表示属于with代码块
# re.sub() 是正则表达式替换函数
# 第一个参数是正则表达式模式
# 第二个参数是替换内容(空字符串)
# 第三个参数是要处理的文本内容
##########################################################
# 正则表达式详细解释:r'[\x00-\x1F]+'
# r'...':表示原始字符串,避免Python解释器处理转义字符
# [ ]:方括号表示字符集合,匹配括号内的任意一个字符
# \x00-\x1F:\x表示十六进制,00到1F表示ASCII码0到31的控制字符
# +:表示匹配前面的模式一次或多次
# 整体:匹配一个或多个ASCII控制字符(不可打印字符)
##########################################################
return re.sub(r'[\x00-\x1F]+', '', content) # 替换控制字符
# Exception 是所有异常的基类(捕获任何错误)
except Exception as e: # as e 表示将错误对象赋值给变量e
# print() 函数打印信息到控制台
# f"..." 是格式化字符串(f-string),可以在字符串中插入变量
print(f"文件解码错误: {e}") # 打印错误信息
return None # 返回None(空值)
# 函数定义:解析日志文件
def parse_log_file(log_content, normalization_point):
"""
解析DDR校准日志文件,提取关键数据
参数:
log_content - 日志文件的内容(一大段文本)
normalization_point - 归一化点(十进制数值)
"""
# = 赋值运算符,创建空字典
data = {} # 花括号{}表示空字典
# None 是Python中的特殊值,表示"无"或"空"
current_vref = None # 初始化当前vref
# 创建空字典
pending_data = {}
current_offset = None # 初始化当前偏移量
# .split() 是字符串方法,将字符串分割成列表
# '\n' 是换行符
# for 循环遍历列表中的每个元素
for line in log_content.split('\n'): # 冒号表示循环体开始
# .strip() 方法去除字符串两端的空白字符
line = line.strip()
# if 条件判断
# not 逻辑非运算符
# 如果line是空字符串(长度为0)
if not line: # 冒号表示条件成立时的代码块
# continue 跳过当前循环的剩余部分,进入下一轮循环
continue
# 正则表达式匹配vref行
# re.match() 尝试从字符串开头匹配模式
# 如果匹配成功,返回匹配对象;否则返回None
##########################################################
# 正则表达式详细解释:r'.*vref:\s*0x([0-9a-fA-F]+)'
# .*:点号(.)匹配任意单个字符(换行符除外),星号(*)表示匹配前面的模式0次或多次
# vref::匹配字面字符串"vref:"
# \s*:\s匹配空白字符(空格、制表符等),星号(*)表示0个或多个
# 0x:匹配字面字符串"0x"
# ([0-9a-fA-F]+):括号表示捕获组,[0-9a-fA-F]匹配十六进制数字(0-9, a-f, A-F),+表示匹配1次或多次
##########################################################
vref_match = re.match(r'.*vref:\s*0x([0-9a-fA-F]+)', line)
# if 条件判断:如果匹配成功且current_vref不为None
if vref_match: # 冒号
# int() 将字符串转换为整数
# vref_match.group(1) 获取第一个捕获组的内容(括号内的部分)
# 16 表示按十六进制解析
current_vref = int(vref_match.group(1), 16)
# 字典赋值:data[key] = value
data[current_vref] = {} # 创建嵌套字典
# 重置临时数据
pending_data = {}
current_offset = None
# continue 跳过当前循环剩余部分
continue
# 匹配0x38c行获取偏移量
##########################################################
# 正则表达式详细解释:r'.*0x38c:\s*0x([0-9a-fA-F]+)'
# .*:匹配任意字符0次或多次
# 0x38c::匹配字面字符串"0x38c:"
# \s*:匹配0个或多个空白字符
# 0x:匹配字面字符串"0x"
# ([0-9a-fA-F]+):捕获组,匹配1个或多个十六进制数字
##########################################################
offset_match = re.match(r'.*0x38c:\s*0x([0-9a-fA-F]+)', line)
# and 逻辑与运算符(两个条件都成立)
# is not None 判断变量不是None
if offset_match and current_vref is not None: # 冒号
# try-except 结构
try:
offset_value = int(offset_match.group(1), 16)
# 计算偏移量(十进制减法)
current_offset = normalization_point - offset_value
# 捕获值错误(转换失败)
except ValueError: # 冒号
print(f"警告: 无法解析偏移量: {offset_match.group(1)}")
current_offset = None
continue
# 匹配max点
##########################################################
# 正则表达式详细解释:r'.*dq(\d+)\s+max_(\w+)_point\s*:\s*(-?\d+)'
# .*:匹配任意字符0次或多次
# dq:匹配字面字符串"dq"
# (\d+):捕获组,\d匹配数字(0-9),+表示1个或多个
# \s+:匹配1个或多个空白字符
# max_:匹配字面字符串"max_"
# (\w+):捕获组,\w匹配字母、数字或下划线,+表示1个或多个
# _point:匹配字面字符串"_point"
# \s*:匹配0个或多个空白字符
# ::匹配冒号
# \s*:匹配0个或多个空白字符
# (-?\d+):捕获组,-?表示可选的负号,\d+匹配1个或多个数字
##########################################################
max_match = re.match(r'.*dq(\d+)\s+max_(\w+)_point\s*:\s*(-?\d+)', line)
# 多重条件判断
if max_match and current_vref is not None and current_offset is not None: # 冒号
# int() 将字符串转换为整数
dq_index = int(max_match.group(1)) # 获取第一个捕获组(数字)
# 获取匹配的字符串
direction = max_match.group(2) # 获取第二个捕获组(方向)
max_val = int(max_match.group(3)) # 获取第三个捕获组(数值)
# 创建元组作为键(不可变的有序集合)
key = (dq_index, direction)
# 字典赋值:pending_data[key] = 新字典
# 应用偏移量(十进制加法)
pending_data[key] = {'max': max_val + current_offset}
continue
# 匹配min点(类似max点处理)
##########################################################
# 正则表达式详细解释:r'.*dq(\d+)\s+min_(\w+)_point\s*:\s*(-?\d+)'
# 与max_match类似,只是将"max"替换为"min"
##########################################################
min_match = re.match(r'.*dq(\d+)\s+min_(\w+)_point\s*:\s*(-?\d+)', line)
if min_match and current_vref is not None and current_offset is not None: # 冒号
dq_index = int(min_match.group(1))
direction = min_match.group(2)
min_val = int(min_match.group(3))
key = (dq_index, direction)
# if 条件判断:key是否在pending_data字典中
if key in pending_data: # 冒号
# 字典操作:pending_data[key] 访问值
# 然后添加新的键值对
pending_data[key]['min'] = min_val + current_offset
continue
# 匹配窗口行
##########################################################
# 正则表达式详细解释:r'.*dq(\d+)\s+(\w+)_windows\s*:\s*(-?\d+)'
# .*:匹配任意字符0次或多次
# dq:匹配字面字符串"dq"
# (\d+):捕获组,匹配1个或多个数字
# \s+:匹配1个或多个空白字符
# (\w+):捕获组,匹配1个或多个字母、数字或下划线(方向)
# _windows:匹配字面字符串"_windows"
# \s*:匹配0个或多个空白字符
# ::匹配冒号
# \s*:匹配0个或多个空白字符
# (-?\d+):捕获组,匹配可选的负号后跟1个或多个数字
##########################################################
win_match = re.match(r'.*dq(\d+)\s+(\w+)_windows\s*:\s*(-?\d+)', line)
if win_match and current_vref is not None and current_offset is not None: # 冒号
dq_index = int(win_match.group(1))
direction = win_match.group(2)
windows = int(win_match.group(3))
key = (dq_index, direction)
# 多重条件判断:key存在且包含min和max
if key in pending_data and 'min' in pending_data[key] and 'max' in pending_data[key]: # 冒号
# 字典访问:pending_data[key]['min']
min_val = pending_data[key]['min']
max_val = pending_data[key]['max']
# 三元条件表达式:A if condition else B
# 如果方向是'read',则最大延迟为0x7F(127),否则为0xFF(255)
max_delay = 0x7F if direction == 'read' else 0xFF
# max() 函数取较大值
min_val = max(0, min_val) # 确保不小于0
# min() 函数取较小值
max_val = min(max_delay, max_val) # 确保不大于最大延迟
# 条件判断:min_val > max_val 或 windows < 0
if min_val > max_val or windows < 0: # 冒号
result = None
else: # 否则
# 计算窗口大小
result = (min_val, max_val, max_val - min_val + 1)
# 多层字典访问和赋值
if dq_index not in data[current_vref]: # 冒号
data[current_vref][dq_index] = {}
data[current_vref][dq_index][direction] = result
# del 关键字删除字典中的键
del pending_data[key]
# return 返回结果
return data
# 函数定义:计算眼图指标
def calculate_eye_metrics(data, avddq, dq_index, direction):
# 初始化变量
max_eye_height = 0.0
max_eye_width = 0.0
# defaultdict 创建默认字典(访问不存在的键时返回默认值)
# float 表示默认值为0.0
voltage_windows = defaultdict(float)
# for 循环遍历字典的键值对
# .items() 方法返回键值对元组
for vref, dq_data in data.items(): # 冒号
# 计算电压值
# 0x1FF是十六进制数,等于十进制511
voltage = (vref / 0x1FF) * avddq
# 字典的链式访问
# .get() 方法安全访问键(键不存在时返回默认值)
# 第一个get获取dq_index对应的字典,如果不存在返回空字典{}
# 第二个get从返回的字典中获取direction对应的值
dq_info = dq_data.get(dq_index, {}).get(direction)
# if 条件判断:dq_info是否为None
if dq_info is None: # 冒号
continue # 跳过当前循环
# 元组解包:将元组元素赋值给多个变量
min_point, max_point, windows = dq_info
# 计算窗口大小
window_size = max_point - min_point + 1
# 条件表达式
max_delay = 0x7F if direction == 'read' else 0xFF
ui_range = 2 if direction == 'read' else 4
# 计算UI单位窗口
window_ui = (window_size / max_delay) * ui_range
# if 条件更新最大值
if window_ui > max_eye_width: # 冒号
max_eye_width = window_ui
# 字典赋值
voltage_windows[voltage] = window_ui
# sorted() 函数排序字典键
sorted_voltages = sorted(voltage_windows.keys())
current_height = 0
max_height = 0
# for 循环遍历索引(range生成整数序列)
# len(sorted_voltages) 获取列表长度
for i in range(1, len(sorted_voltages)): # 冒号
# 计算电压差
voltage_diff = sorted_voltages[i] - sorted_voltages[i-1]
# 检查两个电压是否都在字典中
if sorted_voltages[i] in voltage_windows and sorted_voltages[i-1] in voltage_windows: # 冒号
current_height += voltage_diff
if current_height > max_height: # 冒号
max_height = current_height
else: # 否则
current_height = 0
max_eye_height = max_height
# 返回多个值(元组)
return max_eye_width, max_eye_height
# 函数定义:生成眼图数据点
def generate_eye_diagram(data, avddq, ui_ps, dq_index, direction):
# 创建空列表
pass_points = [] # 方括号[]表示列表,存储通过点
fail_points = [] # 存储失败点
# 确定最大延迟值
max_delay = 0x7F if direction == 'read' else 0xFF
ui_range = 2 if direction == 'read' else 4
# 嵌套循环
# 外层循环:遍历所有vref值
for vref, dq_data in data.items(): # 冒号
# 计算电压值
voltage = (vref / 0x1FF) * avddq
# 获取当前DQ和方向的数据
dq_info = dq_data.get(dq_index, {}).get(direction)
# 内层循环:遍历所有可能的延迟点
# range(0, max_delay + 1) 生成从0到max_delay的整数序列
for delay in range(0, max_delay + 1): # 冒号
# 计算UI值(将延迟转换为UI单位)
ui_value = (delay / max_delay) * ui_range
# if-else 条件判断
if dq_info is None: # 冒号
# .append() 方法向列表添加元素
# (ui_value, voltage) 是一个元组(不可变的有序集合)
fail_points.append((ui_value, voltage)) # 添加到失败点列表
else: # 否则
# 元组解包:min_point, max_point, _
# _表示忽略第三个值(windows)
min_point, max_point, _ = dq_info
# 条件判断:delay是否在min_point和max_point之间
# 使用链式比较:min_point <= delay <= max_point
if min_point <= delay <= max_point: # 冒号
pass_points.append((ui_value, voltage)) # 添加到通过点列表
else: # 否则
fail_points.append((ui_value, voltage)) # 添加到失败点列表
# 返回两个列表
return pass_points, fail_points
# 函数定义:绘制眼图
def plot_eye_diagrams(log_content, data_rate, avddq, log_path, normalization_point):
# 计算UI时间(单位:皮秒)
# 1e6表示1000000(科学计数法)
# 数据速率单位:Mbps = 兆比特/秒
ui_ps = 1e6 / data_rate # 每个UI的时间(微秒)
# 调用解析函数
data = parse_log_file(log_content, normalization_point)
# if not data: 判断字典是否为空
if not data: # 冒号
print("错误: 无法从日志中解析出有效数据")
return None, None
# plt.subplots() 创建图表和坐标轴网格
# 4,4 表示4行4列(16个子图)
# figsize=(20, 20) 设置图表大小为20英寸×20英寸
fig_write, axes_write = plt.subplots(4, 4, figsize=(20, 20))
fig_read, axes_read = plt.subplots(4, 4, figsize=(20, 20))
# 格式化字符串
# f-string 格式化字符串,{normalization_point:X} 将整数格式化为十六进制大写
norm_title = f" (Normalized to 0x{normalization_point:X})"
# 设置主标题
# suptitle() 设置整个图表的标题
# {ui_ps:.2f} 表示浮点数格式化为两位小数
fig_write.suptitle(f'DDR Write Eye Diagram (Data Rate: {data_rate} Mbps, UI: {ui_ps:.2f} ps){norm_title}',
fontsize=18) # 字体大小18
fig_read.suptitle(f'DDR Read Eye Diagram (Data Rate: {data_rate} Mbps, UI: {ui_ps:.2f} ps){norm_title}',
fontsize=18)
# .flatten() 将二维数组展平为一维
# 因为axes_write是4×4的数组,展平后变为16个元素的列表
axes_write = axes_write.flatten()
axes_read = axes_read.flatten()
# 创建图例元素
# Line2D 用于创建自定义图例项
legend_elements = [
# 创建通过点图例项
Line2D([0], [0], marker='o', color='w', label='Pass',
markerfacecolor='green', markersize=10),
# 创建失败点图例项
Line2D([0], [0], marker='o', color='w', label='Fail',
markerfacecolor='red', markersize=10)
]
# 循环处理16个DQ通道
# range(16) 生成0到15的整数序列
for dq_index in range(16): # 冒号
# 调用函数获取眼图指标
write_width, write_height = calculate_eye_metrics(data, avddq, dq_index, 'write')
# 调用函数生成眼图数据点
write_pass, write_fail = generate_eye_diagram(data, avddq, ui_ps, dq_index, 'write')
# 同上,处理读眼图
read_width, read_height = calculate_eye_metrics(data, avddq, dq_index, 'read')
read_pass, read_fail = generate_eye_diagram(data, avddq, ui_ps, dq_index, 'read')
# 绘制写眼图失败点
if write_fail: # 检查列表是否非空
# zip(*list) 将元组列表解压为多个列表
# 例如:[(1,2), (3,4)] -> [1,3] 和 [2,4]
x_fail, y_fail = zip(*write_fail)
# 散点图绘制
# s=1:点的大小为1
# c='red':点颜色为红色
# alpha=0.1:透明度为10%
# zorder=1:绘制顺序为1(在底层)
axes_write[dq_index].scatter(x_fail, y_fail, s=1, c='red', alpha=0.1, zorder=1)
# 绘制写眼图通过点
if write_pass:
x_pass, y_pass = zip(*write_pass)
# 通过点使用绿色,透明度50%,在顶层(zorder=2)
axes_write[dq_index].scatter(x_pass, y_pass, s=1, c='green', alpha=0.5, zorder=2)
# 创建标注文本
# {write_width:.3f} 格式化为3位小数
write_text = f"Max Eye Width: {write_width:.3f} UI\nMax Eye Height: {write_height:.3f} V"
# 添加文本标注
axes_write[dq_index].annotate(
write_text,
xy=(0.98, 0.02), # 位置(坐标),右下角
xycoords='axes fraction', # 坐标系类型(相对坐标)
fontsize=9, # 字体大小
ha='right', # 水平对齐(右对齐)
va='bottom', # 垂直对齐(底部对齐)
bbox=dict(boxstyle='round', facecolor='white', alpha=0.8) # 文本框样式
)
# 绘制读眼图(类似写眼图处理)
if read_fail:
x_fail, y_fail = zip(*read_fail)
axes_read[dq_index].scatter(x_fail, y_fail, s=1, c='red', alpha=0.1, zorder=1)
if read_pass:
x_pass, y_pass = zip(*read_pass)
axes_read[dq_index].scatter(x_pass, y_pass, s=1, c='green', alpha=0.5, zorder=2)
read_text = f"Max Eye Width: {read_width:.3f} UI\nMax Eye Height: {read_height:.3f} V"
axes_read[dq_index].annotate(
read_text, xy=(0.98, 0.02), xycoords='axes fraction',
fontsize=9, ha='right', va='bottom',
bbox=dict(boxstyle='round', facecolor='white', alpha=0.8)
)
# 设置公共轴属性
# 使用元组列表进行循环(每个元素是一个坐标轴和方向标签)
for ax, direction in [(axes_write[dq_index], 'Write'), (axes_read[dq_index], 'Read')]: # 冒号
# 设置子图标题
ax.set_title(f'DQ{dq_index} {direction} Eye', fontsize=12)
# 设置X轴标签
ax.set_xlabel('Delay (UI)', fontsize=10)
# 设置Y轴标签
ax.set_ylabel('Voltage (V)', fontsize=10)
# 设置坐标轴范围
# 写操作:X轴0-4UI,读操作:X轴0-2UI
ax.set_xlim(0, 4 if direction == 'Write' else 2)
# Y轴范围:0到AVDDQ电压
ax.set_ylim(0, avddq)
# 显示网格
# True:显示网格
# linestyle='--':虚线样式
# alpha=0.6:透明度60%
ax.grid(True, linestyle='--', alpha=0.6)
# 添加图例
# handles=legend_elements:使用自定义图例项
# loc='upper right':位置在右上角
ax.legend(handles=legend_elements, loc='upper right', fontsize=9)
# 设置刻度标签大小
# axis='both':X轴和Y轴都设置
# which='major':主刻度
# labelsize=9:标签大小为9
ax.tick_params(axis='both', which='major', labelsize=9)
# 调整布局
# tight_layout() 自动调整子图参数
# rect=[0, 0, 1, 0.96]:保留顶部4%的空间给标题
fig_write.tight_layout(rect=[0, 0, 1, 0.96])
fig_read.tight_layout(rect=[0, 0, 1, 0.96])
# 文件路径处理
# os.path.dirname() 获取目录路径
# os.getcwd() 获取当前工作目录
# or 运算符:如果左边为假(空字符串),则使用右边值
log_dir = os.path.dirname(log_path) or os.getcwd()
# datetime.datetime.now() 获取当前时间
# .strftime() 格式化时间为字符串
# %Y:四位年份,%m:两位月份,%d:两位日期
# %H:24小时制小时,%M:分钟,%S:秒
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
# os.path.basename() 获取文件名
log_filename = os.path.basename(log_path)
# .rsplit() 从右侧分割字符串
# 1:表示分割一次
# [0]:取分割后的第一部分
if '.' in log_filename: # 冒号
log_name = log_filename.rsplit('.', 1)[0] # 分割一次,取第一部分
else: # 否则
log_name = log_filename
# os.path.join() 拼接路径
write_filename = os.path.join(log_dir, f"{log_name}_ddr_write_eye_{timestamp}.png")
read_filename = os.path.join(log_dir, f"{log_name}_ddr_read_eye_{timestamp}.png")
# 保存图像
# dpi=300:每英寸300点(高分辨率)
# bbox_inches='tight':紧凑边界(去除多余空白)
fig_write.savefig(write_filename, dpi=300, bbox_inches='tight')
fig_read.savefig(read_filename, dpi=300, bbox_inches='tight')
# 关闭图像释放内存
plt.close(fig_write)
plt.close(fig_read)
# 打印结果
print(f"写眼图已保存至: {write_filename}")
print(f"读眼图已保存至: {read_filename}")
return write_filename, read_filename
# 主函数定义
def main():
"""主函数,程序入口点"""
# 打印分隔线
# *运算符重复字符串
print("=" * 50) # 打印50个等号
print("DDR眼图生成器(带十六进制处理)")
print("=" * 50)
# 用户输入
# input() 获取用户输入
# float() 将输入转换为浮点数
data_rate = float(input("请输入DataRate (Mbps/Pin): "))
avddq = float(input("请输入AVDDQ电压值 (V): "))
# 获取归一化点(十六进制输入)
norm_input = input("请输入归一化点(十六进制,如0x40): ")
# int() 按十六进制解析字符串
normalization_point = int(norm_input, 16)
log_path = input("请输入日志文件路径: ")
# 检查文件是否存在
# os.path.exists() 检查路径是否存在
if not os.path.exists(log_path): # 冒号
print(f"错误: 文件 '{log_path}' 不存在")
return # 退出函数
# os.path.abspath() 获取绝对路径
log_path = os.path.abspath(log_path)
# 调用文件读取函数
log_content = robust_read_file(log_path)
if log_content is None: # 冒号
print("无法读取日志文件")
return
# try-except 捕获任何异常
try:
# 调用眼图生成函数
write_file, read_file = plot_eye_diagrams(
log_content, data_rate, avddq, log_path, normalization_point
)
print("\n眼图生成成功!")
except Exception as e: # 捕获所有异常
print(f"眼图生成失败: {e}")
# Python特殊检查:只有直接运行此文件时才执行main()
# __name__ 是内置变量
# 当Python文件直接运行时,__name__ 被设置为 '__main__'
# 当文件被导入时,__name__ 被设置为模块名
if __name__ == "__main__": # 冒号
main() # 调用主函数
```
### 正则表达式核心概念总结
1. **基本结构**:
- `r'...'`:原始字符串(避免转义问题)
- `.`:匹配任意单个字符(换行符除外)
- `*`:匹配前面的模式0次或多次
- `+`:匹配前面的模式1次或多次
- `?`:匹配前面的模式0次或1次
2. **字符类**:
- `[abc]`:匹配a、b或c中的任意一个字符
- `[a-z]`:匹配a到z之间的任意小写字母
- `[0-9]`:匹配任意数字
- `\d`:匹配数字(等价于[0-9])
- `\w`:匹配字母、数字或下划线
- `\s`:匹配空白字符(空格、制表符等)
3. **分组和捕获**:
- `( )`:捕获组,提取匹配的内容
- `(?: )`:非捕获组(不提取内容)
- `|`:或操作(匹配左侧或右侧模式)
4. **位置锚定**:
- `^`:匹配字符串开头
- `$`:匹配字符串结尾
- `\b`:单词边界
5. **特殊字符**:
- `\xHH`:匹配十六进制值HH对应的字符
- `\n`:换行符
- `\t`:制表符