|
|
@ -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) |
|
|
|