FPGA实现SD卡读写:从协议选型到仿真上板全流程解析

发布时间:2026/10/28 23:16:28
FPGA实现SD卡读写:从协议选型到仿真上板全流程解析

简介:面向FPGA开发者的SD卡读写设计资源,聚焦SD卡初始化、SPI/SD模式通信、数据块读写及CRC校验等关键环节,适合电子竞赛备赛、数字系统设计课程实践与入门进阶学习。资源为Xilinx工程文件包,共71个文件,涵盖Verilog源码(v)、综合网表(ngc/ngd)、布线结果(ncd)、约束文件(ucf/pcf)、编程比特流(bit)以及映射/布局布线报告(map/par)等,压缩包约2.09MB,便于快速查看工程完整流程。已有617人学习下载。通过该工程可对照学习FPGA与SD卡交互的顶层状态机设计、CMD17/CMD24等命令时序、块传输与CRC校验逻辑,并能参考其初始化序列和错误处理思路;同时项目自带的仿真与综合报告有助于理解Xilinx ISE工具链使用,适合直接复用或在此基础上完成二次开发。

1. 把 SD 卡读写跑在 FPGA 上:先搞清这份资源能帮你省多少事

做 FPGA 开发的人迟早会遇到 SD 卡——不管是存配置参数、存采集数据,还是做简易文件系统,SD 卡都是性价比最高的存储方案。但 SD 卡写操作在 FPGA 里一直是个“玄学重灾区”:SDIO 时序手册上百页,命令响应有时候是0xFF有时候是0x00,明明仿真全对板上一跑就翻车。这份sd_test.rar资源的核心就是给 FPGA 做 SD 卡读写,特别是写方向,直接给了一套可仿真的工程和测试代码。它适合两类人:一是做 FPGA 入门到项目实战过渡、卡在 SD 卡读写上的人;二是已经在用 SPI 或 SDIO 读 SD 卡、想补上写操作和扇区管理这块短板的工程师。资源本身包含的是 Verilog 源码、测试平台和仿真脚本,覆盖初始化、单块读、单块写、多块写这几条主链路,拿到手可以直接套到自己的工程里改。下面我按自己拆这类工程的顺序,从协议选型讲到仿真、上板、排错,把这套资源的用法和边界一次说清。

2. SD 卡读写在 FPGA 里的实现路径:协议选型与工程结构先立住

2.1 SD 卡两种工作模式:SDIO 和 SPI,FPGA 该选哪条路

SD 卡在物理层上有两种访问方式:SDIO 模式和 SPI 模式。SDIO 模式又分 1-bit 和 4-bit,4-bit 数据线需要 4 根数据引脚加时钟加命令一共 6 根线;SPI 模式只需要 4 根线,但吞吐上限低,经典 SPI 跑 25 MHz 时理论写速度约 3 MB/s,遇到高容量卡还会被卡内固件瓶颈压在更低的实际值。

对于 FPGA 来说,选型不是越宽越好。大多数中低端 FPGA 芯片(比如黑金、正点原子的 Artix-7 或 Cyclone IV 板卡)逻辑资源和引脚都有限,SD 卡在项目里往往只是配角,不是主数据通路。我一般建议:如果实际吞吐需求低于 2 MB/s,优先做 SPI 模式,代码量小、时序简单、调试手段多(逻辑分析仪看两根线就行);如果需要在几秒内写入几十 MB 数据,那就老老实实上 SDIO 1-bit,数据线只有一根但协议复杂度会上升一个台阶。这套资源里默认是 SDIO 1-bit 方向,但 SPI 模式的初始化序列也保留着,方便对照。

判断资源是否适合你,先看它初始化序列怎么写的。SDIO 模式的初始化步骤是固定套路:上电延时、CMD0 进入 idle、CMD8 检查电压、ACMD41 协商容量、CMD2/CMD3 拿 CID 和 RCA——这套顺序错了任何一步卡都会不理你。而 SPI 初始化和 SDIO 有细节差异:CMD0 需要带CS=0,ACMD41 的响应是 R1 而不是 R3。这份资源里初始化状态机把 SDIO 和 SPI 的分支都写了出来,改模式只需要改顶层例化的参数。

2.2 扇区读写的基本盘:为什么单块写和多块写必须分开封装

SD 卡的最小寻址单位是扇区,标准 SDSC 是 512 字节一个扇区,SDHC/SDXC 则按块寻址。FPGA 读写 SD 卡本质上就是向卡发送CMD24(单块写)或CMD25(多块写),然后把数据按 512 字节一个扇区喂给卡。

为什么单块写和多块写必须分开封装?因为卡对这两种命令的响应和数据流格式不同。单块写,主机发 CMD24 + 地址,卡回 R1 响应,然后主机发数据起始令牌(0xFE)加 512 字节数据加 2 字节 CRC,卡接收后回写状态令牌,最后卡进入 busy 状态用DOUT拉低表示正在写 Flash,写完才释放。多块写是 CMD25,数据是一次性连续发多个块,每块前面都要加0xFC令牌(不是0xFE),最后发0xFD结束。

这套资源里单块写和多块写是两个独立模块,驱动层有一个sd_wr_single.v和一个sd_wr_multi.v,顶层用状态机选路。这样做的好处是:调试时可以先让单块写跑通,验证卡和 FPGA 之间的握手没问题,再切到多块写测吞吐,出了问题也能快速定位是发令牌发错了还是 busy 判断超时了。

2.3 工程目录拆解:拿到压缩包后先认这些文件

按这类工程最常见的组织方式,解压后应该看到下面这些部分。资源不同具体命名会有差别,但功能对应关系基本一致:

目录或文件作用使用时机
rtl/全部 Verilog 源码,含顶层、初始化状态机、读/写驱动、FIFO 接口综合时使用
sim/仿真测试平台、sd_card_model.v卡仿真模型、测试用例脚本跑仿真时使用
constraints/引脚约束和时钟约束(xdc或qsf文件)上板综合前必须核对
doc/时序说明、命令列表、常见问题记录排查问题时查阅

我拿到任何 FPGA 资源的第一步一定是先开sim/目录。如果这个工程连仿真模型都没有,那上板调试的代价会非常大——SD 卡写入的时序用逻辑分析仪抓很方便,但每次写卡都真实擦写 Flash,几百个周期的调试循环会明显拖慢进度。好的做法是先在仿真里把初始化、单块写、多块写全部跑通,再上板验证一次真实卡,确认跟仿真结论一致。这套资源的好处就是把卡模型和 testbench 都给了,省掉自己写卡模型的功夫。

2.4 验证手段:仿真模型和真实卡要各承担一半信任

SD 卡仿真模型通常只会模拟命令响应和基本数据流,不会真的模拟 Flash 擦写延迟和磨损均衡。所以仿真过了只能证明你的逻辑状态机没有协议错误,不能证明卡在真实环境下仍然稳定。

我测试时的顺序是:先用仿真模型把 CMD0→CMD8→ACMD41→CMD2→CMD3→CMD7 这套初始化全跑一遍,看卡模型的响应是否在自己的状态机预期分支里;再跑单块写,重点看数据令牌、写入 busy 信号、下一块写入的间隔;全绿之后上板,用 ILA 抓真实卡的响应。真实卡最容易出问题的环节往往是 ACMD41 的 HCS 位——如果初始化时没有声明自己支持高容量卡,SDHC 卡会拒绝进入就绪状态。这个坑在仿真模型里经常被忽略,因为很多仿真模型不校验这个位,所以你会遇到“仿真全过、上板卡死”的经典翻车场景。

3. 上手顺序:从解压到跑通仿真,一步步把工程转起来

3.1 先用脚本和 testbench 跑仿真,别急着上板

拿到压缩包先别打开 Vivado 或 Quartus 去综合,先跑仿真。因为综合一次十几分钟,上板一次来回折腾更久,而仿真在几秒内就能告诉你协议栈有没有低级错误。

常见做法是打开sim/下的仿真脚本(可能是.do文件给 ModelSim/QuestaSim 用,也可能是.tcl给 Vivado Simulator 用)。如果是 ModelSim 跑,类似这样:

vlib work vlog -f filelist.f vsim -c work.sd_test_tb run -all

跑完观察波形,重点看cmd_sent和cmd_rsp两个信号。初始化阶段每个命令发出后,cmd_rsp上要在预期窗口内出现有效响应——SDIO 模式下响应是 48-bit 的 R1/R3,SPI 模式下是 8-bit 的 R1。如果响应一直是全1或者超时,先查仿真模型的参数配置和时钟频率是否匹配。

参数这里多说一句:SD 卡初始化阶段时钟频率通常要低于 400 kHz,这是 SD 协议规定的最大初始化时钟;等卡进入 transfer state 之后才能把时钟升到 25 MHz 或更高。仿真脚本里一般会有一个clk_div_init参数控制这个分频比。如果你仿真时发现初始化老是超时,先看看这个参数是不是设得过大,把时钟压得太低导致命令间隔超了卡的容忍范围。

3.2 修改时钟分频和 FIFO 深度:两个必调的参数

SD 卡读写的顶层模块一般会暴露几个可调参数,这份资源里大概率有两类你必须要改:CLK_DIV_INIT和CLK_DIV_DATA,以及FIFO_DEPTH。

// 从顶层模块中摘出的关键参数 parameter CLK_DIV_INIT = 125; // 系统时钟 50MHz,初始化时钟 400kHz 以内 parameter CLK_DIV_DATA = 4; // 数据阶段时钟 = 50MHz / 4 = 12.5MHz,保守安全 parameter FIFO_DEPTH = 4096; // 写数据 FIFO 深度,按 512B 扇区数对齐

逻辑说明:SD 卡初始化要求时钟不超过 400 kHz,如果你的系统时钟是 50 MHz,CLK_DIV_INIT至少要设成50MHz / 0.4MHz = 125。数据阶段可以放宽,但别一上来就拉到 50 MHz 直连——SD 卡数据线和 FPGA 之间没有经过电平转换或阻抗匹配时,跑太高容易采到毛刺。FIFO_DEPTH建议按扇区数的整数倍来设,比如 8 个扇区就是 4096 字节,这样多块写时可以从 FIFO 连续搬数据,不用每写一个扇区都等上游供给。

这里有一个常见误用:很多人图省事把CLK_DIV_DATA设成 1,也就是全速跑。系统时钟直连 SD 卡不是不行,但前提是板级走线短、卡座质量好、电平标准一致。否则你会看到偶发性的0xFF响应,代码逻辑却完全查不出毛病。我一般会先在 12.5 MHz 上把数据链路跑稳,再用 ILA 实测时序裕量,有余量再升频。资源里默认参数大多偏保守,这是好事。

3.3 顶层例化要点:单块写和多块写怎么接到自己的数据通路

这份资源的顶层会暴露一个统一的写接口,一般是这样的形式:

// 顶层写接口示例 module sd_writer_top #( parameter CLK_DIV_INIT = 125, parameter CLK_DIV_DATA = 4 )( input wire clk_sys, input wire rst_n, output wire sd_clk, output wire sd_cmd, inout wire [3:0] sd_dat, // 用户侧写接口 input wire wr_en, input wire [31:0] wr_sector, input wire [511:0] wr_data, output wire wr_busy, output wire wr_done );

接到自己的数据采集逻辑时,控制流是:先把 512 字节拼到一个wr_data总线或 FIFO 里,置wr_en一个周期,然后等wr_busy拉低表示这一扇区写完。特别注意wr_sector是 LBA 地址,不是字节地址。很多人直接把字节地址传进来,结果卡写到了错误的扇区,读回来数据错位,还以为是卡坏了。

4. 核心时序拆解:初始化、单块写、多块写的状态机设计

4.1 初始化序列状态机:CMD0 到 CMD7 的每一步都不能跳

SD 卡初始化是整套读写流程里最容易返工的地方。这个状态机一般按下面的顺序排列,每个状态发出命令后都需要等待响应和超时判断:

// 初始化状态机骨架 localparam IDLE = 4'd0; localparam CMD0 = 4'd1; // 进入 idle 状态 localparam CMD8 = 4'd2; // 电压检查,支持 2.7-3.6V localparam ACMD41 = 4'd3; // 卡容量协商,发送前先发 CMD55 localparam CMD2 = 4'd4; // 获取 CID localparam CMD3 = 4'd5; // 获取 RCA localparam CMD7 = 4'd6; // 选择卡,进入 transfer state localparam DONE = 4'd7;

逻辑说明:ACMD41的发送方式比较特殊——必须先发CMD55把卡切换到 APP 命令模式,再发ACMD41,否则卡直接把命令丢弃。CMD2和CMD3在单卡场景下也不能省略,因为卡必须先经过CMD3拿到 RCA,CMD7才能带着这个 RCA 选中卡。资源里的 state machine 在这几个状态都实现了超时计数,比如等待响应超过 200 个时钟周期就跳回 IDLE 重新来。这个超时值是用参数暴露的,上板调试时如果卡一直初始化失败,可以先用 ILA 抓初始化结束时卡在哪个状态。

实际测试中有一个值得注意的现象:有的卡初始化对供电斜坡时序敏感,上电后需要等待至少 1 ms 再发 CMD0,这个延时通常在顶层用计数器实现。如果你用了高速 ADC 或图像采集的 FPGA 板卡,电源轨上同时有大电流支路,SD 卡供电纹波可能会比较大,此时更稳妥的做法是把 CMD0 发送延时长到 5 ms 以上。资源里默认用的是 1 ms,如果卡偶尔初始化失败,优先改这个值而不是改协议状态机。

4.2 单块写时序:CMD24 的完整波形和数据令牌细节

单块写是验证 SD 卡写通路的最小闭环。主机需要向卡发送 CMD24,地址为 32-bit LBA 地址;卡回复 R1 响应;随后主机把数据块发出去,数据块结构是:1 字节起始令牌0xFE+ 512 字节数据 + 2 字节 CRC。卡在接收到完整数据后会回写一个数据状态令牌,然后拉低DAT0表示忙,等内部 Flash 写完后释放。

// 单块写数据发送逻辑片段 always @(posedge clk_sys) begin if (state == WR_DATA) begin case (byte_cnt) 8'd0: sd_dat <= 8'hFE; // 起始令牌 8'd1: sd_dat <= wr_data[511:504]; // ... 按 8-bit 依次发送 512 字节 8'd511: sd_dat <= crc_hi; // CRC 高字节 8'd512: sd_dat <= crc_lo; // CRC 低字节 endcase end end

参数说明:byte_cnt从 0 到 512 一共 513 个周期,其中索引 0 是令牌,1~512 是数据加 CRC。CRC 在 SD 卡协议里用的是 CRC7 多项式x^7 + x^3 + 1,发送时先发高位。很多工程图省事会把 CRC 固定成0xFF 0xFF,虽然能绕过大多数卡的校验,但严格来说是不合规的,碰到校验严格的卡会写失败。资源里单块写模块带了 CRC 生成逻辑,多块写模块的 CRC 也是同一个子模块复用的,这是加分项。

写完成后的 busy 判断也是重点:卡把DAT0拉低表示忙,拉高表示空闲。判断 busy 的窗口要足够宽,SD 卡写一个扇区的典型时间是 10~30 ms,极端情况可能到 100 ms。如果你的 FPGA 逻辑里 busy 判断超时设得太短,比如 5 ms,高速卡没问题,但低速卡或接近寿命末期的卡会频繁超时。我自己的习惯是给这个超时留到 200 ms,代价只是多了一组计数器,收益是彻底告别偶发写失败误报。

4.3 多块写时序:CMD25 和结束令牌的配合

多块写是 CMD25,数据块之间是连续发送的,每块都要带0xFC起始令牌(注意不是单块的0xFE)。全部数据发送完后,主机发送0xFD结束令牌,卡才认为事务结束。如果中途因为 FIFO 空导致数据断流超过卡的容忍时间,卡会报写错误并中止事务。

资源和大多数工程一样,多块写的核心思路是:FIFO 预存 N 个扇区,逻辑按 FIFO 非空信号驱动数据发送,全部发完后补结束令牌。逻辑上要注意的是,发送完最后一个数据块的数据字节后,紧接着要发0xFD,中间不能插循环等待——如果你在最后一块的 CRC 之后插了一个判断状态的分支,等状态机切过来再发0xFD,时间差很容易超过卡的容错窗口。

// 多块写结束令牌发送 always @(posedge clk_sys) begin if (state == WR_END) begin sd_dat <= 8'hFD; // 结束令牌 state <= WR_IDLE; end end

逻辑说明:WR_END状态在最后一个数据块的 CRC 发送完成后一拍进入。理解这段代码的关键在于流转条件:不是等 FIFO 空了才切WR_END,而是自己在内部维护一个扇区计数器,发出第 N 个块之后直接跳转。这样才能保证结束令牌是紧跟在数据后面的,没有多余的空洞周期。这也是多块写模块比单块写模块难调的地方——单块写每块之间有卡 busy 阶段帮你兜底,多块写要求主机侧连续供给,任何断流都会导致卡中止写入。

资源里多块写模块还暴露了一个MAX_BLOCKS参数,用于限制单次事务的块数。这个参数不是 SD 协议要求的,而是工程侧的保护机制:防止上层逻辑失控时无限发送数据把卡写坏。有些卡对单次多块写长度有限制,超过之后也会中止事务,所以给这个参数留个可配置口子是对的。

4.4 CRC 校验模块:CRC7 在 FPGA 里的实现与适配

SD 卡命令和数据都带 CRC。命令是 48-bit,其中 40-bit 是命令内容,后 8-bit 是 CRC7 + 结束位。数据块是每个扇区 2 字节 CRC16。资源里 CRC 模块一般分两个:crc7_gen.v和crc16_gen.v。

CRC7 的实现用 LFSR 可以做到每比特周期计算一次。SD 协议规定 CRC7 从命令的第一个比特开始算,直到命令的 40-bit 内容结束,把生成的多项式结果放在后 8 位的高 7 位。写 Verilog 时常见错误是忘了先清 LFSR 再开始计算,导致响应校验一直失败。

// CRC7 生成器核心逻辑 reg [6:0] crc; always @(posedge clk_sys) begin if (crc_en) begin crc_next = {crc[5:0], 1'b0} ^ (crc[6] ? 7'h09 : 7'h00); crc_next[0] = crc_next[0] ^ data_in; crc <= crc_next; end end

参数说明:7'h09是 CRC7 多项式的低位表示。crc_next[0]异或输入数据,相当于把数据比特注入 LFSR 最低位。这段代码是组合逻辑生成下一状态,时序逻辑拍一拍的写法,适合在系统时钟下运行。要注意的是crc_en必须在命令数据的 40 个比特期间保持有效,第 41 个周期开始就拉低,否则会把后 8 位 CRC 字段也卷进计算里,发出去的命令被卡判为 CRC 错误直接丢弃。

5. 常见问题与避坑记录:从仿真全绿到上板必翻的四个坑

5.1 仿真通过但上板初始化失败:ACMD41 的 HCS 位没置位

现象:仿真一切正常,上板后逻辑分析仪抓到主机一直在重发 ACMD41,卡就是不回0x00。

原因:仿真模型大多不校验 HCS 位,但真实 SDHC 卡会检查。ACMD41 的 bit 30 是 HCS(Host Capacity Support),置 1 表示主机支持高容量卡,卡才会进入 ready 状态;如果这个位是 0,卡认为自己面对的是一个只认识 SDSC 的老主机,SDHC 卡拒绝初始化。

解决:检查初始化状态下发 ACMD41 时的参数值,bit 30 必须为 1。通常写法是acmd41_arg = 32'h40000000。另外 ACMD41 的响应里 bit 31 是卡侧 CCS 位,用于向主机报告卡容量类型,可以拿这个位判断初始化是否真正完成。

5.2 写操作偶发失败,重发一次就成功:命令超时参数太激进

现象:多块写大文件时,偶尔出现某个扇区写不进去,但重试一次就成功。日志层面可能表现为写速度抖动或校验错误。

原因:SD 卡内部 Flash 编程的过程中,如果遇到坏块管理、磨损均衡或温控降速,单块写 busy 时间会显著变长。你的超时窗口设得太短,卡还在忙,你就开始下一个命令,自然失败。

解决:把 busy 超时从 50 ms 放宽到 200 ms 以上。代价只是一个计数器位宽从 22 位变 24 位,FPGA 里几乎不占资源。另外,多块写场景下,如果检测到 busy 超时,不要立即重发整段数据——先等待卡释放 busy,再确认是当前块失败还是后续块全部失败,逐块重试比整段重发的成功率高得多。

5.3 ILA 抓到的命令波形是对的,但响应全是 0xFF:引脚约束和电平问题

现象:逻辑分析仪看到主机发出的命令波形和仿真完全一致,但DAT0上采到的响应数据全为 1。用万用表量 SD 卡座的引脚电压,发现 DAT 线电压只有 1.8V 而不是 3.3V。

原因:SD 卡座和 FPGA 之间有分压电阻、转换芯片或者板级寄生效应,导致信号幅度不足或上升沿变缓。FPGA 内部引脚配置成了默认的输入模式,没有使能上拉或施密特触发器。SD 卡规范要求主机侧 DAT/CMD 线上要有上拉电阻,很多开发板上靠 FPGA 的内部上拉凑合,但内部上拉阻值往往偏大(几十 kΩ),在 DAT 线被卡驱动时没问题,在卡释放总线、主机要采样的瞬间就可能因为阻抗太高采样到毛刺。

解决:把CMD、DAT0、DAT1-3的引脚约束改成PULLUP使能。如果板上有物理上拉电阻,确认阻值在 10kΩ 以内;如果只是 FPGA 内部上拉,数据速率超过 25 MHz 时大概率出问题,建议降速或者改板补焊上拉电阻。

5.4 多块写时 FIFO 偶尔下溢:读写两侧时钟域没对齐

现象:多块写前几个扇区正常,写到第 5 个左右开始出现数据断流,然后卡返回写错误。单块写完全正常。

原因:数据源侧的时钟域和 SD 卡发送侧时钟域不同步。比如你的采集逻辑时钟是 100 MHz,SD 卡发送时钟是 12.5 MHz,中间 FIFO 的读侧使能如果只在fifo_empty为假时置位,但 SD 侧状态机切换状态需要额外一个周期,极端情况下会出现 FIFO 明明还有数据但读使能已经同步到 SD 侧时晚了一拍的情况。

解决:FIFO 设计上给读侧加一个预取机制——当 FIFO 内数据量大于FIFO_DEPTH/2时才开始发送,发送过程中只要数据量不低于一个阈值就持续读。此外,多块写的wr_en信号必须来自 SD 时钟域,不能直接从源时钟域寄存器接过来,否则跨时钟域采样会产生亚稳态,导致写进卡的数据字节错位。资源里多块写模块如果直接用了源时钟域信号,我通常会在这里加一个同步器或异步 FIFO 适配层,这是需要自己补的常见缺口。

6. 进阶用法:把写卡速度从“能写”变成“按需调速”

SD 卡读写跑通只是开始,实际项目中真正花时间的是性能调优和稳定性验证。这里给三个进阶方向,也都是我调试这类工程时必走的路。

第一个是用 ILA 实测 SD 时钟沿和数据线的相对位置,确认采样窗口。具体做法是把sd_clk、sd_cmd、sd_dat[0]三个信号同时接到 ILA 上,在写某个固定扇区时抓一段波形。重点看数据线上数据变化沿和时钟上升沿的距离——如果数据在时钟上升沿附近翻转,说明时序裕量很小,要么降速要么调整输出延迟。

# Vivado ILA 调试核例化示意,在线调试时使用 set_property -dict {list \ [get_nets {sd_clk}] [get_nets {sd_cmd}] [get_nets {sd_dat[0]}] \ } [get_ips ila_0]

逻辑说明:把这三根线引进 ILA 核,触发条件设为sd_cmd == 0(命令发起的前沿),采样深度设成 65536。触发后看 200 个采样点就能看到完整的命令帧和响应帧。如果数据线的翻转时刻落在时钟上升沿 3 ns 以内,这个项目在温度变化或电压波动时大概率会偶发失败。

第二个是对写入数据做校验:写一扇区数据后立即读回,比对内容。这个功能资源里如果没直接给,我会在 testbench 里加一个回环测试用例:将固定图案AA 55 00 FF写入扇区 0~15,再通过读模块读回,比对不一致就报错。这段测试逻辑既可以放在仿真里验证两个独立模块的一致性,也可以搬到上板后的在线测试中——上板跑一遍回环测试,基本能确认整个写链路从数据生成到卡内部 Flash 全程无错。卡内部写失败的概率很低,大部分校验不过的情况都出在主机侧逻辑,回环测试能帮你快速区分是卡的问题还是自己代码的问题。

// 回环校验测试片段 for (int i = 0; i < 16; i++) begin wr_sector = i; wr_data = {16{32'hAA55_00FF}}; wait (wr_done); rd_sector = i; wait (rd_done); if (rd_data !== {16{32'hAA55_00FF}}) begin $display("ERROR: sector %0d mismatch", i); $finish; end end

参数说明:wr_sector和rd_sector是 LBA 地址,wr_data是 512-bit 总线,{16{32'hAA55_00FF}}表示 16 个 32-bit 重复拼接成 512 位。wr_done在单块写完成信号拉高一个周期,rd_done同理。整个回环测试跑完如果无报错,说明读写两个方向的时序和逻辑都是对的。

第三个方向才是我最想强调的:调速。资源和大多数工程会提供速度档位参数,可以从初始化完成后的CLK_DIV_DATA=4慢慢调到1,每档跑一次回环测试,测出你这块板子的稳定上限。这个上限不是固定的——同一块卡在不同温度、不同使用时长下会有差异。所以我会在数据链路上保留一个速度寄存器,平时用保守速度跑,关键数据传输时再动态切换到高速档。

从那次被 SD 卡折腾了两天、最后发现只是 CMD 线上拉了 50kΩ 上拉电阻没焊之后,我每次做 FPGA 外设调试都强制自己先跑一遍仿真模型加回环测试,再碰真实卡;拿到任何 SD 卡相关资源包,也一定先看有没有仿真模型、有没有 CRC 模块、有没有多块写结束令牌的处理,这三个缺一个都会是后面调试的坑。这套思路和这份资源配合使用,能让你少走不少弯路,希望帮到你。

本文还有配套的精品资源,点击获取