DDR4 SPD read/write adapter

DDR4 SPD plank arrived, and a used SODIMM just fell through the mail slot.

  • The wrong header was soldered
  • Still need to figure out the 9volt supply, probably a 9 volt battery.

SA1 is held high to ground A1 pin, then we can read out the 256 bytes of contents with [0b10100001 r:256].

2 Likes

Set page and address

DDR4 SPD chips typically have 2 pages of 256 bytes each.

  1. Send 0b0110+1100 to set page 0, requires 2 dummy bytes
  2. Send 0b1010+0000 then the address to read from (0x00)
  3. Send 0b1010+0001 then read 256 bytes
  4. Repeat to read page 1

I2C> [0b01101100 0b0 0b0] D:1 [0b10100000 0] D:1 [0b10100001 r:256]
Read page 0.

I2C> [0b01101110 0b0 0b0] D:1 [0b1010000 0] D:1 [0b10100001 r:256]
Read page 1.

Decode a bit

0x23 ACK 0x11 ACK 0x0C ACK 0x03 ACK 0x84 ACK 0x19 ACK 0x00 ACK 0x08 ACK

Let’s decode that first row. I’m using simtester reference and the JEDEC standard (free account required).

0x23 = 0b00100011
bits 6:4 = 010 = 512 bytes total
bits 3:0 = 0011 = 384 bits actually used

0x11 = version.revision = 1.1

0x0c = DDR4 SDRAM device type

0x03 = SO-DIMM module type

0x84 = 0b10000100
bits 7:6 = 4 bank groups
bits 5:4 = 4 banks address bits
bits 3:0 = 4Gb capacity per die

0x19 = 0b00011001
bits 5:3 = 15 row address bits
bits 2:0 = 10 column address bits

0x00 = single die

0x08 = 0b00001000
bits 5:4 = 8192*tREFI max active window
bits 3:0 = unlimited maximum active count

2 Likes

Page 1 info

    0x01 ACK 0x98 ACK 0x05 ACK 0x18 ACK 0x38 ACK 0x84 ACK 0x24 ACK 0xA0 ACK
    0x92 ACK 0x41 ACK 0x43 ACK 0x52 ACK 0x32 ACK 0x34 ACK 0x44 ACK 0x34 ACK
    0x53 ACK 0x37 ACK 0x53 ACK 0x38 ACK 0x4D ACK 0x42 ACK 0x2D ACK 0x34 ACK
    0x20 ACK 0x20 ACK 0x20 ACK 0x20 ACK 0x20 ACK 0x00 ACK 0x80 ACK 0x2C ACK
    0x42 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK
    0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x88 ACK
    0x07 ACK 0x38 ACK 0x31 ACK 0x35 ACK 0x33 ACK 0x31 ACK 0x37 ACK 0x33 ACK
    0x00 ACK 0x00 ACK 0x01 ACK 0x01 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK

Starting at byte 320 (page 1) we find some module info.

0x9801 = module JEDEC ID = kingston

0x05 = manuf. location

0x18 0x38 = manufacturing year/week (matches label)

0x84 0x24 0xA0 0x92 = serial number (no match)

0x41 0x43 0x52 0x32 0x34 0x44 0x34 0x53 0x37 0x53 0x38 0x4D 0x42 0x2D 0x34 0x20 0x20 0x20 0x20 0x20 = Module serial number (I bet this is ASCII)

Yes: ACR24D4S7S8MB-4 (matches label)

0x00 = revision 0
0x2c80 = DRAM chip manuf. ID = Micron
0x42 = DRAM stepping

0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK
0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x88 ACK
0x07 ACK 0x38 ACK 0x31 ACK 0x35 ACK 0x33 ACK 0x31 ACK 0x37 ACK 0x33 ACK
0x00 ACK 0x00 ACK 0x01 ACK 0x01 ACK 0x00 ACK 0x00 ACK 0x00 ACK 0x00 ACK

Now several rows of manufacturer specific data.

1 Like

There are four protected ranges in the EEPROM. If protection is enabled the chip with respond to the RPSx command with NACK, if not then ACK.

The ACK/NAK happens after the address. Two additional dummy bytes are required.

I2C> [0b01100011 0b0 0b0] [0b01101001 0b0 0b0] [0b01101011 0b0 0b0] [0b01100001 0b0 0b0]

I2C START
TX: 0b01100011 NACK 0b00000000 NACK 0b00000000 NACK
I2C STOP
I2C START
TX: 0b01101001 NACK 0b00000000 NACK 0b00000000 NACK
I2C STOP
I2C START
TX: 0b01101011 ACK 0b00000000 NACK 0b00000000 NACK
I2C STOP
I2C START
TX: 0b01100001 ACK 0b00000000 NACK 0b00000000 NACK
I2C STOP
I2C>

It appears blocks 0 and 1 are protected, while 2 and 3 are unprotected. That seems pretty normal.

The next step should probably be to make a full backup of the SPD :slight_smile: What I actually want to do is hook up the 9volt supply and change the protection bits. Backup first though, backup first :slight_smile:

3 Likes

Used the generic i2c dump command to backup the DDR4 SPD to files. Noticed that i2c did not actually have save to file enabled, so I’ll push that shortly.

[0b01101100 0b0 0b0]
i2c dump -a 0x50 -w 1 -r 0x00 -b 256
[0b01101110 0b0 0b0]
i2c dump -a 0x50 -w 1 -r 0x00 -b 256

Command to dump to hex viewer.

DDR4.zip (497 Bytes)

Here’s the contents if you’d like to have a look, perhaps with this editor GUI for ddr4.

Now that we have a backup, let’s do the fun bits with the 9 volt programming voltage (lock and unlock block protection).

2 Likes
I2C> [0b01101011 0b0 0b0]

I2C START
TX: 0b01101011 ACK 0b00000000 NACK 0b00000000 NACK

Check the protected status of block 2. ACK = not protected.

I2C> [ 0b01101010 0b 0b ]

I2C START
TX: 0b01101010 NACK 0b00000000 NACK 0b00000000 NACK
I2C STOP

Try to enable protection on block two, without 9 volts on SA0. NACK = unsuccessful.

I2C> [ 0b01101010 0b 0b ]

I2C START
TX: 0b01101010 ACK 0b00000000 ACK 0b00000000 ACK
I2C STOP

Enable 9 volts on SA0 pin, then try to protect the block again. No blue smoke = success! ACK = success!

I2C> [0b01101011 0b0 0b0]

I2C START
TX: 0b01101011 NACK 0b00000000 NACK 0b00000000 NACK
I2C STOP

Check the protected status again, NACK = protected!

image

Notice some voltage droop when driving the optocoupler.

This is the current circuit for enabling 9volts.

I think this is a good change for the next revision. Enable the optocoupler via a FET instead of from the Bus Pirate pin directly.

Also add high voltage feedback to Bus Pirate with zener protection diode so we can check that 9V HV programming supply.


I gained a lot of my understanding from this project, which has a SA1 control circuit. This may be needed for older DDR, but it seems useless on the DDR4 board. I’m going to remove SA1 control.

1 Like

I also think a hole in the board right here might be nice. Big enough for a zip-tie to hold the 9volt power cable to the board so it doesn’t get lost in the parts bin.

2 Likes

From social media: Small addition of a pull-down resistor on the FET (Q1) to keep it from floating when not connected.

2 Likes

I had to do that on my blank plank glitcher. The gate on the FET floated when I disconnected the plank momentarily and burned out the small FET shorting the target to ground. Oops.

Edited: gate, not base. It’s a FET, not a BJT :blush:

1 Like

ddr4 dump command up and running.

The proper thing to do is build a ddr framework to reuse for every version, but at this stage I’m just enjoying writing bespoke code for each. Maybe we can do a framework later, or maybe this is good enough.

I2C> ddr4 probe
Block 0: Locked
Block 1: Locked
Block 2: Unlocked
Block 3: Unlocked

I2C>

Lock status.

ETA: Locking and unlocking blocks:

ETA: Read contents to file:

I2C> ddr4 read -f ddr4.bin
Read SPD NVM to file: ddr4.bin
Success :)
I2C>

Verify SPD against file:

I2C> ddr4 verify -f ddr4.bin
Verifying SPD NVM against file: ddr4.bin
Success :)

I2C>

Verify file CRC:

I2C> ddr4 crc -f ddr4.bin
Checking CRC for bytes 0-125, file: ddr4.bin
CRC verify
Stored CRC (bytes 126:127): 0x34 0x9E
Calculated CRC: 0x34 0x9E
CRC okay :)

I2C>

Patch correct CRC into file:

I2C> ddr4 patch -f ddr4e.bin
Checking CRC for bytes 0-125, file: ddr4e.bin
Stored CRC (bytes 126:127): 0x00 0x00
Calculated CRC: 0x34 0x9E
Patching CRC in file to: 0x34 0x9E
File patched successfully :)
Verifying patched file CRC:
CRC verify
Stored CRC (bytes 126:127): 0x34 0x9E
Calculated CRC: 0x34 0x9E
CRC okay :)

I2C>

TODO:

  • write from file
  • probe (show stats)
  • read to file
  • verify against file
  • check CRC
  • patch CRC in file
2 Likes

Read a data sheet for a DDR1/2/3 EEPROM. Seems really similar to DDR4, I could see them becoming part of a combined DDR framework which could make implementation really fast.

The older eeprom is where manipulation of address pin 1 seems to come into play.

I’ll try to source a full set of common sockets and have boards made up for the remaining DDR versions.