Close

Implementing DRAM access functions

A project log for Compute In Memory in Ancient DRAM

Massively parallel operations in a 64kx1 DRAM from the 1980ies

timTim 05/10/2025 at 12:040 Comments

Reading and writing can be easily implemented by bitbanging.  To meet DRAM timing requirements we have to introduce delays by introducing various NOP cycles.

// Compile-time delay macros for exact cycle counts without loop overhead
#define DELAY_1_CYCLES() __asm volatile ("nop")
#define DELAY_2_CYCLES() __asm volatile ("nop\nnop")
#define DELAY_3_CYCLES() __asm volatile ("nop\nnop\nnop")
#define DELAY_4_CYCLES() __asm volatile ("nop\nnop\nnop\nnop")
#define DELAY_5_CYCLES() __asm volatile ("nop\nnop\nnop\nnop\nnop")

// Specific delay macros for each timing parameter
#define DELAY_RAS_CYCLES() DELAY_5_CYCLES() // ~100ns
#define DELAY_CAS_CYCLES() DELAY_5_CYCLES() // ~100ns
#define DELAY_RCD_CYCLES() DELAY_2_CYCLES()  // ~20ns
#define DELAY_RP_CYCLES()  DELAY_5_CYCLES() // ~100ns

The code below cycles first RAS and then CAS to read a bit from the array at the given address (designated as rows and columns).

// Read a bit from DRAM
uint8_t dram_read_bit(uint8_t row, uint8_t col) {
    uint8_t data;
    
    // Ensure read mode
    GPIOD->BSHR = DRAM_WR_PIN;  // W/R high (read mode)
    
    // Set row address
    DRAM_ADDR_PORT->OUTDR = row;
    DELAY_2_CYCLES(); // Delay for address setup time    
    GPIOD->BCR = DRAM_RAS_PIN;  // RAS low (active)    
    DELAY_RCD_CYCLES();        // RAS to CAS delay
   
    // Set column address
    DRAM_ADDR_PORT->OUTDR = col;
    GPIOD->BCR = DRAM_CAS_PIN;  // CAS low (active)
    DELAY_CAS_CYCLES();        // CAS pulse width
    
    // Read data bit
    data = (GPIOD->INDR & DRAM_DOUT_PIN) ? 1 : 0;
    
    // End cycle
    GPIOD->BSHR = DRAM_CAS_PIN; // CAS high (inactive)
    GPIOD->BSHR = DRAM_RAS_PIN; // RAS high (inactive)
    DELAY_RP_CYCLES();         // RAS precharge time
    
    return data;
}

In case more than one bit needs to be read from one ROW, it is possible to use mutliple CAS cycles without opening another row.

// Read a int32 value from DRAM using fast page mode
uint32_t dram_read_fpm(uint8_t row, uint8_t col, uint8_t bits) {
    uint32_t data=0;
    uint32_t bitcount=0;

    if (bits>32) {
        bits=32;
    }   
    
    // Ensure read mode
    GPIOD->BSHR = DRAM_WR_PIN;  // W/R high (read mode)
    
    // Set row address
    DRAM_ADDR_PORT->OUTDR = row;
    DELAY_2_CYCLES(); // Delay for address setup time    
    GPIOD->BCR = DRAM_RAS_PIN;  // RAS low (active)    
    DELAY_RCD_CYCLES();        // RAS to CAS delay
   
    for (bitcount=0; bitcount<bits; bitcount++) {
        // Set column address
        DRAM_ADDR_PORT->OUTDR = col + bitcount;
        GPIOD->BCR = DRAM_CAS_PIN;  // CAS low (active)
        DELAY_CAS_CYCLES();        // CAS pulse width
        
        // Read data bit
        if (GPIOD->INDR & DRAM_DOUT_PIN) {
            data |= (1<<bitcount);
        }
        
        // End cycle
        GPIOD->BSHR = DRAM_CAS_PIN; // CAS high (inactive)
        DELAY_CAS_CYCLES();        // CAS pulse width
    }

    GPIOD->BSHR = DRAM_RAS_PIN; // RAS high (inactive)
    DELAY_RP_CYCLES();         // RAS precharge time
    
    return data;
}

In the same way, it is possible to implement writes (pull R/W low). It is also possible to implement Read-Modify-Write cycles on the same row. There are quite some optimizations to improve memory throughput even on these very old DRAMs. Curiously, the old home computers do not use any of these optimizations (FPM, RMW), since their CPUs and remaining chipsets where so slow that they limited system speed.

Refresh

An important additional aspect is to refresh the DRAM array. The charge on the capacitors is slowly leaking away and the information will be lost after a while. To counteract this, it is  necessary to read out every row within 4 ms cycle and write it back.

For this, it is only necessary to cycle RAS.

// Refresh a single row
void dram_refresh_row(uint8_t row) {
    // RAS-only refresh cycle
    DRAM_ADDR_PORT->OUTDR = row;  // Set row address
    GPIOD->BCR = DRAM_RAS_PIN;    // RAS low (active)
    DELAY_RAS_CYCLES();          // RAS pulse width    
    GPIOD->BSHR = DRAM_RAS_PIN;   // RAS high (inactive)
    DELAY_RP_CYCLES();           // RAS precharge time
}

Discussions