中文网站建设英文网站建设酷炫html5网站
中文网站建设英文网站建设,酷炫html5网站,大学生ppt免费模板网站,wordpress网站上传到服务器PYNQ-Z2开发板实战#xff1a;手把手教你用AXI4总线实现DDR3读写测试#xff08;附消抖模块设计#xff09;
如果你刚接触ZYNQ或PYNQ平台#xff0c;面对PS与PL之间复杂的通信#xff0c;尤其是如何让FPGA逻辑#xff08;PL#xff09;高效、稳定地读写PS端的DDR内存 reg [3:0] mst_exec_state; reg [31:0] write_index, read_index; reg [31:0] expected_data; reg compare_error; // 状态转移逻辑 always (posedge M_AXI_ACLK) begin if (M_AXI_ARESETN 1b0) begin mst_exec_state IDLE; // ... 其他寄存器复位 end else begin case (mst_exec_state) IDLE: begin if (init_txn_pulse_sync) // 经过同步和消抖后的启动信号 mst_exec_state INIT_WRITE; end INIT_WRITE: begin // 控制AXI写通道信号发起突发写 if (/* 写突发完成条件 */) mst_exec_state INIT_READ; end INIT_READ: begin // 控制AXI读通道信号发起突发读 if (/* 读突发完成条件 */) mst_exec_state INIT_COMPARE; end INIT_COMPARE: begin // 进行数据对比更新compare_error mst_exec_state IDLE; // 对比完成回到空闲 end default: mst_exec_state IDLE; endcase end end // 输出赋值 assign txn_done (mst_exec_state INIT_COMPARE) (compare_error 1‘b0); assign error compare_error;编写完核心逻辑后在IP封装界面点击Review and Package - Re-package IP完成IP的更新。这样一个具备基本DDR读写测试功能的AXI4 Master IP就准备好了。3. 构建硬件系统在Block Design中集成所有组件有了自定义IP我们回到主工程开始搭建完整的硬件系统。创建Block Design在Flow Navigator中点击Create Block Design。添加ZYNQ7 Processing System在Diagram窗口中点击“”号搜索并添加ZYNQ7 Processing SystemIP。配置ZYNQ PS双击添加的ZYNQ IP块进行配置。在Page Navigator的MIO Configuration中根据PYNQ-Z2的原理图确保UART 1被启用用于串口打印。切换到PS-PL Configuration。在HP Slave AXI Interface下勾选S AXI HP0 interface。这就是我们PL访问DDR的高速通道。在Clock Configuration-PL Fabric Clocks下确保FCLK_CLK0被启用并设置一个合适的频率例如50MHz或100MHz这将作为PL侧的主时钟。配置完成后点击OKVivado会自动添加FCLK_CLK0和FCLK_RESET0_N等端口。添加自定义IP在Diagram中再次点击“”这次在IP Catalog的User Repository下应该能找到我们刚刚创建的DDR RW AXI MasterIP将其添加到设计中。添加AXI Interconnect由于我们有多个Master/Slave需要互联目前是自定义IP Master要连接到ZYNQ的HP0 Slave需要添加一个AXI InterconnectIP来自动管理连接。添加后Vivado通常会自动将ZYNQ7的S_AXI_HP0连接到Interconnect的Slave端将我们自定义IP的M00_AXI连接到Interconnect的Master端。如果没有自动连接可以手动连线。连接时钟与复位将ZYNQ7输出的FCLK_CLK0连接到AXI Interconnect的ACLK、自定义IP的m00_axi_aclk以及后续消抖模块的时钟。将FCLK_RESET0_N连接到Interconnect和自定义IP的复位端注意复位极性通常是低有效而FCLK_RESET0_N是低有效复位直接连接即可。引出关键信号我们需要将自定义IP的init_txn_pulse、txn_done、error端口以及ZYNQ的UART端口引出到顶层。在Diagram中右键这些端口选择Make External。init_txn_pulse将来会连接到消抖模块的输出txn_done和error可以连接到板载LEDUART端口用于连接USB-UART桥接器。至此一个不含消抖模块的基础硬件连接图就完成了。接下来我们解决那个关键的“抖动”问题。4. 核心实战技巧设计一个稳健的按键消抖模块直接使用机械按键产生的信号驱动精密的状态机是危险的。按键在闭合和断开的瞬间由于金属触点的弹性会产生一系列频率和幅度不规则的毛刺这就是抖动。对于我们的应用一次按键按下理想情况是产生一个干净的上升沿假设按键按下为高电平。消抖的目的就是过滤掉这些毛刺确保输出一个稳定的电平变化。4.1 消抖原理与状态机设计消抖的经典方法是延时采样。基本思路是当检测到按键输入发生变化时启动一个计时器例如延时20ms在计时期间持续监测输入信号。如果计时结束后信号状态保持稳定则确认这是一次有效的按键动作并输出相应的边沿或电平。我们可以用一个状态机来实现它通常包含四个状态状态描述IDLE空闲状态等待按键按下检测下降沿。FILTER_DOWN检测到下降沿后进入进行按下消抖计时。DOWN消抖完成确认按键已稳定按下输出有效按下标志。FILTER_UP检测到上升沿后进入进行释放消抖计时。下面是一个经过实践检验的Verilog消抖模块代码它输出一个按键标志脉冲key_flag和当前稳定状态key_state。我们将利用key_flag的上升沿作为我们DDR读写测试的启动信号。timescale 1ns / 1ps module key_debounce #( parameter CLK_FREQ 50_000_000, // 输入时钟频率单位Hz parameter DEBOUNCE_MS 20 // 消抖时间单位ms )( input wire clk, // 时钟信号 input wire rst_n, // 异步复位低有效 input wire key_in, // 按键输入低电平按下根据PYNQ-Z2硬件 output reg key_flag, // 按键动作标志脉冲下降沿和上升沿各产生一个脉冲 output reg key_state // 按键稳定状态0-按下1-释放 ); // 计算消抖计数器最大值 localparam CNT_MAX (CLK_FREQ / 1000) * DEBOUNCE_MS - 1; reg [31:0] debounce_cnt; // 同步器防止亚稳态 reg key_in_sync0, key_in_sync1; always (posedge clk or negedge rst_n) begin if (!rst_n) begin key_in_sync0 1b1; // 默认按键释放为高 key_in_sync1 1b1; end else begin key_in_sync0 key_in; key_in_sync1 key_in_sync0; end end // 边沿检测 reg key_tmp; wire key_negedge, key_posedge; always (posedge clk or negedge rst_n) begin if (!rst_n) key_tmp 1b1; else key_tmp key_in_sync1; end assign key_negedge (~key_in_sync1) key_tmp; // 检测到下降沿 assign key_posedge key_in_sync1 (~key_tmp); // 检测到上升沿 // 消抖状态机 localparam [1:0] S_IDLE 2b00, S_FILTER_DOWN 2b01, S_DOWN 2b10, S_FILTER_UP 2b11; reg [1:0] state; reg cnt_en; // 计数器使能 always (posedge clk or negedge rst_n) begin if (!rst_n) begin state S_IDLE; key_flag 1b0; key_state 1b1; // 初始为释放状态 cnt_en 1b0; debounce_cnt 32d0; end else begin key_flag 1b0; // 默认拉低只在产生标志时拉高一个周期 case (state) S_IDLE: begin if (key_negedge) begin // 检测到疑似按下 state S_FILTER_DOWN; cnt_en 1b1; end end S_FILTER_DOWN: begin if (debounce_cnt CNT_MAX) begin // 消抖时间到 if (key_in_sync1 1b0) begin // 确认仍是低电平 state S_DOWN; key_flag 1b1; // 产生按下标志脉冲 key_state 1b0; // 状态变为按下 end else begin // 期间电平跳回高是抖动回到空闲 state S_IDLE; end cnt_en 1b0; debounce_cnt 32d0; end else if (key_posedge) begin // 消抖期间检测到上升沿是抖动 state S_IDLE; cnt_en 1b0; debounce_cnt 32d0; end end S_DOWN: begin if (key_posedge) begin // 检测到疑似释放 state S_FILTER_UP; cnt_en 1b1; end end S_FILTER_UP: begin if (debounce_cnt CNT_MAX) begin // 消抖时间到 if (key_in_sync1 1b1) begin // 确认已是高电平 state S_IDLE; key_flag 1b1; // 产生释放标志脉冲 key_state 1b1; // 状态变为释放 end else begin // 期间电平又变低是抖动回到按下状态 state S_DOWN; end cnt_en 1b0; debounce_cnt 32d0; end else if (key_negedge) begin // 消抖期间检测到下降沿是抖动 state S_DOWN; cnt_en 1b0; debounce_cnt 32d0; end end default: state S_IDLE; endcase end end // 消抖计数器 always (posedge clk or negedge rst_n) begin if (!rst_n) debounce_cnt 32d0; else if (cnt_en) debounce_cnt debounce_cnt 1b1; else debounce_cnt 32d0; end endmodule4.2 将消抖模块集成到系统中现在我们需要将这个消抖模块添加到我们的Block Design中。添加消抖模块由于这是一个自定义的RTL模块而非IP核我们需要通过另一种方式添加。在Sources面板的Design Sources上右键选择Add Sources - Add or create design sources然后创建或添加上述Verilog文件例如key_debounce.v。在Block Design中实例化在Diagram空白处右键选择Add Module然后从列表中找到key_debounce模块并添加。连接消抖模块将clk连接到FCLK_CLK0。将rst_n连接到FCLK_RESET0_N注意我们的消抖模块是低电平复位而FCLK_RESET0_N是低有效复位信号直接连接即可。将key_in端口Make External并命名为btn_i这将在顶层连接到PYNQ-Z2的物理按键例如BTN0。将key_flag输出连接到自定义DDR读写IP的init_txn_pulse输入。这里有个重要细节我们的DDR读写状态机期望的是一个上升沿触发。而key_flag在按键按下和释放时都会产生一个高脉冲。我们需要确保只有按下时的脉冲触发读写。有两种方法方法A修改消抖模块使其只输出按下时的脉冲。上述代码已经实现了key_flag在按下和释放时都会拉高一个周期。我们可以利用key_state信号来区分或者直接使用key_flag ~key_state即按下时的脉冲作为触发信号。在Block Design中可以用一个Utility Vector LogicIP配置为与门来实现。方法B更简单在我们的DDR读写IP的状态机中将触发条件改为检测init_txn_pulse的上升沿并且忽略其持续时间。这样无论是按下还是释放脉冲只要消抖模块认为是一次有效的边沿变化就会触发。为了保险起见我们通常选择按键按下作为触发时机。由于PYNQ-Z2按键默认上拉按下为低所以我们的消抖模块检测的是下降沿。因此连接key_flag到init_txn_pulse是可行的但要注意IP内部的状态机需要是边沿敏感而非电平敏感。连接LED指示将自定义IP的txn_done和error输出端口分别连接到两个LED灯例如led0_o和led1_o上。完成所有连接后你的Block Design应该类似下图示意图[ZYNQ7 PS] ---[AXI_HP0]--- [AXI Interconnect] ---[M00_AXI]--- [DDR RW AXI Master IP] | | | |--- FCLK_CLK0 ---------------|--------------------------------------|--- [clk] |--- FCLK_RESET0_N -----------|--------------------------------------|--- [rst_n] |--- UART1_TX/RX -------------|--------------------------------------|--- [外部端口] | |--- txn_done -- [led0_o] |--- error ----- [led1_o] |--- init_txn_pulse -- [key_debounce.key_flag] | [key_debounce] --- btn_i (外部按键)4.3 引脚约束与生成比特流创建顶层HDL Wrapper在Sources面板中右键你的Block Design例如design_1选择Create HDL Wrapper让Vivado自动生成顶层的Verilog或VHDL文件。引脚约束根据PYNQ-Z2的原理图创建或编辑约束文件.xdc。关键约束如下# 时钟 set_property PACKAGE_PIN H16 [get_ports FCLK_CLK0] set_property IOSTANDARD LVCMOS33 [get_ports FCLK_CLK0] # 复位可选如果使用PS产生的复位则无需约束外部引脚 # set_property PACKAGE_PIN R11 [get_ports rst_n] # set_property IOSTANDARD LVCMOS33 [get_ports rst_n] # 按键 (BTN0) set_property PACKAGE_PIN D19 [get_ports btn_i] set_property IOSTANDARD LVCMOS33 [get_ports btn_i] set_property PULLUP true [get_ports btn_i] # PYNQ-Z2按键外部有上拉这里声明确保无误 # LED (LD0, LD1) set_property PACKAGE_PIN R14 [get_ports led0_o] set_property IOSTANDARD LVCMOS33 [get_ports led0_o] set_property PACKAGE_PIN P14 [get_ports led1_o] set_property IOSTANDARD LVCMOS33 [get_ports led1_o] # UART (通过PMOD或USB-UART需根据实际连接调整) # 例如如果使用PMODA的UART # set_property PACKAGE_PIN Y11 [get_ports UART_tx] # set_property IOSTANDARD LVCMOS33 [get_ports UART_tx] # set_property PACKAGE_PIN AA11 [get_ports UART_rx] # set_property IOSTANDARD LVCMOS33 [get_ports UART_rx]注意PYNQ-Z2的PS端UART通常通过USB转串口芯片连接到电脑其引脚在ZYNQ PS的MIO上已固定无需在PL端约束。我们这里引出的UART端口是备用的PL端UART。为了简化验证阶段我们可以先不连接UART专注于LED指示。或者使用PS端的UART这需要在ZYNQ配置中启用并在SDK中调用标准输出函数。综合、实现与生成比特流点击Vivado左侧的Generate Bitstream。这个过程会花费一些时间期间Vivado会进行综合、布局布线最终生成一个.bit文件。5. 软件验证使用SDK进行DDR数据读取与调试硬件比特流生成后我们需要在PS端运行一个简单的程序来验证PL端是否成功写入了数据。导出硬件与启动SDK在Vivado中点击File - Export - Export Hardware。勾选“Include bitstream”然后点击OK。接着点击File - Launch SDK。创建应用工程在Xilinx SDK中选择File - New - Application Project。输入工程名如ddr_test其他保持默认点击Next。在模板选择页面为了最简化可以选择“Hello World”或“Empty Application”然后Finish。编写测试代码在src文件夹下创建或修改main.c文件。代码如下#include stdio.h #include xil_cache.h #include xil_io.h #include xparameters.h // 包含系统参数如DDR基地址 #include sleep.h // DDR的测试起始地址需要与PL端IP中配置的Base Address一致 #define TEST_BASE_ADDR 0x10000000 #define TEST_DATA_COUNT 256 // 读取的数据个数可以根据PL写入的量调整 int main() { int i; u32 read_data; print(--- DDR Read Test Started ---\n\r); // 禁用数据缓存确保我们直接读取DDR物理内存而非缓存 Xil_DCacheDisable(); // 第一次读取按键按下前DDR该区域应为随机值或上次残留值 print(Reading DDR before PL write (random data):\n\r); for (i 0; i TEST_DATA_COUNT; i) { // Xil_In32用于从内存映射地址读取32位数据 read_data Xil_In32(TEST_BASE_ADDR i * 4); xil_printf(Addr 0x%08x: 0x%08x\n\r, TEST_BASE_ADDR i*4, read_data); } print(\n\rPlease press the button (BTN0) to start PL writing...\n\r); // 此处等待用户按下按键。在实际项目中可以通过中断或轮询GPIO来检测 // 但这里为了简单我们只是延时给用户足够时间按下按键。 // 更好的方法是在PL端完成写入后通过中断通知PS这里我们用延时模拟。 sleep(10); // 休眠10秒等待按键触发PL操作 print(\n\rReading DDR after PL write (should be 1,2,3,...):\n\r); for (i 0; i TEST_DATA_COUNT; i) { read_data Xil_In32(TEST_BASE_ADDR i * 4); // 预期数据是从1开始递增的序列 if (read_data ! (i1)) { xil_printf(Mismatch at Addr 0x%08x: Expected 0x%08x, Got 0x%08x\n\r, TEST_BASE_ADDR i*4, i1, read_data); } else { xil_printf(Addr 0x%08x: 0x%08x (OK)\n\r, TEST_BASE_ADDR i*4, read_data); } } print(\n\r--- DDR Read Test Finished ---\n\r); return 0; }这段代码做了以下几件事禁用数据缓存Xil_DCacheDisable()这是直接访问物理内存时的常见操作。在PL操作前先读取一次DDR目标区域显示其初始内容通常是随机的。提示用户按下按键并等待一段时间sleep(10)。在这10秒内用户应按下PYNQ-Z2上的BTN0。再次读取DDR相同区域。如果PL端的写入和消抖模块工作正常这里读出的数据应该是一个从1开始的递增序列对应PL写入的1024个数据中的前256个。代码会对比并打印是否匹配。配置运行环境将PYNQ-Z2板卡通过JTAG和USB-UART线连接到电脑。在SDK中确保连接了硬件服务器Hardware Server。下载并运行右键工程选择Run As - Launch on Hardware (System Debugger)。程序将被下载到板卡的DDR中并运行。打开串口终端如Putty、Tera Term等配置正确的COM口和波特率通常是115200你就能看到程序输出的信息。预期的成功现象程序启动先打印出一堆随机十六进制数。打印提示“Please press the button...”。此时按下PYNQ-Z2的BTN0按键。你应该会看到LD0txn_done和LD1error灯的状态变化。理想情况下LD0会短暂亮起表示一次读写事务完成LD1应保持熄灭表示数据对比无误。如果LD1亮起说明读写或对比过程出错。大约10秒后程序继续执行打印第二次读取的结果。如果一切正常你会看到按顺序排列的十六进制数0x00000001,0x00000002...并标注(OK)。6. 高级调试使用ILA集成逻辑分析仪抓取波形如果测试失败或者你想深入观察AXI总线上的信号时序以及消抖模块的内部状态Vivado的ILAIntegrated Logic AnalyzerIP核是强大的片上调试工具。在Block Design中添加ILA回到Vivado工程在Block Design中点击“”添加ILA (Integrated Logic Analyzer)IP核。配置ILANumber of Probes根据你要观察的信号数量设置。至少需要消抖模块的key_in,key_flag,key_state自定义IP的init_txn_pulse,txn_done,error以及AXI总线上的关键信号如AWVALID,AWREADY,WVALID,WREADY等。Sample Data Depth设置采样深度例如4096这决定了能捕获多长时间的波形。Trigger Mode选择Basic并设置触发条件例如在init_txn_pulse的上升沿触发。连接探测信号将ILA的probe0,probe1...端口连接到你想观察的网络线上。对于AXI总线信号可能需要将自定义IP的AXI接口展开才能找到。重新生成比特流添加ILA后需要重新综合、实现并生成比特流。硬件调试导出带ILA的硬件在SDK中下载程序运行。然后在Vivado中打开Hardware Manager连接板卡下载新的比特流。在Hardware Manager中设置触发条件与ILA配置一致然后让系统运行在SDK中运行程序并按下按键。触发后Vivado会捕获波形并显示出来。通过分析ILA波形你可以清晰地看到按键原始信号key_in的抖动毛刺。消抖模块输出的干净key_flag脉冲。init_txn_pulse信号如何触发AXI状态机。AXI总线握手信号如AWVALID/AWREADY的时序确认读写事务是否正确执行。txn_done和error信号在何时拉高。这几乎是定位FPGA设计问题的终极手段尤其对于时序和交互逻辑问题非常有效。整个项目从IP创建、系统集成、消抖设计到软硬件验证和调试形成了一个完整的闭环。通过这个实战练习你不仅掌握了PYNQ-Z2上AXI4总线操作DDR的基本流程更获得了处理实际工程中信号完整性如按键消抖和系统调试如ILA使用的宝贵经验。这些技能在更复杂的FPGA-SoC系统开发中至关重要。