bios/sdram: add automatic cdly calibration during write leveling
authorJędrzej Boczar <yendreij@gmail.com>
Thu, 23 Apr 2020 11:52:28 +0000 (13:52 +0200)
committerJędrzej Boczar <yendreij@gmail.com>
Fri, 24 Apr 2020 12:00:42 +0000 (14:00 +0200)
litex/soc/software/bios/main.c
litex/soc/software/bios/sdram.c
litex/soc/software/bios/sdram.h

index 0d2e6d863e8dfab1a1b94507baec8563b8078c38..423d4f28d7adb1adce6dbdd98b199578d33d0fa3 100644 (file)
@@ -394,6 +394,7 @@ static void help(void)
        puts("sdram_cal                       - run SDRAM calibration");
        puts("sdram_mpr                       - read SDRAM MPR");
        puts("sdram_mrwr reg value            - write SDRAM mode registers");
+       puts("sdram_cdly_scan enabled         - enable/disable cdly scan");
 #endif
 #ifdef CSR_SPISDCARD_BASE
         puts("spisdcardboot   - boot from SDCard via SPI hardware bitbang");
@@ -506,6 +507,11 @@ static void do_command(char *c)
                sdrmrwr(reg, value);
                sdrhw();
        }
+       else if(strcmp(token, "sdram_cdly_scan") == 0) {
+               unsigned int enabled;
+               enabled = atoi(get_token(&c));
+               sdr_cdly_scan(enabled);
+       }
 #endif
 #ifdef CSR_SPISDCARD_BASE
         else if(strcmp(token, "spisdcardboot") == 0) spisdcardboot();
index 53333cd84b1fe1cb6e52328cefca33041c12f39c..ff094bd0e1a11abeca9b4083f1fa10cb89151ac5 100644 (file)
@@ -310,7 +310,7 @@ static void write_delay_inc(int module) {
        ddrphy_dly_sel_write(0);
 }
 
-int write_level(void)
+static int write_level_scan(int *delays, int show)
 {
        int i, j, k;
 
@@ -322,20 +322,17 @@ int write_level(void)
        int one_window_start, one_window_best_start;
        int one_window_count, one_window_best_count;
 
-       int delays[SDRAM_PHY_MODULES];
-
        unsigned char buf[DFII_PIX_DATA_BYTES];
 
        int ok;
 
        err_ddrphy_wdly = SDRAM_PHY_DELAYS - ddrphy_half_sys8x_taps_read();
 
-       printf("Write leveling:\n");
-
        sdrwlon();
        cdelay(100);
        for(i=0;i<SDRAM_PHY_MODULES;i++) {
-               printf("m%d: |", i);
+               if (show)
+                       printf("m%d: |", i);
 
                /* rst delay */
                write_delay_rst(i);
@@ -344,9 +341,9 @@ int write_level(void)
                for(j=0;j<err_ddrphy_wdly;j++) {
                        int zero_count = 0;
                        int one_count = 0;
-                       int show = 1;
+                       int show_iter = show;
 #if SDRAM_PHY_DELAYS > 32
-                       show = (j%16 == 0);
+                       show_iter = (j%16 == 0) && show;
 #endif
                        for (k=0; k<128; k++) {
                                ddrphy_wlevel_strobe_write(1);
@@ -362,19 +359,20 @@ int write_level(void)
                                taps_scan[j] = 1;
                        else
                                taps_scan[j] = 0;
-                       if (show)
+                       if (show_iter)
                                printf("%d", taps_scan[j]);
                        write_delay_inc(i);
                        cdelay(10);
                }
-               printf("|");
+               if (show)
+                       printf("|");
 
                /* find longer 1 window and set delay at the 0/1 transition */
                one_window_active = 0;
                one_window_start = 0;
                one_window_count = 0;
                one_window_best_start = 0;
-               one_window_best_count = 0;
+               one_window_best_count = -1;
                delays[i] = -1;
                for(j=0;j<err_ddrphy_wdly;j++) {
                        if (one_window_active) {
@@ -393,13 +391,17 @@ int write_level(void)
                                }
                        }
                }
-               delays[i] = one_window_best_start;
-
-               /* configure write delay */
-               write_delay_rst(i);
-               for(j=0; j<delays[i]; j++)
-                       write_delay_inc(i);
-               printf(" delay: %02d\n", delays[i]);
+               /* succeed only if the start of a 1s window has been found */
+               if (one_window_best_count > 0 && one_window_best_start > 0) {
+                       delays[i] = one_window_best_start;
+
+                       /* configure write delay */
+                       write_delay_rst(i);
+                       for(j=0; j<delays[i]; j++)
+                               write_delay_inc(i);
+               }
+               if (show)
+                       printf(" delay: %02d\n", delays[i]);
        }
 
        sdrwloff();
@@ -413,6 +415,106 @@ int write_level(void)
        return ok;
 }
 
+static void write_level_cdly_range(unsigned int *best_error, int *best_cdly,
+               int cdly_start, int cdly_stop, int cdly_step)
+{
+       int cdly;
+       int cdly_actual = 0;
+       int delays[SDRAM_PHY_MODULES];
+
+       /* scan through the range */
+       ddrphy_cdly_rst_write(1);
+       for (cdly = cdly_start; cdly < cdly_stop; cdly += cdly_step) {
+               /* increment cdly to current value */
+               while (cdly_actual < cdly) {
+                       ddrphy_cdly_inc_write(1);
+                       cdelay(10);
+                       cdly_actual++;
+               }
+
+               /* write level using this delay */
+               if (write_level_scan(delays, 0)) {
+                       /* use the mean of delays for error calulation */
+                       int delay_mean = 0;
+                       for (int i=0; i < SDRAM_PHY_MODULES; ++i) {
+                               delay_mean += delays[i];
+                       }
+                       delay_mean /= SDRAM_PHY_MODULES;
+
+                       /* we want it to be in the middle */
+                       int ideal_delay = (SDRAM_PHY_DELAYS - ddrphy_half_sys8x_taps_read()) / 2;
+                       int error = ideal_delay - delay_mean;
+                       if (error < 0)
+                               error *= -1;
+
+                       if (error < *best_error) {
+                               printf("+");
+                               *best_cdly = cdly;
+                               *best_error = error;
+                       } else {
+                               printf("-");
+                       }
+               } else {
+                       printf(".");
+               }
+       }
+}
+
+int write_level(void)
+{
+       int delays[SDRAM_PHY_MODULES];
+       unsigned int best_error = ~0u;
+       int best_cdly = -1;
+       int cdly_range_start;
+       int cdly_range_end;
+       int cdly_range_step;
+
+       printf("cdly scan: ");
+
+       /* Center write leveling by varying cdly. Searching through all possible
+        * values is slow, but we can use a simple optimization method of iterativly
+        * scanning smaller ranges with decreasing step */
+       cdly_range_start = 0;
+       cdly_range_end = 512;
+       cdly_range_step = 64;
+       while (cdly_range_step > 0) {
+               printf("|");
+               write_level_cdly_range(&best_error, &best_cdly,
+                               cdly_range_start, cdly_range_end, cdly_range_step);
+
+               /* small optimization - stop if we have zero error */
+               if (best_error == 0)
+                       break;
+
+               /* use best result as the middle of next range */
+               cdly_range_start = best_cdly - cdly_range_step;
+               cdly_range_end = best_cdly + cdly_range_step + 1;
+               if (cdly_range_start < 0)
+                       cdly_range_start = 0;
+               if (cdly_range_end > 512)
+                       cdly_range_end = 512;
+
+               cdly_range_step /= 4;
+       }
+       printf("| best: %d\n", best_cdly);
+
+       /* if we found any working delay then set it */
+       if (best_cdly >= 0) {
+               ddrphy_cdly_rst_write(1);
+               for (int i = 0; i < best_cdly; ++i) {
+                       ddrphy_cdly_inc_write(1);
+                       cdelay(10);
+               }
+       }
+
+       /* re-run write leveling the final time */
+       if (!write_level_scan(delays, 1))
+               return 0;
+
+       return best_cdly >= 0;
+}
+
+
 #endif /*  SDRAM_PHY_WRITE_LEVELING_CAPABLE */
 
 static void read_delay_rst(int module) {
@@ -905,7 +1007,8 @@ int memtest(void)
 #ifdef CSR_SDRAM_BASE
 
 #if defined(SDRAM_PHY_WRITE_LEVELING_CAPABLE) || defined(SDRAM_PHY_READ_LEVELING_CAPABLE)
-int sdrlevel(void)
+
+static void read_leveling(void)
 {
        int module;
        int bitslip;
@@ -913,23 +1016,6 @@ int sdrlevel(void)
        int best_score;
        int best_bitslip;
 
-       sdrsw();
-
-       for(module=0; module<SDRAM_PHY_MODULES; module++) {
-#ifdef SDRAM_PHY_WRITE_LEVELING_CAPABLE
-               write_delay_rst(module);
-#endif
-               read_delay_rst(module);
-               read_bitslip_rst(module);
-       }
-
-#ifdef SDRAM_PHY_WRITE_LEVELING_CAPABLE
-       if(!write_level())
-               return 0;
-#endif
-
-#ifdef SDRAM_PHY_READ_LEVELING_CAPABLE
-       printf("Read leveling:\n");
        for(module=0; module<SDRAM_PHY_MODULES; module++) {
                /* scan possible read windows */
                best_score = 0;
@@ -960,8 +1046,40 @@ int sdrlevel(void)
                read_level(module);
                printf("\n");
        }
+}
+
+int _write_level_cdly_scan = 1;
+
+int sdrlevel(void)
+{
+       int module;
+       sdrsw();
+
+       for(module=0; module<SDRAM_PHY_MODULES; module++) {
+#ifdef SDRAM_PHY_WRITE_LEVELING_CAPABLE
+               write_delay_rst(module);
+#endif
+               read_delay_rst(module);
+               read_bitslip_rst(module);
+       }
+
+#ifdef SDRAM_PHY_WRITE_LEVELING_CAPABLE
+       printf("Write leveling:\n");
+       if (_write_level_cdly_scan) {
+               if(!write_level())
+                       return 0;
+       } else {
+               /* use only the current cdly */
+               int delays[SDRAM_PHY_MODULES];
+               if (!write_level_scan(delays, 1))
+                       return 0;
+       }
 #endif
 
+#ifdef SDRAM_PHY_READ_LEVELING_CAPABLE
+       printf("Read leveling:\n");
+       read_leveling();
+#endif
 
        return 1;
 }
@@ -978,12 +1096,12 @@ int sdrinit(void)
 
        init_sequence();
 #ifdef CSR_DDRPHY_BASE
-#if CSR_DDRPHY_EN_VTC_ADDR
-       ddrphy_en_vtc_write(0);
-#endif
 #ifdef DDRPHY_CMD_DELAY
        ddrphy_cdly(DDRPHY_CMD_DELAY);
 #endif
+#if CSR_DDRPHY_EN_VTC_ADDR
+       ddrphy_en_vtc_write(0);
+#endif
 #if defined(SDRAM_PHY_WRITE_LEVELING_CAPABLE) || defined(SDRAM_PHY_READ_LEVELING_CAPABLE)
        sdrlevel();
 #endif
@@ -1087,6 +1205,12 @@ void sdrmpr(void)
        sdrhw();
 }
 
+void sdr_cdly_scan(int enabled)
+{
+       printf("Turning cdly scan %s\n", enabled ? "ON" : "OFF");
+       _write_level_cdly_scan = enabled;
+}
+
 #endif
 
 #endif
index 15629eba83e9e0a85216524d0ba6e4c48c8f732a..a2571191980db53322ebea884300fba732543507 100644 (file)
@@ -29,6 +29,7 @@ void ddrphy_cdly(unsigned int delay);
 void sdrcal(void);
 void sdrmrwr(char reg, int value);
 void sdrmpr(void);
+void sdr_cdly_scan(int enabled);
 #endif
 
 #endif /* __SDRAM_H */