ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spartan-6 FPGA例程解析:从DDR3读写到以太网与视频采集的Verilog设计

Spartan-6 FPGA例程解析:从DDR3读写到以太网与视频采集的Verilog设计 简介这套赛灵思Spartan-6 FPGA开发板配套资料基于LX16芯片共整合30个Verilog逻辑例程及配套教程文档工程环境为ISE 14.7适合正在学习FPGA数字逻辑设计、准备课程设计或需要参考实际工程源码的中高级开发者。例程覆盖DDR3读写、千兆以太网通信、OV5640/OV7670摄像头采集、VGA/LCD显示、UART串口、EEPROM/RTC、Flash、USB、AD/DA转换等常用外设与接口并包含按键、LED、PLL、ROM等基础入门模块便于从基础实验到综合项目循序渐进使用部分示例还演示了Flash与DDR联动、SD卡音频播放等较完整的数据通路。压缩包约250MB内含各例程的完整工程源码、引脚约束文件及教程说明目录按编号排列结构清晰便于按需检索。目前已有616人学习使用可作为Spartan-6平台硬件调试、接口验证和逻辑设计时的直接参考资料。1. Spartan-6 这套例程盘为什么还值得翻Spartan-6 是 Xilinx 十几年前的 45nm 器件但这套 ISE 14.7 例程的价值不在工艺而在它把 DDR3 读写、千兆以太网、视频采集显示这些今天仍在用的外设用裸 Verilog 全部摊开。Vivado 里双击封装好的 IP在这里能看到状态机怎么跳、地址线怎么拉、约束怎么落。合集内 30 个工程从 04_led_test 到 20_sd_ddr_vga、ethernet_100由点灯逐级做到 DDR3 帧缓存与千兆网口每一级都能单独仿真和上板。它适合刚入门 Verilog 但缺完整板级参考的学习者也适合要把旧设计移植到新板卡的在职工程师。2. 时钟与存储主线ISE 14.7 工程结构、DCM 与 DDR3 MIG2.1 例程目录与工程组织方式压缩包解开后是 30 个独立工程目录工程软件统一为 ISE 14.7没有依赖 Vivado 的包装层。每个工程里通常能看到src/放 Verilog 源码、ucf/放引脚约束、ipcore_dir/放 DCM、MIG 这类生成的 IP 核。顶层模块文件名与工程名一致例如 11_ddr3_test 的顶层就是ddr3_test.v这种命名约定在上板定位问题时会省很多事也方便用脚本批量替换引脚约束做移植。按功能把 30 个例程聚类可以快速定位自己需要的参考设计例程编号功能方向可直接复用的模块04/05/06基本 IO 与时钟按键消抖、边沿检测、DCM_SP 配置07/08/09串行通信uart_tx/rx、I2C master、RTC 读写10/11/12存储读写Block RAM、DDR3 MIG 用户接口、SPI Flash13/14/15/16接口外设USB、RGMII 以太网、I2S 音频、SD 卡03/18~26视频链路SCCB 配置、VGA/LCD 时序、DDR3 帧缓存27~30混合信号AD9226 采集、DDS 波形输出、滑动平均滤波这些例程之间不是孤立的20_sd_ddr_vga 就是 SD 卡读取图像写入 DDR3再经 VGA 输出的完整数据通路理解它等于把 11、16、18 三个例程串起来了。2.2 06_pll_testDCM_SP 的倍频、分频与复位处理ISE 14.7 里生成时钟最常用的是 DCM_SP 原语它比 Vivado 的 MMCM/PLL 更接近底层寄存器。DCM_SP 的 CLK0 输出与输入同频CLK2X 是 2 倍频CLKFX 由独立乘法器和除法器组合得到。下面这段例化把 50MHz 板载时钟变成 100MHz 逻辑主频和 25MHz 视频像素时钟// 50MHz 输入输出 100MHz 逻辑时钟与 25MHz 像素时钟 DCM_SP #( .CLKIN_DIVIDE(1), // 输入不分频 .CLKIN_PERIOD(20.0), // 50MHz 周期为 20ns .CLKFX_MULTIPLY(1), // CLKFX 50MHz * 1 / 2 25MHz .CLKFX_DIVIDE(2) ) u_dcm ( .CLKIN(clock_50m), .CLKFB(clk_100m), // 反馈接 CLK2X/CLK0保证零延时 .RST(~rst_n), // DCM 复位是高有效 .CLK0(clk_50m), // 与输入同频的时钟 .CLK2X(clk_100m), // 100MHz 主时钟 .CLKFX(clk_25m), // 25MHz 视频像素时钟 .LOCKED(pll_locked) );这段代码的关键点是 RST 输入在 Spartan-6 的 DCM 原语里是高有效所以外部的低有效复位要先取反再接。LOCKED 信号要经过两级同步器后再作为其余模块的复位释放信号否则上电瞬间可能出现半个周期的亚稳态。CLKFB 反馈必须接回 CLK0 或 CLK2X 中的一个否则 DCM 无法完成相位对齐。输出信号频率用途clk_50m50MHz系统低速逻辑、UART 波特率基准clk_100m100MHzDDR3 MIG ui_clk、以太网局部逻辑clk_25m25MHzVGA 640x48060Hz 像素时钟如果板载晶振不是 50MHz只需要改 CLKIN_PERIOD 和倍频分频系数不需要改动后续模块的接口。这一点在第五章移植时会再次用到。2.3 11_ddr3_testMIG 用户接口读写状态机Spartan-6 上的 DDR3 控制器由 ISE 的 MIGMemory Interface Generator生成例程重点不在 PHY 层而在用户接口怎么把数据写进去、读出来。MIG 给用户的时钟是ui_clk用户数据位宽按 DDR3 颗粒配置成 256 bit对应一次突发长度 8 的 32 bit 颗粒。理解这一层之后DDR3 在你眼里就不再是时序图而是一个带命令通道和独立数据通道的交错接口。信号方向含义app_cmd[2:0]用户→MIG3b000 写3b001 读app_addr[27:0]用户→MIG突发地址单位是 8 个连续 32bit 字app_en用户→MIG命令有效app_rdyMIG→用户命令通道可接收新命令app_wdf_data[255:0]用户→MIG写数据app_wdf_wren用户→MIG写数据有效app_wdf_rdyMIG→用户写数据通道可接收app_rd_data[255:0]MIG→用户读数据app_rd_data_validMIG→用户读数据有效读写状态机最简实现如下假设一次操作只做一个突发reg [2:0] state; reg [27:0] wr_ptr, rd_ptr; localparam IDLE 3d0, WRITE 3d1, READ 3d2, WAIT 3d3; always (posedge ui_clk) begin if (ui_clk_sync_rst) begin state IDLE; app_en 1b0; app_wdf_wren 1b0; end else begin app_en 1b0; app_wdf_wren 1b0; case (state) IDLE: if (wr_req app_rdy) begin app_cmd 3b000; // 写命令 app_addr wr_ptr; app_en 1b1; state WRITE; end else if (rd_req app_rdy) begin app_cmd 3b001; // 读命令 app_addr rd_ptr; app_en 1b1; state READ; end WRITE: begin app_wdf_data {8{wdata}}; // 256bit 拼出 app_wdf_wren 1b1; state WAIT; end READ: state WAIT; WAIT: if (wr_req || rd_req) state IDLE; endcase end end命令通道和数据通道是独立握手的常见错误是只等app_rdy就拉app_en忽略app_wdf_rdy。在 ISE 14.7 的 MIG 版本里写入数据通道与命令通道可能相差若干个ui_clk周期写数据必须在app_wdf_rdy和app_wdf_wren同时有效时才被真正采样。读数据则要看app_rd_data_valid它拉高时 256 bit 数据已经稳定在总线上。注意例程的地址是按突发数递增的不是字节地址。要把 DDR3 当普通 SRAM 用地址需要左移 3 位再做字节对齐否则读写会交叉错位。MIG 配置界面里需要选择具体 DDR3 颗粒型号或手工填写行列 bank 位宽2Gbit 颗粒如三星 K4B2G1646F 常见配置是 8 列、14 行、3 bank 位这些参数决定app_addr的实际有效位宽移植到不同颗粒时必须重新生成 MIG 核。3. 视频采集显示链路OV5640/OV7670、VGA/LCD 与 DDR3 帧缓存3.1 SCCB/I2C 寄存器初始化序列OV7670 和 OV5640 上电后默认输出格式与窗口大小通常不是目标分辨率需要 FPGA 通过 SCCB 接口写一组寄存器才能输出合格图像。SCCB 与 I2C 的差异在于不支持连续读写时序基本兼容。例程里的初始化序列一般放在 ROM 或二维数组里每个表项是{寄存器地址, 值}由启动状态机逐条下发。参数OV7670OV5640SCCB 设备写地址0x420x78典型输出格式RGB565 / Bayer RAWRGB565 / YUV422初始化寄存器数量上百个数百个I2C 写寄存器核心状态机如下这里只给出框架重点是 START 和 STOP 的时序// 简化版 I2C 写单字节状态机SCL 约 100kHz reg [3:0] bit_cnt; reg i2c_scl, i2c_sda; always (posedge clk or negedge rst_n) begin if (!rst_n) begin i2c_scl 1b1; i2c_sda 1b1; end else case (state) I2C_IDLE: begin i2c_scl 1b1; i2c_sda 1b1; if (cfg_start) begin i2c_sda 1b0; // 起始条件SCL 高时 SDA 拉低 state I2C_SEND; end end I2C_SEND: begin i2c_scl 1b0; // 拉低 SCL 准备切换 SDA // 按 bit_cnt 移位发送设备地址/寄存器地址/数据 if (bit_cnt 4d8) state I2C_ACK; end I2C_ACK: begin i2c_scl 1b1; // 释放 SCL采样从设备 ACK // 读 i2c_sda若为高则寄存器写失败 end endcase end关键点是 SCL 低电平时才能切换 SDASCL 高电平时 SDA 必须保持稳定这是 I2C 总线的仲裁基础。OV5640 的寄存器表很长但很多是分辨率相关比如输出窗口从 640x480 改到 1280x720 时只需要改 HTS/VTS 和裁剪起止地址几项不需要整表重写。例程中 03、25、26 三个工程分别对应 OV5640 在不同显示链路上的配置对比它们的寄存器表差异就能看出哪些字段控制分辨率、哪些控制数据格式。3.2 VGA/LCD 时序生成行列计数器与有效区判断VGA 640x48060Hz 是 FPGA 学习最标准的时序模板行参数为 96 像素同步、48 像素后沿、640 像素有效、16 像素前沿总计 800 像素帧参数为 2 行同步、33 行后沿、480 行有效、10 行前沿总计 525 行。LCD 屏如 4.3 寸 TFT通常也是同一套参数只是换成 DE 模式不需要行场同步极性对齐。// VGA 行场计数器自由运行rgb_en 指示有效区 reg [9:0] h_cnt, v_cnt; always (posedge pixel_clk or negedge rst_n) begin if (!rst_n) begin h_cnt 10d0; v_cnt 10d0; end else begin if (h_cnt 10d799) begin h_cnt 10d0; v_cnt (v_cnt 10d524) ? 10d0 : v_cnt 1b1; end else begin h_cnt h_cnt 1b1; end end end wire h_active (h_cnt 10d144) (h_cnt 10d784); wire v_active (v_cnt 10d35) (v_cnt 10d515); wire rgb_en h_active v_active; assign vga_r rgb_en ? pixel_r : 4h0; assign vga_g rgb_en ? pixel_g : 4h0; assign vga_b rgb_en ? pixel_b : 4h0;这段代码里 144 是同步加后沿的总和784 是再加有效宽度后的边界。改动分辨率时只需替换这几个宏定义不需要重写计数器逻辑。VGA 侧的像素时钟由第二章的 DCM 输出 25MHz如果换到 800x600 就需要 40MHz 像素时钟DCM 参数要同步调整。3.3 DDR3 帧缓存乒乓切换与异步 FIFO视频数据通路20_sd_ddr_vga、25_ov5640_vga、26_ov5640_lcd的典型结构是采集端按传感器像素时钟写入 DDR3显示端按 VGA 像素时钟从 DDR3 读出。两个时钟域之间用异步 FIFO 隔离DDR3 的ui_clk只负责把 FIFO 数据搬运到缓存地址。乒乓缓冲是避免画面撕裂的常规做法DDR3 里划出两个帧缓冲区采集端写 A 区时显示端读 B 区VSYNC 到来时交换// 帧缓冲区地址双缓冲切换 reg [27:0] buf_base; reg buf_index; always (posedge vsync) begin buf_index ~buf_index; if (buf_index) buf_base FRAME0_BASE; // 第二个场切回 A 区 else buf_base FRAME1_BASE; // 第一个场切到 B 区 end这里FRAME0_BASE和FRAME1_BASE是宏定义的两个地址区域中间至少要留出一帧数据量的空间。640x480 的 RGB565 每帧约 614400 字节换算成 256 bit 突发就是 19200 个地址这个数可以作为地址偏移的校验依据。如果只有 128MB DDR3 的板卡两个缓冲区只占 1.2MB完全够用。在帧率不匹配的场景下写侧和读侧各需要一个 FIFO 做弹性缓冲。异步 FIFO 的读写指针用格雷码同步例程里一般是直接调 ISE FIFO Generator 生成需要注意读侧 empty 信号到写侧 full 信号之间有两个时钟周期的同步延迟DDR3 搬运状态机必须允许 FIFO 的空/满标志有回滞否则会频繁启停导致带宽暴跌。4. 通信与混合信号外设参考UART、EEPROM、SPI 存储、RGMII 与 AD/DA4.1 uart_test串口收发状态机与波特率参数串口是验证 FPGA 逻辑最简单的外部通道07_uart_test 里的发送机和接收机拆开就是两个独立状态机。以 50MHz 系统时钟、115200bps 为例每 bit 周期是 434 个时钟发送状态机在起始位拉低 TXD 后逐位移出 8 个数据位再拉高停止位。module uart_tx #( parameter CLK_FREQ 50_000_000, parameter BAUD_RATE 115_200 )( input wire clk, input wire rst_n, input wire [7:0] tx_data, input wire tx_start, output reg txd, output reg tx_busy ); localparam BIT_PERIOD CLK_FREQ / BAUD_RATE; reg [15:0] baud_cnt; reg [3:0] bit_cnt; reg [7:0] tx_shift; reg state; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state 1b0; txd 1b1; end else begin case (state) 1b0: begin if (tx_start) begin tx_shift tx_data; baud_cnt BIT_PERIOD - 1b1; bit_cnt 4d0; state 1b1; end end 1b1: begin if (baud_cnt 0) begin baud_cnt BIT_PERIOD - 1b1; if (bit_cnt 0) txd 1b0; else if (bit_cnt 8) txd tx_shift[0]; else txd 1b1; tx_shift {1b0, tx_shift[7:1]}; bit_cnt bit_cnt 1b1; if (bit_cnt 9) state 1b0; end else begin baud_cnt baud_cnt - 1b1; end end endcase end end endmodule发送周期从 9 个 bit 周期抽头到 10 个分别对应起始位、8 个数据位和停止位。要支持多字节连续发送需要在外围加一个发送 FIFO状态机在state 1b0时检查 FIFO 非空并自动取数这就是 UART 多字节收发的常见实现路径。接收端同理用 16 倍波特率过采样找起始位下降沿再在每 bit 中间点采样可以躲开边沿抖动。4.2 eeprom_testI2C 读写状态机与页写边界08_eeprom_test 用的是 AT24C02写时序由 START、设备地址、字地址、数据、STOP 组成。容易踩的坑是页写边界AT24C02 每页只有 8 字节跨页连续写会回卷覆盖页内前面的数据。EEPROM 参数值设备写地址0xA0页大小8 字节写周期约 5ms最大 SCL400kHz快速模式I2C 读操作要先写一个伪字节把内部地址指针设到目标位置再发读命令两次 START 之间的地址指针保持有效。工程里如果从 AT24C02 换到 AT24C128页大小变成 64 字节页写状态机里的计数器位宽和跨页判断逻辑都要改。4.3 flash_test 与 sd_testSPI 控制器与命令字差异12_flash_test 的 SPI Flash 和 16_sd_test 的 SD 卡都跑在 SPI 模式下但命令体系和地址粒度不同。W25Q 系列 Flash 用 0x03 读数据、0x02 页编程、0x20 扇区擦除地址是 24 bit 字节地址页大小 256 字节。SD 卡初始化要依次发 CMD0、CMD8、ACMD41 进入就绪态之后 CMD17/CMD24 按 512 字节块地址读写。// SPI Flash 读命令时序CS 拉低发 0x03 24bit 地址后接读时钟 // 每 8 个 SCK 采一个字节MOSI 在 SCK 上升沿前稳定 sck_idle: begin cs_n 1b0; mosi cmd_buffer[7]; // 先发命令字节最高位 state SHIFT_CMD; endSPI 模式极性 CPOL0、相位 CPHA0 时从设备在 SCK 上升沿采样 MOSIFPGA 在下降沿采样 MISO 最稳妥。SD 卡初始化阶段必须把 SPI 时钟压到 400kHz 以下进入数据传输模式后再切到 25MHz 或更高直接高速上电经常卡在 ACMD41 上。这个现象在换不同品牌 SD 卡时最容易暴露调试时先用示波器看卡有没有回 ACK 脉冲。4.4 ethernet_testRGMII 接口与 MAC 侧数据通路14_ethernet_test 和 ethernet_100 两个工程对应千兆和百兆两种模式。RGMII 在千兆下用 125MHz GTX_CLK4 位数据总线在时钟上升沿发送低 4 位、下降沿发送高 4 位等于用 DDR 方式把 8 位数据压进 4 根线。FPGA 发送侧通常直接用 ODDR2 原语实现// RGMII 发送上升沿出 D0下降沿出 D1 ODDR2 #( .DDR_ALIGNMENT(NONE), .INIT(1b0), .SRTYPE(SYNC) ) u_oddr ( .D0(tx_data[0]), // 低 4 位 .D1(tx_data[4]), // 高 4 位 .CE(1b1), .CLK(gtx_clk), // 125MHz 或 25MHz .C0(gtx_clk), .C1(~gtx_clk), // 下降沿采样 .Q(rgmii_txd[0]) );以太网 MAC 侧要处理的还有 CRC32 校验多项式是 0x04C11DB7按 IEEE 802.3 规定对帧内容和填充位做计算结果取反后低字节在前发送。工程里通常用查表法或组合逻辑展开实现逐 bit 移位在 125MHz 下也能跑但占用 LUT 较多。百兆模式下 RGMII 时钟降到 25MHz数据接口不变。MDIO 管理接口用来配置 PHY 寄存器比如协商速率、开启自动协商、读取链路状态。MDIO 时钟最高 2.5MHz帧格式是 PRE 32 位高电平、ST 01、OP 10读、PHYAD、REGAD、TA 两位翻转之后 PHY 回 16 位数据。调试时如果 ping 不通先用 MDIO 读 PHY 的 BMSR 寄存器看链路是否 up再测 GTX_CLK 有没有输出这两步能排除一半问题。4.5 adda_test 与 dds_waveAD 采样、滑动平均与波形输出27_adda_test、29_ad9226_test、30_ad706_test 是并行 ADC 采样例程28_dds_wave 是 DDS 信号发生。AD9226 是 12 位并行输出 ADC转换时钟由 FPGA 给出数据在时钟边沿后延迟若干个 ns 稳定。工程里直接把 ADC 数据寄存一拍后用没有做跨时钟域处理因为 ADC 时钟就是从 FPGA 输出的。DDS 波形发生器用相位累加器实现输出频率累加器位宽决定频率分辨率// DDSf_out f_clk * phase_inc / 2^N // 设 f_clk 50MHzN 32phase_inc 429497 约 5kHz reg [31:0] phase_acc; wire [31:0] phase_inc; always (posedge clk) begin phase_acc phase_acc phase_inc; end相位累加器高 8 位做 ROM 地址查正弦表输出经 DAC 就成了正弦波。改变phase_inc就是改变输出频率不需要重新生成 ROM这就是 DDS 被大量信号发生器采用的原因。做 AD 采样后处理时滑动窗口滤波是性价比最高的去毛刺手段// 16 点滑动平均每个时钟加新值减最旧值 reg [15:0] sum; reg [11:0] dly [0:15]; always (posedge clk) begin dly[0] adc_data; sum sum adc_data - dly[15]; end滑动平均输出是sum 4但注意前面 16 个时钟周期里 dly 里是无效数据需要在设计里加一个数据有效计数器等窗口填满后再输出结果。ND 位数和噪声特征有关实际工程里 N4 到 N32 之间调整N 太大会把真实信号变化也抹平。5. 把例程移植到自己板卡的五个检查点5.1 UCF 引脚约束与电平标准的批量修改ISE 14.7 的引脚约束在 UCF 文件里DDR3 引脚要按 MIG 生成时的引脚分配写普通 IO 则可以自由映射。以 VGA 为例原板卡可能把时钟、RGB、行场同步分配在不同 BANK换板卡后需要逐个核对 LOC 和 IOSTANDARDNET vga_clk LOC P17 | IOSTANDARD LVCMOS33; NET vga_hsync LOC P18 | IOSTANDARD LVCMOS33; NET ddr3_dq[0] LOC R7 | IOSTANDARD SSTL15;DDR3 必须用 SSTL15 电平普通 GPIO 不能直接驱动否则时序全乱。UCF 里 DDR3 引脚组DQ、DQS、CLK、ADDR的约束必须和 MIG 核保持严格一致最安全的做法是在 MIG 生成时直接导入引脚文件不要手工复制。5.2 PLL/MIG 参数按板级时钟重配板载晶振不是 50MHz 时DCM_SP 的CLKIN_PERIOD和倍频分频系数都要改。例如 27MHz 晶振要得到 100MHz可以用CLKIN_DIVIDE27、CLKFX_MULTIPLY100得到 100MHz但要验证 DCM 的工作范围在数据手册允许值内。DDR3 MIG 的输入时钟同样如此MIG 界面里选了输入时钟频率后ui_clk输出是固定分频比改动后要重新生成核不能只在约束里改。5.3 用 ChipScope/ILA 验证移植结果ISE 14.7 内置 ChipScope Pro相当于 Vivado 的 ILA。移植后先抓 DCM 的LOCKED、MIG 的app_rdy和app_en确认命令通道在跑再抓app_rd_data_valid确认读回数据有效最后抓 VGA 的h_cnt/v_cnt确认计数器跳变。检查点观察信号期望结果PLL 锁定pll_locked复位释放后一直为高DDR3 命令握手app_en、app_rdy两者同时拉高至少一次DDR3 读数据app_rd_data_valid读命令后周期性拉高以太网发送rgmii_tx_en帧发送期间保持高电平视频消隐rgb_en无图像时保持低电平ChipScope 插入 ILA 核后要注意采样时钟必须与待测信号同源否则数据会出现总能抓到的假象。例如 DDR3 侧用ui_clk采样VGA 侧用pixel_clk采样两个 ILA 不要混用时钟。验证完删除 ILA 核重新综合避免不必要的资源占用和时序约束变差。本文还有配套的精品资源点击获取
返回列表