bug:
眼图生成失败: name 'plot_eye_diagrams' is not defined。请不要纠结了。请不要再继续错上加错,我这里这份代码确认可用。在这上面加注释就行。要求:
1、包含超级详细的代码注释
2、包含尽可能多的运用到的编程知识的介绍
3、每一处正则表达式都要进行不遗余力的讲解
# =====================================================
# 导入必要的库模块
# =====================================================
import numpy as np # 科学计算库
import matplotlib.pyplot as plt # 数据可视化库
import re # 正则表达式模块
import datetime # 日期时间处理
from matplotlib.lines import Line2D # 图例元素
import os # 操作系统接口
from collections import defaultdict # 带默认值的字典
# =====================================================
# 健壮的文件读取函数
# =====================================================
def robust_read_file(file_path):
"""
健壮的文件读取函数,处理不同编码的文件
参数:
file_path - 文件在电脑上的完整路径
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
return f.read()
except UnicodeDecodeError:
try:
with open(file_path, 'r', encoding='latin-1') as f:
content = f.read()
return re.sub(r'[\x00-\x1F]+', '', content) # 删除控制字符
except Exception as e:
print(f"文件解码错误: {e}")
return None
# =====================================================
# 日志解析函数(修改:只对读方向应用偏移)
# =====================================================
def parse_log_file(log_content, normalization_point):
"""
解析DDR校准日志文件,提取关键数据
参数:
log_content - 日志文件的内容
normalization_point - 归一化点(十六进制值)
"""
# 创建空字典存储解析结果
data = {} # {vref: {dq_index: {direction: (min, max, window)}}}
# 初始化当前处理的变量
current_vref = None # 当前处理的vref值
pending_data = {} # 临时存储待处理的数据
current_offset = None # 当前偏移量
raw_data = {} # 存储原始数据(偏移前)用于输出
# 按行处理日志内容
for line in log_content.split('\n'):
line = line.strip()
if not line:
continue
# 匹配vref行(十六进制值)
vref_match = re.match(r'.*vref:\s*0x([0-9a-fA-F]+)', line)
if vref_match:
hex_str = vref_match.group(1)
current_vref = int(hex_str, 16)
data[current_vref] = {}
raw_data[current_vref] = {}
pending_data = {}
current_offset = None
continue
# 匹配0x38c行(简写形式)
offset_match = re.match(r'.*0x38c:\s*(?:0x)?([0-9a-fA-F]+)', line)
if offset_match and current_vref is not None:
try:
hex_str = offset_match.group(1)
offset_value = int(hex_str, 16)
current_offset = normalization_point - offset_value
except ValueError:
print(f"警告: 无法解析偏移量: {offset_match.group(1)}")
current_offset = None
continue
# 匹配max点(十进制值)
max_match = re.match(r'.*dq(\d+)\s+max_(\w+)_point\s*:\s*(-?\d+)', line)
if max_match and current_vref is not None:
dq_index = int(max_match.group(1))
direction = max_match.group(2)
max_val = int(max_match.group(3))
key = (dq_index, direction)
# 存储原始值(不应用偏移)
if current_vref not in raw_data:
raw_data[current_vref] = {}
if dq_index not in raw_data[current_vref]:
raw_data[current_vref][dq_index] = {}
if direction not in raw_data[current_vref][dq_index]:
raw_data[current_vref][dq_index][direction] = {'min': None, 'max': None}
raw_data[current_vref][dq_index][direction]['max'] = max_val
# 只有读方向应用偏移
if direction == 'read' and current_offset is not None:
max_val += current_offset
pending_data[key] = {'max': max_val}
continue
# 匹配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:
dq_index = int(min_match.group(1))
direction = min_match.group(2)
min_val = int(min_match.group(3))
key = (dq_index, direction)
# 存储原始值(不应用偏移)
if current_vref not in raw_data:
raw_data[current_vref] = {}
if dq_index not in raw_data[current_vref]:
raw_data[current_vref][dq_index] = {}
if direction not in raw_data[current_vref][dq_index]:
raw_data[current_vref][dq_index][direction] = {'min': None, 'max': None}
raw_data[current_vref][dq_index][direction]['min'] = min_val
# 只有读方向应用偏移
if direction == 'read' and current_offset is not None:
min_val += current_offset
if key in pending_data:
pending_data[key]['min'] = min_val
continue
# 匹配窗口行(十进制值)
win_match = re.match(r'.*dq(\d+)\s+(\w+)_windows\s*:\s*(-?\d+)', line)
if win_match and current_vref 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)
if key in pending_data and 'min' in pending_data[key] and 'max' in pending_data[key]:
min_val = pending_data[key]['min']
max_val = pending_data[key]['max']
max_delay = 0x7F if direction == 'read' else 0xFF
min_val = max(0, min_val)
max_val = min(max_delay, max_val)
if min_val > max_val or windows < 0:
result = None
else:
window_size = max_val - min_val + 1
result = (min_val, max_val, window_size)
if dq_index not in data[current_vref]:
data[current_vref][dq_index] = {}
data[current_vref][dq_index][direction] = result
del pending_data[key]
# 返回解析结果和原始数据
return data, raw_data
# =====================================================
# 眼图指标计算函数
# =====================================================
def calculate_eye_metrics(data, avddq, dq_index, direction):
"""
计算眼图的最大宽度和最大高度
参数:
data - 解析后的日志数据
avddq - AVDDQ电压值
dq_index - DQ索引(0-15)
direction - 方向('read'或'write')
"""
max_eye_height = 0.0
max_eye_width = 0.0
voltage_windows = defaultdict(float)
for vref, dq_data in data.items():
voltage = (vref / 0x1FF) * avddq
dq_info = dq_data.get(dq_index, {}).get(direction)
if dq_info is None:
continue
min_point, max_point, window_size = 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
window_ui = (window_size / max_delay) * ui_range
if window_ui > max_eye_width:
max_eye_width = window_ui
voltage_windows[voltage] = window_ui
sorted_voltages = sorted(voltage_windows.keys())
current_height = 0
max_height = 0
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):
"""
生成眼图数据点
参数:
data - 解析后的日志数据
avddq - AVDDQ电压值
ui_ps - 每个UI的时间(皮秒)
dq_index - DQ索引(0-15)
direction - 方向('read'或'write')
"""
pass_points = []
fail_points = []
max_delay = 0x7F if direction == 'read' else 0xFF
ui_range = 2 if direction == 'read' else 4
for vref, dq_data in data.items():
voltage = (vref / 0x1FF) * avddq
dq_info = dq_data.get(dq_index, {}).get(direction)
for delay in range(0, max_delay + 1):
ui_value = (delay / max_delay) * ui_range
if dq_info is None:
fail_points.append((ui_value, voltage))
else:
min_point, max_point, _ = dq_info
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 export_raw_data(raw_data, normalization_point, log_path):
"""
输出原始数据到新日志文件(按DQ划分)
参数:
raw_data - 原始数据(偏移前)
normalization_point - 归一化点
log_path - 原始日志文件路径
"""
# 获取当前时间戳
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
# 获取日志文件名(不含扩展名)
log_filename = os.path.basename(log_path)
if '.' in log_filename:
log_name = log_filename.rsplit('.', 1)[0]
else:
log_name = log_filename
# 创建输出目录
log_dir = os.path.dirname(log_path) or os.getcwd()
output_dir = os.path.join(log_dir, "raw_data_export")
os.makedirs(output_dir, exist_ok=True)
# 创建输出文件路径
output_file = os.path.join(output_dir, f"{log_name}_raw_data_{timestamp}.txt")
# 写入原始数据
with open(output_file, 'w', encoding='utf-8') as f:
# 写入标题信息
f.write("=" * 80 + "\n")
f.write(f"DDR校准原始数据报告 (归一化点: 0x{normalization_point:X})\n")
f.write(f"生成时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"原始日志: {log_path}\n")
f.write("=" * 80 + "\n\n")
# 按vref排序
sorted_vrefs = sorted(raw_data.keys())
for vref in sorted_vrefs:
# 写入vref标题
f.write(f"VREF: 0x{vref:03X}\n")
f.write("-" * 60 + "\n")
# 按DQ索引排序
sorted_dq = sorted(raw_data[vref].keys())
for dq_index in sorted_dq:
# 写入DQ标题
f.write(f" DQ{dq_index}:\n")
# 处理读方向数据
if 'read' in raw_data[vref][dq_index]:
rd = raw_data[vref][dq_index]['read']
f.write(f" 读方向:\n")
f.write(f" 原始最小值: {rd['min']}\n")
f.write(f" 原始最大值: {rd['max']}\n")
f.write(f" 窗口大小: {rd['max'] - rd['min'] + 1}\n")
# 处理写方向数据
if 'write' in raw_data[vref][dq_index]:
wr = raw_data[vref][dq_index]['write']
f.write(f" 写方向:\n")
f.write(f" 原始最小值: {wr['min']}\n")
f.write(f" 原始最大值: {wr['max']}\n")
f.write(f" 窗口大小: {wr['max'] - wr['min'] + 1}\n")
f.write("\n")
f.write("\n")
print(f"原始数据已导出至: {output_file}")
return output_file
# =====================================================
# 眼图绘制函数
# =====================================================
def plot_eye_diagrams(log_content, data_rate, avddq, log_path, normalization_point):
"""
绘制DDR眼图
参数:
log_content - 日志内容
data_rate - 数据速率(Mbps)
avddq - AVDDQ电压(V)
log_path - 日志文件路径
normalization_point - 归一化点
"""
# 计算UI时间(皮秒)
ui_ps = (1 / (data_rate * 1e6)) * 1e12
# 解析日志文件(获取处理后的数据和原始数据)
data, raw_data = parse_log_file(log_content, normalization_point)
# 导出原始数据到新日志
raw_data_file = export_raw_data(raw_data, normalization_point, log_path)
# 检查是否解析到有效数据
if not data:
print("错误: 无法从日志中解析出有效数据")
return None, None
# 创建图表对象
fig_write, axes_write = plt.subplots(4, 4, figsize=(20, 20))
fig_read, axes_read = plt.subplots(4, 4, figsize=(20, 20))
# 设置标题(包含原始数据文件路径)
norm_title = f" (归一化点: 0x{normalization_point:X}, 原始数据: {os.path.basename(raw_data_file)})"
fig_write.suptitle(f'DDR写眼图 (数据速率: {data_rate} Mbps, UI: {ui_ps:.2f} ps){norm_title}', fontsize=18)
fig_read.suptitle(f'DDR读眼图 (数据速率: {data_rate} Mbps, UI: {ui_ps:.2f} ps){norm_title}', fontsize=18)
# 展平坐标轴数组
axes_write = axes_write.flatten()
axes_read = axes_read.flatten()
# 创建图例元素
legend_elements = [
Line2D([0], [0], marker='o', color='w', label='通过',
markerfacecolor='green', markersize=10),
Line2D([0], [0], marker='o', color='w', label='失败',
markerfacecolor='red', markersize=10)
]
# 遍历16个DQ通道
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:
x_fail, y_fail = zip(*write_fail)
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)
axes_write[dq_index].scatter(x_pass, y_pass, s=1, c='green', alpha=0.5, zorder=2)
# 添加写眼图标注
write_text = f"最大眼宽: {write_width:.3f} UI\n最大眼高: {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"最大眼宽: {read_width:.3f} UI\n最大眼高: {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], '写'), (axes_read[dq_index], '读')]:
ax.set_title(f'DQ{dq_index} {direction}眼图', fontsize=12)
ax.set_xlabel('延迟 (UI)', fontsize=10)
ax.set_ylabel('电压 (V)', fontsize=10)
ax.set_xlim(0, 4 if direction == '写' else 2)
ax.set_ylim(0, avddq)
ax.grid(True, linestyle='--', alpha=0.6)
ax.legend(handles=legend_elements, loc='upper right', fontsize=9)
ax.tick_params(axis='both', which='major', labelsize=9)
# 调整布局
fig_write.tight_layout(rect=[0, 0, 1, 0.96])
fig_read.tight_layout(rect=[0, 0, 1, 0.96])
# 文件路径处理
log_dir = os.path.dirname(log_path) or os.getcwd()
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
log_filename = os.path.basename(log_path)
log_name = log_filename.rsplit('.', 1)[0] if '.' in log_filename else log_filename
# 构建输出文件路径
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")
# 保存图像
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, raw_data_file
# =====================================================
# 主函数(修改:包含原始数据导出)
# =====================================================
def main():
"""主函数,程序入口点"""
print("=" * 50)
print("DDR眼图生成器(带原始数据导出)")
print("=" * 50)
# 用户输入
data_rate = float(input("请输入DataRate (Mbps/Pin): "))
avddq = float(input("请输入AVDDQ电压值 (V): "))
# 归一化点输入处理
while True:
norm_input = input("请输入归一化点(十六进制值,如0x40或40): ").strip()
if not norm_input:
print("错误: 输入不能为空,请重新输入")
continue
try:
if norm_input.startswith(("0x", "0X")):
hex_str = norm_input[2:]
else:
hex_str = norm_input
normalization_point = int(hex_str, 16)
break
except ValueError:
print(f"错误: '{norm_input}' 不是有效的十六进制数,请重新输入")
# 日志文件路径输入
while True:
log_path = input("请输入日志文件路径: ").strip()
if not os.path.exists(log_path):
print(f"错误: 文件 '{log_path}' 不存在,请重新输入")
else:
break
# 获取绝对路径
log_path = os.path.abspath(log_path)
# 读取文件内容
log_content = robust_read_file(log_path)
if log_content is None:
print("无法读取日志文件")
return
# 尝试生成眼图
try:
# 调用眼图生成函数(返回三个值)
write_file, read_file, raw_data_file = plot_eye_diagrams(
log_content, data_rate, avddq, log_path, normalization_point
)
print("\n眼图生成成功!")
print(f"原始数据文件: {raw_data_file}")
except Exception as e:
print(f"眼图生成失败: {e}")
# Python特殊检查
if __name__ == "__main__":
main()
最新发布