板子使用的是米联客的XC7A35TFGG484-2的开发板,上面带有256MB的型号为Micron MT41K128M16的DDR3内存。板子上的V4引脚上接了50MHz的晶振。
用MIG核来驱动这片DDR3内存。DDR3的运行时钟Clock Period为400MHz(由MIG核自己产生这个时钟,从ddr3_ck_p和ddr3_ck_n引脚输出出来,用来驱动DDR3):
因为PHY to Controller Clock Ratio为4:1,所以MIG核输出的ddr3_ui_clk时钟是400MHz进行四分频后得到的100MHz时钟。ddr3_ui_clk_sync_rst为低电平时,表示ddr3_ui_clk时钟已稳定,可以使用。
PLL Input Clock Period(sys_clk_i)为200MHz:
sys_clk_i和clk_ref_i都配置为No Buffer,然后在代码中都共用同一个200MHz的时钟:
.sys_clk_i(clock200),
.clk_ref_i(clock200),
最后在综合的时候,选择Out of context per IP的方式:
这个200MHz的时钟由Clock Wizard核产生,使用MMCM模式,从外部晶振输入50MHz(clock),倍频后输出200MHz(clock200):
wire clock200;
wire locked;
clk_wiz_0 clk_wiz_0(
.reset(0),
.clk_in1(clock),
.clk_out1(clock200),
.locked(locked)
);
当clock200时钟稳定时,locked信号将变为高电平。这个信号可用作其他系统的复位信号。
配置好之后,编译代码,在Implementation阶段,提示:
[Place 30-575] Sub-optimal placement for a clock-capable IO pin and MMCM pair. If this sub optimal condition is acceptable for this design, you may use the CLOCK_DEDICATED_ROUTE constraint in the .xdc file to demote this message to a WARNING. However, the use of this override is highly discouraged. These examples can be used directly in the .xdc file to override this clock rule.
< set_property CLOCK_DEDICATED_ROUTE BACKBONE [get_nets clk_wiz_0/inst/clk_in1_clk_wiz_0] >
clk_wiz_0/inst/clkin1_ibufg (IBUF.O) is locked to IOB_X1Y26
clk_wiz_0/inst/mmcm_adv_inst (MMCME2_ADV.CLKIN1) is provisionally placed by clockplacer on MMCME2_ADV_X1Y1
The above error could possibly be related to other connected instances. Following is a list of
all the related clock rules and their respective instances.
Clock Rule: rule_mmcm_bufg
Status: PASS
Rule Description: An MMCM driving a BUFG must be placed on the same half side (top/bottom) of the device
clk_wiz_0/inst/mmcm_adv_inst (MMCME2_ADV.CLKFBOUT) is provisionally placed by clockplacer on MMCME2_ADV_X1Y1
and clk_wiz_0/inst/clkf_buf (BUFG.I) is provisionally placed by clockplacer on BUFGCTRL_X0Y31
研究了一下午,最后在Xilinx官方的ug586_7Series_MIS手册里面发现:
这说明MIG核的时钟是可以用PLL或MMCM倍频输出的时钟的,不一定非要用引脚上接的晶振提供的时钟。配置后需要在自己的引脚约束文件*.xdc中加入下面这句话(从错误提示里面复制出来):
加了这句话之后,编译就可以通过了!这跟之前选的Global还是Out of context per IP没有一点关系。
下载到板子上运行,先写数据,然后是可以读出来的!
测试程序如下:
【main.v】
module main(
input clock,
inout [15:0] ddr3_dq,
inout [1:0] ddr3_dqs_n,
inout [1:0] ddr3_dqs_p,
output [13:0] ddr3_addr,
output [2:0] ddr3_ba,
output ddr3_ras_n,
output ddr3_cas_n,
output ddr3_we_n,
output ddr3_reset_n,
output ddr3_ck_p,
output ddr3_ck_n,
output ddr3_cke,
output ddr3_cs_n,
output [1:0] ddr3_dm,
output ddr3_odt,
output [3:0] leds,
output uart_tx
);
wire clock200;
wire locked;
clk_wiz_0 clk_wiz_0(
.reset(0),
.clk_in1(clock),
.clk_out1(clock200),
.locked(locked)
);
reg [27:0] ddr3_app_addr;
reg [2:0] ddr3_app_cmd;
reg ddr3_app_en;
reg [15:0] ddr3_app_wdf_data;
reg ddr3_app_wdf_end;
reg ddr3_app_wdf_wren;
wire [15:0] ddr3_app_rd_data;
wire ddr3_app_rd_data_end;
wire ddr3_app_rd_data_valid;
wire ddr3_app_rdy;
wire ddr3_app_wdf_rdy;
wire ddr3_app_sr_active;
wire ddr3_app_ref_ack;
wire ddr3_app_zq_ack;
wire ddr3_ui_clk;
wire ddr3_ui_clk_sync_rst;
wire ddr3_init_calib_complete;
wire [11:0] ddr3_device_temp;
mig_7series_0 mig_7series_0(
.ddr3_dq(ddr3_dq),
.ddr3_dqs_n(ddr3_dqs_n),
.ddr3_dqs_p(ddr3_dqs_p),
.ddr3_addr(ddr3_addr),
.ddr3_ba(ddr3_ba),
.ddr3_ras_n(ddr3_ras_n),
.ddr3_cas_n(ddr3_cas_n),
.ddr3_we_n(ddr3_we_n),
.ddr3_reset_n(ddr3_reset_n),
.ddr3_ck_p(ddr3_ck_p),
.ddr3_ck_n(ddr3_ck_n),
.ddr3_cke(ddr3_cke),
.ddr3_cs_n(ddr3_cs_n),
.ddr3_dm(ddr3_dm),
.ddr3_odt(ddr3_odt),
.sys_clk_i(clock200),
.clk_ref_i(clock200),
.app_addr(ddr3_app_addr),
.app_cmd(ddr3_app_cmd),
.app_en(ddr3_app_en),
.app_wdf_data(ddr3_app_wdf_data),
.app_wdf_end(ddr3_app_wdf_end),
.app_wdf_mask(2'b00),
.app_wdf_wren(ddr3_app_wdf_wren),
.app_rd_data(ddr3_app_rd_data),
.app_rd_data_end(ddr3_app_rd_data_end),
.app_rd_data_valid(ddr3_app_rd_data_valid),
.app_rdy(ddr3_app_rdy),
.app_wdf_rdy(ddr3_app_wdf_rdy),
.app_sr_req(1'b0),
.app_ref_req(1'b0),
.app_zq_req(1'b0),
.app_sr_active(ddr3_app_sr_active),
.app_ref_ack(ddr3_app_ref_ack),
.app_zq_ack(ddr3_app_zq_ack),
.ui_clk(ddr3_ui_clk),
.ui_clk_sync_rst(ddr3_ui_clk_sync_rst),
.init_calib_complete(ddr3_init_calib_complete),
.device_temp(ddr3_device_temp),
.sys_rst(locked)
);
wire uart_tx_request;
wire [7:0] uart_tx_data;
wire uart_tx_ready;
wire uart_sent;
UARTTransmitter uart_transmitter(clock, uart_tx, 24'd115200, uart_tx_request, uart_tx_data, uart_tx_ready, uart_sent);
reg uart_bytearray_tx_mode;
reg [15:0] uart_bytearray_tx_data;
reg uart_bytearray_tx_request = 0;
reg [7:0] uart_bytearray_tx_size;
wire uart_bytearray_tx_ready;
wire uart_bytearray_sent;
ByteArrayTransmitter #(2) uart_bytearray_transmitter(clock, uart_tx_request, uart_tx_data, uart_tx_ready, uart_sent,
uart_bytearray_tx_mode, uart_bytearray_tx_request, uart_bytearray_tx_data, uart_bytearray_tx_size, uart_bytearray_tx_ready, uart_bytearray_sent);
reg [3:0] state;
//assign leds = {locked, ddr3_ui_clk_sync_rst, ddr3_app_rdy, ddr3_app_wdf_rdy};
assign leds = state;
always @(posedge ddr3_ui_clk, posedge ddr3_ui_clk_sync_rst) begin
if (ddr3_ui_clk_sync_rst) begin
ddr3_app_en <= 0;
ddr3_app_wdf_wren <= 0;
state <= 0;
end
else begin
case (state)
0: begin
if (ddr3_app_en == 0) begin
if (ddr3_app_rdy && ddr3_app_wdf_rdy) begin
ddr3_app_cmd <= 0;
ddr3_app_addr <= 0;
ddr3_app_en <= 1;
ddr3_app_wdf_data <= 16'hfedc;
ddr3_app_wdf_wren <= 1;
ddr3_app_wdf_end <= 1;
end
end
else begin
ddr3_app_en <= 0;
ddr3_app_wdf_wren <= 0;
ddr3_app_wdf_end <= 0;
state <= 1;
end
end
1: begin
if (ddr3_app_en == 0) begin
if (ddr3_app_rdy) begin
ddr3_app_cmd <= 1;
ddr3_app_addr <= 0;
ddr3_app_en <= 1;
end
end
else if (ddr3_app_rd_data_valid) begin
uart_bytearray_tx_data <= ddr3_app_rd_data; // 读数据
ddr3_app_en <= 0;
state <= 2;
end
end
2: begin
if (uart_bytearray_tx_ready) begin
if (uart_bytearray_tx_request == 0) begin
uart_bytearray_tx_mode <= 0;
uart_bytearray_tx_size <= 2;
uart_bytearray_tx_request <= 1;
end
end
else
state <= 3;
end
3: begin
if (uart_bytearray_tx_ready) begin
// 字符串已发送
uart_bytearray_tx_request <= 0; // 关闭发送请求
state <= 4;
end
end
endcase
end
end
endmodule
【UARTTransmitter.v】
`include "config.vh"
module UARTTransmitter(
input clock, // 系统时钟
output reg tx = 1, // 串口发送引脚
input [23:0] baud_rate, // 波特率
input request, // 请求发送字符 (ready=1后应该及时撤销请求, 否则会再次发送同样的字符)
input [7:0] data, // 发送的字符内容 (ready=0时必须保持不变)
output ready, // 是否可以送入新字符
output sent // 是否发送完毕
);
integer counter = 0; // 波特率计数器
reg [3:0] bit_i = 10; // 当前正在发送第几位 (0为起始位, 1-8为数据位, 9为停止位, 10为空闲)
wire bit_start = (counter == 0); // 位开始信号
wire bit_sample = (counter == `SYSCLK / baud_rate / 2 - 1); // 接收端采样点信号
wire bit_end = (counter == `SYSCLK / baud_rate - 1); // 位结束信号
/*
ready引脚的真值表:
bit_i request | ready
-------------------------------------------------------------
0-8 X | 0 (正在发送起始位和数据位)
9 X | 1 (正在发送停止位, 允许送入新字符)
10 0 | 1 (空闲, 允许送入新字符)
10 1 | 0 (已请求发送字符, 但还没开始发送)
*/
assign ready = (bit_i == 9 || sent);
assign sent = (bit_i == 10 && !request);
always @(posedge clock) begin
if (bit_i <= 9) begin
if (bit_start) begin
counter <= 1;
if (bit_i == 0)
tx <= 0; // 起始位
else if (bit_i >= 1 && bit_i <= 8)
tx <= data[bit_i - 1]; // 数据位
else
tx <= 1; // 停止位
end
else if (bit_end) begin
counter <= 0;
if (bit_i == 9 && request)
bit_i <= 0; // 继续发送下一字符, 中间无停顿
else
bit_i <= bit_i + 1'b1; // 继续发送下一位, 或停止发送
end
else
counter <= counter + 1;
end
else if (request)
bit_i <= 0; // 开始发送
else
counter <= 0; // 空闲
end
endmodule
【ByteArrayTransmitter.v】
`define MAX_BIT (MAX_SIZE * 8 - 1)
module ByteArrayTransmitter #(
parameter MAX_SIZE = 16
)(
input clock,
output reg byte_request = 0,
output reg [7:0] byte,
input byte_ready,
input byte_sent,
input mode, // 0:二进制模式, 1:字符串模式
input request,
input [`MAX_BIT:0] data,
input [7:0] size,
output ready,
output sent
);
localparam STATE_LOAD = 0;
localparam STATE_REQUESTED = 1;
localparam STATE_SENDING = 2;
reg [`MAX_BIT:0] buffer;
reg [7:0] count = 0;
reg [1:0] state = STATE_LOAD;
assign ready = (count == 0 && ((byte_ready && !byte_sent) || sent));
assign sent = (byte_sent && !request);
always @(posedge clock) begin
case (state)
STATE_LOAD: begin
if (byte_ready) begin
if (count == size)
count <= 0; // 发送结束
else if ((count == 0 && byte_sent && request && size > 0 && size <= MAX_SIZE) || count != 0) begin
// 开始发送
if (count == 0)
buffer = data << (8 * (MAX_SIZE - size)); // 载入请求发送的数据, 并靠左对齐
else
buffer = buffer << 8; // 继续发送下一个字节
count <= count + 1'b1;
byte = buffer[`MAX_BIT:`MAX_BIT - 7];
if (mode == 0 || byte != 0) begin
byte_request <= 1;
state <= STATE_REQUESTED;
end
end
end
end
STATE_REQUESTED: begin
// 等待发送开始
if (!byte_ready)
state <= STATE_SENDING;
end
STATE_SENDING: begin
// 等待发送结束
if (byte_ready) begin
byte_request <= 0;
state <= STATE_LOAD;
end
end
endcase
end
endmodule
【config.vh】
`define SYSCLK 50000000 // 系统时钟频率
程序运行后,能够看到串口里面输出了FE DC,证明DDR3内存读写成功!
由于Test bench里面什么都没写,工程里面也没有引入DDR3的Simulation model,所以我们是无法仿真的,只能在真正的板子上调试程序。
【test.v】
module test();
reg clock = 0;
always begin
#10 clock = !clock;
end
main main(clock);
endmodule
运行仿真后,app_rdy信号和init_calib_complete信号一直为低电平。而在真正的板子上并不是这样。