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字节)的数据,它就必须占据 0x00、0x01、0x02、0x03 这 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"的惯性思维,就能完美避开这个坑。

相关推荐
低慢小uav1 小时前
(已解决)Zynq芯片lwip协议栈,使用tcp作为服务器Service,接收大量数据时,长度接收正常,接收对数据校验,校验和异常分析
网络协议·tcp/ip·fpga开发
不昀2 小时前
户外防水RJ45选型笔记:IP67、出线角度、面板厚度与机壳接地
硬件设计·防水rj45·ip67·以太网接口·户外设备
szxinmai主板定制专家3 小时前
ARM+FPGA 数控机床控制器:选 CODESYS SoftMotion CNC,还是基于 LinuxCNC 自研搭建?架构、授权、实时性、量产风险深度对比
arm开发·fpga开发·codesys·数据采集卡·linuxcnc·rk3576
女神下凡3 小时前
芯参谋(79):嵌入式学习进阶_(硬件与软件)深入实践指南
arm开发·单片机·嵌入式硬件·学习·fpga开发·设计规范
ELITECHIPLJW3 小时前
便携锂电设备电源控制方案实践:ECJ5055-297512 长按 2 秒自断电开关机 IC 选型与电路思路
硬件设计·开关机芯片·低功耗 ic
路上有只喵5 小时前
认识元器件 · 单片机(MCU/MPU/SOC) · 实战篇5
单片机·嵌入式硬件·fpga开发
爱吃汽的小橘6 小时前
FPGA/数字IC设计的时序优化
fpga开发
FPGA小迷弟6 小时前
FPGA 与无线电:从软件定义无线电到 5G 基带的硬件实现之路
5g·fpga开发·架构·dsp开发·fpga·无线电
yingheceping6 小时前
2026 国产 FPGA 核心板怎么选?工控/视觉/通信/科研,四场景对板指南
fpga开发·fpga
樱木花道長6 小时前
AMD Ross 深度系列 7:六组对照实验看Ross如何赋能AI完成Vivado 硬核IP配置
人工智能·fpga开发