對SDRAM基本概念的介紹以及芯片手冊說明,請參考上一篇文章SDRAM操作說明。
1. 說明
如圖所示為狀態機的簡化圖示,過程大概可以描述為:SDRAM(IS42S16320D)上電初始化完成后,進入“空閑”狀態,此時一直監控外部控制模塊給予的控制信號。初始化完成后,外部定時器開始定時,定時周期為SDRAM刷新周期(7.7us),一旦計數到刷新周期后,向狀態機發送auto_ref_req(自動刷新請求),此時狀態機進入“刷新”狀態,這樣就確保在無任何操作時,SDRAM能正常完成刷新。刷新完成后回到“空閑”狀態。
當處於空閑狀態時,接收到寫命令(wr_en),進入“寫”狀態(有效接收讀寫命令的時刻有特殊要求,后面再詳細說明),在full_page下連續寫600個數據(100MHz,恰好耗時6us多一點,這樣方便不用考慮定時刷新),寫完之后,發送wr_done命令,進入“刷新”狀態,相對於每次連續寫完成后,提前刷新一次。此時,定時刷新的計數器清零,重新開始計數。
讀多過程跟寫過程類似,讀完600個數據之后,手動完成刷新。
現在就來說一說,“空閑”狀態接收讀寫命令的特殊要求。理論上充電周期為7.8125us,為保證600次讀寫在充電周期內完成,並且前后預留一些其他命令的時間,所以推薦在0~1us這個時間內接受讀寫命令,這樣讀寫的時候專注讀寫就可以了。當然這是我的設計方式,如有更好的設計方式,那更好,歡迎分享。
2. 代碼實現
狀態機的代碼如下所示,清晰的描述了各狀態之間的跳變及其跳變條件。其中信號ctrl_valid即為上圖中命令有效期的時間區間。在各狀態描述的時序邏輯模塊中,只是產生了讀、寫或刷新執行模塊的使能信號,即在“寫”狀態的時候,使能寫模塊,完成相信的寫操作。
always @ (posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
current_status <= IDLE;
end
else if(init_ing == 1'b0)
begin
current_status <= next_status;
end
else
begin
current_status <= IDLE;
end
end
always @ (rst_n or current_status or sdram_wrreq or sdram_rdreq or ref_req_auto or wr_done or rd_done or ref_done or ctrl_valid)
begin
next_status = 5'dx;
case(current_status)
IDLE:
begin
if(ref_req_auto == 1'b1) //收到自動刷新請求
begin
next_status = AUTO_REF;
end
else if(ctrl_valid == 1'b1 && sdram_wrreq == 1'b1)//在讀寫控制有效區內收到寫請求
begin
next_status = WRITE;
end
else if(ctrl_valid == 1'b1 && sdram_rdreq == 1'b1) //在讀寫控制有效區內收到讀請求
begin
next_status = READ;
end
else
begin
next_status = IDLE;
end
end
WRITE:
begin
if(wr_done == 1'b1)
begin
next_status = AUTO_REF;
end
else
begin
next_status = WRITE;
end
end
READ:
begin
if(rd_done == 1'b1)
begin
next_status = AUTO_REF;
end
else
begin
next_status = READ;
end
end
AUTO_REF:
begin
if(ref_done == 1'b1)
begin
next_status = IDLE;
end
else
begin
next_status = AUTO_REF;
end
end
default:
begin
next_status = IDLE;
end
endcase
end
//各個狀態下的使能信號,以控制相應的模塊執行相應的操作
always @ (posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
wr_start <= 1'b0;
rd_start <= 1'b0;
ref_start <= 1'b0;
end
else
begin
case(next_status)
IDLE:
begin
wr_start <= 1'b0;
rd_start <= 1'b0;
ref_start <= 1'b0;
end
WRITE:
begin
wr_start <= 1'b1;
rd_start <= 1'b0;
ref_start <= 1'b0;
end
READ:
begin
wr_start <= 1'b0;
rd_start <= 1'b1;
ref_start <= 1'b0;
end
AUTO_REF:
begin
wr_start <= 1'b0;
rd_start <= 1'b0;
ref_start <= 1'b1;
end
default:
begin
wr_start <= 1'b0;
rd_start <= 1'b0;
ref_start <= 1'b0;
end
endcase
end
end
以下給出寫操作模塊的部分代碼,讀操作和刷新同理。中間有些信號是我工程需要,參考一下思路即可。
always @(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
cke_wr <= 1'b0;
cmd_wr <= NOP;
dqm_wr <= DQM_DIS;
bank_addr_wr <= BANK0;
addr_wr <= DONT_CARE_ADDR;
wr_done <= 1'b0;
wr_first_flag_r <= 1'b0;
status_wr <= 4'd0;
end
else if(wr_start == 1'b1)
begin
case(status_wr)
4'd0:
begin
cke_wr <= 1'b1;
cmd_wr <= NOP;
dqm_wr <= DQM_EN;
bank_addr_wr <= BANK0;
addr_wr <= DONT_CARE_ADDR;
wr_done <= 1'b0;
wr_first_flag_r <= 1'b0;
status_wr <= status_wr + 4'd1;
end
4'd1:
begin
cke_wr <= 1'b1;
cmd_wr <= ACT;
dqm_wr <= DQM_EN;
bank_addr_wr <= BANK0;
addr_wr <= row_addr; //行地址
wr_done <= 1'b0;
wr_first_flag_r <= 1'b0;
status_wr <= status_wr + 4'd1;
end
4'd2: //4'd2和4'd3是為了延時T_RCD,即兩個時鍾
begin
cke_wr <= 1'b1;
cmd_wr <= NOP;
dqm_wr <= DQM_EN;
bank_addr_wr <= BANK0;
addr_wr <= DONT_CARE_ADDR;
wr_done <= 1'b0;
wr_first_flag_r <= 1'b0;
status_wr <= status_wr + 4'd1;
end
4'd3:
begin
cke_wr <= 1'b1;
cmd_wr <= NOP;
dqm_wr <= DQM_EN;
bank_addr_wr <= BANK0;
addr_wr <= DONT_CARE_ADDR;
wr_done <= 1'b0;
wr_first_flag_r <= 1'b0;
status_wr <= status_wr + 4'd1;
end
4'd4:
begin
cke_wr <= 1'b1;
cmd_wr <= NOP;
dqm_wr <= DQM_EN;
bank_addr_wr <= BANK0;
addr_wr <= DONT_CARE_ADDR;
wr_done <= 1'b0;
wr_first_flag_r <= 1'b1; //用於寫入第一個數據的時序標記
status_wr <= status_wr + 4'd1;
end
4'd5:
begin
cke_wr <= 1'b1;
cmd_wr <= WR;
dqm_wr <= DQM_EN;
bank_addr_wr <= BANK0;
addr_wr <= column_addr; //{A12A11,A10,column_address}
wr_done <= 1'b0;
wr_first_flag_r <= 1'b0;
status_wr <= status_wr + 4'd1;
end
4'd6:
begin
if(sdram_wr_done == 1'b1) //用於增加NOP持續周期
begin
cke_wr <= 1'b1;
cmd_wr <= NOP;
dqm_wr <= DQM_DIS;
bank_addr_wr <= BANK0;
addr_wr <= DONT_CARE_ADDR;
wr_done <= 1'b1;
wr_first_flag_r <= 1'b0;
status_wr <= status_wr + 4'd1;
end
else
begin
cke_wr <= 1'b1;
cmd_wr <= NOP;
dqm_wr <= DQM_EN;
bank_addr_wr <= BANK0;
addr_wr <= DONT_CARE_ADDR;
wr_done <= 1'b0;
wr_first_flag_r <= 1'b0;
status_wr <= status_wr;
end
end
4'd7:
begin
cke_wr <= 1'b1;
cmd_wr <= NOP;
dqm_wr <= DQM_DIS;
bank_addr_wr <= BANK0;
addr_wr <= DONT_CARE_ADDR;
wr_done <= 1'b0;
wr_first_flag_r <= 1'b0;
status_wr <= 4'd0;
end
default:
begin
cke_wr <= 1'b1;
cmd_wr <= NOP;
dqm_wr <= DQM_EN;
bank_addr_wr <= BANK0;
addr_wr <= DONT_CARE_ADDR;
wr_done <= 1'b0;
wr_first_flag_r <= 1'b0;
status_wr <= 4'd0;
end
endcase
end
else
begin
cke_wr <= 1'b1;
cmd_wr <= NOP;
dqm_wr <= DQM_EN;
bank_addr_wr <= BANK0;
addr_wr <= DONT_CARE_ADDR;
wr_done <= 1'b0;
wr_first_flag_r <= 1'b0;
status_wr <= 4'd0;
end
end