Testing DRAM

Hi guys, I have an old hardware here, which I’m trying to fix. It has some DRAM memory and I would like to test it to exclude this parts from my suspicion. Does anybody have any experience testing DRAM using Arduino? I wrote a short code, but it doesn’t seem to work. When I try to write and read from an address, I’m getting a wrong result. The memory is this HY53C646 (HY53C464 datasheet).

MY sketch is a work in progress. Should it be possible to write essential functionality, I’d write more substantial tests. However, it doesn’t seem to work. I am trying to write 0xA to address 0x0 and then 0xF to address 0x1 and when reading from address 0x0 again, I’m getting 0xF. Actually I’m always getting the last value I set to the data pins. So, neither do I know if the data was written into the memory, nor do I know, if it couldn’t be read.

Any ideas on this?

const int WE  = A0;
const int OE  = A1;
const int RAS = A2;
const int CAS = A3;
const int D[] = { 10, 11, 12, 13};
const int A[] = { 2, 3, 4, 5, 6, 7, 8, 9 };

const int WORD_WIDTH = sizeof(D) / sizeof(D[0]);
const int ADDR_WIDTH = sizeof(A) / sizeof(A[0]);

void setAddress(unsigned int value) {
#ifdef DEBUG  
  Serial.print("Setting address ");
  Serial.println(value, HEX);
#endif  
  for (int i = 0; i < ADDR_WIDTH; i++) {
    digitalWrite(A[i], (value >> i) & 0x1);
  }
}

void setData(unsigned int value) {
  for (int i = 0; i < WORD_WIDTH; i++) {
    pinMode(D[i], OUTPUT);
    digitalWrite(D[i], (value >> i) & 0x1);
  }
}

unsigned int getData() {
  unsigned int value = 0;
  for (int i = 0; i < WORD_WIDTH; i++) {
    pinMode(D[i], INPUT);    
    value |= digitalRead(D[i]) << i; 
  }
  return value;
}

void setup() {
  Serial.begin(9600);
  Serial.println("DRAM tester");
  
  pinMode(OE, OUTPUT);
  pinMode(WE, OUTPUT);
  pinMode(RAS, OUTPUT);
  pinMode(CAS, OUTPUT);

  for (int i = 0; i < ADDR_WIDTH; i++) {
    pinMode(A[i], OUTPUT);
    pinMode(A[i], LOW);
  }

  for (int i = 0; i < WORD_WIDTH; i++) {
    pinMode(D[i], OUTPUT);
    pinMode(D[i], LOW);
  }

  digitalWrite(OE, HIGH);
  digitalWrite(WE, HIGH);
  digitalWrite(RAS, HIGH);
  digitalWrite(CAS, HIGH);
  
  // "power on" initialization after some pause must toggle RAS
  // at least as many times as the width of the address bus
  for (int i = 0; i < ADDR_WIDTH; i++) {
    digitalWrite(RAS, LOW);
    digitalWrite(RAS, HIGH);
  }
}

void writeData(unsigned int addr, unsigned int data) {
#ifdef DEBUG
  Serial.print("Writing data ");
  Serial.print(data, HEX);
  Serial.print(" at address ");
  Serial.println(addr, HEX);
#endif  

  setAddress(addr & 0xFF);
  digitalWrite(RAS, LOW);
  setAddress((addr >> 8) & 0xFF);
  digitalWrite(CAS, LOW);
  setData(data);  
  digitalWrite(WE, LOW);
  digitalWrite(WE, HIGH);
  digitalWrite(CAS, HIGH);
  digitalWrite(RAS, HIGH);
}

int readData(unsigned int addr) {
#ifdef DEBUG
  Serial.print("Reading data from address ");
  Serial.println(addr, HEX);
#endif  
  setAddress(addr & 0xFF);
  digitalWrite(RAS, LOW);
  digitalWrite(OE, LOW);
  setAddress((addr >> 8) & 0xFF);
  digitalWrite(CAS, LOW);
  digitalWrite(OE, HIGH);
  digitalWrite(CAS, HIGH);
  digitalWrite(RAS, HIGH);
  return getData();
}

void loop() {
  Serial.println("Start");
  writeData(0x0, 0xA);
  writeData(0x1, 0xF);
  unsigned int data = readData(0);
  Serial.println(data, HEX);
  Serial.flush();
  exit(0);
}

This is odd:

    pinMode(A[i], OUTPUT);
    pinMode(A[i], LOW);

Why are you flipping the pinMode in Setup?

wildbill:
This is odd:

    pinMode(A[i], OUTPUT);

pinMode(A[i], LOW);




Why are you flipping the pinMode in Setup?

Ah, just a leftover from messing around, I already removed that again, however, this doesn't seem to have any influence on the behavior. I assume the problem could be somehow in timing, so I did a lot of strange things like this to see, if something changes :slight_smile:

As it stands it looks like you're just writing on the data pins as outputs. If you read from them, you'll just get back what you wrote to them, as you observe. You will need to set their pin modes to INPUT before reading so the DRAM can drive them.

Then I suspect you will need to get the timing on the control pins correct, but I only scanned the datasheet rather than studied it.

wildbill:
As it stands it looks like you're just writing on the data pins as outputs. If you read from them, you'll just get back what you wrote to them, as you observe. You will need to set their pin modes to INPUT before reading so the DRAM can drive them.

Then I suspect you will need to get the timing on the control pins correct, but I only scanned the datasheet rather than studied it.

Actually, this is what I'm doing. If you look into getData() / setData() functions, which are used to read and write the pins, there the related pins are set to INPUT/OUTPUT appropriately. However, I'm quite inexperienced with Arduino and Co., so I might doing it wrong?

Oops, I missed that. Apologies. I'm out of ideas now, looks like it's oscilloscope time.

wildbill:
Oops, I missed that. Apologies. I'm out of ideas now, looks like it's oscilloscope time.

Yes, probably :slight_smile: Thanks anyway!

Oh boy, if I got it right, it seems to be impossible to make a DRAM memory tester with Arduino. At least, when using normal sketch methods, it is just too slow. I tested the delays with logic analyzer and boy, oh, boy. There are some timings in the spec, like RAS pulse width and RAS to CAS delay, unfortunately they have a maximum value and I'm missing this values with the sketch above by ages. Anyway, I have rewritten the setAddress() and setData() methods to use registers instead of writing every bit by using digitalWrite(), where I got with the RAS pulse impulse into the given limits, however RAS to CAS delay of maximum 65ns is not doable. Just the call to set CAS pin low costs me about 2µs and I have also to set the address between RAS and CAS. I guess, I have to give it up. Didn't expect that, but Arduino seems to be to slow to drive DRAM..... :smiley:

This is an old sketch I found long ago that tests 41256 DRAM IC, modified just a tiny bit to support 4164:

// DRAM tester
// Copyright 2014 - Len Bayes
// tweaked 2016 - Eric Chapin - added switch support to go between 64 and 256k DRAM
//              - can use jumper in lieu of switch, middle header to 17, and sides to 5v and gnd.
// Works with 41256 DRAMS - EDITED to work with 4164
// Green LED goes on untill an error is detected.
// Red LED will blink after all test have been run
// A solid RED means the tests failed
// Blue LED goes on after all test have completed

#define  DIN  2  // data input pin
#define  DOUT 3  // data output pin
#define  CAS  5  // column pin
#define  RAS  6  // row pin
#define  WE   7  // /WE pin
#define  GREEN    4
#define  RED      19
#define  BLUE     18
#define  DRAMSIZE 128 // 128 for 4164, mode check below will change to 512
#define  SWPIN    17  // switch or jumper input

void  fillZero();
void  fillOne();
void  fillZeroAlternateOne();
void  fillOneAlternateZero();

void setup()
{
  pinMode(DIN, OUTPUT);      // Data to DRAM
  pinMode(DOUT, INPUT);      // Data from DRAM
  pinMode(CAS, OUTPUT);
  pinMode(RAS, OUTPUT);
  pinMode(WE, OUTPUT);
  pinMode(SWPIN, INPUT);     // switch or jumper to toggle between 64 and 256 mode
  DDRB = B00111111;          // DRAM A0 through A5 on pin D8 - D13
  pinMode(14, OUTPUT);       // DRAM A6
  pinMode(15, OUTPUT);       // DRAM A7
  pinMode(16, OUTPUT);       // DRAM A8 will not be used for 4164, usually NC on 4164's pin 1 but check datasheet!
  digitalWrite(CAS, HIGH);
  digitalWrite(RAS, HIGH);
  digitalWrite(WE, HIGH);
  pinMode(GREEN, OUTPUT);     // Green LED on = no error yet, off = error found
  pinMode(RED, OUTPUT);       // Red Led   blinking = busy, steady = error found
  pinMode(BLUE, OUTPUT);      // Blue Led  done test, no error found.

  digitalWrite(GREEN, HIGH);
  digitalWrite(RED, LOW);
  digitalWrite(BLUE, LOW);
}

int  blue = 0;

void loop()
{
  if (digitalRead(SWPIN)) {
    DRAMSIZE == 512;         // switch on pin 17 is high
  }
  int  i;
  fillZero();
  fillOne();
  fillZeroAlternateOne();
  fillOneAlternateZero();
  for (i = 10 ; i ; i--)
  {
    digitalWrite(GREEN, LOW);
    delay(500);
    digitalWrite(GREEN, HIGH);
    delay(500);
  }
  blue = 1 << 4;
}

// TEST ONE
void  fillZero()
{
  int  row, col, miss = 0;
  // First write all Zeros to DRAM
  digitalWrite(DIN, LOW);
  for (col = 0 ; col < DRAMSIZE ; col++)
  {
    for (row = 0 ; row < 512 ; row++)
    {
      PORTB = row & 0x3f;            // Write out ROW address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(RAS, LOW);        // Set RAS LOW
      digitalWrite(WE, LOW);         // Set WE low for write
      PORTB = col & 0x3f;            // Write out COL address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(CAS, LOW);        // Set CAS LOW
      digitalWrite(WE, HIGH);        // Set WE high to complete write
      digitalWrite(CAS, HIGH);       // Set CAS HIGH
      digitalWrite(RAS, HIGH);       // Set RAS HIGH
    }
  }
  // Read back and check for Zero
  digitalWrite(DIN, HIGH);
  for (col = 0 ; col < DRAMSIZE ; col++)
  {
    for (row = 0 ; row < 512 ; row++)
    {
      PORTB = row & 0x3f;            // Write out ROW address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(RAS, LOW);        // Set RAS LOW
      PORTB = col & 0x3f;            // Write out COL address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(CAS, LOW);        // Set CAS LOW
      if (digitalRead(DOUT) == HIGH)
        miss = 1;
      digitalWrite(CAS, HIGH);       // Set CAS HIGH
      digitalWrite(RAS, HIGH);       // Set RAS HIGH
    }
  }
  if (miss)
  {
    digitalWrite(GREEN, LOW);
    digitalWrite(RED, HIGH);
    while (1);
  }
}

// TEST TWO
void  fillOne()
{
  int  row, col, miss = 0;
  // First write all Ones to DRAM
  digitalWrite(DIN, HIGH);
  for (col = 0 ; col < DRAMSIZE ; col++)
  {
    for (row = 0 ; row < 512 ; row++)
    {
      PORTB = row & 0x3f;            // Write out ROW address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(RAS, LOW);        // Set RAS LOW
      digitalWrite(WE, LOW);         // Set WE low for write
      PORTB = col & 0x3f;            // Write out COL address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(CAS, LOW);        // Set CAS LOW
      digitalWrite(WE, HIGH);        // Set WE high to complete write
      digitalWrite(CAS, HIGH);       // Set CAS HIGH
      digitalWrite(RAS, HIGH);       // Set RAS HIGH
    }
  }
  // Read back and check for One
  digitalWrite(DIN, LOW);
  for (col = 0 ; col < DRAMSIZE ; col++)
  {
    for (row = 0 ; row < 512 ; row++)
    {
      PORTB = row & 0x3f;            // Write out ROW address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(RAS, LOW);        // Set RAS LOW
      PORTB = col & 0x3f;            // Write out COL address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(CAS, LOW);        // Set CAS LOW
      if (digitalRead(DOUT) == LOW)
        miss = 1;
      digitalWrite(CAS, HIGH);       // Set CAS HIGH
      digitalWrite(RAS, HIGH);       // Set RAS HIGH
    }
  }
  if (miss)
  {
    digitalWrite(GREEN, LOW);
    digitalWrite(RED, HIGH);
    while (1);
  }
}

// TEST THREE
void  fillZeroAlternateOne()
{
  int  row, col, i = 0, miss = 0;
  for (col = 0 ; col < DRAMSIZE ; col++)
  {
    for (row = 0 ; row < 512 ; row++)
    {
      digitalWrite(DIN, i);
      if (i)
        i = 0;
      else
        i = 1;
      PORTB = row & 0x3f;            // Write out ROW address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(RAS, LOW);        // Set RAS LOW
      digitalWrite(WE, LOW);         // Set WE low for write
      PORTB = col & 0x3f;            // Write out COL address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(CAS, LOW);        // Set CAS LOW
      digitalWrite(WE, HIGH);        // Set WE high to complete write
      digitalWrite(CAS, HIGH);       // Set CAS HIGH
      digitalWrite(RAS, HIGH);       // Set RAS HIGH
    }
  }
  i = 0;
  // Read back and check for One
  digitalWrite(DIN, LOW);
  for (col = 0 ; col < DRAMSIZE ; col++)
  {
    for (row = 0 ; row < 512 ; row++)
    {
      PORTB = row & 0x3f;            // Write out ROW address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(RAS, LOW);        // Set RAS LOW
      PORTB = col & 0x3f;            // Write out COL address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(CAS, LOW);        // Set CAS LOW
      if (i)
      {
        i = 0;
        if (digitalRead(DOUT) == LOW)
          miss = 1;
      }
      else
      {
        i = 1;
        if (digitalRead(DOUT) == HIGH)
          miss = 1;
      }
      digitalWrite(CAS, HIGH);        // Set CAS HIGH
      digitalWrite(RAS, HIGH);        // Set RAS HIGH
    }
  }
  if (miss)
  {
    digitalWrite(GREEN, LOW);
    digitalWrite(RED, HIGH);
    while (1);
  }
}

// TEST FOUR
void  fillOneAlternateZero()
{
  int  row, col, i = 1, miss = 0;
  for (col = 0 ; col < DRAMSIZE ; col++)
  {
    for (row = 0 ; row < 512 ; row++)
    {
      digitalWrite(DIN, i);
      if (i)
        i = 0;
      else
        i = 1;
      PORTB = row & 0x3f;            // Write out ROW address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(RAS, LOW);        // Set RAS LOW
      digitalWrite(WE, LOW);         // Set WE low for write
      PORTB = col & 0x3f;            // Write out COL address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(CAS, LOW);        // Set CAS LOW
      digitalWrite(WE, HIGH);        // Set WE high to complete write
      digitalWrite(CAS, HIGH);       // Set CAS HIGH
      digitalWrite(RAS, HIGH);       // Set RAS HIGH
    }
  }
  i = 1;

to be continued below

  // Read back and check for One
  digitalWrite(DIN, LOW);
  for (col = 0 ; col < DRAMSIZE ; col++)
  {
    for (row = 0 ; row < 512 ; row++)
    {
      PORTB = row & 0x3f;            // Write out ROW address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(RAS, LOW);        // Set RAS LOW
      PORTB = col & 0x3f;            // Write out COL address
      PORTC = ((col & 0x1c0) >> 6) | blue;
      digitalWrite(CAS, LOW);        // Set CAS LOW
      if (i)
      {
        i = 0;
        if (digitalRead(DOUT) == LOW)
          miss = 1;
      }
      else
      {
        i = 1;
        if (digitalRead(DOUT) == HIGH)
          miss = 1;
      }
      digitalWrite(CAS, HIGH);        // Set CAS HIGH
      digitalWrite(RAS, HIGH);        // Set RAS HIGH
    }
  }
  if (miss)
  {
    digitalWrite(GREEN, LOW);
    digitalWrite(RED, HIGH);
    while (1);
  }
}

Old DRAM are slow enough that it could be tested with Arduino, I used this to check out pile of old DRAM salvaged out of scrapped XT class motherboard. It is also possible to test the old 30 pin SIMM and maybe 72 pin SIMMs but beyond that, forget it. You’d need faster code and faster chip, possibly FPGA or Raspberry Pi

wilykat:
Old DRAM are slow enough that it could be tested with Arduino, I used this to check out pile of old DRAM salvaged out of scrapped XT class motherboard. It is also possible to test the old 30 pin SIMM and maybe 72 pin SIMMs but beyond that, forget it. You’d need faster code and faster chip, possibly FPGA or Raspberry Pi

Yeah, I saw the DRAM access code for 4164 ICs, however, they seem to be a lot slower and have only 1bit data width. The ICs I want to check have 4bit wirds and seem to be faster. Anyway, as I saw the timings in the logic analyzer, I realized, that there is no way to get near the required timings. However, just out of curiosity, I decided to tweak all the stuff a little bit, just to see, how fast can I make it. I ended up in the following mess:

#define DATA_PORT PORTB
#define CTRL_PORT PORTC
#define ADDR_PORT PORTD

#define WE_MASK  B0001
#define OE_MASK  B0010
#define RAS_MASK B0100
#define CAS_MASK B1000

#define CTRL_MASK (WE_MASK | OE_MASK | RAS_MASK | CAS_MASK)
#define DATA_MASK (B1111)
#define ADDR_MASK (B11111111)

#define CTRL_TOGGLE(mask) CTRL_PORT ^= mask

void setup() {
  // Setup control pins
  DDRC = CTRL_MASK;
  CTRL_PORT = CTRL_MASK;
  
  // Setup address pins
  DDRD = ADDR_MASK;

  // Setup data pins
  DDRB = DATA_MASK;
  
  // "power on" initialization after some pause must toggle RAS
  // at least as many times as the width of the address bus
  for (int i = 0; i < 16; i++) {
    CTRL_TOGGLE(RAS_MASK);
  }
}

void setAddress(unsigned int addr) {
  ADDR_PORT = addr & ADDR_MASK; // Set RAS address
  CTRL_TOGGLE(RAS_MASK); // Set RAS low
  ADDR_PORT = ((addr >> 8) & ADDR_MASK); // Set CAS address
  CTRL_TOGGLE(CAS_MASK);// Set CAS low
}

void setData(unsigned int data) {
  DDRB |= DATA_MASK; // Set data pins to output mode
  DATA_PORT = data & DATA_MASK; // Setup data pins
}

unsigned int getData() {
  DDRB &= ~DATA_MASK; // Set Data to input mode
  return DATA_PORT & DATA_MASK;
}

void writeData(unsigned int addr, unsigned int data) {
  setData(data);
  setAddress(addr);
  CTRL_TOGGLE(WE_MASK); // Set WE low to write data
  CTRL_TOGGLE(WE_MASK); // Set WE high
  CTRL_TOGGLE(CAS_MASK); // Set CAS high
  CTRL_TOGGLE(RAS_MASK); // Set RAS high
} 

unsigned int readData(unsigned int addr) {
  setAddress(addr);
  CTRL_TOGGLE(OE_MASK); // Set OE low
  unsigned int data = getData(); // Read data <-- this seems not to work!
  CTRL_TOGGLE(OE_MASK); // Set OE high
  CTRL_TOGGLE(CAS_MASK); // Set CAS high
  CTRL_TOGGLE(RAS_MASK); // Set RAS high
  return data;
}

void loop() {
  writeData(0x0, 0xA);
  writeData(0x1, 0xF);
  writeData(0x2, 0x0);
  writeData(0x3, 0x7);
  unsigned int data = readData(0);
  DDRB |= DATA_MASK; // Set Data to output mode
  setData(0);
  setData(data);
  exit(0);
}

I hate this code, it is ugly as hell, but the result is like factor 100 faster. Amazing, I didn’t expect that. Anyway, I still miss the timings requirements by far, however, when I look at the logic analyzer, I can see, that it actually is working. At least I’m getting the right values on the pins. However, the function readData() gives me the last value I set to the data pins, instead of what was on the pins. I clearly can see the right values in the logic analyzer, but the data collected from the arduino is the wrong one. I’m writing four values 0xA, 0xF, 0x0 and 0x7 at adresses 0x0, 0x1, 0x2, 0x3. Then I read the value from memory at address 0x0 again and I can see 0xA in the logic analyzer, however arduino gets 0x7 somehow :slight_smile: May be it is now too fast for arduino to read the values? But everything looks like the idea still could work.

Here is the output of the logic analyzer (Imgur: The magic of the Internet):

In case s.o. is interested, I found the issue with my code. I'm trying to read from PORTB, but it has to be PINB, then everything works as it should. However the next problem is, if I massively writing and reading the DRAM, I'm getting errors. I implemented a loop, where I just write patterns into the memory and trying to validate them by reading afterwards. After a while, I can't read the values I wrote into the memory, may be timing issues again, or may be the DRAM IC is bad. But I tend to say it's because of the timings.