我们前面观察了下利用ip核读写ddr3芯片,这一节我们对上一节代码进行初步解析,以起到理解mig ip核的作用。
1.我们看到代码分为三个模块,top层封装了mem_burst和mem_test及ip核。我们首先看看mem_test部分代码:
module mem_test
#(
parameter MEM_DATA_BITS = 64,
parameter ADDR_BITS = 24
)
(
input rst, /*复位*/
input mem_clk, /*接口时钟*/
output reg rd_burst_req, /*读请求*/
output reg wr_burst_req, /*写请求*/
output reg[9:0] rd_burst_len, /*读数据长度*/
output reg[9:0] wr_burst_len, /*写数据长度*/
output reg[ADDR_BITS - 1:0] rd_burst_addr, /*读首地址*/
output reg[ADDR_BITS - 1:0] wr_burst_addr, /*写首地址*/
input rd_burst_data_valid, /*读出数据有效*/
input wr_burst_data_req, /*写数据信号*/
input[MEM_DATA_BITS - 1:0] rd_burst_data, /*读出的数据*/
output[MEM_DATA_BITS - 1:0] wr_burst_data, /*写入的数据*/
input rd_burst_finish, /*读完成*/
input wr_burst_finish, /*写完成*/
output reg error
);
localparam IDLE = 3'd0;
localparam MEM_READ = 3'd1;
localparam MEM_WRITE = 3'd2;
reg[2:0] state;
reg[7:0] wr_cnt;
reg[MEM_DATA_BITS - 1:0] wr_burst_data_reg;
assign wr_burst_data = wr_burst_data_reg;
reg[7:0] rd_cnt;
always@(posedge mem_clk or posedge rst)
begin
if(rst)
error <= 1'b0;
else if((state == MEM_READ) && rd_burst_data_valid && (rd_burst_data != {(MEM_DATA_BITS/8){rd_cnt}}))
error <= 1'b1;
else
error <= error;//写入数据和读出数据不一致
end
always@(posedge mem_clk or posedge rst)
begin
if(rst)
begin
wr_burst_data_reg <= {MEM_DATA_BITS{1'b0}};
wr_cnt <= 8'd0;
end
else if(state == MEM_WRITE)
begin
if(wr_burst_data_req)
begin
wr_burst_data_reg <= {(MEM_DATA_BITS/8){wr_cnt}};//写入数据
wr_cnt <= wr_cnt + 8'd1;
end
else if(wr_burst_finish)
wr_cnt <= 8'd0;
end
end
//读数据
always@(posedge mem_clk or posedge rst)
begin
if(rst)
begin
rd_cnt <= 8'd0;
end
else if(state == MEM_READ)
begin
if(rd_burst_data_valid)
begin
rd_cnt <= rd_cnt + 8'd1;
end
else if(rd_burst_finish)
rd_cnt <= 8'd0;
end
else
rd_cnt <= 8'd0;
end
//状态机
always@(posedge mem_clk or posedge rst)
begin
if(rst)
begin
state <= IDLE;
wr_burst_req <= 1'b0;
rd_burst_req <= 1'b0;
rd_burst_len <= 10'd128;//数据长度
wr_burst_len <= 10'd128;
rd_burst_addr <= 0;
wr_burst_addr <= 0;
end
else
begin
case(state)
IDLE:
begin
state <= MEM_WRITE;
wr_burst_req <= 1'b1;
wr_burst_len <= 10'd128;
end
MEM_WRITE:
begin
if(wr_burst_finish)
begin
state <= MEM_READ;
wr_burst_req <= 1'b0;
rd_burst_req <= 1'b1;
rd_burst_len <= 10'd128;
rd_burst_addr <= wr_burst_addr;//地址
end
end
MEM_READ:
begin
if(rd_burst_finish)
begin
state <= MEM_WRITE;
wr_burst_req <= 1'b1;
wr_burst_len <= 10'd128;
rd_burst_req <= 1'b0;
wr_burst_addr <= wr_burst_addr + 128;
end
end
default:
state <= IDLE;
endcase
end
end
endmodule
2.我们看到82-124行这个状态机主要产生读写请求,读写长度和读写地址。程序进入先进入idle状态确定了写请求和写长度,再进入写数据状态,在写数据状态等待写数据完成,如果没有写完wr burst reg=1,wr burst len=128,如果wr_burst_finish进入读数据状态,这是rd_burst_req=1,rd_burst_len <= 10’d128;意思就是读取128个数据,读完后写地址增加128,。总的流程就是写入128个数据,独处128个数据,地址增加128,继续写入读出,并且将读出数据与写入数据做对比。
3.我们看到32-38行就是将读出的数据与写入的数据进行比较,如果不一样会拉高电平,点亮led灯。
39-55行产生了需要写入ddr3的值。58-74是一个读数据计数器。这里重点讲下37行,{(MEM_DATA_BITS/8){rd_cnt}}中MEM_DATA_BITS=512,所以也就等效于{64{rd_cnt}},意思就是64个rd_cnt,rd_cnt是8位数据,所以{64{rd_cnt}}就是将rd_cnt复制64次连在一起,一共512位。
顺便问下为什么我们写入数据是512位而不是其他?这个就要结合我们上一节时钟结构了,我们ddr3接口传输数据速率位800M2=1600M,我们逻辑代码使用mig ip给的ui时钟位200M,1600/200=8,也就说ddr3接口处数据速率是我们逻辑代码处速率的8倍,如果速率要一样,那么接口处位宽为64位,我们只需要给一个648=512位的数据,速率就一样了。这就是512位数据位宽的由来。
4.我们再看看mem_burst模块
可以看到,这个状态机主要是和mig ip接口进行数据交互,接收到写数据请求后进入写数据状态,由于写数据与写地址及指令是分开的,所以写数据和写地址同时进行。
MEM_WRITE:
begin
if(app_rdy)
begin
app_addr_r <= app_addr_r + 'b1000;
if(wr_addr_cnt == wr_burst_len - 1)
begin
app_wdf_end_r <= 1'b0;
app_en_r <= 1'b0;
end
else
begin
wr_addr_cnt <= wr_addr_cnt + 1;
end
end
if(wr_burst_data_req)
begin
if(wr_data_cnt == wr_burst_len - 1)
begin
state <= MEM_WRITE_WAIT;
end
else
begin
wr_data_cnt <= wr_data_cnt + 1;
end
end
end
如果数据写入128个就进入MEM_WRITE_WAIT,继续写地址,直到写地址完成条入到idle状态。
同理,收到读取指令时进入到读数据状态,一边写入地址,一百年读取数据,如果写入地址完成读取数据未完成则在MEM_READ_WAIT状态中继续读取数据,直到读完数据后进入idle状态。
MEM_READ:
begin
if(app_rdy)
begin
app_addr_r <= app_addr_r + 8;
if(rd_addr_cnt == rd_burst_len - 1)
begin
state <= MEM_READ_WAIT;
rd_addr_cnt <= 0;
app_en_r <= 1'b0;
end
else
rd_addr_cnt <= rd_addr_cnt + 1;
end
if(app_rd_data_valid)
begin
if(rd_data_cnt == rd_burst_len - 1)
begin
rd_data_cnt <= 0;
state <= READ_END;
end
else
begin
rd_data_cnt <= rd_data_cnt + 1;
end
end
end
MEM_READ_WAIT:
begin
if(app_rd_data_valid)
begin
if(rd_data_cnt == rd_burst_len - 1)
begin
rd_data_cnt <= 0;
state <= READ_END;
end
else
begin
rd_data_cnt <= rd_data_cnt + 1;
end
end
end
以上就是大概思路。mig ip接口端具体的信号及其含义如下:
具体的时序,我们后面有时间进行挨个仿真测试。