Decode the memory module size and timings of DDR4 memory. --- eeprom/decode-dimms | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 176 insertions(+), 1 deletion(-) --- i2c-tools.orig/eeprom/decode-dimms 2017-11-17 11:21:44.196398606 +0100 +++ i2c-tools/eeprom/decode-dimms 2017-11-17 11:24:29.028405403 +0100 @@ -1702,15 +1702,190 @@ sub decode_ddr3_sdram($) } -# Parameter: EEPROM bytes 0-127 (using 1-1) +# Return combined time in ns +sub ddr4_mtb_ftb($$$$) +{ + my ($byte1, $byte2, $mtb, $ftb) = @_; + + # byte1 is unsigned in ps, but byte2 is signed in ps + $byte2 -= 0x100 if $byte2 & 0x80; + + return ($byte1 * $mtb + $byte2 * $ftb) / 1000; +} + +# Rounded per DDR4 specifications +sub ddr4_core_timings($$$$$) +{ + my ($cas, $ctime, $trcd, $trp, $tras) = @_; + + return $cas . "-" . ceil($trcd/$ctime - 0.025) . + "-" . ceil($trp/$ctime - 0.025) . + "-" . ceil($tras/$ctime - 0.025); +} + +use constant DDR4_UNBUFFERED => 1; +use constant DDR4_REGISTERED => 2; +use constant DDR4_LOAD_REDUCED => 4; + +# Parameter: EEPROM bytes 0-383 (using 1-125) sub decode_ddr4_sdram($) { my $bytes = shift; + my ($ctime, $ctime_max); + my ($ftb, $mtb); + my $ii; + + my @module_types = ( + { type => "Extended type", }, + { type => "RDIMM", family => DDR4_REGISTERED }, + { type => "UDIMM", family => DDR4_UNBUFFERED }, + { type => "SO-DIMM", family => DDR4_UNBUFFERED }, + { type => "LRDIMM", family => DDR4_LOAD_REDUCED }, + { type => "Mini-RDIMM", family => DDR4_REGISTERED }, + { type => "Mini-UDIMM", family => DDR4_UNBUFFERED }, + { type => "Reserved (0x07)", }, + { type => "72b-SO-RDIMM", family => DDR4_REGISTERED }, + { type => "72b-SO-UDIMM", family => DDR4_UNBUFFERED }, + { type => "Reserved (0x0A)", }, + { type => "Reserved (0x0B)", }, + { type => "16b-SO-DIMM", family => DDR4_UNBUFFERED }, + { type => "32b-SO-DIMM", family => DDR4_UNBUFFERED }, + { type => "Reserved (0x0E)", }, + { type => "No base memory", }, + ); # SPD revision printl_cond($bytes->[1] != 0xff, "SPD Revision", ($bytes->[1] >> 4) . "." . ($bytes->[1] & 0xf)); + printl("Module Type", $module_types[$bytes->[3] & 0x0f]->{type}); + +# time bases + if (($bytes->[17] & 0x03) != 0x00 || ($bytes->[17] & 0xc0) != 0x00) { + print STDERR "Unknown time base values, can't decode\n"; + return; + } + $ftb = 1; # ps + $mtb = 125; # ps + +# speed + prints("Memory Characteristics"); + + $ctime = ddr4_mtb_ftb($bytes->[18], $bytes->[125], $mtb, $ftb); + $ctime_max = ddr4_mtb_ftb($bytes->[19], $bytes->[124], $mtb, $ftb); + + my $ddrclk = 2 * (1000 / $ctime); + my $tbits = 8 << ($bytes->[13] & 7); + my $pcclk = int ($ddrclk * $tbits / 8); + # Round down to comply with Jedec + $pcclk = $pcclk - ($pcclk % 100); + $ddrclk = int ($ddrclk); + printl("Maximum module speed", "$ddrclk MHz (PC4-${pcclk})"); + +# Size computation + my $sdram_width = 4 << ($bytes->[12] & 0x07); + my $ranks = (($bytes->[12] >> 3) & 0x07) + 1; + my $signal_loading = $bytes->[6] & 0x03; + my $die_count = (($bytes->[6] >> 4) & 0x07) + 1; + my $cap = (256 << ($bytes->[4] & 0x0f)) / 8; + $cap *= (8 << ($bytes->[13] & 0x07)) / $sdram_width; + $cap *= $ranks; + $cap *= $die_count if $signal_loading == 0x02; # 3DS + printl("Size", $cap . " MB"); + + printl("Banks x Rows x Columns x Bits", + join(' x ', (1 << ($bytes->[4] >> 6)) * (4 << (($bytes->[4] >> 4) & 0x03)), + ((($bytes->[5] >> 3) & 7) + 12), + ( ($bytes->[5] & 7) + 9), + (8 << ($bytes->[13] & 0x07)))); + + printl("SDRAM Device Width", "$sdram_width bits"); + printl("Ranks", $ranks); + printl_cond($ranks > 1, "Rank Mix", + $bytes->[12] & 0x40 ? "Asymmetrical" : "Symmetrical"); + printl_cond($bytes->[13] & 0x18, "Bus Width Extension", ($bytes->[13] & 0x18)." bits"); + + my $taa; + my $trcd; + my $trp; + my $tras; + + $taa = ddr4_mtb_ftb($bytes->[24], $bytes->[123], $mtb, $ftb); + $trcd = ddr4_mtb_ftb($bytes->[25], $bytes->[122], $mtb, $ftb); + $trp = ddr4_mtb_ftb($bytes->[26], $bytes->[121], $mtb, $ftb); + $tras = ((($bytes->[27] & 0x0f) << 8) + $bytes->[28]) * $mtb / 1000; + + printl("AA-RCD-RP-RAS (cycles)", + ddr4_core_timings(ceil($taa/$ctime - 0.025), $ctime, + $trcd, $trp, $tras)); + +# latencies + my %cas; + my $cas_sup = ($bytes->[23] << 24) + ($bytes->[22] << 16) + + ($bytes->[21] << 8) + $bytes->[20]; + my $base_cas = $bytes->[23] & 0x80 ? 23 : 7; + + for ($ii = 0; $ii < 30; $ii++) { + if ($cas_sup & (1 << $ii)) { + $cas{$base_cas + $ii}++; + } + } + printl("Supported CAS Latencies", cas_latencies(keys %cas)); + +# standard DDR4 speeds + prints("Timings at Standard Speeds"); + foreach my $ctime_at_speed (15/24, 15/22, 15/20, 15/18, 15/16, 15/14, 15/12) { + my $best_cas = 0; + + # Find min CAS latency at this speed + for ($ii = 29; $ii >= 0; $ii--) { + next unless ($cas_sup & (1 << $ii)); + if (ceil($taa/$ctime_at_speed - 0.025) <= $base_cas + $ii) { + $best_cas = $base_cas + $ii; + } + } + + printl_cond($best_cas && $ctime_at_speed >= $ctime + && $ctime_at_speed <= $ctime_max, + "AA-RCD-RP-RAS (cycles)" . as_ddr(4, $ctime_at_speed), + ddr4_core_timings($best_cas, $ctime_at_speed, + $trcd, $trp, $tras)); + } + +# more timing information + prints("Timing Parameters"); + + printl("Minimum Cycle Time (tCKmin)", tns3($ctime)); + printl("Maximum Cycle Time (tCKmax)", tns3($ctime_max)); + printl("Minimum CAS Latency Time (tAA)", tns3($taa)); + printl("Minimum RAS to CAS Delay (tRCD)", tns3($trcd)); + printl("Minimum Row Precharge Delay (tRP)", tns3($trp)); + printl("Minimum Active to Precharge Delay (tRAS)", tns3($tras)); + printl("Minimum Active to Auto-Refresh Delay (tRC)", + tns3(ddr4_mtb_ftb((($bytes->[27] & 0xf0) << 4) + $bytes->[29], + $bytes->[120], $mtb, $ftb))); + printl("Minimum Recovery Delay (tRFC1)", + tns3((($bytes->[31] << 8) + $bytes->[30]) * $mtb / 1000)); + printl("Minimum Recovery Delay (tRFC2)", + tns3((($bytes->[33] << 8) + $bytes->[32]) * $mtb / 1000)); + printl("Minimum Recovery Delay (tRFC4)", + tns3((($bytes->[35] << 8) + $bytes->[34]) * $mtb / 1000)); + printl("Minimum Four Activate Window Delay (tFAW)", + tns3(((($bytes->[36] & 0x0f) << 8) + $bytes->[37]) * $mtb / 1000)); + printl("Minimum Row Active to Row Active Delay (tRRD_S)", + tns3(ddr4_mtb_ftb($bytes->[38], $bytes->[119], $mtb, $ftb))); + printl("Minimum Row Active to Row Active Delay (tRRD_L)", + tns3(ddr4_mtb_ftb($bytes->[39], $bytes->[118], $mtb, $ftb))); + printl("Minimum CAS to CAS Delay (tCCD_L)", + tns3(ddr4_mtb_ftb($bytes->[40], $bytes->[117], $mtb, $ftb))); + + # Optional? + my $twr = ((($bytes->[41] & 0x0f) << 8) + $bytes->[42]) * $mtb / 1000; + printl_cond($twr, "Minimum Write Recovery Time (tWR)", tns3($twr)); + my $twtr = ((($bytes->[43] & 0x0f) << 8) + $bytes->[44]) * $mtb / 1000; + printl_cond($twtr, "Minimum Write to Read Time (tWTR_S)", tns3($twtr)); + $twtr = ((($bytes->[43] & 0xf0) << 4) + $bytes->[45]) * $mtb / 1000; + printl_cond($twtr, "Minimum Write to Read Time (tWTR_L)", tns3($twtr)); } # Parameter: EEPROM bytes 0-127 (using 4-5) -- Jean Delvare SUSE L3 Support