Sunday, July 8, 2012

at91sam9g45 DDR controller half drive strength problem

I'm putting this material in blog so I could publish the same question in many places.

Choosing driver strength for DDR2 memory on at91sam9g45 SoC


Our ARM board uses at91sam9g45 SoC by Atmel. The board has 64 MB DDR2 memory, implemented on a single chip -- Micron MT47H32M16, connected to standalone DDR2 controller (DDRSDRC0, 0xFFFFE600).

As we have so called point-to-point configuration, it is supposed that working with memory is done with half driver strength of DDR controller.

UPDATE
The problem was solved. Actually, in this project we didn't really care if driver strength is correct, we just need the product to boot up and work, so if driver strength is wrong but there are no RAM errors. we're good.

So, the actual problem was improper power source for 1V CPU. We didn't look closely to reference power schematics, so we missed a capacitor that smoothes consumption pulses on 1V voltage.

Everything works ok now. The rest of the text is left for historical purposes.



According to at91sam9g45 manual (6438I–ATARM–16-Dec-11), p.260 there is a field in configuration register for this case:

• DIC/DS: Output Driver Impedance Control
Reset value is 0.
This field defines the output drive strength.
0 = Normal driver strength.
1 = Weak driver strength.
This value is used during the power-up sequence. This parameter is found in the datasheet as DIC or DS.

So, to set "weak driver strength", programmer is supposed to set 8-th bit (1 << 8) in CR of DDRSDRC0.

According to Micron manual (PDF: 09005aef82f1e6e2 512MbDDR2.pdf - Rev. S 7/11 EN)), p.81 output drive strength is set in EMR register (bit 1). EMR is loaded in initialization sequence of DDR.

This is our memory initialization (2 parts -- configuration, mode, timing registers in terms of at91bootstrap v1.16 and actual initialization sequence):

 static     SDdramConfig ddram_config = {  
      .ddramc_mdr = (AT91C_DDRC2_DBW_16_BITS | AT91C_DDRC2_MD_DDR2_SDRAM),  
      .ddramc_cr  = (AT91C_DDRC2_NC_DDR10_SDR9 |        // 10 column bits (1K)  
                AT91C_DDRC2_NR_13     |        // 13 row bits  (8K)  
                AT91C_DDRC2_CAS_3     |        // CAS Latency 3  
                AT91C_DDRC2_DLL_RESET_DISABLED |  
                AT91C_DDRC2_OCD_DEFAULT |  
                AT91C_DDRC2_DIC_DS_WEEK  // low output driver impendance  
           ),     // DLL not reset  
      .ddramc_rtr =      0x208,  
      .ddramc_t0pr = (AT91C_DDRC2_TRAS_6 |          // 6 * 7.5 = 45  ns  
                AT91C_DDRC2_TRCD_2 |          // 2 * 7.5 = 22.5 ns  
                AT91C_DDRC2_TWR_2  |          // 2 * 7.5 = 15  ns  
                AT91C_DDRC2_TRC_8 |          // 8 * 7.5 = 75  ns  
                AT91C_DDRC2_TRP_2  |          // 2 * 7.5 = 22.5 ns  
                AT91C_DDRC2_TRRD_2 |          // 1 * 7.5 = 7.5  ns  
                AT91C_DDRC2_TWTR_1 |          // 1 clock cycle  
                AT91C_DDRC2_TMRD_2),          // 2 clock cycles  
      .ddramc_t1pr = (AT91C_DDRC2_TXP_2 |          // 2 * 7.5 = 15 ns  
                200 << 16      |          // 200 clock cycles, TXSRD: Exit self refresh delay to Read command  
                16 << 8       |          // 16 * 7.5 = 120 ns TXSNR: Exit self refresh delay to non read command  
                AT91C_DDRC2_TRFC_14 << 0),     // 14 * 7.5 = 105 ns (must be 105 ns for 512Mb DDR)  
      .ddramc_t2pr = (AT91C_DDRC2_TRTP_1  |          // 1 * 7.5 = 7.5 ns  
                AT91C_DDRC2_TRPA_0  |          // 0 * 7.5 = 0 ns  
                AT91C_DDRC2_TXARDS_7 |          // 7 clock cycles  
                AT91C_DDRC2_TXARD_2),          // 2 clock cycles  
 };  
OCD_DEFAULT is set according to recommendation of Micron memory manual. "DIC_DS_WEEK" is the key parameter, the spelling is taken from SAM-BA sources :-) It does set 8-th bit of CR. All other parameters are set for 133 MHz master clock frequency.

And here goes initialization sequence:

 int ddram_init(unsigned int ddram_controller_address, unsigned int ddram_address, struct SDdramConfig *ddram_config)  
 {  
      volatile unsigned int i;  
      unsigned int cr = 0;  
      // Step 1: Program the memory device type  
      // Configure the DDR controller  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MDR, ddram_config->ddramc_mdr);  
      // Program the DDR Controller  
      write_ddramc(ddram_controller_address, HDDRSDRC2_CR, ddram_config->ddramc_cr);   
 
      // assume timings for 7.5ns min clock period  
      write_ddramc(ddram_controller_address, HDDRSDRC2_T0PR, ddram_config->ddramc_t0pr);  
      // pSDDRC->HDDRSDRC2_T1PR  
      write_ddramc(ddram_controller_address, HDDRSDRC2_T1PR, ddram_config->ddramc_t1pr);  
      // pSDDRC->HDDRSDRC2_T2PR  
      write_ddramc(ddram_controller_address, HDDRSDRC2_T2PR, ddram_config->ddramc_t2pr);  
      // Initialization Step 3: NOP command -> allow to enable clk  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_NOP_CMD);  
      *((unsigned volatile int*) ddram_address) = 0;  
      // Initialization Step 3 (must wait 200 us) (6 core cycles per iteration, core is at 396MHz: min 13200 loops)  
      for (i = 0; i < 13300; i++) {  
           asm("  nop");  
      }  
      // Step 4: An NOP command is issued to the DDR2-SDRAM  
      // NOP command -> allow to enable cke  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_NOP_CMD);  
      *((unsigned volatile int*) ddram_address) = 0;  
      // wait 400 ns min  
      for (i = 0; i < 100; i++) {  
           asm("  nop");  
      }  
      // Initialization Step 5: Set All Bank Precharge  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_PRCGALL_CMD);  
      *((unsigned volatile int*) ddram_address) = 0;  
      // wait 400 ns min  
      for (i = 0; i < 100; i++) {  
           asm("  nop");  
      }  
     // Initialization Step 6: Set EMR operation (EMRS2)  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_EXT_LMR_CMD);  
      *((unsigned int *)(ddram_address + 0x2000000)) = 0; // BA[1] is set to 1 and BA[0] are set to 0.  
      // wait 2 cycles min  
      for (i = 0; i < 100; i++) {  
           asm("  nop");  
      }  
      // Initialization Step 7: Set EMR operation (EMRS3)  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_EXT_LMR_CMD);  
      *((unsigned int *)(ddram_address + 0x3000000)) = 0; //BA[1] is set to 1 and BA[0] are set to 1.  
      // wait 2 cycles min  
      for (i = 0; i < 100; i++) {  
           asm("  nop");  
      }  
      // Initialization Step 8: Set EMR operation (EMRS1)  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_EXT_LMR_CMD);  
      *((unsigned int *)(ddram_address + 0x1000000)) = 0; //BA[1] is set to 0 and BA[0] is set to 1.  
      // wait 200 cycles min  
      for (i = 0; i < 10000; i++) {  
           asm("  nop");  
      }  
      // Initialization Step 9: enable DLL reset  
      cr = read_ddramc(ddram_controller_address, HDDRSDRC2_CR);  
      write_ddramc(ddram_controller_address, HDDRSDRC2_CR, cr | AT91C_DDRC2_DLL_RESET_ENABLED);  
      // Initialization Step 10: reset DLL  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_LMR_CMD);  
      *(((unsigned volatile int*) ddram_address)) = 0;  
      // wait 200 cycles min  
      for (i = 0; i < 10000; i++) {  
           asm("  nop");  
      }  
      // Initialization Step 11: Set All Bank Precharge  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_PRCGALL_CMD);  
      *(((unsigned volatile int*) ddram_address)) = 0;  
      // wait 400 ns min  
      for (i = 0; i < 100; i++) {  
           asm("  nop");  
      }  
      // Initialization Step 12: Two auto-refresh (CBR) cycles are provided. Program the auto refresh command (CBR) into the Mode Register.  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_RFSH_CMD);  
      *(((unsigned volatile int*) ddram_address)) = 0;  
      // wait 10 cycles min  
      for (i = 0; i < 100; i++) {  
           asm("  nop");  
      }  
      // Set 2nd CBR  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_RFSH_CMD);  
      *(((unsigned volatile int*) ddram_address)) = 0;  
      // wait 10 cycles min  
      for (i = 0; i < 100; i++) {  
           asm("  nop");  
      }  
      // Initialization Step 13: Program DLL field into the Configuration Register to low(Disable DLL reset).  
      cr = read_ddramc(ddram_controller_address, HDDRSDRC2_CR);  
      write_ddramc(ddram_controller_address, HDDRSDRC2_CR, cr & (~AT91C_DDRC2_DLL_RESET_ENABLED));  
      // Initialization Step 14: A Mode Register set (MRS) cycle is issued to program the parameters of the DDR2-SDRAM devices.  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_LMR_CMD);  
      *(((unsigned volatile int*) ddram_address)) = 0;  
      // Step 15: Program OCD field into the Configuration Register to high (OCD calibration default).  
      cr = read_ddramc(ddram_controller_address, HDDRSDRC2_CR);  
      write_ddramc(ddram_controller_address, HDDRSDRC2_CR, cr | AT91C_DDRC2_OCD_DEFAULT);  
      // Step 16: An Extended Mode Register set (EMRS1) cycle is issued to OCD default value.  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_EXT_LMR_CMD);  
      *(((unsigned int*) (ddram_address + 0x1000000))) = 0; //BA[1] is set to 0 and BA[0] is set to 1  
      // wait 2 cycles min  
      for (i = 0; i < 100; i++) {  
           asm("  nop");  
      }  
      // Step 17: Program OCD field into the Configuration Register to low (OCD calibration mode exit).  
      cr = read_ddramc(ddram_controller_address, HDDRSDRC2_CR);  
      write_ddramc(ddram_controller_address, HDDRSDRC2_CR, cr & (~AT91C_DDRC2_OCD));  
      // Step 18: An Extended Mode Register set (EMRS1) cycle is issued to enable OCD exit.  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_EXT_LMR_CMD);  
      *(((unsigned int*) (ddram_address + 0x1000000))) = 0; //BA[1] is set to 1and BA[0] is set to 1.  
      // wait 200 cycles min  
      for (i = 0; i < 10000; i++) {  
           asm("  nop");  
      }  
      // Step 19,20: A mode Normal command is provided. Program the Normal mode into Mode Register.  
      write_ddramc(ddram_controller_address, HDDRSDRC2_MR, AT91C_DDRC2_MODE_NORMAL_CMD);  
      *(((unsigned volatile int*) ddram_address)) = 0;  
      // Step 21: Write the refresh rate into the count field in the Refresh Timer register. The DDR2-SDRAM device requires a  
      // refresh every 15.625 ¦Is or 7.81 ¦Мs. With a 100MHz frequency, the refresh timer count register must to be set with  
      // (15.625 /100 MHz) = 1562 i.e. 0x061A or (7.81 /100MHz) = 781 i.e. 0x030d.  
      // Set Refresh timer  
      write_ddramc(ddram_controller_address, HDDRSDRC2_RTR, ddram_config->ddramc_rtr);  
      // OK now we are ready to work on the DDRSDR  
      // wait for end of calibration  
      for (i = 0; i < 500; i++) {  
           asm("  nop");  
      }  
      return 0;  
 }  
Step numbers are taken from SoC data sheet.

Compared to at91bootstrap code some problems were fixed:

  1. At step 18 bank address was wrong (bank address choses which # of EMR# is taken, if in step 16 EMR1 was addressed, in step 18 the offset of (ddram_address + x) should be the same
  2. For some reason, at step 17 OCD wasn't reset correctly (there is 3 << 10 bit mask for OCD_DEFAULT and 0 for OCD_EXIT, and unsetting the bit was done as & ~OCD_EXIT, which does effectively nothing)
  3. Some other places I don't quite remember where it is told that EMR# is addressed and the offset is given for #', different one.
Those offsets are used to set BA1 and BA0 address bits. As I have 64 MB of RAM (0x4000000), offsets are:
  1. 0x0000000 for MR
  2. 0x1000000 for EMR1
  3. 0x2000000 for EMR2
  4. 0x3000000 for EMR3
With this knowledge, one could easily determine that EMR1 (that's holding driver strength bit) is accessed in steps 8, 16 and 18.

Using JTAG debugger, I can step through the initialization code and check if DDR controller actually sets the corresponding bit by oscilloscope probe. With full strength A1 (address line 1) bit is set to 0 and with half strength selected A1 is set to 1.

So we finally approached to the core of problem I'm having:

These settings do not change anything (for me).


There are 2 proofs for that:
  1. Our devices do not work (actually, 20% success rate of current production), while they were designed for half-strength DDR memory driver.
  2. Oscilloscope graphs, which are hereby shown. There is some difference, but that's not how 100 mA is supposed to differ from 50-60 mA driver strength (as the difference supposed to be). Specifically, distortions are too high for our devices to work stably in these conditions.
Read, full strength, cpu side

Read, full strength, memory side

Read, half strength, cpu side

Read, half strength, memory side

Write, full strength, cpu side

Write, fill strength, memory side 
Write, half strength, cpu side

Write, half strength, memory side



Memory side == probe was at most close to memory point possible. CPU side -- same logic.

Modelled reality looked like following (Hyperlynx enabled, green == CPU, red == memory):
read cpu full, memory full, zoom

read cpu full, memory full

read cpu half, memory half, zoom

read cpu half, memory half

write cpu full, memory full, zoom

write cpu full memory full

write cpu half, memory half, zoom

write cpu half, memory half

As you can see, modeling != reality :-(


As result of this problem, my company is having huge difficulties with board bring up from manufacturing. Some boards work ok, some boards work only with CPU caches disabled (memory access speed drops significantly by that).

The question is:

What was done wrong in half-strength memory driver configuration?

PS. As versions of what I've missed, I got to:
  1. DDR_DRIVE of CCFG_EBICSA: DDR_DRIVE: DDR2 dedicated port I/O slew rate selection
    This allows to avoid overshoots and give the best performances according to the bus load and external memories.
    0 = Low Drive, optimized for load capacitance < 30 pF. 1 = High Drive, optimized for load capacitance < 55 pF. Note: This concerns only stand-alone DDR controller.

    No result (I did & ~(1 << 18) for this register).

  2. Driver strength bit of LPR (low-power register) of D DRSDRC. But that's not supposed to do anything for DDR2. No result too.
  3. I've found SFR (special function register) of at91sam9g45 that has: 
     #define SFR_DDRCFG   ( 4) // DDR2 SSTL18 control.   
     #define AT91C_SFR_DDRCFG_SSTL   (0x1 << 0) // (SFR) Control DDR2 pads SSTL mode control  
     #define      AT91C_SFR_DDRCFG_SSTL_NORMAL        (0x0) // (SFR) Force pads in SSTL18 mode when DDR2 is connected  
     #define      AT91C_SFR_DDRCFG_SSTL_COMPATIBLE      (0x1) // (SFR) LVCMOS level (compatible SSTL18)  
    
    Very interesting, tried both SSTL_NORMAL and COMPATIBLE -- still no noticeable result.
What are the magic words?

1 comment:

  1. I'm repeating here so that anyone wouldn't miss:

    UPDATE
    The problem was solved. Actually, in this project we didn't really care if driver strength is correct, we just need the product to boot up and work, so if driver strength is wrong but there are no RAM errors. we're good.

    So, the actual problem was improper power source for 1.8V CPU line. We didn't look closely to reference power schematics, so we missed additional capacitors that smoothen consumption pulses on 1.8V voltage which occurred on intensive memory operation (like, with MMU enabled).

    Everything works ok now. The rest of the text is left as is for historical purposes.

    ReplyDelete