我现在有新LOG,但是发现源代码解析不出数据了。输出都是空的。请帮忙在源代码基础上修正。
新LOG片段如下,不用管LOG中的具体值是正式负,主要看格式。
[2025-07-31 21:26:28.200] n萔兄@刃兄`豥@蘊耯@z灶Pggddddd吕J蔲f豥鴍麼OTICE: Booting HSN2155 BL1 Firmware
[2025-07-31 21:26:45.758] NOTICE: Built : 15:59:30, Feb 6 2025
[2025-07-31 21:26:45.760] NOTICE: Strap boot mode: 0xde
[2025-07-31 21:26:45.762] NOTICE: SPI NAND pagesize: 2048
[2025-07-31 21:26:45.785] VERBOSE: secure_ctrl offset 112.
[2025-07-31 21:26:45.788] INFO: Loading BL2
[2025-07-31 21:26:45.791] VERBOSE: Step size x20000
[2025-07-31 21:26:45.792] NOTICE: Booting @ flash address 0x0...
[2025-07-31 21:26:45.795] VERBOSE: Header magic looks OK
[2025-07-31 21:26:45.848] VERBOSE: Secure flags: 0x0
[2025-07-31 21:26:45.850] NOTICE: Secure boot enable: 0
[2025-07-31 21:26:45.869] VERBOSE: Payload check integrity succeeded
[2025-07-31 21:26:45.896]
[2025-07-31 21:26:45.896] ###### Hsn2155 Set PLL And Init DDR ######
[2025-07-31 21:26:45.900] Set pcm pll succ!
[2025-07-31 21:26:45.902] Set serdes pll succ!
[2025-07-31 21:26:45.903] Set cpu pll 1.2G succ!
[2025-07-31 21:26:45.905] DDR init succ!
[2025-07-31 21:26:45.906] Switch CPU PLL
[2025-07-31 21:26:45.908] Switch BUS DIVIDE
[2025-07-31 21:26:46.330] zq value check : 0xc0c0c0c
[2025-07-31 21:26:46.357] vref: 0x0
[2025-07-31 21:26:46.367] 0x38c: 1f
[2025-07-31 21:26:46.379] PHY_160_zq_result : 0x0
[2025-07-31 21:26:46.381] PHY_280_wrlvl_byte0 : 0xff0080
[2025-07-31 21:26:46.384] PHY_380_wrlvl_byte1 : 0xff0080
[2025-07-31 21:26:46.387] PHY_284_gate_byte0 : 0x280
[2025-07-31 21:26:46.389] PHY_384_gate_byte1 : 0x285
[2025-07-31 21:26:46.391] PHY_2a0_read : 0x0
[2025-07-31 21:26:46.393] PHY_2a4_read : 0x0
[2025-07-31 21:26:46.395] PHY_294_read : 0x7f7f7f7f
[2025-07-31 21:26:46.397] PHY_298_read : 0x7f7f7f7f
[2025-07-31 21:26:46.402] dq0 max_read_point : 0
[2025-07-31 21:26:46.402] dq0 min_read_point : 127
[2025-07-31 21:26:46.404] dq0 read_windows : -127
[2025-07-31 21:26:46.406] dq1 max_read_point : 0
[2025-07-31 21:26:46.408] dq1 min_read_point : 127
[2025-07-31 21:26:46.410] dq1 read_windows : -127
[2025-07-31 21:26:46.413] dq2 max_read_point : 0
[2025-07-31 21:26:46.416] dq2 min_read_point : 127
[2025-07-31 21:26:46.417] dq2 read_windows : -127
[2025-07-31 21:26:46.419] dq3 max_read_point : 0
[2025-07-31 21:26:46.422] dq3 min_read_point : 127
[2025-07-31 21:26:46.424] dq3 read_windows : -127
[2025-07-31 21:26:46.426] dq4 max_read_point : 0
[2025-07-31 21:26:46.427] dq4 min_read_point : 127
[2025-07-31 21:26:46.431] dq4 read_windows : -127
[2025-07-31 21:26:46.432] dq5 max_read_point : 0
[2025-07-31 21:26:46.435] dq5 min_read_point : 127
[2025-07-31 21:26:46.437] dq5 read_windows : -127
[2025-07-31 21:26:46.439] dq6 max_read_point : 0
[2025-07-31 21:26:46.441] dq6 min_read_point : 127
[2025-07-31 21:26:46.443] dq6 read_windows : -127
[2025-07-31 21:26:46.445] dq7 max_read_point : 0
[2025-07-31 21:26:46.447] dq7 min_read_point : 127
[2025-07-31 21:26:46.450] dq7 read_windows : -127
[2025-07-31 21:26:46.451] PHY_3a0_read : 0x0
[2025-07-31 21:26:46.453] PHY_3a4_read : 0x0
[2025-07-31 21:26:46.455] PHY_394_read : 0x7f7f7f7f
[2025-07-31 21:26:46.458] PHY_398_read : 0x7f7f7f7f
[2025-07-31 21:26:46.459] dq8 max_read_point : 0
[2025-07-31 21:26:46.462] dq8 min_read_point : 127
[2025-07-31 21:26:46.464] dq8 read_windows : -127
[2025-07-31 21:26:46.467] dq9 max_read_point : 0
[2025-07-31 21:26:46.469] dq9 min_read_point : 127
[2025-07-31 21:26:46.471] dq9 read_windows : -127
[2025-07-31 21:26:46.473] dq10 max_read_point : 0
[2025-07-31 21:26:46.475] dq10 min_read_point : 127
[2025-07-31 21:26:46.477] dq10 read_windows : -127
[2025-07-31 21:26:46.480] dq11 max_read_point : 0
[2025-07-31 21:26:46.482] dq11 min_read_point : 127
[2025-07-31 21:26:46.484] dq11 read_windows : -127
[2025-07-31 21:26:46.486] dq12 max_read_point : 0
[2025-07-31 21:26:46.489] dq12 min_read_point : 127
[2025-07-31 21:26:46.491] dq12 read_windows : -127
[2025-07-31 21:26:46.493] dq13 max_read_point : 0
[2025-07-31 21:26:46.495] dq13 min_read_point : 127
[2025-07-31 21:26:46.498] dq13 read_windows : -127
[2025-07-31 21:26:46.500] dq14 max_read_point : 0
[2025-07-31 21:26:46.502] dq14 min_read_point : 127
[2025-07-31 21:26:46.504] dq14 read_windows : -127
[2025-07-31 21:26:46.507] dq15 max_read_point : 0
[2025-07-31 21:26:46.511] dq15 min_read_point : 127
[2025-07-31 21:26:46.512] dq15 read_windows : -127
[2025-07-31 21:26:46.513] PHY_2e0_read : 0x0
[2025-07-31 21:26:46.516] PHY_2e4_read : 0x0
[2025-07-31 21:26:46.517] PHY_2c4_read : 0xffffffff
[2025-07-31 21:26:46.520] PHY_2c8_read : 0xffffffff
[2025-07-31 21:26:46.522] dq0 max_write_point : 0
[2025-07-31 21:26:46.524] dq0 min_write_point : 255
[2025-07-31 21:26:46.527] dq0 write_windows : -255
[2025-07-31 21:26:46.529] dq1 max_write_point : 0
[2025-07-31 21:26:46.531] dq1 min_write_point : 255
[2025-07-31 21:26:46.533] dq1 write_windows : -255
[2025-07-31 21:26:46.536] dq2 max_write_point : 0
[2025-07-31 21:26:46.538] dq2 min_write_point : 255
[2025-07-31 21:26:46.540] dq2 write_windows : -255
[2025-07-31 21:26:46.542] dq3 max_write_point : 0
[2025-07-31 21:26:46.545] dq3 min_write_point : 255
[2025-07-31 21:26:46.547] dq3 write_windows : -255
[2025-07-31 21:26:46.549] dq4 max_write_point : 0
[2025-07-31 21:26:46.551] dq4 min_write_point : 255
[2025-07-31 21:26:46.554] dq4 write_windows : -255
[2025-07-31 21:26:46.556] dq5 max_write_point : 0
[2025-07-31 21:26:46.558] dq5 min_write_point : 255
[2025-07-31 21:26:46.560] dq5 write_windows : -255
[2025-07-31 21:26:46.563] dq6 max_write_point : 0
[2025-07-31 21:26:46.565] dq6 min_write_point : 255
[2025-07-31 21:26:46.567] dq6 write_windows : -255
[2025-07-31 21:26:46.569] dq7 max_write_point : 0
[2025-07-31 21:26:46.572] dq7 min_write_point : 255
[2025-07-31 21:26:46.574] dq7 write_windows : -255
[2025-07-31 21:26:46.576] PHY_3e0_read : 0x0
[2025-07-31 21:26:46.578] PHY_3e4_read : 0x0
[2025-07-31 21:26:46.580] PHY_3c4_read : 0xffffffff
[2025-07-31 21:26:46.583] PHY_3c8_read : 0xffffffff
[2025-07-31 21:26:46.585] dq8 max_write_point : 0
[2025-07-31 21:26:46.586] dq8 min_write_point : 255
[2025-07-31 21:26:46.589] dq8 write_windows : -255
[2025-07-31 21:26:46.592] dq9 max_write_point : 0
[2025-07-31 21:26:46.593] dq9 min_write_point : 255
[2025-07-31 21:26:46.596] dq9 write_windows : -255
[2025-07-31 21:26:46.597] dq10 max_write_point : 0
[2025-07-31 21:26:46.600] dq10 min_write_point : 255
[2025-07-31 21:26:46.602] dq10 write_windows : -255
[2025-07-31 21:26:46.606] dq11 max_write_point : 0
[2025-07-31 21:26:46.607] dq11 min_write_point : 255
[2025-07-31 21:26:46.610] dq11 write_windows : -255
[2025-07-31 21:26:46.612] dq12 max_write_point : 0
[2025-07-31 21:26:46.615] dq12 min_write_point : 255
[2025-07-31 21:26:46.617] dq12 write_windows : -255
[2025-07-31 21:26:46.620] dq13 max_write_point : 0
[2025-07-31 21:26:46.621] dq13 min_write_point : 255
[2025-07-31 21:26:46.624] dq13 write_windows : -255
[2025-07-31 21:26:46.626] dq14 max_write_point : 0
[2025-07-31 21:26:46.628] dq14 min_write_point : 255
[2025-07-31 21:26:46.631] dq14 write_windows : -255
[2025-07-31 21:26:46.633] dq15 max_write_point : 0
[2025-07-31 21:26:46.635] dq15 min_write_point : 255
[2025-07-31 21:26:46.638] dq15 write_windows : -255
[2025-07-31 21:26:47.014] zq value check : 0xc0c0c0c
[2025-07-31 21:26:47.041] vref: 0x1
[2025-07-31 21:26:47.051] 0x38c: 1f
[2025-07-31 21:26:47.064] PHY_160_zq_result : 0x0
[2025-07-31 21:26:47.066] PHY_280_wrlvl_byte0 : 0xff0080
[2025-07-31 21:26:47.069] PHY_380_wrlvl_byte1 : 0xff0080
[2025-07-31 21:26:47.072] PHY_284_gate_byte0 : 0x280
[2025-07-31 21:26:47.074] PHY_384_gate_byte1 : 0x284
[2025-07-31 21:26:47.077] PHY_2a0_read : 0x0
[2025-07-31 21:26:47.078] PHY_2a4_read : 0x0
[2025-07-31 21:26:47.080] PHY_294_read : 0x7f7f7f7f
[2025-07-31 21:26:47.082] PHY_298_read : 0x7f7f7f7f
[2025-07-31 21:26:47.084] dq0 max_read_point : 0
[2025-07-31 21:26:47.086] dq0 min_read_point : 127
[2025-07-31 21:26:47.089] dq0 read_windows : -127
[2025-07-31 21:26:47.091] dq1 max_read_point : 0
[2025-07-31 21:26:47.093] dq1 min_read_point : 127
[2025-07-31 21:26:47.095] dq1 read_windows : -127
[2025-07-31 21:26:47.097] dq2 max_read_point : 0
[2025-07-31 21:26:47.099] dq2 min_read_point : 127
[2025-07-31 21:26:47.102] dq2 read_windows : -127
[2025-07-31 21:26:47.105] dq3 max_read_point : 0
[2025-07-31 21:26:47.107] dq3 min_read_point : 127
[2025-07-31 21:26:47.109] dq3 read_windows : -127
[2025-07-31 21:26:47.110] dq4 max_read_point : 0
[2025-07-31 21:26:47.113] dq4 min_read_point : 127
[2025-07-31 21:26:47.115] dq4 read_windows : -127
[2025-07-31 21:26:47.117] dq5 max_read_point : 0
[2025-07-31 21:26:47.120] dq5 min_read_point : 127
[2025-07-31 21:26:47.122] dq5 read_windows : -127
[2025-07-31 21:26:47.123] dq6 max_read_point : 0
[2025-07-31 21:26:47.125] dq6 min_read_point : 127
[2025-07-31 21:26:47.129] dq6 read_windows : -127
[2025-07-31 21:26:47.130] dq7 max_read_point : 0
[2025-07-31 21:26:47.132] dq7 min_read_point : 127
[2025-07-31 21:26:47.135] dq7 read_windows : -127
[2025-07-31 21:26:47.136] PHY_3a0_read : 0x0
[2025-07-31 21:26:47.138] PHY_3a4_read : 0x0
[2025-07-31 21:26:47.139] PHY_394_read : 0x7f7f7f7f
[2025-07-31 21:26:47.142] PHY_398_read : 0x7f7f7f7f
[2025-07-31 21:26:47.144] dq8 max_read_point : 0
[2025-07-31 21:26:47.146] dq8 min_read_point : 127
[2025-07-31 21:26:47.148] dq8 read_windows : -127
[2025-07-31 21:26:47.151] dq9 max_read_point : 0
[2025-07-31 21:26:47.153] dq9 min_read_point : 127
[2025-07-31 21:26:47.155] dq9 read_windows : -127
[2025-07-31 21:26:47.158] dq10 max_read_point : 0
[2025-07-31 21:26:47.159] dq10 min_read_point : 127
[2025-07-31 21:26:47.162] dq10 read_windows : -127
[2025-07-31 21:26:47.165] dq11 max_read_point : 0
[2025-07-31 21:26:47.166] dq11 min_read_point : 127
[2025-07-31 21:26:47.169] dq11 read_windows : -127
[2025-07-31 21:26:47.170] dq12 max_read_point : 0
[2025-07-31 21:26:47.174] dq12 min_read_point : 127
[2025-07-31 21:26:47.175] dq12 read_windows : -127
[2025-07-31 21:26:47.178] dq13 max_read_point : 0
[2025-07-31 21:26:47.180] dq13 min_read_point : 127
[2025-07-31 21:26:47.183] dq13 read_windows : -127
[2025-07-31 21:26:47.184] dq14 max_read_point : 0
[2025-07-31 21:26:47.187] dq14 min_read_point : 127
[2025-07-31 21:26:47.189] dq14 read_windows : -127
[2025-07-31 21:26:47.191] dq15 max_read_point : 0
[2025-07-31 21:26:47.194] dq15 min_read_point : 127
[2025-07-31 21:26:47.195] dq15 read_windows : -127
[2025-07-31 21:26:47.198] PHY_2e0_read : 0x0
[2025-07-31 21:26:47.200] PHY_2e4_read : 0x0
[2025-07-31 21:26:47.202] PHY_2c4_read : 0xffffffff
[2025-07-31 21:26:47.204] PHY_2c8_read : 0xffffffff
[2025-07-31 21:26:47.206] dq0 max_write_point : 0
[2025-07-31 21:26:47.208] dq0 min_write_point : 255
[2025-07-31 21:26:47.211] dq0 write_windows : -255
[2025-07-31 21:26:47.213] dq1 max_write_point : 0
[2025-07-31 21:26:47.215] dq1 min_write_point : 255
[2025-07-31 21:26:47.217] dq1 write_windows : -255
[2025-07-31 21:26:47.220] dq2 max_write_point : 0
[2025-07-31 21:26:47.222] dq2 min_write_point : 255
[2025-07-31 21:26:47.224] dq2 write_windows : -255
[2025-07-31 21:26:47.226] dq3 max_write_point : 0
[2025-07-31 21:26:47.229] dq3 min_write_point : 255
[2025-07-31 21:26:47.231] dq3 write_windows : -255
[2025-07-31 21:26:47.233] dq4 max_write_point : 0
[2025-07-31 21:26:47.235] dq4 min_write_point : 255
[2025-07-31 21:26:47.238] dq4 write_windows : -255
[2025-07-31 21:26:47.240] dq5 max_write_point : 0
[2025-07-31 21:26:47.243] dq5 min_write_point : 255
[2025-07-31 21:26:47.244] dq5 write_windows : -255
[2025-07-31 21:26:47.247] dq6 max_write_point : 0
[2025-07-31 21:26:47.249] dq6 min_write_point : 255
[2025-07-31 21:26:47.251] dq6 write_windows : -255
[2025-07-31 21:26:47.253] dq7 max_write_point : 0
[2025-07-31 21:26:47.256] dq7 min_write_point : 255
[2025-07-31 21:26:47.258] dq7 write_windows : -255
[2025-07-31 21:26:47.260] PHY_3e0_read : 0x0
[2025-07-31 21:26:47.262] PHY_3e4_read : 0x0
[2025-07-31 21:26:47.264] PHY_3c4_read : 0xffffffff
[2025-07-31 21:26:47.267] PHY_3c8_read : 0xffffffff
[2025-07-31 21:26:47.268] dq8 max_write_point : 0
[2025-07-31 21:26:47.271] dq8 min_write_point : 255
[2025-07-31 21:26:47.273] dq8 write_windows : -255
[2025-07-31 21:26:47.276] dq9 max_write_point : 0
[2025-07-31 21:26:47.278] dq9 min_write_point : 255
[2025-07-31 21:26:47.280] dq9 write_windows : -255
[2025-07-31 21:26:47.282] dq10 max_write_point : 0
[2025-07-31 21:26:47.285] dq10 min_write_point : 255
[2025-07-31 21:26:47.286] dq10 write_windows : -255
[2025-07-31 21:26:47.290] dq11 max_write_point : 0
[2025-07-31 21:26:47.291] dq11 min_write_point : 255
[2025-07-31 21:26:47.294] dq11 write_windows : -255
[2025-07-31 21:26:47.296] dq12 max_write_point : 0
[2025-07-31 21:26:47.299] dq12 min_write_point : 255
[2025-07-31 21:26:47.300] dq12 write_windows : -255
[2025-07-31 21:26:47.303] dq13 max_write_point : 0
[2025-07-31 21:26:47.306] dq13 min_write_point : 255
[2025-07-31 21:26:47.308] dq13 write_windows : -255
[2025-07-31 21:26:47.310] dq14 max_write_point : 0
[2025-07-31 21:26:47.313] dq14 min_write_point : 255
[2025-07-31 21:26:47.316] dq14 write_windows : -255
[2025-07-31 21:26:47.317] dq15 max_write_point : 0
[2025-07-31 21:26:47.319] dq15 min_write_point : 255
[2025-07-31 21:26:47.322] dq15 write_windows : -255
[2025-07-31 21:26:47.699] zq value check : 0xc0c0c0c
源代码如下:
import numpy as np
"""
NumPy (Numerical Python) - 科学计算基础库
主要功能:
- 提供高效的N维数组对象(ndarray)
- 支持广播功能函数
- 提供线性代数、傅里叶变换、随机数生成等功能
在本代码中主要用于:
- 数值计算和数组操作
- 科学计算支持
"""
import matplotlib.pyplot as plt
"""
Matplotlib - Python中最强大的绘图库
主要功能:
- 创建静态、动态和交互式图表
- 支持多种图表类型(线图、散点图、柱状图等)
- 高度可定制化(颜色、线型、标签等)
在本代码中主要用于:
- 绘制眼图
- 可视化DDR校准数据
- 创建图表和图形界面
"""
import re
"""
re (Regular Expression) - 正则表达式模块
主要功能:
- 文本搜索和模式匹配
- 文本替换
- 复杂字符串处理
在本代码中主要用于:
- 解析日志文件中的关键数据
- 提取VREF、偏移量、数据点等信息
- 处理复杂的文本匹配任务
"""
import datetime
"""
datetime - 日期和时间处理模块
主要功能:
- 日期和时间的表示
- 日期和时间的计算
- 日期和时间的格式化
在本代码中主要用于:
- 生成时间戳
- 创建带时间戳的文件名
- 记录报告生成时间
"""
from matplotlib.lines import Line2D
"""
Line2D - 用于创建二维线条对象
主要功能:
- 表示二维坐标系中的线条
- 控制线条属性(颜色、线宽、样式等)
在本代码中主要用于:
- 创建图例元素
- 自定义图表中的线条样式
"""
import os
"""
os (Operating System) - 操作系统接口模块
主要功能:
- 文件和目录操作
- 路径操作
- 进程管理
在本代码中主要用于:
- 文件路径处理
- 目录创建
- 文件存在性检查
"""
from collections import defaultdict
"""
defaultdict - 带默认值的字典
主要功能:
- 当访问不存在的键时返回默认值
- 避免KeyError异常
在本代码中主要用于:
- 存储电压-窗口映射关系
- 简化字典初始化操作
"""
import math
"""
math - 数学函数模块
主要功能:
- 提供数学运算函数
- 包括三角函数、对数、取整等
在本代码中新增用于:
- 计算中位数
"""
# 健壮的文件读取函数 - 详细解释每个编程概念
def robust_read_file(file_path):
"""
健壮的文件读取函数,处理不同编码的文件
参数:
file_path - 文件在电脑上的完整路径(字符串)
编程概念详解:
1. 函数定义:def关键字用于定义函数,函数是一段可重复使用的代码块
2. 参数传递:file_path是形式参数,调用时传入实际文件路径
3. 异常处理:try-except结构用于捕获和处理运行时错误
4. 上下文管理器:with语句用于资源管理,确保文件正确关闭
5. 编码处理:不同文件可能使用不同编码(UTF-8, Latin-1等)
6. 正则表达式:用于过滤控制字符
"""
##########################################################
# try-except 异常处理结构
# try: 尝试执行可能出错的代码块
##########################################################
try:
##########################################################
# with 上下文管理器
# 语法:with expression [as variable]:
# 特点:自动管理资源(如文件),确保资源正确释放
# open() 函数:打开文件
# 'r':只读模式
# encoding='utf-8':指定UTF-8编码
##########################################################
with open(file_path, 'r', encoding='utf-8') as f:
# 文件对象方法:f.read()读取整个文件内容
return f.read()
##########################################################
# except 捕获特定异常
# UnicodeDecodeError:当文件编码不匹配时抛出
##########################################################
except UnicodeDecodeError:
try:
# 尝试使用Latin-1编码(支持所有256个字节值)
with open(file_path, 'r', encoding='latin-1') as f:
content = f.read()
##########################################################
# 正则表达式详解:r'[\x00-\x1F]+'
# 用途:匹配并删除控制字符(ASCII 0-31)
# 分解:
# r'':原始字符串,避免转义字符处理
# []:字符类,匹配括号内任意字符
# \x00-\x1F:十六进制范围,表示ASCII控制字符(0-31)
# +:量词,匹配1次或多次
# re.sub():替换匹配项
# 参数1:模式
# 参数2:替换内容(空字符串)
# 参数3:输入字符串
##########################################################
return re.sub(r'[\x00-\x1F]+', '', content)
##########################################################
# Exception 捕获所有异常
# 通用异常处理,打印错误信息
##########################################################
except Exception as e:
# 格式化字符串:f-string (Python 3.6+)
print(f"文件解码错误: {e}")
return None
# 日志解析函数 - 重点讲解正则表达式
def parse_log_file(log_content, normalization_point):
"""
解析DDR校准日志文件,提取关键数据
参数:
log_content - 日志文件的内容(字符串)
normalization_point - 归一化点(十六进制整数)
数据结构说明:
data = {
vref: {
dq_index: {
'read': (min, max, window),
'write': (min, max, window)
}
}
}
raw_data = {
vref: {
dq_index: {
'read': {'min': min_val, 'max': max_val},
'write': {'min': min_val, 'max': max_val}
}
}
}
"""
# 初始化数据结构
data = {} # 主数据结构,存储解析后的数据
current_vref = None # 当前处理的vref值
pending_data = {} # 临时存储待处理的数据(字典)
current_offset = None # 当前偏移量
raw_data = {} # 存储原始数据(偏移前)
# 按行处理日志内容
# 字符串方法:split('\n') 按换行符分割字符串
for line in log_content.split('\n'):
# 字符串方法:strip() 移除首尾空白字符
line = line.strip()
# 空行检查
if not line:
continue # 跳过空行
##########################################################
# 正则表达式1:匹配VREF行
# 模式:r'.*vref:\s*0x([0-9a-fA-F]+)'
# 目标示例: "Setting vref: 0x1A3"
#
# 详细分解:
# .* - 匹配任意字符(除换行符外)0次或多次(贪婪匹配)
# vref: - 匹配字面字符串 "vref:"
# \s* - 匹配0个或多个空白字符(空格、制表符等)
# 0x - 匹配字面字符串 "0x"
# ( - 开始捕获组
# [0-9a-fA-F] - 字符类,匹配十六进制字符(0-9, a-f, A-F)
# + - 匹配前面的元素1次或多次
# ) - 结束捕获组
#
# 匹配过程:
# "Setting vref: 0x1A3" -> 匹配整个字符串
# 捕获组1: "1A3"
##########################################################
vref_match = re.match(r'.*vref:\s*0x([0-9a-fA-F]+)', line)
if vref_match:
# 获取捕获组内容
hex_str = vref_match.group(1)
# int()函数:字符串转整数
# 参数1:字符串
# 参数2:基数(16表示十六进制)
current_vref = int(hex_str, 16)
# 字典初始化
data[current_vref] = {}
# 嵌套字典初始化
raw_data[current_vref] = {}
pending_data = {} # 重置临时数据
current_offset = None
continue # 跳过后续处理
##########################################################
# 正则表达式2:匹配偏移量行
# 模式:r'.*0x38c:\s*(?:0x)?([0-9a-fA-F]+)'
# 目标示例: "Offset 0x38c: 0x25" 或 "0x38c: 25"
#
# 详细分解:
# .* - 匹配任意字符0次或多次
# 0x38c: - 匹配字面字符串 "0x38c:"
# \s* - 匹配0个或多个空白字符
# (?: - 开始非捕获组
# 0x - 匹配字面字符串 "0x"
# )? - 非捕获组出现0次或1次
# ( - 开始捕获组
# [0-9a-fA-F]+ - 匹配1个或多个十六进制字符
# ) - 结束捕获组
#
# 特殊说明:
# (?:...) 是非捕获组,匹配但不捕获内容
# 用于处理可选前缀而不创建额外捕获组
##########################################################
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
##########################################################
# 正则表达式3:匹配最大值点
# 模式:r'.*dq(\d+)\s+max_(\w+)_point\s*:\s*(-?\d+)'
# 目标示例: "dq5 max_read_point: 120"
#
# 详细分解:
# .* - 匹配任意字符0次或多次
# dq - 匹配字面字符串 "dq"
# (\d+) - 捕获组1:匹配1个或多个数字(DQ索引)
# \s+ - 匹配1个或多个空白字符
# max_ - 匹配字面字符串 "max_"
# (\w+) - 捕获组2:匹配1个或多个单词字符(方向:read/write)
# _point - 匹配字面字符串 "point"
# \s*:\s* - 匹配冒号前后任意空白
# (-?\d+) - 捕获组3:匹配可选负号后跟1个或多个数字
#
# 捕获组说明:
# 组1: DQ索引 (如 "5")
# 组2: 方向 (如 "read")
# 组3: 最大值 (如 "120")
##########################################################
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)) # 转换为整数
# 字典操作:检查键是否存在并初始化
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
# 存储到临时数据字典
key = (dq_index, direction) # 元组作为字典键
pending_data[key] = {'max': max_val} # 字典值也是字典
continue
##########################################################
# 正则表达式4:匹配最小值点(结构类似最大值匹配)
# 模式:r'.*dq(\d+)\s+min_(\w+)_point\s*:\s*(-?\d+)'
# 目标示例: "dq5 min_read_point: 32"
##########################################################
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
##########################################################
# 正则表达式5:匹配窗口行
# 模式:r'.*dq(\d+)\s+(\w+)_windows\s*:\s*(-?\d+)'
# 目标示例: "dq5 read_windows: 88"
#
# 详细分解:
# .* - 匹配任意字符0次或多次
# dq - 匹配字面字符串 "dq"
# (\d+) - 捕获组1:匹配1个或多个数字(DQ索引)
# \s+ - 匹配1个或多个空白字符
# (\w+) - 捕获组2:匹配1个或多个单词字符(方向)
# _windows - 匹配字面字符串 "_windows"
# \s*:\s* - 匹配冒号前后任意空白
# (-?\d+) - 捕获组3:匹配可选负号后跟1个或多个数字
##########################################################
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']
# 确定最大延迟值(读0x7F=127,写0xFF=255)
max_delay = 0x7F if direction == 'read' else 0xFF
# 确保值在有效范围内
min_val = max(0, min_val) # 最小值不小于0
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',字符串)
算法说明:
1. 遍历所有VREF值
2. 计算实际电压 = (vref / 0x1FF) * avddq
3. 获取当前DQ和方向的数据
4. 计算窗口大小(UI单位)
5. 确定最大眼宽(所有窗口中的最大值)
6. 计算最大眼高(连续电压范围的最大高度)
7. 计算眼图中心点(最大眼高和最大眼宽的交点)
"""
# 初始化变量
max_eye_width = 0.0
max_eye_height = 0.0
# 存储每个电压对应的窗口大小(用于计算眼高)
voltage_windows = defaultdict(float)
# 存储每个电压对应的延迟范围(用于计算眼宽)
voltage_delay_ranges = {}
# 存储每个延迟位置对应的电压范围(用于计算眼高)
delay_voltage_ranges = defaultdict(list)
# 确定最大延迟值(读0x7F=127,写0xFF=255)
max_delay = 0x7F if direction == 'read' else 0xFF
# 确定UI范围(读2UI,写4UI)
ui_range = 2 if direction == 'read' else 4
# 遍历所有VREF值
for vref, dq_data in data.items():
# 计算实际电压
# 0x1FF = 511(9位最大值)
voltage = (vref / 0x1FF) * avddq
# 字典安全访问:get()方法
# 避免KeyError异常
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
# 计算窗口大小(UI单位)
window_ui = (window_size / max_delay) * ui_range
# 更新最大眼宽
if window_ui > max_eye_width:
max_eye_width = window_ui
# 存储电压-窗口映射
voltage_windows[voltage] = window_ui
# 存储电压-延迟范围映射(用于计算眼宽)
voltage_delay_ranges[voltage] = (min_point, max_point)
# 存储延迟位置对应的电压范围(用于计算眼高)
for delay in range(min_point, max_point + 1):
delay_voltage_ranges[delay].append(voltage)
# 计算最大眼高(连续电压范围)
# 步骤:
# 1. 对电压排序
# 2. 遍历排序后的电压
# 3. 计算连续有效窗口的电压范围
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
# 计算最大眼宽对应的延迟位置(新增)
# 找到具有最大窗口的电压点
best_voltage = None
max_window_ui = 0
for voltage, window_ui in voltage_windows.items():
if window_ui > max_window_ui:
max_window_ui = window_ui
best_voltage = voltage
# 计算最大眼高对应的延迟位置(新增)
# 找到具有最宽电压范围的延迟位置
best_delay = None
max_voltage_range = 0
for delay, voltages in delay_voltage_ranges.items():
if voltages:
min_v = min(voltages)
max_v = max(voltages)
voltage_range = max_v - min_v
if voltage_range > max_voltage_range:
max_voltage_range = voltage_range
best_delay = delay
# 计算眼图中心点
center_ui = None
center_voltage = None
if best_delay is not None and best_voltage is not None:
# 将延迟转换为UI单位
center_ui = (best_delay / max_delay) * ui_range
center_voltage = best_voltage
# 返回计算结果
return max_eye_width, max_eye_height, center_ui, center_voltage, best_delay, best_voltage
# 眼图数据生成函数 - 详细解释算法
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',字符串)
算法说明:
1. 遍历所有VREF值
2. 计算实际电压 = (vref / 0x1FF) * avddq
3. 遍历所有可能的延迟值
4. 将延迟值转换为UI单位
5. 根据数据有效性标记为通过点或失败点
"""
pass_points = [] # 存储通过点(绿色)
fail_points = [] # 存储失败点(红色)
# 确定最大延迟值(读0x7F=127,写0xFF=255)
max_delay = 0x7F if direction == 'read' else 0xFF
# 确定UI范围(读2UI,写4UI)
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)
# 遍历所有可能的延迟值
for delay in range(0, max_delay + 1):
# 将延迟值转换为UI单位
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 - 原始日志文件路径
文件操作详解:
1. 创建输出目录:os.makedirs()
2. 构建文件路径:os.path.join()
3. 写入文件:open()配合write()
4. 格式化输出:f-string
"""
# 获取当前时间戳
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
# 获取日志文件名(不含扩展名)
log_filename = os.path.basename(log_path)
if '.' in log_filename:
# rsplit() 从右边分割字符串,maxsplit=1表示只分割一次
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() 创建目录(如果不存在)
# exist_ok=True 表示目录已存在时不报错
##########################################################
os.makedirs(output_dir, exist_ok=True)
# 创建输出文件路径
output_file = os.path.join(output_dir, f"{log_name}_raw_data.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") # :03X表示3位十六进制大写,不足补0
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")
# 计算并写入窗口大小
window_size = rd['max'] - rd['min'] + 1
f.write(f" 窗口大小: {window_size}\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")
# 计算并写入窗口大小
window_size = wr['max'] - wr['min'] + 1
f.write(f" 窗口大小: {window_size}\n")
f.write("\n") # DQ间空行
f.write("\n") # VREF间空行
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 - 归一化点
主要修改:
1. 分组统计信息移到图顶部显示
2. 顶部显示两行分组统计信息
"""
# 设置中文字体支持
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'Microsoft YaHei', 'WenQuanYi Micro Hei']
plt.rcParams['axes.unicode_minus'] = False
# 计算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, 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" (Normalized to 0x{normalization_point:X}, Raw Data: {os.path.basename(raw_data_file)})"
fig_write.suptitle(f'DDR Write Eye Diagram (Data Rate: {data_rate} Mbps, UI: {ui_ps:.2f} ps){norm_title}', fontsize=18)
fig_read.suptitle(f'DDR Read Eye Diagram (Data Rate: {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='Pass', markerfacecolor='green', markersize=10),
Line2D([0], [0], marker='o', color='w', label='Fail', markerfacecolor='red', markersize=10)
]
# 存储分组中心点数据(读眼图)
group1_center_vrefs = [] # DQ0-DQ7
group1_center_delays = [] # DQ0-DQ7
group2_center_vrefs = [] # DQ8-DQ15
group2_center_delays = [] # DQ8-DQ15
# 遍历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, read_center_ui, read_center_voltage, read_center_delay, read_center_vref = 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"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)
)
# 设置写眼图轴属性
axes_write[dq_index].set_title(f'DQ{dq_index} Write Eye', fontsize=12)
axes_write[dq_index].set_xlabel('Delay (UI)', fontsize=10)
axes_write[dq_index].set_ylabel('Voltage (V)', fontsize=10)
axes_write[dq_index].set_xlim(0, 4) # 写眼图0-4UI
axes_write[dq_index].set_ylim(0, avddq)
axes_write[dq_index].grid(True, linestyle='--', alpha=0.6)
axes_write[dq_index].legend(handles=legend_elements, loc='upper right', fontsize=9)
axes_write[dq_index].tick_params(axis='both', which='major', labelsize=9)
# ================= 读眼图处理 =================
# 绘制读眼图
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)
)
# 设置读眼图轴属性
axes_read[dq_index].set_title(f'DQ{dq_index} Read Eye', fontsize=12)
axes_read[dq_index].set_xlabel('Delay (UI)', fontsize=10)
axes_read[dq_index].set_ylabel('Voltage (V)', fontsize=10)
axes_read[dq_index].set_xlim(0, 2) # 读眼图0-2UI
axes_read[dq_index].set_ylim(0, avddq)
axes_read[dq_index].grid(True, linestyle='--', alpha=0.6)
axes_read[dq_index].legend(handles=legend_elements, loc='upper right', fontsize=9)
axes_read[dq_index].tick_params(axis='both', which='major', labelsize=9)
# 绘制读眼图中心点和辅助线
if read_center_ui is not None and read_center_voltage is not None:
# 绘制中心点
axes_read[dq_index].scatter(
[read_center_ui], [read_center_voltage],
s=100, marker='*', c='yellow', edgecolors='black', zorder=10
)
# 计算原始Vref值
original_vref = int(round((read_center_voltage * 0x1FF) / avddq))
# 添加中心点标注
center_text = f"Center: ({read_center_ui:.3f} UI, {read_center_voltage:.3f} V)\n" \
f"Raw: Vref=0x{original_vref:X}, Delay={read_center_delay}"
axes_read[dq_index].annotate(
center_text,
xy=(read_center_ui, read_center_voltage),
xytext=(read_center_ui + 0.1, read_center_voltage + 0.05),
arrowprops=dict(facecolor='black', shrink=0.05),
fontsize=8,
ha='left'
)
# 绘制辅助线:最大眼宽竖线(蓝色虚线)
axes_read[dq_index].axvline(
x=read_center_ui,
color='blue',
linestyle='--',
alpha=0.7,
label=f'Max Width Line'
)
# 绘制辅助线:最大眼高横线(蓝色虚线)
axes_read[dq_index].axhline(
y=read_center_voltage,
color='blue',
linestyle='--',
alpha=0.7,
label=f'Max Height Line'
)
# 添加辅助线图例
line_legend = [
Line2D([0], [0], color='blue', linestyle='--', label='Max Width Line'),
Line2D([0], [0], color='blue', linestyle='--', label='Max Height Line')
]
axes_read[dq_index].legend(handles=legend_elements + line_legend, loc='upper right', fontsize=9)
# 根据DQ索引分组存储中心点数据
if dq_index < 8:
group1_center_vrefs.append(original_vref)
group1_center_delays.append(read_center_delay)
else:
group2_center_vrefs.append(original_vref)
group2_center_delays.append(read_center_delay)
# 计算分组统计值
def calculate_group_stats(vrefs, delays):
"""计算一组中心点的统计值"""
if not vrefs:
return None, None, None, None
# 计算Vref平均值和中位数
avg_vref = sum(vrefs) / len(vrefs)
sorted_vrefs = sorted(vrefs)
mid = len(sorted_vrefs) // 2
if len(sorted_vrefs) % 2 == 0:
median_vref = (sorted_vrefs[mid-1] + sorted_vrefs[mid]) / 2
else:
median_vref = sorted_vrefs[mid]
# 计算延迟平均值和中位数
avg_delay = sum(delays) / len(delays)
sorted_delays = sorted(delays)
mid = len(sorted_delays) // 2
if len(sorted_delays) % 2 == 0:
median_delay = (sorted_delays[mid-1] + sorted_delays[mid]) / 2
else:
median_delay = sorted_delays[mid]
return avg_vref, median_vref, avg_delay, median_delay
# 计算第一组(DQ0-DQ7)的统计值
stats1 = calculate_group_stats(group1_center_vrefs, group1_center_delays)
# 计算第二组(DQ8-DQ15)的统计值
stats2 = calculate_group_stats(group2_center_vrefs, group2_center_delays)
# 在图像顶部添加分组汇总信息(两行)
if stats1[0] is not None:
avg_vref1, median_vref1, avg_delay1, median_delay1 = stats1
# 第一组文本(DQ0-DQ7)
summary_text1 = f"DQ0-DQ7 Center Points: " \
f"Avg Vref=0x{int(round(avg_vref1)):X}, " \
f"Median Vref=0x{int(median_vref1):X}, " \
f"Avg Delay={avg_delay1:.1f}, " \
f"Median Delay={median_delay1:.1f}"
# 位置:0.95(顶部)
fig_read.text(0.5, 0.95, summary_text1, ha='center', fontsize=12,
bbox=dict(facecolor='white', alpha=0.8))
if stats2[0] is not None:
avg_vref2, median_vref2, avg_delay2, median_delay2 = stats2
# 第二组文本(DQ8-DQ15)
summary_text2 = f"DQ8-DQ15 Center Points: " \
f"Avg Vref=0x{int(round(avg_vref2)):X}, " \
f"Median Vref=0x{int(median_vref2):X}, " \
f"Avg Delay={avg_delay2:.1f}, " \
f"Median Delay={median_delay2:.1f}"
# 位置:0.92(在上一行下方)
fig_read.text(0.5, 0.92, summary_text2, ha='center', fontsize=12,
bbox=dict(facecolor='white', alpha=0.8))
# 调整布局
fig_write.tight_layout(rect=[0, 0, 1, 0.96])
# 为读眼图顶部留出空间
fig_read.tight_layout(rect=[0, 0, 1, 0.90])
# 文件路径处理(添加时间戳)
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")
# 构建读眼图文件名(包含两组VREF平均值)
group1_avg_vref = int(round(stats1[0])) if stats1[0] is not None else 0
group2_avg_vref = int(round(stats2[0])) if stats2[0] is not None else 0
read_filename = os.path.join(
log_dir,
f"{log_name}_ddr_read_eye_{timestamp}_G1Vref_0x{group1_avg_vref:X}_G2Vref_0x{group2_avg_vref:X}.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():
"""
主函数,程序入口点
功能:
- 获取用户输入
- 读取日志文件
- 解析数据
- 生成眼图
- 导出结果
用户交互详解:
1. 使用input()获取用户输入
2. 使用循环处理无效输入
3. 使用try-except捕获异常
"""
# 打印欢迎信息
print("=" * 50)
print("DDR眼图生成器(带原始数据导出)")
print("=" * 50)
# 用户输入DataRate(带异常处理)
while True:
try:
data_rate = float(input("请输入DataRate (Mbps/Pin): "))
break
except ValueError:
print("错误: 请输入有效的数字")
# 用户输入AVDDQ电压(带异常处理)
while True:
try:
avddq = float(input("请输入AVDDQ电压值 (V): "))
break
except ValueError:
print("错误: 请输入有效的数字")
# 归一化点输入处理(带错误检查)
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
# 字符串转整数(16进制)
normalization_point = int(hex_str, 16)
break
except ValueError:
print(f"错误: '{norm_input}' 不是有效的十六进制数,请重新输入")
# 日志文件路径输入(带文件存在检查)
while True:
log_path = input("请输入日志文件路径: ").strip()
# 检查文件是否存在
# os.path.exists() 判断路径是否存在
if not os.path.exists(log_path):
print(f"错误: 文件 '{log_path}' 不存在,请重新输入")
else:
# 获取绝对路径
log_path = os.path.abspath(log_path)
break
# 读取文件内容
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}")
# 异常对象:e.args 获取异常参数
print(f"错误详情: {e.args}")
# Python特殊检查 - 模块执行控制
if __name__ == "__main__":
"""
__name__ 是Python的内置变量
当脚本直接运行时,__name__ 等于 "__main__"
当脚本被导入时,__name__ 等于模块名
这种结构允许:
1. 直接运行脚本时执行测试代码
2. 作为模块导入时不执行测试代码
"""
main() # 调用主函数
最新发布