第四章 存储控制器顶层架构设计

存储控制器的顶层架构,说白了就是决定怎么把各个功能模块拼起来。我做了这么多年芯片设计,发现很多新手容易一上来就写代码,结果写到一半发现接口对不上、时钟域乱套,最后只能推倒重来。嗯,这节课我们就聊聊顶层架构该怎么搭。

4.1 控制器模块划分

我个人习惯把存储控制器分成三个层次:PHY接口层、命令调度层、数据通路层。这三层各司其职,互不干扰。

4.1.1 PHY接口层

PHY接口层是离物理层最近的一层。它负责处理DDR PHY的初始化、训练、以及时序校准。我在项目中遇到过一个问题:PHY的延迟校准没做好,导致数据采样点偏移,跑起来偶尔出错。后来花了整整两天才定位到问题。

这一层主要包含:

  • PHY初始化状态机
  • 延迟锁定环(DLL)控制
  • 写数据输出和读数据采样
  • ODT(片上端接)控制
注意:PHY接口层的时序非常敏感。我曾经因为忽略了PHY的写延迟参数,导致数据在DDR颗粒上写不进去。建议在仿真阶段就加上PHY的延迟模型。

4.1.2 命令调度层

命令调度层是存储控制器的「大脑」。它接收来自总线接口的请求,然后决定先执行哪个命令。你想想看,如果同时来了读和写请求,先处理哪个?这里就有讲究了。

我一般把命令调度层分成:

  • 命令队列(Command Queue)
  • 仲裁器(Arbiter)
  • 刷新控制器(Refresh Controller)
  • 时序参数检查器(Timing Checker)

仲裁策略我建议用优先级+轮询的方式。为什么?因为纯优先级会导致低优先级请求饿死,纯轮询又无法保证紧急请求的响应。折中一下,效果最好。

4.1.3 数据通路层

数据通路层负责数据的搬移和缓冲。说白了就是数据从总线到DDR颗粒之间的「高速公路」。

这一层的关键模块:

  • 写数据缓冲(Write Data Buffer)
  • 读数据缓冲(Read Data Buffer)
  • ECC校验模块(如果支持ECC)
  • 数据掩码处理(Data Mask)
小技巧:数据通路的宽度要和总线宽度匹配。比如AXI总线是128位,DDR颗粒是16位,那就需要做位宽转换。我习惯用双端口RAM来做这个转换,简单可靠。

4.2 AXI/AHB总线接口设计

总线接口是存储控制器和SoC其他模块通信的桥梁。现在主流的总线协议是AXI和AHB。我个人更推荐AXI,因为它支持乱序传输和突发传输,性能更好。

AXI接口的关键信号:

信号组 方向 说明
AW通道 Master→Slave 写地址通道
W通道 Master→Slave 写数据通道
B通道 Slave→Master 写响应通道
AR通道 Master→Slave 读地址通道
R通道 Slave→Master 读数据通道

设计AXI接口时,我最常踩的坑是握手信号的时序。AXI的VALID和READY信号必须满足一定的依赖关系,否则会死锁。我曾经在一个项目中,因为READY信号拉高的时机不对,导致总线卡死,整个系统都跑不动。

下面是一个简单的AXI写地址通道的Verilog代码示例:

// AXI写地址通道示例
always @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    aw_ready <= 1'b0;
    aw_addr_reg <= 32'b0;
  end else begin
    // 当VALID有效且READY为高时,握手成功
    if (aw_valid && aw_ready) begin
      aw_addr_reg <= aw_addr;
      aw_ready <= 1'b0;  // 拉低READY,等待下一个请求
    end else begin
      aw_ready <= 1'b1;  // 准备好接收地址
    end
  end
end
重点:AXI的握手协议是「双向握手机制」。VALID和READY谁先有效都可以,但数据必须在两者都有效时才能传输。这个细节很多新手会忽略。

4.3 时钟域与复位策略

时钟域和复位策略,是存储控制器设计中最容易出问题的地方。我见过太多因为时钟域没处理好导致的亚稳态问题。

4.3.1 时钟域划分

存储控制器通常有多个时钟域:

  • 总线时钟域(比如200MHz的AXI时钟)
  • 控制器核心时钟域(比如400MHz的DDR时钟)
  • PHY时钟域(比如800MHz的DDR PHY时钟)

不同时钟域之间传输数据,必须用异步FIFO或者双触发器同步。我习惯用异步FIFO来处理大批量数据的跨时钟域传输,用双触发器来处理控制信号的跨时钟域传输。

4.3.2 复位策略

复位策略有两种:同步复位和异步复位。我个人更推荐异步复位、同步释放的方式。为什么?因为异步复位可以保证复位信号立即生效,而同步释放可以避免复位释放时的亚稳态问题。

下面是一个异步复位、同步释放的Verilog实现:

// 异步复位、同步释放
reg rst_sync1, rst_sync2;

always @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    rst_sync1 <= 1'b0;
    rst_sync2 <= 1'b0;
  end else begin
    rst_sync1 <= 1'b1;
    rst_sync2 <= rst_sync1;
  end
end

// 使用rst_sync2作为内部复位信号
always @(posedge clk or negedge rst_sync2) begin
  if (!rst_sync2) begin
    // 复位逻辑
  end else begin
    // 正常工作逻辑
  end
end
避坑指南:我曾经在一个项目中,因为复位信号没有做同步处理,导致复位释放后,部分寄存器进入了不确定状态。从那以后,我所有的跨时钟域复位信号都加了同步器。

4.4 架构图展示

下面我用一张SVG图来展示存储控制器的顶层架构。这张图把三个层次、总线接口、时钟域和复位策略都画出来了。

存储控制器顶层架构图 AXI/AHB总线接口 命令调度层 命令队列 仲裁器 刷新控制器 时序参数检查器 数据通路层 写数据缓冲 读数据缓冲 ECC校验 数据掩码处理 PHY接口层 时钟域边界 复位域边界

这张图里,我特意把时钟域和复位域的边界标出来了。你想想看,如果数据从总线接口直接穿到PHY接口层,中间没有经过同步处理,那亚稳态问题就来了。所以每一层之间都要做跨时钟域处理。

我的经验:架构设计阶段多花点时间画图,后面写代码能省一半时间。我每次做新项目,都会先画一张类似的架构图,然后贴在工位上。写代码的时候看一眼,就知道当前模块该连到哪。

好了,这节课的内容就到这里。存储控制器的顶层架构,说白了就是三个层次加总线接口,再加上时钟域和复位的处理。把这些想清楚了,后面的RTL设计就水到渠成了。

专注资料整理