#!/usr/bin/env ruby
#
# Copyright © 2014 Siarhei Siamashka
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
raise "Please upgrade ruby to at least version 1.9" if RUBY_VERSION =~ /^1\.8/
require_relative 'tpr3-common.rb'
if not ARGV[0] or not File.directory?(ARGV[0]) then
printf("Usage: #{$PROGRAM_NAME} [results_directory] > report.html\n")
printf("\n")
printf("Where:\n")
printf(" results_directory - is the directory populated by\n")
printf(" the scan-for-best-tpr3.rb script\n")
printf(" report.html - is the output of this script\n")
exit(1)
end
def parse_subtest_dir(dir, adj)
mfxdly_list = [0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38]
sdphase_list = [36, 54, 72, 90, 108, 126]
tpr3_status_logs = {}
memtester_logs = {}
tpr3_print_name = {}
score_per_sdphase_label = {}
stability_score = 0
memtester_subtest_stats = {}
lane_fail_stats = [0] * 4
bit_fail_stats = [0] * 32
lane_fail_list = [{}, {}, {}, {}]
tpr3_generator(adj).each {|tpr3_info|
tpr3 = tpr3_info[:tpr3]
tpr3_status_logs[tpr3] = (read_file(dir, sprintf(
"tpr3_0x%08X", tpr3)) or "")
memtester_logs[tpr3] = (read_file(dir, sprintf(
"tpr3_0x%08X.memtester", tpr3)) or "")
tpr3_status_log = tpr3_status_logs[tpr3]
tpr3_print_name[tpr3] = sprintf("0x%06X", tpr3)
if tpr3_status_log =~ /memtester success rate: (\d+)\/(\d+)/ then
stability_score += $1.to_i
tmp = tpr3_info[:sdphase_deg]
score_per_sdphase_label[tmp] = (score_per_sdphase_label[tmp] or 0) + $1.to_i
end
memtester_log = memtester_logs[tpr3]
if memtester_log =~ /FAILURE: 0x([0-9a-f]{8}) != 0x([0-9a-f]{8}).*?\((.*)\)/ then
val1 = $1.to_i(16)
val2 = $2.to_i(16)
memtester_subtest_stats[$3] = (memtester_subtest_stats[$3] or 0) + 1
print_name = sprintf("0x%02X", tpr3 >> 16)
3.downto(0) {|lane|
mask = 0xFF << (lane * 8)
if (val1 & mask) != (val2 & mask) then
lane_fail_list[lane][tpr3] = true
lane_fail_stats[lane] += 1
print_name += sprintf("%X", (tpr3 >> (lane * 4)) & 0xF)
else
print_name += sprintf("%X", (tpr3 >> (lane * 4)) & 0xF)
end
}
tpr3_print_name[tpr3] = print_name
0.upto(31) {|bit|
mask = 1 << bit
if (val1 & mask) != (val2 & mask) then
bit_fail_stats[bit] += 1
end
}
end
}
def get_nice_color(dir, tpr3, data, memtester_log)
workdata = (read_file(dir, "_current_work_item.txt") or "")
if workdata.include?(sprintf("%08X", tpr3)) then
return "#C0C0C0" # gray marker - work is still in progress
end
tpr3_hardened_log = read_file(dir, sprintf("tpr3_0x%08X.hardening", tpr3))
if tpr3_hardened_log then
if tpr3_hardened_log == "FINISHED, memtester success rate: 100/100" then
return "#008000"
else
return "#FFFF00"
end
end
passed_tests = 0
total_tests = 1
if data =~ /memtester success rate: (\d+)\/(\d+)/ then
passed_tests = $1.to_i
total_tests = $2.to_i
end
if passed_tests >= 10 then
color = "#40C040"
else
def lerp(a, b, ratio) return (a + (b - a) * ratio).to_i end
red_part = 0xFF
if memtester_log == "" then
blue_part = lerp(0x40, red_part, Math.sqrt(passed_tests + total_tests) / 4.5)
green_part = 0
else
green_part = lerp(0, red_part, Math.sqrt(passed_tests + total_tests) / 4.5)
blue_part = 0
end
color = sprintf("#%02X%02X%02X", red_part, green_part, blue_part)
end
return color
end
html_report = sprintf("\n")
html_report << sprintf("mfxdly")
sdphase_list.each {|sdphase|
# next if not gen_tpr3(0, sdphase, adj)
html_report << sprintf(" | phase=%d", sdphase)
}
mfxdly_list.each {|mfxdly|
html_report << sprintf(" |
---|
0x%02X", mfxdly)
sdphase_list.each {|sdphase|
tpr3 = gen_tpr3(mfxdly, sdphase, adj)
if not tpr3 then
html_report << " | "
next
end
data = tpr3_status_logs[tpr3]
if data == "" then
html_report << " | "
next
end
memtester_log = memtester_logs[tpr3]
color = get_nice_color(dir, tpr3, data, memtester_log)
html_report << sprintf(" | %s", color, data + "\n" + memtester_log, tpr3_print_name[tpr3])
}
}
html_report << sprintf(" |
---|
\n")
return {
:html_report => html_report,
:memtester_subtest_stats => memtester_subtest_stats,
:lane_fail_stats => lane_fail_stats,
:bit_fail_stats => bit_fail_stats,
:stability_score => stability_score,
:lane_fail_list => lane_fail_list,
:score_per_sdphase_label => score_per_sdphase_label,
}
end
print "
This is a DRAM tuning/overclocking stability report for various Allwinner
A10/A13/A20 based devices.It can be automatically generated by the tools from
https://github.com/ssvb/a10-meminfo.
Here we primarily focus on finding optimal
dram_tpr3
values, tuned individually for every sunxi device. Currently these values need to be hardcoded
into the sources of the u-boot-sunxi bootloader.
The dram_tpr3 parameter is just a hexadecimal number with the following bit fields:
- bits [22:20] - MFWDLY of the command lane
- bits [18:16] - MFBDLY of the command lane
- bits [15:12] - SDPHASE of the byte lane 3
- bits [11:8] - SDPHASE of the byte lane 2
- bits [7:4] - SDPHASE of the byte lane 1
- bits [3:0] - SDPHASE of the byte lane 0
The RK30XX manual
can be checked for more details about the MFWDLY, MFBDLY and SDPHASE bit fields. The Rockchip 30XX family
of SoCs is apparently using the same DRAM controller IP.
Results interpretation:
- The shades of RED/PURPLE/MAGENTA - hardware deadlocks, this usually indicates insufficient dcdc3 voltage.
- The shades of RED/ORANGE/YELLOW - data corruption, this may be attributed to other reasons.
Including, but not limited to phase misalignment between different byte lanes. See the Figure 6 from
Altera
- Utilizing Leveling Techniques in DDR3 SDRAM Memory Interfaces as a reasonaby good illustration.
- GREEN - no problems detected during just a few minutes of running lima-memtester.
These dram_tpr3 values still need thorough verification by a much longer run of lima-memtester (8-10 hours
is reasonable) and other stress tests.
"
dirlist = []
Dir.glob(File.join(ARGV[0], "*")).each {|f|
next if not File.directory?(f)
dirlist.push(f)
}
def strip_html_tags(text)
return text.gsub(/\<[\/]?a.*?\>/, "")
end
# Group results from the same device/configuration/description
tmp = {}
dirlist.sort.each {|f|
if File.basename(f) =~ /(.*MHz\-\d+\.\d+V-[0-9A-F]{8})/ then
id = $1
id = (read_file(f, "_description.txt") or "") + " : " + id
tmp[id] = [] if not tmp.has_key?(id)
tmp[id].push(f)
end
}
dirlist = []
tmp.to_a.sort {|x,y| strip_html_tags(x[0]) <=> strip_html_tags(y[0]) }.each {|x|
dirlist.push(x[1])
}
dirlist.each {|a|
a10_meminfo = read_file(a[0], "_a10_meminfo.txt")
if not a10_meminfo =~ /dram_bus_width\s*=\s*(\d+)/ then
raise("Error: dram_bus_width is not found in the a10-meminfo log\n")
end
$number_of_lanes = $1.to_i / 8
printf("%s
\n",
(read_file(a[0], "_description.txt") or "Unknown device"))
printf("\n")
a.each {|f|
jobs_finder_generator(f, {:sorted => true}).each {|job_info|
adj = job_info[:lane_phase_adjustments]
printf("")
printf("\n")
printf("%s", a10_meminfo.gsub("\n", " "))
printf(" | ")
subtest_results = parse_subtest_dir(f, adj)
printf(" | %s", subtest_results[:html_report])
printf(" | ")
printf("Lane phase adjustments: [%s]
", adj.reverse.join(", "))
memtester_subtest_stats = subtest_results[:memtester_subtest_stats].to_a
memtester_subtest_stats.sort! {|x, y| y[1] <=> x[1] }
printf("Error statistics from memtester: [%s] ",
memtester_subtest_stats.map {|a|
sprintf("%s=%d", a[0], a[1])
}.join(", "))
column_scores = subtest_results[:score_per_sdphase_label].sort.map {|a| a[1] }
printf(" ")
1.upto(column_scores.size) {|s|
score, i = column_scores.each_index.map {|i|
tmp = column_scores.slice(i, s)
tmp.size == s ? (tmp.inject 0, :+) : 0
}.each_with_index.max
printf("Best number of successful memtester runs, which span over %d columns (%d-%d): %d ",
s, i, i + s - 1, score)
}
printf(" Errors per lane: [%s]. ",
subtest_results[:lane_fail_stats].reverse.join(", "))
lane_fail_stats = subtest_results[:lane_fail_stats]
lane_fail_list = subtest_results[:lane_fail_list]
worst_lane_id = lane_fail_stats.each_with_index.max[1]
worst_lane_fail_list = lane_fail_list[worst_lane_id]
printf("Lane %d is the most noisy/problematic.
", worst_lane_id)
something_is_still_bad = false
lane_fail_list.each_with_index {|fail_list, lane_id|
matched_cnt = 0
total_cnt = 0
fail_list.each {|tpr3, dummy|
matched_cnt += 1 if worst_lane_fail_list.has_key?(tpr3)
total_cnt += 1
}
if total_cnt > 0 and lane_id != worst_lane_id then
something_is_still_bad = true if matched_cnt != total_cnt
if matched_cnt > 0 then
printf("Errors from the lane %d are %.1f%% eclipsed by the worst lane %d. ",
lane_id, (matched_cnt.to_f / total_cnt.to_f) * 100,
worst_lane_id)
else
printf("Errors from the lane %d are not intersecting with the errors from the worst line %d. ",
lane_id, worst_lane_id)
end
end
}
if something_is_still_bad then
adj1 = adj.each_with_index.map {|x, i| i == worst_lane_id ? x : x - 1}
adj2 = adj.each_with_index.map {|x, i| i == worst_lane_id ? x : x + 1}
# printf(" Need to try lane phase adjustments [%s] and [%s] for further analysis. ",
# adj1.reverse.join(", "), adj2.reverse.join(", "))
end
}
}
printf(" |
")
printf("\n")
}