I'm putting this material in blog so I could publish the same question in many places.
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:
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):
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.Reset value is 0.
This field defines the output drive strength.
0 = Normal driver strength.
1 = Weak driver strength.
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:
- 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
- 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)
- 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:
- 0x0000000 for MR
- 0x1000000 for EMR1
- 0x2000000 for EMR2
- 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:
- Our devices do not work (actually, 20% success rate of current production), while they were designed for half-strength DDR memory driver.
- 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 |
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:
- DDR_DRIVE of CCFG_EBICSA: DDR_DRIVE: DDR2 dedicated port I/O slew rate selectionThis 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).
- Driver strength bit of LPR (low-power register) of D DRSDRC. But that's not supposed to do anything for DDR2. No result too.
- I've found SFR (special function register) of at91sam9g45 that has:
Very interesting, tried both SSTL_NORMAL and COMPATIBLE -- still no noticeable result.#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)
What are the magic words?
I'm repeating here so that anyone wouldn't miss:
ReplyDeleteUPDATE
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.