在Vivado 2020.1中用MIG核读写DDR3内存,编译代码时提示Sub-optimal placement错误的解决办法

本文转载自:巨大八爪鱼的博客

板子使用的是米联客的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没有一点关系。

下载到板子上运行,先写数据,然后是可以读出来的!

测试程序如下:

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信号一直为低电平。而在真正的板子上并不是这样。

最新文章