#!/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:

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:

" 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") }