Appendix D : SDRAM
===
###### tags: `gnitnaw` `twlkh`
本附錄講的是SDRAM的底層操作。
## D.1: Basics
[動態隨機存取記憶體(Dynamic Random Access Memory,DRAM)](https://zh.wikipedia.org/zh-tw/%E5%8A%A8%E6%80%81%E9%9A%8F%E6%9C%BA%E5%AD%98%E5%8F%96%E5%AD%98%E5%82%A8%E5%99%A8)是一種半導體記憶體,每個bit由一個電容和一個電晶體組成,主要是利用電容內儲存電荷的多寡來代表一個二進位位元(bit)是1還是0。不過正常電容會漏電,所以要常常充電(refresh),當沒電的時候當然資料也會通通消失。[同步動態隨機存取記憶體(SDRAM)](https://zh.wikipedia.org/zh-tw/SDRAM)則是有一個同步接口的DRAM。
SDRAM其實是由一些DRAM陣列組成,像這樣:

要找到某一塊基本儲存單元(cell)的資料(通常大小為8-16bit),就要給定行(row),然後給定列(column)。這樣的儲存單位被稱為Physical Bank(簡稱P-Bank)。
那如果要結合32bit或64bit該怎麼辦?通常我們會用數個P-Bank湊成32bit或64bit(然後這一個大塊被稱為Logical Bank, L-Bank);假設一個P-Bank的cell大小為8bit,那四個P-Bank組成的L-Bank就有32bit。
:::success
stm32F429 Discovery的SDRAM規格如下:
"The 64-Mbit SDRAM is a high speed CMOS, dynamic random-access memory designed to operate in 3.3 V memory systems containing 67,108,864 bits. It is internally configured as a quad-bank DRAM with a synchronous interface. Each 16,777,216-bit bank is organized as 4,096 rows (12bit) by 256 (8 bit) columns by 16 bits. The 64-Mbit SDRAM includes an auto-refresh, a power-saving and a power-down modes. All signals are registered on the positive edge of the clock signal, CLK. "
:::
:::info
目前SDRAM標準是最多4個L-Bank,DDR-II的標準則是8個。由於成本跟實用性的考量,一般來說不會去用含有大cell(32bit或以上)的L-Bank。並不是說做不出來,而是這種大cell的P-Bank還會增加Addressing Collision的機率,大幅降低效率。
Viller Hsiao : address collision 看起來比較像是連取存取同一個 Bank 的話大家就要排隊的問題。
這篇論文 p.59 有一段敘述
http://www.ece.umd.edu/~blj/papers/thesis-PhD-wang--DRAM.pdf
"multiple banks so that multiple, independent accesses to different DRAM arrays can occur in parallel."
:::
下圖為讀取L-Bank的示意圖:先把L-Bank分組(每組32bit,cell=8bit的情況就是一組4個),每次讀取先決定要讀哪組L-Bank,然後讀出那組同樣field的cell(線路並聯就解決了)就能一次解決了。

:::info
有些Memory controller是以B(Bank)->R(Row)->C(Column)找尋對應的資料,其優點為功耗低;有些是R->B->C,好處是性能高。stm32f429用的是B->R->C。
:::
Memory Controller要讀取存在某physical address的資料,就要先把physical address轉換成行列資訊,然後發出行列訊號讀寫該處的資料:行方面為Row Address Select(RAS)訊號,列為Column Address Select(CAS)訊號。
## D.2: Clocking
既然要頻繁送資料給Memory Controller,那使用clocking去同步信號是必要的。
Single Data Rate (SDR) SDRAM在一個clock週期內只傳輸(讀或寫)一次資料,它是在clock上升期傳輸資料;而Double Data Rate (DDR)則是一個clock週期內可傳輸兩次資料(clock上升期和下降期各一次)。
通常CPU/SoC本身的時脈會大幅高於該系統其他的部份,所以處理器一般都會另外使用以震盪器產生,頻率較低的clock source為主要的clock signal(控制SDRAM等重要裝置用),然後搭配鎖相環(PLL: Phase-locked loops)產生該clock singal固定倍數的頻率。
:::success
Discovery kit with STM32F429ZI MCU 的Timing簡述如下:
- 內建RC震盪器(HSI: High Speed Internal clock): 16MHz (1% accuracy),搭配PLL
- 外接RC震盪器(HSE: High Speed External clock): 8MHz
- 低速內建時鐘(LSI: Low Speed Internal clock): RC震盪器,32kHz,給Watchdog用。
- 低速外部時鐘(LSE: Low Speed External clock): 為石英震盪器,32.768kHz,主要給RTC用。
- PLL: PLL_M為divider設定(最多為8,輸出頻率即8MHz/8=1MHz),PLL_N為倍頻倍數(最多為360,不過一般來說沒法設那麼多)
- CPU(ARM 32-bit Cortex-M4) 時脈 : 由外頻(HSI或HSE)與倍頻(PLL)決定。官方說法最高為180MHz(沒超頻過不知道行不行,不過既然RPi都能超頻了這沒道理不行)。
至於clock tree則如下圖,

其行為大略描述為:
- f(vco clock) = f(HSI or HSE) $\times$ PLL_N / PLL_M 。根據手冊f(vco clock)建議設為1-2MHz。
- f(system clock) = f(vco clock) / PLL_P
- f(USB OTG FS, SDIO, RNG clock output) = f(system clock)/PLL_Q。此值根據USB標準為48MHz。
System clock提供所有週邊或外設的clock source。根據stm32f429i discovery其建議設定為180MHz。以下為可能設定方式:

System clock透過AHB(Advanced High-performance Bus)與APB(Advanced Peripheral Bus)提供clock給週邊或外設。AHB是給RAM或DMA等高速裝置,APB就是給低速外設。給RAM用的clock為HCLK,是system clock經過AHB divider後得到的值。
:::
## D.3: Setup
STM32F429使用Flexible Memory Controller(FMC)來管理SRAM、SDRAM、NOR FLASH以及NAND FLSAH等。以下為初始化的過程(不是完全懂,勉強讀):
```C
void BSP_SDRAM_Init(void)
{
/* SDRAM device configuration */
SdramHandle.Instance = FMC_SDRAM_DEVICE;
/* FMC Configuration -------------------------------------------------------*/
/* FMC SDRAM Bank configuration */
/* Timing configuration for 90 Mhz of SD clock frequency (180Mhz/2) */
/* TMRD: load register 與開始執行refresh或column指令之間需要兩個Clock cycles 的延遲 */
Timing.LoadToActiveDelay = 2;
/* TXSR: min=70ns (7x11.11ns) ,為refresh後需等待的時間 */
Timing.ExitSelfRefreshDelay = 7;
/* TRAS: min=42ns (4x11.11ns) max=120k (ns) ,column指令後,過了這段時間才能refresh */
Timing.SelfRefreshTime = 4;
/* TRC: min=70 (7x11.11ns) ,兩個column指令間的間隔時間,或兩個refresh之間的間隔時間 */
Timing.RowCycleDelay = 7;
/* TWR: min=1+ 7ns (1+1x11.11ns) ,寫入命令和refresh之間的延遲 */
Timing.WriteRecoveryTime = 2;
/* TRP: 20ns => 2x11.11ns,refresh與其它命令間的延遲 */
Timing.RPDelay = 2;
/* TRCD: 20ns => 2x11.11ns ,column指令到row指令間的延遲*/
Timing.RCDDelay = 2;
/* FMC SDRAM control configuration , 選擇FMC mapping的SDRAM區域*/
SdramHandle.Init.SDBank = FMC_SDRAM_BANK2;
/* Row addressing: [7:0] ,SDRAM的列地址寬度*/
SdramHandle.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8;
/* Column addressing: [11:0] ,SDRAM的行地址寬度*/
SdramHandle.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12;
/* SDRAM的資料寬度 */
SdramHandle.Init.MemoryDataWidth = SDRAM_MEMORY_WIDTH;
/* SDRAM的L-Bank 數目 */
SdramHandle.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
/* CASLatency,發出讀取命令後與得到資料時間之間的延遲 */
SdramHandle.Init.CASLatency = SDRAM_CAS_LATENCY;
/* 是否能使用保護模式 */
SdramHandle.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
/* 同步時鐘參數 */
SdramHandle.Init.SDClockPeriod = SDCLOCK_PERIOD;
SdramHandle.Init.ReadBurst = SDRAM_READBURST;
SdramHandle.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1;
/* SDRAM controller initialization */
MspInit();
HAL_SDRAM_Init(&SdramHandle, &Timing);
/* SDRAM initialization sequence */
BSP_SDRAM_Initialization_sequence(REFRESH_COUNT);
}
void BSP_SDRAM_Initialization_sequence(uint32_t RefreshCount)
{
__IO uint32_t tmpmrd =0;
/* Step 1: Configure a clock configuration enable command */
Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
Command.AutoRefreshNumber = 1;
Command.ModeRegisterDefinition = 0;
/* Send the command */
HAL_SDRAM_SendCommand(&SdramHandle, &Command, SDRAM_TIMEOUT);
/* Step 2: Insert 100 us minimum delay */
/* Inserted delay is equal to 1 ms due to systick time base unit (ms) */
HAL_Delay(1);
/* Step 3: Configure a PALL (precharge all) command */
Command.CommandMode = FMC_SDRAM_CMD_PALL;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
Command.AutoRefreshNumber = 1;
Command.ModeRegisterDefinition = 0;
/* Send the command */
HAL_SDRAM_SendCommand(&SdramHandle, &Command, SDRAM_TIMEOUT);
/* Step 4: Configure an Auto Refresh command */
Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
Command.AutoRefreshNumber = 4;
Command.ModeRegisterDefinition = 0;
/* Send the command */
HAL_SDRAM_SendCommand(&SdramHandle, &Command, SDRAM_TIMEOUT);
/* Step 5: Program the external memory mode register */
tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 |
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |
SDRAM_MODEREG_CAS_LATENCY_3 |
SDRAM_MODEREG_OPERATING_MODE_STANDARD |
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
Command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
Command.AutoRefreshNumber = 1;
Command.ModeRegisterDefinition = tmpmrd;
/* Send the command */
HAL_SDRAM_SendCommand(&SdramHandle, &Command, SDRAM_TIMEOUT);
/* Step 6: Set the refresh rate counter */
/* Set the device refresh rate */
HAL_SDRAM_ProgramRefreshRate(&SdramHandle, RefreshCount);
}
```
## 參考資料:
- [[技術] SDRAM研究心得](http://lazyflai.blogspot.fr/2010/09/sdram.html)
- [SDRAM](https://www.pctechguide.com/computer-memory/sdram)
- [Memory Controller](http://ivanboyblog.blogspot.fr/2015/08/memory-controller.html)
- [基于mini2440开发板学习S3C2440的内存控制器 ](http://unicornx.github.io/2015/07/28/20150728-mini2440-sdram/)
- [mini2440 貌似复杂的mmu](http://www.programgo.com/article/35455438413/;jsessionid=5D282F08BD28D23AEE09237BD426D6D9)
- [S3C2440的MMU](http://www.cnblogs.com/dudu1990/p/3406265.html)
- [Scalable Many-Core Memory Systems Topic 1: DRAM Basics and DRAM Scaling Prof. Onur Mutlu HiPEAC ACACES Summer.](http://slideplayer.com/slide/7534141/)
- [时钟,分频与倍频](http://www.enixyu.com/blog/2015/10/25/ee-clock-and-pll/)
- [How to properly set clock speed for STM32F4xx devices](https://stm32f4-discovery.net/2015/01/properly-set-clock-speed-stm32f4xx-devices/)
- [第26章 FMC—扩展外部SDRAM](http://www.cnblogs.com/firege/p/5805812.html)
- [Initialization of ILI9341 LCD and STMPE811 TS for STM32F4](http://scaluza.com/stm32f4/getting_started/initialization-of-ili9341-lcd-and-stmpe811-ts-for-stm32f4/#.WGVFpWeVvZs)
- [stm32f407时钟配置方法(感觉很好,分享一下)](http://blog.chinaunix.net/uid-27680183-id-3784602.html)
- [关于STM32里PLL时钟配置PLL_M PLL_N PLL_P PLL_Q](http://blog.sina.com.cn/s/blog_9361c9220102vf5c.html)
- [ [原创] 强仔教你玩stm32f401 Nucleo之系统时钟的配置 ](http://www.eeboard.com/bbs/thread-38051-1-1.html)
- [详解】SDRAM的地址映射方式BRC(Bank Row Column)和RBC(Row Bank Column)](http://www.crifan.com/detailed_sdram_address_mapping_brc_bank_row_column_and_rbc_row_bank_column/)
- [RM0090](http://l.facebook.com/l.php?u=http%3A%2F%2Fwww.st.com%2Fresource%2Fen%2Freference_manual%2FDM00031020.pdf&h=GAQHzgl3O)
- Discovery kit with STM32F429ZI MCU User manual : UM1670, UM1662