ZYNQ 软硬件协同踩坑日记:PS写BRAM后,PL端连续4个地址读出相同数据的原因与解决办法

ZYNQ 软硬件协同踩坑日记:PS写BRAM后,PL端连续4个地址读出相同数据的原因与解决办法

文章目录

  • [ZYNQ 软硬件协同踩坑日记:PS写BRAM后,PL端连续4个地址读出相同数据的原因与解决办法](#ZYNQ 软硬件协同踩坑日记:PS写BRAM后,PL端连续4个地址读出相同数据的原因与解决办法)
      • [📝 背景与问题描述](#📝 背景与问题描述)
      • [🔍 深度剖析:字编址 vs 字节编址的冲突](#🔍 深度剖析:字编址 vs 字节编址的冲突)
        • [1. ARM 与 AXI 永远是"字节编址"](#1. ARM 与 AXI 永远是“字节编址”)
        • [2. AXI BRAM Controller 的强制干预](#2. AXI BRAM Controller 的强制干预)
        • [3. BRAM 底层是如何处理不对齐的地址的?](#3. BRAM 底层是如何处理不对齐的地址的?)
      • [🛠️ 解决办法](#🛠️ 解决办法)
        • [方案一:地址直接加 4(最直观)](#方案一:地址直接加 4(最直观))
        • [方案二:使用计数器左移 2 位(推荐,逻辑更优雅)](#方案二:使用计数器左移 2 位(推荐,逻辑更优雅))
      • [💡 总结](#💡 总结)

📝 背景与问题描述

在 ZYNQ 开发中,PS(ARM处理器)与 PL(FPGA逻辑)之间的数据交互是最核心的环节之一。最近在做一个项目,架构很简单:PS 端通过 AXI BRAM Controller 将数据写入双端口 BRAM(Port A),然后 PL 端自定义逻辑从 BRAM(Port B)将数据读出。

为了方便测试,我在运行 Linux 的 PS 端编写了如下 Shell 脚本,通过 devmem 命令直接向 BRAM 的物理地址写入 10 个递增的数据:

bash 复制代码
#!/bin/sh
BRAM_BASE=0x40000000
DATA_COUNT=10

echo "向 BRAM 基地址 $BRAM_BASE 写入 $DATA_COUNT 个 32 位数据..."

i=0
while [ $i -lt $DATA_COUNT ]; do
    # 计算当前地址:每次加 4 字节
    addr=$(printf "0x%X" $((BRAM_BASE + i*4)))
    data=$i
    devmem "$addr" 32 $data
    echo "BRAM[$addr] = $data"
    i=$((i+1))
done

脚本运行非常成功,PS 端正确地以 0x40000000, 0x40000004, 0x40000008 的地址步长写下了数据 0, 1, 2...

🚨 奇怪的现象出现了:

当我在 PL 端用 Verilog 编写状态机去读 BRAM(设置 BRAM 读出位宽为 32-bit),并且让读取地址每次 +1 时,读出来的数据却变成了这样:

  • 读地址 0 -> 读出数据 0
  • 读地址 1 -> 读出数据 0
  • 读地址 2 -> 读出数据 0
  • 读地址 3 -> 读出数据 0
  • 读地址 4 -> 读出数据 1
  • 读地址 5 -> 读出数据 1
    ...

为什么每 4 个连续地址读出的值是一样的?既然我的 BRAM 位宽是 32 位的,按理说地址递增 1,不就应该读出下一个 32-bit 的数吗?


🔍 深度剖析:字编址 vs 字节编址的冲突

产生这个疑问非常合乎逻辑。如果是纯粹的 FPGA 设计(没有 ARM 和 AXI 总线),你例化一个 32-bit 位宽的 BRAM,确实是"地址0对应第0个数据,地址1对应第1个数据",这叫做字编址(Word Addressing)

但在 ZYNQ 系统中,规则被 AXI 总线 改变了。

1. ARM 与 AXI 永远是"字节编址"

ARM 处理器和 AXI 总线是严格按照字节(Byte,8-bit) 来统一编址的。因为你要存一个 32-bit(4字节)的数据,它就必须占据 0x000x010x020x03 这 4 个连续的地址。下一个 32 位数据,只能从 0x04 开始存。你的 Shell 脚本非常正确地遵循了这一点。

2. AXI BRAM Controller 的强制干预

为了让 ARM 能够通过 AXI 总线顺利访问 PL 端的 BRAM,我们在 Block Design 中加入了 AXI BRAM Controller。这个 IP 核为了兼容 ARM 的规则,会强制把 BRAM 的编址方式统一修改为"字节编址"

这意味着,即使你的 BRAM B端口(PL端)设置成了 32 位宽,它外露的地址线接收的不再是"数据索引",而是"物理字节地址"

3. BRAM 底层是如何处理不对齐的地址的?

当你的 PL 端想读 32 位数据,给出地址 0, 1, 2, 3, 4 时,BRAM 的硬件逻辑是这么处理的:

硬件是 32位对齐的,它不支持错位读取(Unaligned Read)。当你输入一个地址时,BRAM 会直接忽略地址线的最低 2 位(bit 1 和 bit 0)

我们来看一下真值表就一目了然了:

PL 端给出的地址 (十进制) 二进制表示 BRAM 实际响应的字节地址(忽略低2位) 读出的 32 位数据来源
0 ...0000 0 PS 写入的第 0 个数据 (0x40000000)
1 ...0001 0 PS 写入的第 0 个数据 (0x40000000)
2 ...0010 0 PS 写入的第 0 个数据 (0x40000000)
3 ...0011 0 PS 写入的第 0 个数据 (0x40000000)
4 ...0100 4 PS 写入的第 1 个数据 (0x40000004)
5 ...0101 4 PS 写入的第 1 个数据 (0x40000004)

这就是为什么地址给 1、2、3 时,读出的全是地址 0 的数据!


🛠️ 解决办法

搞懂了原理,解决起来就非常简单了。既然地址是按字节计算的,我们每次读取一个 32位(4字节)数据时,PL 端的读地址步长必须是 4。

这里提供两种常用的 Verilog 修改方案:

方案一:地址直接加 4(最直观)

将你原来状态机里地址 +1 的地方,直接改为 +4

verilog 复制代码
reg [31:0] bram_read_addr;

always @(posedge clk) begin
    if (rst) begin
        bram_read_addr <= 32'd0;
    end else if (read_enable) begin
        // 每次读取一个 32-bit 数据,地址需要加 4
        bram_read_addr <= bram_read_addr + 32'd4; 
    end
end
方案二:使用计数器左移 2 位(推荐,逻辑更优雅)

在硬件设计中,我们往往习惯维护一个步长为 1 的"字计数器(Word Counter)"。我们可以保留计数器 +1 的逻辑,只需要在将其连给 BRAM 地址线时,左移 2 位(末尾补两个0,等效于乘以4) 即可。

verilog 复制代码
reg [29:0] word_counter; // 字计数器(0, 1, 2, 3...)

always @(posedge clk) begin
    if (rst) begin
        word_counter <= 0;
    end else if (read_enable) begin
        word_counter <= word_counter + 1; 
    end
end

// 给到 BRAM 的地址:计数器拼接 2'b00
assign bram_read_addr = {word_counter, 2'b00}; 

💡 总结

在 ZYNQ 软硬件协同开发中:
只要数据是挂载在 AXI 总线上的,地址计算就必须遵循"字节编址"的物理法则。

对于 32-bit 数据,地址步长为 4;对于 64-bit 数据,地址步长则为 8。改变以往纯 FPGA 开发中"地址每次加 1"的惯性思维,就能完美避开这个坑。

相关推荐
zlinear数据采集卡1 天前
从0到1硬核拆解:工业级数据采集卡的隔离设计与Modbus通信实战
arm开发·单片机·嵌入式硬件·fpga开发·开源
FakeOccupational1 天前
fpga系列 HDL:Microchip FPGA开发软件 Libero Soc FPGA 在线逻辑分析
fpga开发
FPGA技术联盟2 天前
如何在跨时钟域分析中处理好复位信号?
fpga开发
国科安芯2 天前
基于ASM1042S2S的箭载通信网络抗辐射加固方案研究
服务器·网络·嵌入式硬件·fpga开发·架构·信号处理
YYRAN_ZZU3 天前
Lattice 自定义IP业务逻辑核
嵌入式硬件·fpga开发
FPGA小徐3 天前
FPGA FIFO一篇完整解释
fpga开发
I'm a winner3 天前
【IP核】 Xilinx FPGA LVDS 高速接口,含验证工程与板级测试用例
tcp/ip·fpga开发·测试用例
I'm a winner3 天前
基于Xilinx FPGA的LVDS高速串行通信系统 - 完整源码解决方案(一)(文末附源码)
fpga开发
国科安芯3 天前
航天器多路并联大功率电源系统设计与ASP4644均流特性分析
单片机·嵌入式硬件·fpga开发·安全性测试
techdashen5 天前
从网络栈继续往下:micro:bit、2.4GHz、调制方式,以及一个不太靠谱但很有趣的想法
网络·fpga开发